Start removing the SqlTypeSystem class

This commit is contained in:
Simon Binder 2022-07-12 17:04:01 +02:00
parent 7d940f8fd8
commit 1af6bb78d9
No known key found for this signature in database
GPG Key ID: 7891917E4147B8C0
34 changed files with 305 additions and 439 deletions

View File

@ -8,16 +8,14 @@ import 'package:drift_dev/api/migrations.dart';
const kDebugBuild = true;
abstract class _$MyDatabase extends GeneratedDatabase {
_$MyDatabase(SqlTypeSystem types, QueryExecutor executor)
: super(types, executor);
_$MyDatabase(QueryExecutor executor) : super(executor);
}
// #docregion
class MyDatabase extends _$MyDatabase {
// #enddocregion
MyDatabase(SqlTypeSystem types, QueryExecutor executor)
: super(types, executor);
MyDatabase(QueryExecutor executor) : super(executor);
@override
Iterable<TableInfo<Table, dynamic>> get allTables =>

View File

@ -16,5 +16,6 @@ export 'src/runtime/exceptions.dart';
export 'src/runtime/executor/connection_pool.dart';
export 'src/runtime/executor/executor.dart';
export 'src/runtime/query_builder/query_builder.dart';
export 'src/runtime/types/sql_types.dart';
export 'src/runtime/types/converters.dart';
export 'src/runtime/types/mapping.dart';
export 'src/utils/lazy_database.dart';

View File

@ -1,2 +1,2 @@
// This field is analyzed by drift_dev to easily obtain common types.
export 'runtime/types/sql_types.dart' show TypeConverter, JsonTypeConverter;
export 'runtime/types/converters.dart' show TypeConverter, JsonTypeConverter;

View File

@ -4,7 +4,6 @@ import 'package:drift/src/runtime/api/runtime_api.dart';
import 'package:drift/src/runtime/executor/executor.dart';
import 'package:drift/src/runtime/executor/stream_queries.dart';
import 'package:drift/src/runtime/query_builder/query_builder.dart';
import 'package:drift/src/runtime/types/sql_types.dart';
import 'package:stream_channel/stream_channel.dart';
import '../runtime/cancellation_zone.dart';
@ -21,9 +20,8 @@ class DriftClient {
/// The resulting database connection. Operations on this connection are
/// relayed through the remote communication channel.
late final DatabaseConnection connection = DatabaseConnection(
SqlTypeSystem.defaultInstance,
_RemoteQueryExecutor(this),
_streamStore,
streamQueries: _streamStore,
);
late QueryExecutorUser _connectedDb;

View File

@ -11,7 +11,6 @@ class DriftProtocol {
static const _tag_Response_error = 2;
static const _tag_Response_cancelled = 3;
static const _tag_NoArgsRequest_getTypeSystem = 0;
static const _tag_NoArgsRequest_terminateAll = 1;
static const _tag_ExecuteQuery = 3;
@ -20,7 +19,6 @@ class DriftProtocol {
static const _tag_EnsureOpen = 6;
static const _tag_RunBeforeOpen = 7;
static const _tag_NotifyTablesUpdated = 8;
static const _tag_DefaultSqlTypeSystem = 9;
static const _tag_DirectValue = 10;
static const _tag_SelectResult = 11;
static const _tag_RequestCancellation = 12;
@ -121,10 +119,6 @@ class DriftProtocol {
update.kind?.index,
]
];
} else if (payload is SqlTypeSystem) {
// assume connection uses SqlTypeSystem.defaultInstance, this can't
// possibly be encoded.
return _tag_DefaultSqlTypeSystem;
} else if (payload is SelectResult) {
// We can't necessary transport maps, so encode as list
final rows = payload.rows;
@ -169,8 +163,6 @@ class DriftProtocol {
int? readNullableInt(int index) => fullMessage![index] as int?;
switch (tag) {
case _tag_NoArgsRequest_getTypeSystem:
return NoArgsRequest.getTypeSystem;
case _tag_NoArgsRequest_terminateAll:
return NoArgsRequest.terminateAll;
case _tag_ExecuteQuery:
@ -202,8 +194,6 @@ class DriftProtocol {
OpeningDetails(readNullableInt(1), readInt(2)),
readInt(3),
);
case _tag_DefaultSqlTypeSystem:
return SqlTypeSystem.defaultInstance;
case _tag_NotifyTablesUpdated:
final updates = <TableUpdate>[];
for (var i = 1; i < fullMessage!.length; i++) {
@ -327,10 +317,6 @@ class CancelledResponse extends Message {
/// A request without further parameters
enum NoArgsRequest {
/// Sent from the client to the server. The server will reply with the
/// [SqlTypeSystem] of the connection it's managing.
getTypeSystem,
/// Close the background isolate, disconnect all clients, release all
/// associated resources
terminateAll,

View File

@ -74,8 +74,6 @@ class ServerImplementation implements DriftServer {
if (payload is NoArgsRequest) {
switch (payload) {
case NoArgsRequest.getTypeSystem:
return connection.typeSystem;
case NoArgsRequest.terminateAll:
if (allowRemoteShutdown) {
_backlogUpdated.close();

View File

@ -1,31 +1,27 @@
part of 'runtime_api.dart';
/// A database connection managed by drift. Contains three components:
/// - a [SqlTypeSystem], which is responsible to map between Dart types and
/// values understood by the database engine.
/// - a [QueryExecutor], which runs sql commands
/// A database connection managed by drift. This consists of two components:
///
/// - a [QueryExecutor], which runs sql statements.
/// - a [StreamQueryStore], which dispatches table changes to listening queries,
/// on which the auto-updating queries are based.
class DatabaseConnection {
/// The type system to use with this database. The type system is responsible
/// for mapping Dart objects into sql expressions and vice-versa.
@Deprecated('Only the default type system is supported')
final SqlTypeSystem typeSystem;
/// The executor to use when queries are executed.
final QueryExecutor executor;
/// Manages active streams from select statements.
final StreamQueryStore streamQueries;
/// Constructs a raw database connection from the three components.
DatabaseConnection(this.typeSystem, this.executor, this.streamQueries);
/// Constructs a raw database connection from the [executor] and optionally a
/// specified [streamQueries] implementation to use.
DatabaseConnection(this.executor, {StreamQueryStore? streamQueries})
: streamQueries = streamQueries ?? StreamQueryStore();
/// Constructs a [DatabaseConnection] from the [QueryExecutor] by using the
/// default type system and a new [StreamQueryStore].
DatabaseConnection.fromExecutor(this.executor)
: typeSystem = SqlTypeSystem.defaultInstance,
streamQueries = StreamQueryStore();
@Deprecated('Use the default unnamed constructor of `DatabaseConnection` '
'instead')
DatabaseConnection.fromExecutor(QueryExecutor executor) : this(executor);
/// Database connection that is instantly available, but delegates work to a
/// connection only available through a `Future`.
@ -54,15 +50,15 @@ class DatabaseConnection {
}
return DatabaseConnection(
SqlTypeSystem.defaultInstance,
LazyDatabase(() async => (await connection).executor),
DelayedStreamQueryStore(connection.then((conn) => conn.streamQueries)),
streamQueries: DelayedStreamQueryStore(
connection.then((conn) => conn.streamQueries)),
);
}
/// Returns a database connection that is identical to this one, except that
/// it uses the provided [executor].
DatabaseConnection withExecutor(QueryExecutor executor) {
return DatabaseConnection(typeSystem, executor, streamQueries);
return DatabaseConnection(executor, streamQueries: streamQueries);
}
}

View File

@ -13,14 +13,16 @@ abstract class DatabaseConnectionUser {
@protected
final DatabaseConnection connection;
/// The [DriftDatabaseOptions] to use here.
///
/// Mainly, these options describe how values are mapped from Dart to SQL
/// values. In the future, they could be expanded to dialect-specific options.
DriftDatabaseOptions get options => attachedDatabase.options;
/// The database class that this user is attached to.
@visibleForOverriding
GeneratedDatabase get attachedDatabase;
/// The type system to use with this database. The type system is responsible
/// for mapping Dart objects into sql expressions and vice-versa.
SqlTypeSystem get typeSystem => connection.typeSystem;
/// The executor to use when queries are executed.
QueryExecutor get executor => connection.executor;
@ -31,21 +33,17 @@ abstract class DatabaseConnectionUser {
/// Constructs a database connection user, which is responsible to store query
/// streams, wrap the underlying executor and perform type mapping.
DatabaseConnectionUser(SqlTypeSystem typeSystem, QueryExecutor executor,
DatabaseConnectionUser(QueryExecutor executor,
{StreamQueryStore? streamQueries})
: connection = DatabaseConnection(
typeSystem, executor, streamQueries ?? StreamQueryStore());
: connection = DatabaseConnection(executor, streamQueries: streamQueries);
/// Creates another [DatabaseConnectionUser] by referencing the implementation
/// from the [other] user.
DatabaseConnectionUser.delegate(DatabaseConnectionUser other,
{SqlTypeSystem? typeSystem,
QueryExecutor? executor,
StreamQueryStore? streamQueries})
{QueryExecutor? executor, StreamQueryStore? streamQueries})
: connection = DatabaseConnection(
typeSystem ?? other.connection.typeSystem,
executor ?? other.connection.executor,
streamQueries ?? other.connection.streamQueries,
streamQueries: streamQueries ?? other.connection.streamQueries,
);
/// Constructs a [DatabaseConnectionUser] that will use the provided

View File

@ -15,6 +15,9 @@ abstract class GeneratedDatabase extends DatabaseConnectionUser
@override
GeneratedDatabase get attachedDatabase => this;
@override
DriftDatabaseOptions get options => DriftDatabaseOptions();
/// Specify the schema version of your database. Whenever you change or add
/// tables, you should bump this field and provide a [migration] strategy.
@override
@ -53,9 +56,8 @@ abstract class GeneratedDatabase extends DatabaseConnectionUser
final Type _$dontSendThisOverIsolates = Null;
/// Used by generated code
GeneratedDatabase(SqlTypeSystem types, QueryExecutor executor,
{StreamQueryStore? streamStore})
: super(types, executor, streamQueries: streamStore) {
GeneratedDatabase(QueryExecutor executor, {StreamQueryStore? streamStore})
: super(executor, streamQueries: streamStore) {
assert(_handleInstantiated());
}

View File

@ -0,0 +1,9 @@
import '../types/mapping.dart';
class DriftDatabaseOptions {
final SqlTypes types;
DriftDatabaseOptions({
bool storeDateTimeAsText = false,
}) : types = SqlTypes(storeDateTimeAsText);
}

View File

@ -6,6 +6,8 @@ import 'package:drift/src/runtime/executor/stream_queries.dart';
import 'package:drift/src/runtime/executor/transactions.dart';
import 'package:meta/meta.dart';
import 'options.dart';
part 'batch.dart';
part 'connection.dart';
part 'connection_user.dart';

View File

@ -68,7 +68,9 @@ abstract class Expression<D> implements FunctionParameter {
/// Note that this does not do a meaningful conversion for drift-only types
/// like `bool` or `DateTime`. Both would simply generate a `CAST AS INT`
/// expression.
Expression<D2> cast<D2>() => _CastInSqlExpression<D, D2>(this);
Expression<D2?> cast<D2 extends Object>() {
return _CastInSqlExpression<D, D2>(this);
}
/// An expression that is true if `this` resolves to any of the values in
/// [values].
@ -164,11 +166,6 @@ abstract class Expression<D> implements FunctionParameter {
"Expressions with unknown precedence shouldn't have inner expressions");
inner.writeAroundPrecedence(ctx, precedence);
}
/// Finds the runtime implementation of [D] in the provided [types].
SqlType<D> findType(SqlTypeSystem types) {
return types.forDartType<D>();
}
}
/// Used to order the precedence of sql expressions so that we can avoid
@ -400,7 +397,7 @@ class _DartCastExpression<D1, D2> extends Expression<D2> {
}
}
class _CastInSqlExpression<D1, D2> extends Expression<D2> {
class _CastInSqlExpression<D1, D2 extends Object> extends Expression<D2?> {
final Expression<D1> inner;
@override
@ -410,11 +407,11 @@ class _CastInSqlExpression<D1, D2> extends Expression<D2> {
@override
void writeInto(GenerationContext context) {
final type = context.typeSystem.forDartType<D2>();
final type = DriftSqlType.forType<D2>();
context.buffer.write('CAST(');
inner.writeInto(context);
context.buffer.write(' AS ${type.sqlName(context.dialect)})');
context.buffer.write(' AS ${type.sqlTypeName(context)})');
}
}

View File

@ -60,7 +60,7 @@ class Variable<T> extends Expression<T> {
/// database engine. For instance, a [DateTime] will me mapped to its unix
/// timestamp.
dynamic mapToSimpleValue(GenerationContext context) {
return context.typeSystem.mapToVariable(value);
return context.options.types.mapToSqlVariable(value);
}
@override
@ -119,7 +119,7 @@ class Constant<T> extends Expression<T> {
@override
void writeInto(GenerationContext context) {
context.buffer.write(SqlTypeSystem.mapToSqlConstant(value));
context.buffer.write(context.options.types.mapToSqlLiteral(value));
}
@override

View File

@ -19,9 +19,8 @@ class GenerationContext {
/// All tables that the generated query reads from.
final List<ResultSetImplementation> watchedTables = [];
/// The [SqlTypeSystem] to use when mapping variables to values that the
/// underlying database understands.
final SqlTypeSystem typeSystem;
/// The options to use when mapping values from and to the database.
final DriftDatabaseOptions options;
/// The [SqlDialect] that should be respected when generating the query.
SqlDialect get dialect => executor?.executor.dialect ?? SqlDialect.sqlite;
@ -57,12 +56,13 @@ class GenerationContext {
/// Constructs a [GenerationContext] by copying the relevant fields from the
/// database.
GenerationContext.fromDb(this.executor, {this.supportsVariables = true})
: typeSystem = executor?.typeSystem ?? SqlTypeSystem.defaultInstance;
GenerationContext.fromDb(DatabaseConnectionUser this.executor,
{this.supportsVariables = true})
: options = executor.options;
/// Constructs a custom [GenerationContext] by setting the fields manually.
/// See [GenerationContext.fromDb] for a more convenient factory.
GenerationContext(this.typeSystem, this.executor,
GenerationContext(this.options, this.executor,
{this.supportsVariables = true});
/// Introduces a variable that will be sent to the database engine. Whenever

View File

@ -169,7 +169,7 @@ class Migrator {
for (final row in schemaQuery) {
final type = row.readString('type');
final sql = row.read<String?>('sql');
final sql = row.readNullable<String>('sql');
final name = row.readString('name');
if (sql == null) {

View File

@ -12,6 +12,8 @@ import 'package:meta/meta.dart';
// New files should not be part of this mega library, which we're trying to
// split up.
import '../api/options.dart';
import '../types/mapping.dart';
import 'expressions/case_when.dart';
export 'on_table.dart';

View File

@ -53,12 +53,8 @@ class GeneratedColumn<T> extends Column<T> {
/// Additional checks performed on values before inserts or updates.
final VerificationResult Function(T, VerificationMeta)? additionalChecks;
/// The sql type, such as `StringType` for texts.
final SqlType type;
/// The sql type name, such as `TEXT` for texts.
@Deprecated('Use type.sqlName instead')
String get typeName => type.sqlName(SqlDialect.sqlite);
/// The sql type to use for this column.
final DriftSqlType type;
/// If this column is generated (that is, it is a SQL expression of other)
/// columns, contains information about how to generate this column.
@ -122,7 +118,7 @@ class GeneratedColumn<T> extends Column<T> {
if (isSerial) {
into.buffer.write('$escapedName bigserial PRIMARY KEY NOT NULL');
} else {
into.buffer.write('$escapedName ${type.sqlName(into.dialect)}');
into.buffer.write('$escapedName ${type.sqlTypeName(into)}');
}
if ($customConstraints == null) {
@ -267,7 +263,7 @@ class GeneratedColumnWithTypeConverter<D, S> extends GeneratedColumn<S> {
String tableName,
bool nullable,
S Function()? clientDefault,
SqlType type,
DriftSqlType type,
String? defaultConstraints,
String? customConstraints,
Expression<S>? defaultValue,

View File

@ -72,7 +72,10 @@ mixin TableInfo<TableDsl extends Table, D> on Table
/// Converts a [companion] to the real model class, [D].
///
/// Values that are [Value.absent] in the companion will be set to `null`.
D mapFromCompanion(Insertable<D> companion) {
/// The [database] instance is used so that the raw values from the companion
/// can properly be interpreted as the high-level Dart values exposed by the
/// data class.
D mapFromCompanion(Insertable<D> companion, DatabaseConnectionUser database) {
final asColumnMap = companion.toColumns(false);
if (asColumnMap.values.any((e) => e is! Variable)) {
@ -81,7 +84,7 @@ mixin TableInfo<TableDsl extends Table, D> on Table
'evaluated by a database engine.');
}
final context = GenerationContext(SqlTypeSystem.defaultInstance, null);
final context = GenerationContext.fromDb(database);
final rawValues = asColumnMap
.cast<String, Variable>()
.map((key, value) => MapEntry(key, value.mapToSimpleValue(context)));
@ -175,6 +178,6 @@ extension RowIdExtension on TableInfo {
}
return GeneratedColumn<int?>('_rowid_', aliasedName, false,
type: const IntType());
type: DriftSqlType.int);
}
}

View File

@ -239,7 +239,7 @@ class InsertStatement<T extends Table, D> {
} else if (ctx.dialect == SqlDialect.postgres) {
if (table.$primaryKey.length == 1) {
final id = table.$primaryKey.firstOrNull;
if (id != null && id.type is IntType) {
if (id != null && id.type == DriftSqlType.int) {
ctx.buffer.write(' RETURNING ${id.name}');
}
}

View File

@ -74,10 +74,20 @@ class QueryRow {
/// Reads an arbitrary value from the row and maps it to a fitting dart type.
/// The dart type [T] must be supported by the type system of the database
/// used (mostly contains booleans, strings, numbers and dates).
T read<T>(String key) {
final type = _db.typeSystem.forDartType<T>();
T read<T extends Object>(String key) {
final result = readNullable<T>(key);
if (result == null) {
throw StateError(
'Called read<$T>($key) which would have returned `null`. Please use '
'`readNullable` for reads that might be null instead.');
} else {
return result;
}
}
return type.mapFromDatabaseResponse(data[key]) as T;
T? readNullable<T extends Object>(String key) {
final type = DriftSqlType.forType<T>();
return _db.options.types.read(type, data[key]);
}
/// Reads a bool from the column named [key].

View File

@ -253,7 +253,7 @@ class JoinedSelectStatement<FirstT extends HasResultSet, FirstD>
final value = row[aliasedColumn.value];
final type = expr.findType(ctx.typeSystem);
readColumns[expr] = type.mapFromDatabaseResponse(value);
readColumns[expr] = ctx.options.types.read(type, value);
}
return TypedResult(readTables, QueryRow(row, database), readColumns);

View File

@ -1,4 +1,6 @@
part of 'sql_types.dart';
import 'dart:typed_data';
import '../../dsl/dsl.dart';
import '../data_class.dart';
/// Maps a custom dart object of type [D] into a primitive type [S] understood
/// by the sqlite backend.

View File

@ -0,0 +1,183 @@
import 'dart:core';
import 'dart:core' as core;
import 'dart:typed_data';
import 'package:convert/convert.dart';
import 'package:meta/meta.dart';
import '../query_builder/query_builder.dart';
/// Static helper methods mapping Dart values from and to SQL variables or
/// literals.
@sealed
class SqlTypes {
final bool _storeDateTimesAsText;
/// Creates an [SqlTypes] mapper from the provided options.
@internal
SqlTypes(this._storeDateTimesAsText);
/// Maps a Dart object to a (possibly simpler) object that can be used as a
/// parameter in raw sql queries.
Object? mapToSqlVariable(Object? dartValue) {
if (dartValue == null) return null;
// These need special handling, all other types are a direct mapping
if (dartValue is DateTime) {
if (_storeDateTimesAsText) {
return dartValue.toIso8601String();
} else {
return dartValue.millisecondsSinceEpoch ~/ 1000;
}
}
if (dartValue is bool) {
return dartValue ? 1 : 0;
}
return dartValue;
}
/// Maps the [dart] value into a SQL literal that can be embedded in SQL
/// queries.
String mapToSqlLiteral(Object? dart) {
if (dart == null) return 'NULL';
// todo: Inline and remove types in the next major drift version
if (dart is bool) {
return dart ? '1' : '0';
} else if (dart is String) {
// From the sqlite docs: (https://www.sqlite.org/lang_expr.html)
// A string constant is formed by enclosing the string in single quotes
// (').
// A single quote within the string can be encoded by putting two single
// quotes in a row - as in Pascal. C-style escapes using the backslash
// character are not supported because they are not standard SQL.
final escapedChars = dart.replaceAll('\'', '\'\'');
return "'$escapedChars'";
} else if (dart is num || dart is BigInt) {
return dart.toString();
} else if (dart is DateTime) {
if (_storeDateTimesAsText) {
return "'${dart.toIso8601String()}'";
} else {
return (dart.millisecondsSinceEpoch ~/ 1000).toString();
}
} else if (dart is Uint8List) {
// BLOB literals are string literals containing hexadecimal data and
// preceded by a single "x" or "X" character. Example: X'53514C697465'
return "x'${hex.encode(dart)}'";
}
throw ArgumentError.value(dart, 'dart',
'Must be null, bool, String, int, DateTime, Uint8List or double');
}
/// Maps a raw [sqlValue] to Dart given its sql [type].
T? read<T extends Object>(DriftSqlType<T> type, Object? sqlValue) {
if (sqlValue == null) return null;
// ignore: unnecessary_cast
switch (type as DriftSqlType<Object>) {
case DriftSqlType.bool:
return (sqlValue != 0) as T;
case DriftSqlType.string:
return sqlValue.toString() as T;
case DriftSqlType.bigInt:
if (sqlValue is BigInt) return sqlValue as T?;
if (sqlValue is int) return BigInt.from(sqlValue) as T;
return BigInt.parse(sqlValue.toString()) as T;
case DriftSqlType.int:
if (sqlValue is int) return sqlValue as T;
if (sqlValue is BigInt) return sqlValue.toInt() as T;
return int.parse(sqlValue.toString()) as T;
case DriftSqlType.dateTime:
if (_storeDateTimesAsText) {
return DateTime.parse(read(DriftSqlType.string, sqlValue)!) as T;
} else {
final unixSeconds = read(DriftSqlType.int, sqlValue)!;
return DateTime.fromMillisecondsSinceEpoch(unixSeconds * 1000) as T;
}
case DriftSqlType.blob:
if (sqlValue is String) {
final list = sqlValue.codeUnits;
return Uint8List.fromList(list) as T;
}
return sqlValue as T;
case DriftSqlType.double:
return (sqlValue as num?)?.toDouble() as T;
}
}
}
/// An enumation of type mappings that are builtin to drift and `drift_dev`.
enum DriftSqlType<T extends Object> {
/// A boolean type, represented as `0` or `1` (int) in SQL.
bool<core.bool>(),
/// A textual type, represented as `TEXT` in sqlite.
string<String>(),
/// A 64-bit int type that is represented a [BigInt] in Dart for better
/// compatibility with the web. Represented as an `INTEGER` in sqlite or as
/// a `bigint` in postgres.
bigInt<BigInt>(),
/// A 64-bit int.
///
/// Represented as an `INTEGER` in sqlite or as a `bigint` in postgres.
int<core.int>(),
/// A [DateTime] value.
///
/// Depending on the options choosen at build-time, this is either stored as
/// an unix timestamp (the default) or as a ISO 8601 string.
dateTime<DateTime>(),
/// A [Uint8List] value.
///
/// This is stored as a `BLOB` in sqlite or as a `bytea` type in postgres.
blob<Uint8List>(),
/// A [double] value, stored as a `REAL` type in sqlite.
double<core.double>();
/// Returns a suitable representation of this type in SQL.
String sqlTypeName(GenerationContext context) {
final dialect = context.dialect;
// ignore: unnecessary_cast
switch (this as DriftSqlType<Object>) {
case DriftSqlType.bool:
return dialect == SqlDialect.sqlite ? 'INTEGER' : 'integer';
case DriftSqlType.string:
return dialect == SqlDialect.sqlite ? 'TEXT' : 'text';
case DriftSqlType.bigInt:
case DriftSqlType.int:
return dialect == SqlDialect.sqlite ? 'INTEGER' : 'bigint';
case DriftSqlType.dateTime:
if (context.options.types._storeDateTimesAsText) {
return dialect == SqlDialect.sqlite ? 'INTEGER' : 'bigint';
} else {
return dialect == SqlDialect.sqlite ? 'TEXT' : 'text';
}
case DriftSqlType.blob:
return dialect == SqlDialect.sqlite ? 'BLOB' : 'bytea';
case DriftSqlType.double:
return dialect == SqlDialect.sqlite ? 'REAL' : 'float8';
}
}
/// Attempts to find a suitable SQL type for the [Dart] type passed to this
/// method.
///
/// The [Dart] type must be the type of the instance _after_ applying type
/// converters.
static DriftSqlType<Dart> forType<Dart extends Object>() {
for (final type in values) {
if (type is DriftSqlType<Dart>) return type;
}
throw ArgumentError('Could not find a matching SQL type for $Dart');
}
}

View File

@ -1,242 +0,0 @@
import 'package:convert/convert.dart';
import 'package:drift/drift.dart';
part 'custom_type.dart';
part 'type_system.dart';
const _deprecated =
Deprecated('Types will be removed in drift 5, use the methods on '
'SqlTypeSystem instead.');
/// A type that can be mapped from Dart to sql. The generic type parameter [T]
/// denotes the resolved dart type.
@_deprecated
abstract class SqlType<T> {
/// Constant constructor so that subclasses can be constant
const SqlType();
/// The name of this type in sql, such as `TEXT`.
String sqlName(SqlDialect dialect);
/// Maps the [content] to a value that we can send together with a prepared
/// statement to represent the given value.
dynamic mapToSqlVariable(T? content);
/// Maps the given content to a sql literal that can be included in the query
/// string.
String? mapToSqlConstant(T? content);
/// Maps the response from sql back to a readable dart type.
T? mapFromDatabaseResponse(dynamic response);
}
/// A mapper for boolean values in sql. Booleans are represented as integers,
/// where 0 means false and any other value means true.
@_deprecated
class BoolType extends SqlType<bool> {
/// Constant constructor used by the type system
const BoolType();
@override
String sqlName(SqlDialect dialect) =>
dialect == SqlDialect.sqlite ? 'INTEGER' : 'integer';
@override
bool? mapFromDatabaseResponse(dynamic response) {
// ignore: avoid_returning_null
if (response == null) return null;
return response != 0;
}
@override
String mapToSqlConstant(bool? content) {
if (content == null) {
return 'NULL';
}
return content ? '1' : '0';
}
@override
int? mapToSqlVariable(bool? content) {
if (content == null) {
// ignore: avoid_returning_null
return null;
}
return content ? 1 : 0;
}
}
/// Mapper for string values in sql.
@_deprecated
class StringType extends SqlType<String> {
/// Constant constructor used by the type system
const StringType();
@override
String sqlName(SqlDialect dialect) =>
dialect == SqlDialect.sqlite ? 'TEXT' : 'text';
@override
String? mapFromDatabaseResponse(dynamic response) => response?.toString();
@override
String mapToSqlConstant(String? content) {
if (content == null) return 'NULL';
// From the sqlite docs: (https://www.sqlite.org/lang_expr.html)
// A string constant is formed by enclosing the string in single quotes (').
// A single quote within the string can be encoded by putting two single
// quotes in a row - as in Pascal. C-style escapes using the backslash
// character are not supported because they are not standard SQL.
final escapedChars = content.replaceAll('\'', '\'\'');
return "'$escapedChars'";
}
@override
String? mapToSqlVariable(String? content) => content;
}
/// Maps [int] values from and to sql
@_deprecated
class IntType extends SqlType<int> {
/// Constant constructor used by the type system
const IntType();
@override
String sqlName(SqlDialect dialect) =>
dialect == SqlDialect.sqlite ? 'INTEGER' : 'bigint';
@override
int? mapFromDatabaseResponse(dynamic response) {
if (response == null || response is int?) return response as int?;
if (response is BigInt) return response.toInt();
return int.parse(response.toString());
}
@override
String mapToSqlConstant(int? content) => content?.toString() ?? 'NULL';
@override
int? mapToSqlVariable(int? content) {
return content;
}
}
/// Maps [BigInt] values from and to sql
@_deprecated
class BigIntType extends SqlType<BigInt> {
/// Constant constructor used by the type system
const BigIntType();
@override
String sqlName(SqlDialect dialect) =>
dialect == SqlDialect.sqlite ? 'INTEGER' : 'bigint';
@override
BigInt? mapFromDatabaseResponse(dynamic response) {
if (response == null || response is BigInt?) return response as BigInt?;
if (response is int) return BigInt.from(response);
return BigInt.parse(response.toString());
}
@override
String mapToSqlConstant(BigInt? content) => content?.toString() ?? 'NULL';
@override
BigInt? mapToSqlVariable(BigInt? content) {
return content;
}
}
/// Maps [DateTime] values from and to sql
@_deprecated
class DateTimeType extends SqlType<DateTime> {
/// Constant constructor used by the type system
const DateTimeType();
@override
String sqlName(SqlDialect dialect) =>
dialect == SqlDialect.sqlite ? 'INTEGER' : 'integer';
@override
DateTime? mapFromDatabaseResponse(dynamic response) {
if (response == null) return null;
final unixSeconds = response as int;
return DateTime.fromMillisecondsSinceEpoch(unixSeconds * 1000);
}
@override
String mapToSqlConstant(DateTime? content) {
if (content == null) return 'NULL';
return (content.millisecondsSinceEpoch ~/ 1000).toString();
}
@override
int? mapToSqlVariable(DateTime? content) {
// ignore: avoid_returning_null
if (content == null) return null;
return content.millisecondsSinceEpoch ~/ 1000;
}
}
/// Maps [Uint8List] values from and to sql
@_deprecated
class BlobType extends SqlType<Uint8List> {
/// Constant constructor used by the type system
const BlobType();
@override
String sqlName(SqlDialect dialect) =>
dialect == SqlDialect.sqlite ? 'BLOB' : 'bytea';
@override
Uint8List? mapFromDatabaseResponse(dynamic response) {
if (response is String) {
final list = response.codeUnits;
return Uint8List.fromList(list);
}
return response as Uint8List?;
}
@override
String mapToSqlConstant(Uint8List? content) {
if (content == null) return 'NULL';
// BLOB literals are string literals containing hexadecimal data and
// preceded by a single "x" or "X" character. Example: X'53514C697465'
return "x'${hex.encode(content)}'";
}
@override
Uint8List? mapToSqlVariable(Uint8List? content) => content;
}
/// Maps [double] values from and to sql
@_deprecated
class RealType extends SqlType<double> {
/// Constant constructor used by the type system
const RealType();
@override
String sqlName(SqlDialect dialect) =>
dialect == SqlDialect.sqlite ? 'REAL' : 'float8';
@override
double? mapFromDatabaseResponse(dynamic response) {
return (response as num?)?.toDouble();
}
@override
String mapToSqlConstant(num? content) {
if (content == null) {
return 'NULL';
}
return content.toString();
}
@override
num? mapToSqlVariable(num? content) => content;
}

View File

@ -1,73 +0,0 @@
part of 'sql_types.dart';
/// Manages the set of [SqlType] known to a database. It's also responsible for
/// returning the appropriate sql type for a given dart type.
class SqlTypeSystem {
/// The mapping types maintained by this type system.
final List<SqlType> types;
/// Constructs a [SqlTypeSystem] from the [types].
@Deprecated('Only the default instance is supported')
const factory SqlTypeSystem(List<SqlType> types) = SqlTypeSystem._;
const SqlTypeSystem._(this.types);
/// Constructs a [SqlTypeSystem] from the default types.
const SqlTypeSystem.withDefaults()
: this._(const [
BoolType(),
StringType(),
IntType(),
DateTimeType(),
BlobType(),
RealType(),
]);
/// Constant field of [SqlTypeSystem.withDefaults]. This field exists as a
/// workaround for an analyzer bug: https://dartbug.com/38658
///
/// Used internally by generated code.
static const defaultInstance = SqlTypeSystem.withDefaults();
/// Returns the appropriate sql type for the dart type provided as the
/// generic parameter.
@Deprecated('Use mapToVariable or a mapFromSql method instead')
SqlType<T> forDartType<T>() {
return types.singleWhere((t) => t is SqlType<T>) as SqlType<T>;
}
/// Maps a Dart object to a (possibly simpler) object that can be used as
/// parameters to raw sql queries.
Object? mapToVariable(Object? dart) {
if (dart == null) return null;
// These need special handling, all other types are a direct mapping
if (dart is DateTime) return const DateTimeType().mapToSqlVariable(dart);
if (dart is bool) return const BoolType().mapToSqlVariable(dart);
return dart;
}
/// Maps a Dart object to a SQL constant representing the same value.
static String mapToSqlConstant(Object? dart) {
if (dart == null) return 'NULL';
// todo: Inline and remove types in the next major drift version
if (dart is bool) {
return const BoolType().mapToSqlConstant(dart);
} else if (dart is String) {
return const StringType().mapToSqlConstant(dart);
} else if (dart is int) {
return const IntType().mapToSqlConstant(dart);
} else if (dart is DateTime) {
return const DateTimeType().mapToSqlConstant(dart);
} else if (dart is Uint8List) {
return const BlobType().mapToSqlConstant(dart);
} else if (dart is double) {
return const RealType().mapToSqlConstant(dart);
}
throw ArgumentError.value(dart, 'dart',
'Must be null, bool, String, int, DateTime, Uint8List or double');
}
}

View File

@ -6,7 +6,7 @@ import '../generated/todos.dart';
import '../test_utils/test_utils.dart';
class _FakeDb extends GeneratedDatabase {
_FakeDb(SqlTypeSystem types, QueryExecutor executor) : super(types, executor);
_FakeDb(QueryExecutor executor) : super(executor);
@override
MigrationStrategy get migration {
@ -49,7 +49,7 @@ void main() {
setUp(() {
executor = MockExecutor();
db = _FakeDb(SqlTypeSystem.defaultInstance, executor);
db = _FakeDb(executor);
});
test('onCreate', () async {

View File

@ -3,9 +3,9 @@ import 'package:test/test.dart';
void main() {
final nullable =
GeneratedColumn<DateTime>('name', 'table', true, type: const IntType());
GeneratedColumn<DateTime>('name', 'table', true, type: DriftSqlType.int);
final nonNull =
GeneratedColumn<DateTime>('name', 'table', false, type: const IntType());
GeneratedColumn<DateTime>('name', 'table', false, type: DriftSqlType.int);
test('should write column definition', () {
final nonNullQuery = GenerationContext.fromDb(null);

View File

@ -40,7 +40,7 @@ void main() {
user: Value(4),
);
final user = db.sharedTodos.mapFromCompanion(companion);
final user = db.sharedTodos.mapFromCompanion(companion, db);
expect(
user,
SharedTodo(todo: 3, user: 4),

View File

@ -149,8 +149,8 @@ class MockStreamQueries extends Mock implements StreamQueryStore {
DatabaseConnection createConnection(QueryExecutor executor,
[StreamQueryStore? streams]) {
return DatabaseConnection(
SqlTypeSystem.defaultInstance, executor, streams ?? StreamQueryStore());
return DatabaseConnection(executor,
streamQueries: streams ?? StreamQueryStore());
}
extension on Mock {

View File

@ -101,7 +101,7 @@ class _GenerateFromScratch extends GeneratedDatabase {
final GeneratedDatabase reference;
_GenerateFromScratch(this.reference, QueryExecutor executor)
: super(SqlTypeSystem.defaultInstance, executor);
: super(executor);
@override
Iterable<TableInfo<Table, dynamic>> get allTables => reference.allTables;

View File

@ -1,6 +1,5 @@
import 'package:analyzer/dart/ast/ast.dart';
import 'package:analyzer/dart/element/element.dart';
import 'package:drift/drift.dart';
import 'package:drift_dev/src/analyzer/options.dart';
import 'package:drift_dev/writer.dart';
import 'package:sqlparser/sqlparser.dart' show ReferenceAction;
@ -164,25 +163,6 @@ class DriftColumn implements HasDeclaration, HasType {
return (!checkNullable || nullable) ? '$code?' : code;
}
SqlType sqlType() {
switch (type) {
case ColumnType.integer:
return const IntType();
case ColumnType.bigInt:
return const BigIntType();
case ColumnType.boolean:
return const BoolType();
case ColumnType.datetime:
return const IntType();
case ColumnType.text:
return const StringType();
case ColumnType.blob:
return const BlobType();
case ColumnType.real:
return const RealType();
}
}
@override
bool get isArray => false;

View File

@ -1,5 +1,6 @@
import 'package:analyzer/dart/element/nullability_suffix.dart';
import 'package:analyzer/dart/element/type.dart';
import 'package:drift/drift.dart';
import 'package:drift_dev/src/model/model.dart';
import 'package:drift_dev/src/utils/type_utils.dart';
import 'package:drift_dev/writer.dart';
@ -117,6 +118,26 @@ extension OperationOnTypes on HasType {
return variableTypeCode(options);
}
DriftSqlType sqlType() {
// todo: Just replace ColumnType with DriftSqlType now?
switch (type) {
case ColumnType.integer:
return DriftSqlType.int;
case ColumnType.bigInt:
return DriftSqlType.bigInt;
case ColumnType.boolean:
return DriftSqlType.bool;
case ColumnType.datetime:
return DriftSqlType.dateTime;
case ColumnType.text:
return DriftSqlType.string;
case ColumnType.blob:
return DriftSqlType.blob;
case ColumnType.real:
return DriftSqlType.double;
}
}
}
const Map<ColumnType, String> dartTypeNames = {

View File

@ -80,8 +80,7 @@ class _TestDatabase extends GeneratedDatabase {
@override
MigrationStrategy migration = MigrationStrategy();
_TestDatabase(QueryExecutor executor, this.schemaVersion)
: super(const SqlTypeSystem.withDefaults(), executor);
_TestDatabase(QueryExecutor executor, this.schemaVersion) : super(executor);
@override
Iterable<TableInfo<Table, DataClass>> get allTables {

View File

@ -14,7 +14,7 @@ class SqfliteExecutor extends TestExecutor {
@override
DatabaseConnection createConnection() {
return DatabaseConnection.fromExecutor(
return DatabaseConnection(
SqfliteQueryExecutor.inDatabaseFolder(
path: 'app.db',
singleInstance: false,
@ -109,7 +109,7 @@ Future<void> main() async {
}
class EmptyDb extends GeneratedDatabase {
EmptyDb(QueryExecutor q) : super(SqlTypeSystem.defaultInstance, q);
EmptyDb(QueryExecutor q) : super(q);
@override
final List<TableInfo> allTables = const [];
@override