mirror of https://github.com/AMT-Cheif/drift.git
Handle nullable converters for compiled queries
This commit is contained in:
parent
4fae017d36
commit
bfad77788e
|
@ -114,6 +114,24 @@ abstract class NullAwareTypeConverter<D, S extends Object>
|
||||||
/// Map a non-null value from an object in Dart into something that will be
|
/// Map a non-null value from an object in Dart into something that will be
|
||||||
/// understood by the database.
|
/// understood by the database.
|
||||||
S requireToSql(D value);
|
S requireToSql(D value);
|
||||||
|
|
||||||
|
/// Invokes a non-nullable [inner] type converter for a single conversion from
|
||||||
|
/// SQL to Dart.
|
||||||
|
///
|
||||||
|
/// Returns `null` if [sqlValue] is `null`, [TypeConverter.fromSql] otherwise.
|
||||||
|
/// This method is mostly intended to be used for code generated by drift-dev.
|
||||||
|
static D? wrapFromSql<D, S>(TypeConverter<D, S> inner, S? sqlValue) {
|
||||||
|
return sqlValue == null ? null : inner.fromSql(sqlValue);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Invokes a non-nullable [inner] type converter for a single conversion from
|
||||||
|
/// Dart to SQL.
|
||||||
|
///
|
||||||
|
/// Returns `null` if [dartValue] is `null`, [TypeConverter.toSql] otherwise.
|
||||||
|
/// This method is mostly intended to be used for code generated by drift-dev.
|
||||||
|
static S? wrapToSql<D, S>(TypeConverter<D, S> inner, D? dartValue) {
|
||||||
|
return dartValue == null ? null : inner.toSql(dartValue);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
class _NullWrappingTypeConverter<D, S extends Object>
|
class _NullWrappingTypeConverter<D, S extends Object>
|
||||||
|
|
|
@ -24,7 +24,7 @@ class Config extends DataClass implements Insertable<Config> {
|
||||||
.mapFromDatabaseResponse(data['${effectivePrefix}config_key'])!,
|
.mapFromDatabaseResponse(data['${effectivePrefix}config_key'])!,
|
||||||
configValue: const StringType()
|
configValue: const StringType()
|
||||||
.mapFromDatabaseResponse(data['${effectivePrefix}config_value']),
|
.mapFromDatabaseResponse(data['${effectivePrefix}config_value']),
|
||||||
syncState: ConfigTable.$converter0.fromSql(const IntType()
|
syncState: ConfigTable.$converter0n.fromSql(const IntType()
|
||||||
.mapFromDatabaseResponse(data['${effectivePrefix}sync_state'])),
|
.mapFromDatabaseResponse(data['${effectivePrefix}sync_state'])),
|
||||||
syncStateImplicit: ConfigTable.$converter1.fromSql(const IntType()
|
syncStateImplicit: ConfigTable.$converter1.fromSql(const IntType()
|
||||||
.mapFromDatabaseResponse(
|
.mapFromDatabaseResponse(
|
||||||
|
@ -39,7 +39,7 @@ class Config extends DataClass implements Insertable<Config> {
|
||||||
map['config_value'] = Variable<String?>(configValue);
|
map['config_value'] = Variable<String?>(configValue);
|
||||||
}
|
}
|
||||||
if (!nullToAbsent || syncState != null) {
|
if (!nullToAbsent || syncState != null) {
|
||||||
final converter = ConfigTable.$converter0;
|
final converter = ConfigTable.$converter0n;
|
||||||
map['sync_state'] = Variable<int?>(converter.toSql(syncState));
|
map['sync_state'] = Variable<int?>(converter.toSql(syncState));
|
||||||
}
|
}
|
||||||
if (!nullToAbsent || syncStateImplicit != null) {
|
if (!nullToAbsent || syncStateImplicit != null) {
|
||||||
|
@ -182,7 +182,7 @@ class ConfigCompanion extends UpdateCompanion<Config> {
|
||||||
map['config_value'] = Variable<String?>(configValue.value);
|
map['config_value'] = Variable<String?>(configValue.value);
|
||||||
}
|
}
|
||||||
if (syncState.present) {
|
if (syncState.present) {
|
||||||
final converter = ConfigTable.$converter0;
|
final converter = ConfigTable.$converter0n;
|
||||||
map['sync_state'] = Variable<int?>(converter.toSql(syncState.value));
|
map['sync_state'] = Variable<int?>(converter.toSql(syncState.value));
|
||||||
}
|
}
|
||||||
if (syncStateImplicit.present) {
|
if (syncStateImplicit.present) {
|
||||||
|
@ -229,7 +229,7 @@ class ConfigTable extends Table with TableInfo<ConfigTable, Config> {
|
||||||
type: const IntType(),
|
type: const IntType(),
|
||||||
requiredDuringInsert: false,
|
requiredDuringInsert: false,
|
||||||
$customConstraints: '')
|
$customConstraints: '')
|
||||||
.withConverter<SyncType?>(ConfigTable.$converter0);
|
.withConverter<SyncType?>(ConfigTable.$converter0n);
|
||||||
final VerificationMeta _syncStateImplicitMeta =
|
final VerificationMeta _syncStateImplicitMeta =
|
||||||
const VerificationMeta('syncStateImplicit');
|
const VerificationMeta('syncStateImplicit');
|
||||||
late final GeneratedColumnWithTypeConverter<SyncType?, int?>
|
late final GeneratedColumnWithTypeConverter<SyncType?, int?>
|
||||||
|
@ -281,11 +281,12 @@ class ConfigTable extends Table with TableInfo<ConfigTable, Config> {
|
||||||
return ConfigTable(attachedDatabase, alias);
|
return ConfigTable(attachedDatabase, alias);
|
||||||
}
|
}
|
||||||
|
|
||||||
static TypeConverter<SyncType?, int?> $converter0 =
|
static TypeConverter<SyncType, int> $converter0 = const SyncTypeConverter();
|
||||||
NullAwareTypeConverter.wrap(const SyncTypeConverter());
|
|
||||||
static TypeConverter<SyncType?, int?> $converter1 =
|
static TypeConverter<SyncType?, int?> $converter1 =
|
||||||
const NullAwareTypeConverter.wrap(
|
const NullAwareTypeConverter.wrap(
|
||||||
EnumIndexConverter<SyncType>(SyncType.values));
|
EnumIndexConverter<SyncType>(SyncType.values));
|
||||||
|
static TypeConverter<SyncType?, int?> $converter0n =
|
||||||
|
NullAwareTypeConverter.wrap($converter0);
|
||||||
@override
|
@override
|
||||||
bool get isStrict => true;
|
bool get isStrict => true;
|
||||||
@override
|
@override
|
||||||
|
@ -1476,7 +1477,7 @@ class MyViewData extends DataClass {
|
||||||
.mapFromDatabaseResponse(data['${effectivePrefix}config_key'])!,
|
.mapFromDatabaseResponse(data['${effectivePrefix}config_key'])!,
|
||||||
configValue: const StringType()
|
configValue: const StringType()
|
||||||
.mapFromDatabaseResponse(data['${effectivePrefix}config_value']),
|
.mapFromDatabaseResponse(data['${effectivePrefix}config_value']),
|
||||||
syncState: ConfigTable.$converter0.fromSql(const IntType()
|
syncState: ConfigTable.$converter0n.fromSql(const IntType()
|
||||||
.mapFromDatabaseResponse(data['${effectivePrefix}sync_state'])),
|
.mapFromDatabaseResponse(data['${effectivePrefix}sync_state'])),
|
||||||
syncStateImplicit: ConfigTable.$converter1.fromSql(const IntType()
|
syncStateImplicit: ConfigTable.$converter1.fromSql(const IntType()
|
||||||
.mapFromDatabaseResponse(
|
.mapFromDatabaseResponse(
|
||||||
|
@ -1579,7 +1580,7 @@ class MyView extends ViewInfo<MyView, MyViewData> implements HasResultSet {
|
||||||
late final GeneratedColumnWithTypeConverter<SyncType?, int?> syncState =
|
late final GeneratedColumnWithTypeConverter<SyncType?, int?> syncState =
|
||||||
GeneratedColumn<int?>('sync_state', aliasedName, true,
|
GeneratedColumn<int?>('sync_state', aliasedName, true,
|
||||||
type: const IntType())
|
type: const IntType())
|
||||||
.withConverter<SyncType?>(ConfigTable.$converter0);
|
.withConverter<SyncType?>(ConfigTable.$converter0n);
|
||||||
late final GeneratedColumnWithTypeConverter<SyncType?, int?>
|
late final GeneratedColumnWithTypeConverter<SyncType?, int?>
|
||||||
syncStateImplicit = GeneratedColumn<int?>(
|
syncStateImplicit = GeneratedColumn<int?>(
|
||||||
'sync_state_implicit', aliasedName, true,
|
'sync_state_implicit', aliasedName, true,
|
||||||
|
@ -1677,7 +1678,8 @@ abstract class _$CustomTablesDb extends GeneratedDatabase {
|
||||||
return customSelect(
|
return customSelect(
|
||||||
'SELECT config_key FROM config WHERE ${generatedpred.sql} AND(sync_state = ?1 OR sync_state_implicit IN ($expandedvar2))',
|
'SELECT config_key FROM config WHERE ${generatedpred.sql} AND(sync_state = ?1 OR sync_state_implicit IN ($expandedvar2))',
|
||||||
variables: [
|
variables: [
|
||||||
Variable<int?>(ConfigTable.$converter0.toSql(var1)),
|
Variable<int?>(
|
||||||
|
NullAwareTypeConverter.wrapToSql(ConfigTable.$converter0, var1)),
|
||||||
...generatedpred.introducedVariables,
|
...generatedpred.introducedVariables,
|
||||||
for (var $ in var2) Variable<int?>(ConfigTable.$converter1.toSql($))
|
for (var $ in var2) Variable<int?>(ConfigTable.$converter1.toSql($))
|
||||||
],
|
],
|
||||||
|
@ -1775,8 +1777,8 @@ abstract class _$CustomTablesDb extends GeneratedDatabase {
|
||||||
rowid: row.read<int>('rowid'),
|
rowid: row.read<int>('rowid'),
|
||||||
configKey: row.read<String>('config_key'),
|
configKey: row.read<String>('config_key'),
|
||||||
configValue: row.read<String?>('config_value'),
|
configValue: row.read<String?>('config_value'),
|
||||||
syncState:
|
syncState: NullAwareTypeConverter.wrapFromSql(
|
||||||
ConfigTable.$converter0.fromSql(row.read<int?>('sync_state')),
|
ConfigTable.$converter0, row.read<int?>('sync_state')),
|
||||||
syncStateImplicit: ConfigTable.$converter1
|
syncStateImplicit: ConfigTable.$converter1
|
||||||
.fromSql(row.read<int?>('sync_state_implicit')),
|
.fromSql(row.read<int?>('sync_state_implicit')),
|
||||||
);
|
);
|
||||||
|
|
|
@ -37,7 +37,7 @@ class Category extends DataClass implements Insertable<Category> {
|
||||||
map['desc'] = Variable<String>(description);
|
map['desc'] = Variable<String>(description);
|
||||||
{
|
{
|
||||||
final converter = $CategoriesTable.$converter0;
|
final converter = $CategoriesTable.$converter0;
|
||||||
map['priority'] = Variable<int>(converter.toSql(priority)!);
|
map['priority'] = Variable<int>(converter.toSql(priority));
|
||||||
}
|
}
|
||||||
return map;
|
return map;
|
||||||
}
|
}
|
||||||
|
@ -162,7 +162,7 @@ class CategoriesCompanion extends UpdateCompanion<Category> {
|
||||||
}
|
}
|
||||||
if (priority.present) {
|
if (priority.present) {
|
||||||
final converter = $CategoriesTable.$converter0;
|
final converter = $CategoriesTable.$converter0;
|
||||||
map['priority'] = Variable<int>(converter.toSql(priority.value)!);
|
map['priority'] = Variable<int>(converter.toSql(priority.value));
|
||||||
}
|
}
|
||||||
return map;
|
return map;
|
||||||
}
|
}
|
||||||
|
@ -1109,7 +1109,7 @@ class TableWithoutPKCompanion extends UpdateCompanion<CustomRowClass> {
|
||||||
}
|
}
|
||||||
if (custom.present) {
|
if (custom.present) {
|
||||||
final converter = $TableWithoutPKTable.$converter0;
|
final converter = $TableWithoutPKTable.$converter0;
|
||||||
map['custom'] = Variable<String>(converter.toSql(custom.value)!);
|
map['custom'] = Variable<String>(converter.toSql(custom.value));
|
||||||
}
|
}
|
||||||
return map;
|
return map;
|
||||||
}
|
}
|
||||||
|
@ -1246,7 +1246,7 @@ class PureDefault extends DataClass implements Insertable<PureDefault> {
|
||||||
factory PureDefault.fromData(Map<String, dynamic> data, {String? prefix}) {
|
factory PureDefault.fromData(Map<String, dynamic> data, {String? prefix}) {
|
||||||
final effectivePrefix = prefix ?? '';
|
final effectivePrefix = prefix ?? '';
|
||||||
return PureDefault(
|
return PureDefault(
|
||||||
txt: $PureDefaultsTable.$converter0.fromSql(const StringType()
|
txt: $PureDefaultsTable.$converter0n.fromSql(const StringType()
|
||||||
.mapFromDatabaseResponse(data['${effectivePrefix}insert'])),
|
.mapFromDatabaseResponse(data['${effectivePrefix}insert'])),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
@ -1254,7 +1254,7 @@ class PureDefault extends DataClass implements Insertable<PureDefault> {
|
||||||
Map<String, Expression> toColumns(bool nullToAbsent) {
|
Map<String, Expression> toColumns(bool nullToAbsent) {
|
||||||
final map = <String, Expression>{};
|
final map = <String, Expression>{};
|
||||||
if (!nullToAbsent || txt != null) {
|
if (!nullToAbsent || txt != null) {
|
||||||
final converter = $PureDefaultsTable.$converter0;
|
final converter = $PureDefaultsTable.$converter0n;
|
||||||
map['insert'] = Variable<String?>(converter.toSql(txt));
|
map['insert'] = Variable<String?>(converter.toSql(txt));
|
||||||
}
|
}
|
||||||
return map;
|
return map;
|
||||||
|
@ -1270,7 +1270,7 @@ class PureDefault extends DataClass implements Insertable<PureDefault> {
|
||||||
{ValueSerializer? serializer}) {
|
{ValueSerializer? serializer}) {
|
||||||
serializer ??= driftRuntimeOptions.defaultSerializer;
|
serializer ??= driftRuntimeOptions.defaultSerializer;
|
||||||
return PureDefault(
|
return PureDefault(
|
||||||
txt: $PureDefaultsTable.$converter0
|
txt: $PureDefaultsTable.$converter0n
|
||||||
.fromJson(serializer.fromJson<String?>(json['txt'])),
|
.fromJson(serializer.fromJson<String?>(json['txt'])),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
@ -1284,7 +1284,7 @@ class PureDefault extends DataClass implements Insertable<PureDefault> {
|
||||||
serializer ??= driftRuntimeOptions.defaultSerializer;
|
serializer ??= driftRuntimeOptions.defaultSerializer;
|
||||||
return <String, dynamic>{
|
return <String, dynamic>{
|
||||||
'txt': serializer
|
'txt': serializer
|
||||||
.toJson<String?>($PureDefaultsTable.$converter0.toJson(txt)),
|
.toJson<String?>($PureDefaultsTable.$converter0n.toJson(txt)),
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1333,7 +1333,7 @@ class PureDefaultsCompanion extends UpdateCompanion<PureDefault> {
|
||||||
Map<String, Expression> toColumns(bool nullToAbsent) {
|
Map<String, Expression> toColumns(bool nullToAbsent) {
|
||||||
final map = <String, Expression>{};
|
final map = <String, Expression>{};
|
||||||
if (txt.present) {
|
if (txt.present) {
|
||||||
final converter = $PureDefaultsTable.$converter0;
|
final converter = $PureDefaultsTable.$converter0n;
|
||||||
map['insert'] = Variable<String?>(converter.toSql(txt.value));
|
map['insert'] = Variable<String?>(converter.toSql(txt.value));
|
||||||
}
|
}
|
||||||
return map;
|
return map;
|
||||||
|
@ -1359,7 +1359,7 @@ class $PureDefaultsTable extends PureDefaults
|
||||||
late final GeneratedColumnWithTypeConverter<MyCustomObject?, String?> txt =
|
late final GeneratedColumnWithTypeConverter<MyCustomObject?, String?> txt =
|
||||||
GeneratedColumn<String?>('insert', aliasedName, true,
|
GeneratedColumn<String?>('insert', aliasedName, true,
|
||||||
type: const StringType(), requiredDuringInsert: false)
|
type: const StringType(), requiredDuringInsert: false)
|
||||||
.withConverter<MyCustomObject?>($PureDefaultsTable.$converter0);
|
.withConverter<MyCustomObject?>($PureDefaultsTable.$converter0n);
|
||||||
@override
|
@override
|
||||||
List<GeneratedColumn> get $columns => [txt];
|
List<GeneratedColumn> get $columns => [txt];
|
||||||
@override
|
@override
|
||||||
|
@ -1388,8 +1388,10 @@ class $PureDefaultsTable extends PureDefaults
|
||||||
return $PureDefaultsTable(attachedDatabase, alias);
|
return $PureDefaultsTable(attachedDatabase, alias);
|
||||||
}
|
}
|
||||||
|
|
||||||
static JsonTypeConverter<MyCustomObject?, String?> $converter0 =
|
static JsonTypeConverter<MyCustomObject, String> $converter0 =
|
||||||
JsonTypeConverter.asNullable(const CustomJsonConverter());
|
const CustomJsonConverter();
|
||||||
|
static JsonTypeConverter<MyCustomObject?, String?> $converter0n =
|
||||||
|
JsonTypeConverter.asNullable($converter0);
|
||||||
}
|
}
|
||||||
|
|
||||||
class CategoryTodoCountViewData extends DataClass {
|
class CategoryTodoCountViewData extends DataClass {
|
||||||
|
@ -1689,7 +1691,7 @@ abstract class _$TodoDb extends GeneratedDatabase {
|
||||||
readsFrom: {
|
readsFrom: {
|
||||||
tableWithoutPK,
|
tableWithoutPK,
|
||||||
}).map((QueryRow row) =>
|
}).map((QueryRow row) =>
|
||||||
$TableWithoutPKTable.$converter0.fromSql(row.read<String>('custom'))!);
|
$TableWithoutPKTable.$converter0.fromSql(row.read<String>('custom')));
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
|
|
|
@ -135,16 +135,15 @@ UsedTypeConverter? readTypeConverter(
|
||||||
final appliesToJsonToo = helper.isJsonAwareTypeConverter(staticType, library);
|
final appliesToJsonToo = helper.isJsonAwareTypeConverter(staticType, library);
|
||||||
|
|
||||||
// Make the type converter support nulls by just mapping null to null if this
|
// Make the type converter support nulls by just mapping null to null if this
|
||||||
// converter is otherwise non-nullable in both directions but applied on a
|
// converter is otherwise non-nullable in both directions.
|
||||||
// nullable column
|
final canBeSkippedForNulls = !dartTypeNullable && !sqlTypeNullable;
|
||||||
final skipForNull = !dartTypeNullable && !sqlTypeNullable && columnIsNullable;
|
|
||||||
|
|
||||||
if (sqlTypeNullable != columnIsNullable) {
|
if (sqlTypeNullable != columnIsNullable) {
|
||||||
if (!columnIsNullable) {
|
if (!columnIsNullable) {
|
||||||
reportError('This column is non-nullable in the database, but has a '
|
reportError('This column is non-nullable in the database, but has a '
|
||||||
'type converter with a nullable SQL type, meaning that it may '
|
'type converter with a nullable SQL type, meaning that it may '
|
||||||
"potentially map to `null` which can't be stored in the database.");
|
"potentially map to `null` which can't be stored in the database.");
|
||||||
} else if (!skipForNull) {
|
} else if (!canBeSkippedForNulls) {
|
||||||
final alternative = appliesToJsonToo
|
final alternative = appliesToJsonToo
|
||||||
? 'JsonTypeConverter.asNullable'
|
? 'JsonTypeConverter.asNullable'
|
||||||
: 'NullAwareTypeConverter.wrap';
|
: 'NullAwareTypeConverter.wrap';
|
||||||
|
@ -156,7 +155,7 @@ UsedTypeConverter? readTypeConverter(
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
_checkType(columnType, null, sqlType, library.typeProvider,
|
_checkType(columnType, columnIsNullable, null, sqlType, library.typeProvider,
|
||||||
library.typeSystem, reportError);
|
library.typeSystem, reportError);
|
||||||
|
|
||||||
return UsedTypeConverter(
|
return UsedTypeConverter(
|
||||||
|
@ -166,7 +165,7 @@ UsedTypeConverter? readTypeConverter(
|
||||||
dartTypeIsNullable: dartTypeNullable,
|
dartTypeIsNullable: dartTypeNullable,
|
||||||
sqlTypeIsNullable: sqlTypeNullable,
|
sqlTypeIsNullable: sqlTypeNullable,
|
||||||
alsoAppliesToJsonConversion: appliesToJsonToo,
|
alsoAppliesToJsonConversion: appliesToJsonToo,
|
||||||
skipForNulls: skipForNull,
|
canBeSkippedForNulls: canBeSkippedForNulls,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -184,7 +183,7 @@ void _checkParameterType(
|
||||||
}
|
}
|
||||||
|
|
||||||
final nullableDartType = column.typeConverter != null
|
final nullableDartType = column.typeConverter != null
|
||||||
? column.typeConverter!.mapsToNullableDart
|
? column.typeConverter!.mapsToNullableDart(column.nullable)
|
||||||
: column.nullableInDart;
|
: column.nullableInDart;
|
||||||
|
|
||||||
if (library.isNonNullableByDefault &&
|
if (library.isNonNullableByDefault &&
|
||||||
|
@ -197,6 +196,7 @@ void _checkParameterType(
|
||||||
|
|
||||||
_checkType(
|
_checkType(
|
||||||
column.type,
|
column.type,
|
||||||
|
column.nullable,
|
||||||
column.typeConverter,
|
column.typeConverter,
|
||||||
element.type,
|
element.type,
|
||||||
library.typeProvider,
|
library.typeProvider,
|
||||||
|
@ -207,6 +207,7 @@ void _checkParameterType(
|
||||||
|
|
||||||
void _checkType(
|
void _checkType(
|
||||||
ColumnType columnType,
|
ColumnType columnType,
|
||||||
|
bool columnIsNullable,
|
||||||
UsedTypeConverter? typeConverter,
|
UsedTypeConverter? typeConverter,
|
||||||
DartType typeToCheck,
|
DartType typeToCheck,
|
||||||
TypeProvider typeProvider,
|
TypeProvider typeProvider,
|
||||||
|
@ -216,7 +217,7 @@ void _checkType(
|
||||||
DriftDartType expectedDartType;
|
DriftDartType expectedDartType;
|
||||||
if (typeConverter != null) {
|
if (typeConverter != null) {
|
||||||
expectedDartType = typeConverter.dartType;
|
expectedDartType = typeConverter.dartType;
|
||||||
if (typeConverter.skipForNulls) {
|
if (typeConverter.canBeSkippedForNulls && columnIsNullable) {
|
||||||
typeToCheck = typeSystem.promoteToNonNull(typeToCheck);
|
typeToCheck = typeSystem.promoteToNonNull(typeToCheck);
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
|
|
|
@ -75,7 +75,8 @@ class DriftDartType {
|
||||||
extension OperationOnTypes on HasType {
|
extension OperationOnTypes on HasType {
|
||||||
/// Whether this type is nullable in Dart
|
/// Whether this type is nullable in Dart
|
||||||
bool get nullableInDart {
|
bool get nullableInDart {
|
||||||
return (nullable && !isArray) || typeConverter?.mapsToNullableDart == true;
|
return (nullable && !isArray) ||
|
||||||
|
typeConverter?.mapsToNullableDart(nullable) == true;
|
||||||
}
|
}
|
||||||
|
|
||||||
/// the Dart type of this column that can be handled by moors type mapping.
|
/// the Dart type of this column that can be handled by moors type mapping.
|
||||||
|
@ -111,7 +112,7 @@ extension OperationOnTypes on HasType {
|
||||||
final converter = typeConverter;
|
final converter = typeConverter;
|
||||||
if (converter != null) {
|
if (converter != null) {
|
||||||
var inner = converter.dartType.codeString(options);
|
var inner = converter.dartType.codeString(options);
|
||||||
if (converter.skipForNulls) inner += '?';
|
if (converter.canBeSkippedForNulls && nullable) inner += '?';
|
||||||
return isArray ? 'List<$inner>' : inner;
|
return isArray ? 'List<$inner>' : inner;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -46,22 +46,36 @@ class UsedTypeConverter {
|
||||||
/// serialization.
|
/// serialization.
|
||||||
final bool alsoAppliesToJsonConversion;
|
final bool alsoAppliesToJsonConversion;
|
||||||
|
|
||||||
/// Whether this type converter should be skipped for `null` values.
|
/// Whether this type converter can be skipped for `null` values.
|
||||||
///
|
///
|
||||||
/// This applies to type converters with a non-nullable Dart and SQL type if
|
/// This applies to type converters with a non-nullable Dart and SQL type if
|
||||||
/// the column is nullable. For those converters, drift maps `null` to `null`
|
/// the column is nullable. For those converters, drift maps `null` to `null`
|
||||||
/// without calling the type converter at all.
|
/// without calling the type converter at all.
|
||||||
///
|
///
|
||||||
/// This is implemented by wrapping it in a `NullAwareTypeConverter` in the
|
/// For nullable columns, this is implemented by wrapping it in a
|
||||||
/// generated code.
|
/// `NullAwareTypeConverter` in the generated code for table classes. For
|
||||||
final bool skipForNulls;
|
/// nullable references to non-nullable columns (e.g. from outer joins), this
|
||||||
|
/// is done with static helper methods on `NullAwareTypeConverter`.
|
||||||
|
final bool canBeSkippedForNulls;
|
||||||
|
|
||||||
/// Type converters are stored as static fields in the table that created
|
/// Type converters are stored as static fields in the table that created
|
||||||
/// them. This will be the field name for this converter.
|
/// them. This will be the field name for this converter.
|
||||||
String get fieldName => '\$converter$index';
|
String get fieldName => '\$converter$index';
|
||||||
|
|
||||||
|
/// If this converter [canBeSkippedForNulls] and is applied to a nullable
|
||||||
|
/// column, drift generates a new wrapped type converter which will deal with
|
||||||
|
/// `null` values.
|
||||||
|
/// That converter is stored in this field.
|
||||||
|
String get nullableFieldName => '${fieldName}n';
|
||||||
|
|
||||||
/// A Dart expression resolving to this converter.
|
/// A Dart expression resolving to this converter.
|
||||||
String get tableAndField => '${table!.entityInfoName}.$fieldName';
|
String tableAndField({bool forNullableColumn = false}) {
|
||||||
|
final field = canBeSkippedForNulls && forNullableColumn
|
||||||
|
? nullableFieldName
|
||||||
|
: fieldName;
|
||||||
|
|
||||||
|
return '${table!.entityInfoName}.$field';
|
||||||
|
}
|
||||||
|
|
||||||
UsedTypeConverter({
|
UsedTypeConverter({
|
||||||
required this.expression,
|
required this.expression,
|
||||||
|
@ -70,7 +84,7 @@ class UsedTypeConverter {
|
||||||
required this.dartTypeIsNullable,
|
required this.dartTypeIsNullable,
|
||||||
required this.sqlTypeIsNullable,
|
required this.sqlTypeIsNullable,
|
||||||
this.alsoAppliesToJsonConversion = false,
|
this.alsoAppliesToJsonConversion = false,
|
||||||
this.skipForNulls = false,
|
this.canBeSkippedForNulls = false,
|
||||||
});
|
});
|
||||||
|
|
||||||
factory UsedTypeConverter.forEnumColumn(
|
factory UsedTypeConverter.forEnumColumn(
|
||||||
|
@ -114,24 +128,27 @@ class UsedTypeConverter {
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
bool get mapsToNullableDart => dartTypeIsNullable || skipForNulls;
|
bool mapsToNullableDart(bool nullableInSql) {
|
||||||
|
return dartTypeIsNullable || (canBeSkippedForNulls && nullableInSql);
|
||||||
|
}
|
||||||
|
|
||||||
String dartTypeCode(GenerationOptions options) {
|
String dartTypeCode(GenerationOptions options, bool nullableInSql) {
|
||||||
var type = dartType.codeString(options);
|
var type = dartType.codeString(options);
|
||||||
if (options.nnbd && skipForNulls) type += '?';
|
if (options.nnbd && (canBeSkippedForNulls && nullableInSql)) type += '?';
|
||||||
|
|
||||||
return type;
|
return type;
|
||||||
}
|
}
|
||||||
|
|
||||||
/// A suitable typename to store an instance of the type converter used here.
|
/// A suitable typename to store an instance of the type converter used here.
|
||||||
String converterNameInCode(GenerationOptions options) {
|
String converterNameInCode(GenerationOptions options,
|
||||||
|
{bool makeNullable = false}) {
|
||||||
var sqlDartType = sqlType.getDisplayString(withNullability: options.nnbd);
|
var sqlDartType = sqlType.getDisplayString(withNullability: options.nnbd);
|
||||||
if (options.nnbd && skipForNulls) sqlDartType += '?';
|
if (makeNullable) sqlDartType += '?';
|
||||||
|
|
||||||
final className =
|
final className =
|
||||||
alsoAppliesToJsonConversion ? 'JsonTypeConverter' : 'TypeConverter';
|
alsoAppliesToJsonConversion ? 'JsonTypeConverter' : 'TypeConverter';
|
||||||
|
|
||||||
return '$className<${dartTypeCode(options)}, $sqlDartType>';
|
return '$className<${dartTypeCode(options, makeNullable)}, $sqlDartType>';
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -182,13 +182,18 @@ class QueryWriter {
|
||||||
final dartLiteral = asDartLiteral(specialName ?? column.name);
|
final dartLiteral = asDartLiteral(specialName ?? column.name);
|
||||||
var code = 'row.read<$rawDartType>($dartLiteral)';
|
var code = 'row.read<$rawDartType>($dartLiteral)';
|
||||||
|
|
||||||
if (column.typeConverter != null) {
|
final converter = column.typeConverter;
|
||||||
final nullableDartType = column.typeConverter!.mapsToNullableDart;
|
if (converter != null) {
|
||||||
final needsAssert = !nullableDartType && generationOptions.nnbd;
|
if (converter.canBeSkippedForNulls && column.nullable) {
|
||||||
|
// The type converter maps non-nullable types, but the column may be
|
||||||
final converter = column.typeConverter;
|
// nullable in SQL => just map null to null and only invoke the type
|
||||||
code = '${_converter(converter!)}.fromSql($code)';
|
// converter for non-null values.
|
||||||
if (needsAssert) code += '!';
|
code = 'NullAwareTypeConverter.wrapFromSql(${_converter(converter)}, '
|
||||||
|
'$code)';
|
||||||
|
} else {
|
||||||
|
// Just apply the type converter directly.
|
||||||
|
code = '${_converter(converter)}.fromSql($code)';
|
||||||
|
}
|
||||||
}
|
}
|
||||||
return code;
|
return code;
|
||||||
}
|
}
|
||||||
|
@ -865,9 +870,16 @@ class _ExpandedVariableWriter {
|
||||||
final buffer = StringBuffer('Variable<$type>(');
|
final buffer = StringBuffer('Variable<$type>(');
|
||||||
final capture = element.forCaptured;
|
final capture = element.forCaptured;
|
||||||
|
|
||||||
if (element.typeConverter != null) {
|
final converter = element.typeConverter;
|
||||||
// Apply the converter
|
if (converter != null) {
|
||||||
buffer.write('${_converter(element.typeConverter!)}.toSql($dartExpr)');
|
// Apply the converter.
|
||||||
|
if (element.nullable && converter.canBeSkippedForNulls) {
|
||||||
|
buffer.write('NullAwareTypeConverter.wrapToSql('
|
||||||
|
'${_converter(element.typeConverter!)}, $dartExpr)');
|
||||||
|
} else {
|
||||||
|
buffer
|
||||||
|
.write('${_converter(element.typeConverter!)}.toSql($dartExpr)');
|
||||||
|
}
|
||||||
|
|
||||||
final needsNullAssertion =
|
final needsNullAssertion =
|
||||||
!element.nullable && scope.generationOptions.nnbd;
|
!element.nullable && scope.generationOptions.nnbd;
|
||||||
|
|
|
@ -59,7 +59,7 @@ class DataClassWriter {
|
||||||
..write('({')
|
..write('({')
|
||||||
..write(columns.map((column) {
|
..write(columns.map((column) {
|
||||||
final nullableDartType = column.typeConverter != null
|
final nullableDartType = column.typeConverter != null
|
||||||
? column.typeConverter!.mapsToNullableDart
|
? column.typeConverter!.mapsToNullableDart(column.nullable)
|
||||||
: column.nullable;
|
: column.nullable;
|
||||||
|
|
||||||
if (nullableDartType) {
|
if (nullableDartType) {
|
||||||
|
@ -143,10 +143,11 @@ class DataClassWriter {
|
||||||
if (typeConverter != null && typeConverter.alsoAppliesToJsonConversion) {
|
if (typeConverter != null && typeConverter.alsoAppliesToJsonConversion) {
|
||||||
final type = column.innerColumnType(scope.generationOptions);
|
final type = column.innerColumnType(scope.generationOptions);
|
||||||
final fromConverter = "serializer.fromJson<$type>(json['$jsonKey'])";
|
final fromConverter = "serializer.fromJson<$type>(json['$jsonKey'])";
|
||||||
|
final converterField =
|
||||||
|
typeConverter.tableAndField(forNullableColumn: column.nullable);
|
||||||
final notNull =
|
final notNull =
|
||||||
!column.nullable && scope.generationOptions.nnbd ? '!' : '';
|
!column.nullable && scope.generationOptions.nnbd ? '!' : '';
|
||||||
deserialized =
|
deserialized = '$converterField.fromJson($fromConverter)$notNull';
|
||||||
'${typeConverter.tableAndField}.fromJson($fromConverter)$notNull';
|
|
||||||
} else {
|
} else {
|
||||||
final type = column.dartTypeCode(scope.generationOptions);
|
final type = column.dartTypeCode(scope.generationOptions);
|
||||||
|
|
||||||
|
@ -183,7 +184,9 @@ class DataClassWriter {
|
||||||
|
|
||||||
final typeConverter = column.typeConverter;
|
final typeConverter = column.typeConverter;
|
||||||
if (typeConverter != null && typeConverter.alsoAppliesToJsonConversion) {
|
if (typeConverter != null && typeConverter.alsoAppliesToJsonConversion) {
|
||||||
value = '${typeConverter.tableAndField}.toJson($value)';
|
final converterField =
|
||||||
|
typeConverter.tableAndField(forNullableColumn: column.nullable);
|
||||||
|
value = '$converterField.toJson($value)';
|
||||||
dartType = '${column.innerColumnType(scope.generationOptions)}';
|
dartType = '${column.innerColumnType(scope.generationOptions)}';
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -268,14 +271,13 @@ class DataClassWriter {
|
||||||
if (column.typeConverter != null) {
|
if (column.typeConverter != null) {
|
||||||
// apply type converter before writing the variable
|
// apply type converter before writing the variable
|
||||||
final converter = column.typeConverter;
|
final converter = column.typeConverter;
|
||||||
final fieldName = converter!.tableAndField;
|
final fieldName =
|
||||||
final assertNotNull = !column.nullable && scope.generationOptions.nnbd;
|
converter!.tableAndField(forNullableColumn: column.nullable);
|
||||||
|
|
||||||
_buffer
|
_buffer
|
||||||
..write('final converter = $fieldName;\n')
|
..write('final converter = $fieldName;\n')
|
||||||
..write(mapSetter)
|
..write(mapSetter)
|
||||||
..write('(converter.toSql(${column.dartGetterName})');
|
..write('(converter.toSql(${column.dartGetterName})');
|
||||||
if (assertNotNull) _buffer.write('!');
|
|
||||||
_buffer.write(');');
|
_buffer.write(');');
|
||||||
} else {
|
} else {
|
||||||
// no type converter. Write variable directly
|
// no type converter. Write variable directly
|
||||||
|
@ -375,9 +377,8 @@ class RowMappingWriter {
|
||||||
// result.
|
// result.
|
||||||
if (column.typeConverter != null) {
|
if (column.typeConverter != null) {
|
||||||
// stored as a static field
|
// stored as a static field
|
||||||
final converter = column.typeConverter!;
|
final loaded = column.typeConverter!
|
||||||
final loaded =
|
.tableAndField(forNullableColumn: column.nullable);
|
||||||
'${converter.table!.entityInfoName}.${converter.fieldName}';
|
|
||||||
loadType = '$loaded.fromSql($loadType)';
|
loadType = '$loaded.fromSql($loadType)';
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -100,14 +100,17 @@ abstract class TableOrViewWriter {
|
||||||
if (converter != null) {
|
if (converter != null) {
|
||||||
// Generate a GeneratedColumnWithTypeConverter instance, as it has
|
// Generate a GeneratedColumnWithTypeConverter instance, as it has
|
||||||
// additional methods to check for equality against a mapped value.
|
// additional methods to check for equality against a mapped value.
|
||||||
final mappedType = converter.dartTypeCode(options);
|
final mappedType = converter.dartTypeCode(options, column.nullable);
|
||||||
|
|
||||||
|
final converterCode =
|
||||||
|
converter.tableAndField(forNullableColumn: column.nullable);
|
||||||
|
|
||||||
type = 'GeneratedColumnWithTypeConverter<$mappedType, $innerType>';
|
type = 'GeneratedColumnWithTypeConverter<$mappedType, $innerType>';
|
||||||
expressionBuffer
|
expressionBuffer
|
||||||
..write('.withConverter<')
|
..write('.withConverter<')
|
||||||
..write(mappedType)
|
..write(mappedType)
|
||||||
..write('>(')
|
..write('>(')
|
||||||
..write(converter.tableAndField)
|
..write(converterCode)
|
||||||
..write(')');
|
..write(')');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -311,18 +314,32 @@ class TableWriter extends TableOrViewWriter {
|
||||||
void _writeConvertersAsStaticFields() {
|
void _writeConvertersAsStaticFields() {
|
||||||
for (final converter in table.converters) {
|
for (final converter in table.converters) {
|
||||||
final typeName = converter.converterNameInCode(scope.generationOptions);
|
final typeName = converter.converterNameInCode(scope.generationOptions);
|
||||||
var code = converter.expression;
|
final code = converter.expression;
|
||||||
|
|
||||||
if (converter.skipForNulls) {
|
|
||||||
if (converter.alsoAppliesToJsonConversion) {
|
|
||||||
code = 'JsonTypeConverter.asNullable($code)';
|
|
||||||
} else {
|
|
||||||
code = 'NullAwareTypeConverter.wrap($code)';
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
buffer.write('static $typeName ${converter.fieldName} = $code;');
|
buffer.write('static $typeName ${converter.fieldName} = $code;');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Generate wrappers for non-nullable type converters that are applied to
|
||||||
|
// nullable converters.
|
||||||
|
for (final column in table.columns) {
|
||||||
|
final converter = column.typeConverter;
|
||||||
|
if (converter != null &&
|
||||||
|
converter.canBeSkippedForNulls &&
|
||||||
|
column.nullable) {
|
||||||
|
final nullableTypeName = converter
|
||||||
|
.converterNameInCode(scope.generationOptions, makeNullable: true);
|
||||||
|
|
||||||
|
final wrap = converter.alsoAppliesToJsonConversion
|
||||||
|
? 'JsonTypeConverter.asNullable'
|
||||||
|
: 'NullAwareTypeConverter.wrap';
|
||||||
|
|
||||||
|
final code = '$wrap(${converter.fieldName})';
|
||||||
|
|
||||||
|
buffer
|
||||||
|
.write('static $nullableTypeName ${converter.nullableFieldName} = '
|
||||||
|
'$code;');
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void _writeColumnVerificationMeta(MoorColumn column) {
|
void _writeColumnVerificationMeta(MoorColumn column) {
|
||||||
|
|
|
@ -188,16 +188,13 @@ class UpdateCompanionWriter {
|
||||||
final converter = column.typeConverter;
|
final converter = column.typeConverter;
|
||||||
if (converter != null) {
|
if (converter != null) {
|
||||||
// apply type converter before writing the variable
|
// apply type converter before writing the variable
|
||||||
final fieldName = '${table.entityInfoName}.${converter.fieldName}';
|
final fieldName =
|
||||||
|
converter.tableAndField(forNullableColumn: column.nullable);
|
||||||
_buffer
|
_buffer
|
||||||
..write('final converter = $fieldName;\n')
|
..write('final converter = $fieldName;\n')
|
||||||
..write(mapSetter)
|
..write(mapSetter)
|
||||||
..write('(converter.toSql($getterName.value)');
|
..write('(converter.toSql($getterName.value)')
|
||||||
|
..write(');');
|
||||||
if (!column.nullable && scope.generationOptions.nnbd) {
|
|
||||||
_buffer.write('!');
|
|
||||||
}
|
|
||||||
_buffer.write(');');
|
|
||||||
} else {
|
} else {
|
||||||
// no type converter. Write variable directly
|
// no type converter. Write variable directly
|
||||||
_buffer
|
_buffer
|
||||||
|
|
|
@ -94,7 +94,7 @@ CREATE TABLE users (
|
||||||
);
|
);
|
||||||
|
|
||||||
final implicitlyNullAware = table.columns[3];
|
final implicitlyNullAware = table.columns[3];
|
||||||
expect(implicitlyNullAware.typeConverter?.skipForNulls, isTrue);
|
expect(implicitlyNullAware.typeConverter?.canBeSkippedForNulls, isTrue);
|
||||||
});
|
});
|
||||||
|
|
||||||
test('json converters in drift files', () {
|
test('json converters in drift files', () {
|
||||||
|
|
|
@ -20,7 +20,7 @@ class Category extends DataClass implements Insertable<Category> {
|
||||||
name: const StringType()
|
name: const StringType()
|
||||||
.mapFromDatabaseResponse(data['${effectivePrefix}name'])!,
|
.mapFromDatabaseResponse(data['${effectivePrefix}name'])!,
|
||||||
color: $CategoriesTable.$converter0.fromSql(const IntType()
|
color: $CategoriesTable.$converter0.fromSql(const IntType()
|
||||||
.mapFromDatabaseResponse(data['${effectivePrefix}color']))!,
|
.mapFromDatabaseResponse(data['${effectivePrefix}color'])!),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@override
|
@override
|
||||||
|
@ -30,7 +30,7 @@ class Category extends DataClass implements Insertable<Category> {
|
||||||
map['name'] = Variable<String>(name);
|
map['name'] = Variable<String>(name);
|
||||||
{
|
{
|
||||||
final converter = $CategoriesTable.$converter0;
|
final converter = $CategoriesTable.$converter0;
|
||||||
map['color'] = Variable<int>(converter.toSql(color)!);
|
map['color'] = Variable<int>(converter.toSql(color));
|
||||||
}
|
}
|
||||||
return map;
|
return map;
|
||||||
}
|
}
|
||||||
|
@ -135,7 +135,7 @@ class CategoriesCompanion extends UpdateCompanion<Category> {
|
||||||
}
|
}
|
||||||
if (color.present) {
|
if (color.present) {
|
||||||
final converter = $CategoriesTable.$converter0;
|
final converter = $CategoriesTable.$converter0;
|
||||||
map['color'] = Variable<int>(converter.toSql(color.value)!);
|
map['color'] = Variable<int>(converter.toSql(color.value));
|
||||||
}
|
}
|
||||||
return map;
|
return map;
|
||||||
}
|
}
|
||||||
|
@ -646,7 +646,8 @@ abstract class _$AppDatabase extends GeneratedDatabase {
|
||||||
return CategoriesWithCountResult(
|
return CategoriesWithCountResult(
|
||||||
id: row.read<int?>('id'),
|
id: row.read<int?>('id'),
|
||||||
name: row.read<String?>('name'),
|
name: row.read<String?>('name'),
|
||||||
color: $CategoriesTable.$converter0.fromSql(row.read<int?>('color')),
|
color: NullAwareTypeConverter.wrapFromSql(
|
||||||
|
$CategoriesTable.$converter0, row.read<int?>('color')),
|
||||||
amount: row.read<int>('amount'),
|
amount: row.read<int>('amount'),
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
|
@ -35,12 +35,12 @@ mixin AutoIncrementingPrimaryKey on Table {
|
||||||
IntColumn get id => integer().autoIncrement()();
|
IntColumn get id => integer().autoIncrement()();
|
||||||
}
|
}
|
||||||
|
|
||||||
class ColorConverter extends NullAwareTypeConverter<Color, int> {
|
class ColorConverter extends TypeConverter<Color, int> {
|
||||||
const ColorConverter();
|
const ColorConverter();
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Color requireFromSql(int fromDb) => Color(fromDb);
|
Color fromSql(int fromDb) => Color(fromDb);
|
||||||
|
|
||||||
@override
|
@override
|
||||||
int requireToSql(Color value) => value.value;
|
int toSql(Color value) => value.value;
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in New Issue