mirror of https://github.com/AMT-Cheif/drift.git
Automatically make some converters nullable
This commit is contained in:
parent
e874b78698
commit
cf1e94d384
|
@ -22,17 +22,16 @@ class Preferences {
|
|||
|
||||
// #docregion converter
|
||||
// stores preferences as strings
|
||||
class PreferenceConverter extends NullAwareTypeConverter<Preferences, String>
|
||||
with JsonTypeConverter<Preferences?, String?> {
|
||||
class PreferenceConverter extends TypeConverter<Preferences, String> {
|
||||
const PreferenceConverter();
|
||||
|
||||
@override
|
||||
Preferences requireMapToDart(String fromDb) {
|
||||
Preferences mapToDart(String fromDb) {
|
||||
return Preferences.fromJson(json.decode(fromDb) as Map<String, dynamic>);
|
||||
}
|
||||
|
||||
@override
|
||||
String requireMapToSql(Preferences value) {
|
||||
String mapToSql(Preferences value) {
|
||||
return json.encode(value.toJson());
|
||||
}
|
||||
}
|
||||
|
|
|
@ -282,7 +282,7 @@ class ConfigTable extends Table with TableInfo<ConfigTable, Config> {
|
|||
}
|
||||
|
||||
static TypeConverter<SyncType?, int?> $converter0 =
|
||||
const NullAwareTypeConverter.wrap(SyncTypeConverter());
|
||||
NullAwareTypeConverter.wrap(const SyncTypeConverter());
|
||||
static TypeConverter<SyncType?, int?> $converter1 =
|
||||
const NullAwareTypeConverter.wrap(
|
||||
EnumIndexConverter<SyncType>(SyncType.values));
|
||||
|
|
|
@ -1,4 +1,3 @@
|
|||
import 'package:drift/drift.dart';
|
||||
import 'data_classes.dart';
|
||||
import 'converter.dart';
|
||||
|
||||
|
@ -22,7 +21,7 @@ CREATE TABLE with_constraints (
|
|||
create table config (
|
||||
config_key TEXT not null primary key,
|
||||
config_value TEXT,
|
||||
sync_state INTEGER MAPPED BY `const NullAwareTypeConverter.wrap(SyncTypeConverter())`,
|
||||
sync_state INTEGER MAPPED BY `const SyncTypeConverter()`,
|
||||
sync_state_implicit ENUM(SyncType)
|
||||
) STRICT AS "Config";
|
||||
|
||||
|
|
|
@ -98,10 +98,8 @@ class CustomRowClass {
|
|||
|
||||
class PureDefaults extends Table {
|
||||
// name after keyword to ensure it's escaped properly
|
||||
TextColumn get txt => text()
|
||||
.named('insert')
|
||||
.map(JsonTypeConverter.asNullable(const CustomJsonConverter()))
|
||||
.nullable()();
|
||||
TextColumn get txt =>
|
||||
text().named('insert').map(const CustomJsonConverter()).nullable()();
|
||||
|
||||
@override
|
||||
Set<Column> get primaryKey => {txt};
|
||||
|
|
|
@ -134,8 +134,17 @@ UsedTypeConverter? readTypeConverter(
|
|||
|
||||
final appliesToJsonToo = helper.isJsonAwareTypeConverter(staticType, library);
|
||||
|
||||
// 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
|
||||
// nullable column
|
||||
final skipForNull = !dartTypeNullable && !sqlTypeNullable && columnIsNullable;
|
||||
|
||||
if (sqlTypeNullable != columnIsNullable) {
|
||||
if (columnIsNullable) {
|
||||
if (!columnIsNullable) {
|
||||
reportError('This column is non-nullable in the database, but has a '
|
||||
'type converter with a nullable SQL type, meaning that it may '
|
||||
"potentially map to `null` which can't be stored in the database.");
|
||||
} else if (!skipForNull) {
|
||||
final alternative = appliesToJsonToo
|
||||
? 'JsonTypeConverter.asNullable'
|
||||
: 'NullAwareTypeConverter.wrap';
|
||||
|
@ -144,23 +153,20 @@ UsedTypeConverter? readTypeConverter(
|
|||
"nullable SQL type, meaning that it won't be able to map `null` "
|
||||
'from the database to Dart.\n'
|
||||
'Try wrapping the converter in `$alternative`');
|
||||
} else {
|
||||
reportError('This column is non-nullable in the database, but has a '
|
||||
'type converter with a nullable SQL type, meaning that it may '
|
||||
"potentially map to `null` which can't be stored in the database.");
|
||||
}
|
||||
}
|
||||
|
||||
_checkType(columnType, columnIsNullable, null, sqlType, library.typeProvider,
|
||||
_checkType(columnType, null, sqlType, library.typeProvider,
|
||||
library.typeSystem, reportError);
|
||||
|
||||
return UsedTypeConverter(
|
||||
expression: dartExpression.toSource(),
|
||||
dartType: resolvedDartType ?? DriftDartType.of(dartType),
|
||||
sqlType: sqlType,
|
||||
mapsToNullableDart: dartTypeNullable,
|
||||
mapsToNullableSql: sqlTypeNullable,
|
||||
dartTypeIsNullable: dartTypeNullable,
|
||||
sqlTypeIsNullable: sqlTypeNullable,
|
||||
alsoAppliesToJsonConversion: appliesToJsonToo,
|
||||
skipForNulls: skipForNull,
|
||||
);
|
||||
}
|
||||
|
||||
|
@ -191,7 +197,6 @@ void _checkParameterType(
|
|||
|
||||
_checkType(
|
||||
column.type,
|
||||
column.nullable,
|
||||
column.typeConverter,
|
||||
element.type,
|
||||
library.typeProvider,
|
||||
|
@ -202,7 +207,6 @@ void _checkParameterType(
|
|||
|
||||
void _checkType(
|
||||
ColumnType columnType,
|
||||
bool columnIsNullable,
|
||||
UsedTypeConverter? typeConverter,
|
||||
DartType typeToCheck,
|
||||
TypeProvider typeProvider,
|
||||
|
@ -212,6 +216,9 @@ void _checkType(
|
|||
DriftDartType expectedDartType;
|
||||
if (typeConverter != null) {
|
||||
expectedDartType = typeConverter.dartType;
|
||||
if (typeConverter.skipForNulls) {
|
||||
typeToCheck = typeSystem.promoteToNonNull(typeToCheck);
|
||||
}
|
||||
} else {
|
||||
expectedDartType = DriftDartType.of(typeProvider.typeFor(columnType));
|
||||
}
|
||||
|
|
|
@ -110,7 +110,8 @@ extension OperationOnTypes on HasType {
|
|||
String dartTypeCode([GenerationOptions options = const GenerationOptions()]) {
|
||||
final converter = typeConverter;
|
||||
if (converter != null) {
|
||||
final inner = converter.dartType.codeString(options);
|
||||
var inner = converter.dartType.codeString(options);
|
||||
if (converter.skipForNulls) inner += '?';
|
||||
return isArray ? 'List<$inner>' : inner;
|
||||
}
|
||||
|
||||
|
|
|
@ -20,8 +20,12 @@ class UsedTypeConverter {
|
|||
/// vice-versa.
|
||||
final String expression;
|
||||
|
||||
/// The "Dart" type of this type converter. This is the type used on
|
||||
/// companions and data classes.
|
||||
/// The "Dart" type of this type converter.
|
||||
///
|
||||
/// Note that, even when this type is non-nullable, the actual type used on
|
||||
/// data classes and companions can still be nullable. For instance, when a
|
||||
/// non-nullable type converter is applied to a nullable column, drift will
|
||||
/// implicitly map `null` values to `null` without invoking the converter.
|
||||
final DriftDartType dartType;
|
||||
|
||||
/// The "SQL" type of this type converter. This is the type used to represent
|
||||
|
@ -31,17 +35,27 @@ class UsedTypeConverter {
|
|||
/// Whether the Dart-value output of this type converter is nullable.
|
||||
///
|
||||
/// In other words, [dartType] is potentially nullable.
|
||||
final bool mapsToNullableDart;
|
||||
final bool dartTypeIsNullable;
|
||||
|
||||
/// Whether the SQL-value output of this type converter is nullable.
|
||||
///
|
||||
/// In other words, [sqlType] is potentially nullable.
|
||||
final bool mapsToNullableSql;
|
||||
final bool sqlTypeIsNullable;
|
||||
|
||||
/// Whether this type converter should also be used in the generated JSON
|
||||
/// serialization.
|
||||
final bool alsoAppliesToJsonConversion;
|
||||
|
||||
/// Whether this type converter should be skipped for `null` values.
|
||||
///
|
||||
/// 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`
|
||||
/// without calling the type converter at all.
|
||||
///
|
||||
/// This is implemented by wrapping it in a `NullAwareTypeConverter` in the
|
||||
/// generated code.
|
||||
final bool skipForNulls;
|
||||
|
||||
/// Type converters are stored as static fields in the table that created
|
||||
/// them. This will be the field name for this converter.
|
||||
String get fieldName => '\$converter$index';
|
||||
|
@ -53,9 +67,10 @@ class UsedTypeConverter {
|
|||
required this.expression,
|
||||
required this.dartType,
|
||||
required this.sqlType,
|
||||
required this.mapsToNullableDart,
|
||||
required this.mapsToNullableSql,
|
||||
required this.dartTypeIsNullable,
|
||||
required this.sqlTypeIsNullable,
|
||||
this.alsoAppliesToJsonConversion = false,
|
||||
this.skipForNulls = false,
|
||||
});
|
||||
|
||||
factory UsedTypeConverter.forEnumColumn(
|
||||
|
@ -89,8 +104,8 @@ class UsedTypeConverter {
|
|||
overiddenSource: creatingClass.name,
|
||||
nullabilitySuffix: suffix,
|
||||
),
|
||||
mapsToNullableDart: nullable,
|
||||
mapsToNullableSql: nullable,
|
||||
sqlTypeIsNullable: nullable,
|
||||
dartTypeIsNullable: nullable,
|
||||
sqlType: nullable
|
||||
? typeProvider.intElement.instantiate(
|
||||
typeArguments: const [],
|
||||
|
@ -99,13 +114,24 @@ class UsedTypeConverter {
|
|||
);
|
||||
}
|
||||
|
||||
bool get mapsToNullableDart => dartTypeIsNullable || skipForNulls;
|
||||
|
||||
String dartTypeCode(GenerationOptions options) {
|
||||
var type = dartType.codeString(options);
|
||||
if (options.nnbd && skipForNulls) type += '?';
|
||||
|
||||
return type;
|
||||
}
|
||||
|
||||
/// A suitable typename to store an instance of the type converter used here.
|
||||
String converterNameInCode(GenerationOptions options) {
|
||||
final sqlDartType = sqlType.getDisplayString(withNullability: options.nnbd);
|
||||
var sqlDartType = sqlType.getDisplayString(withNullability: options.nnbd);
|
||||
if (options.nnbd && skipForNulls) sqlDartType += '?';
|
||||
|
||||
final className =
|
||||
alsoAppliesToJsonConversion ? 'JsonTypeConverter' : 'TypeConverter';
|
||||
|
||||
return '$className<${dartType.codeString(options)}, $sqlDartType>';
|
||||
return '$className<${dartTypeCode(options)}, $sqlDartType>';
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -100,7 +100,8 @@ abstract class TableOrViewWriter {
|
|||
if (converter != null) {
|
||||
// Generate a GeneratedColumnWithTypeConverter instance, as it has
|
||||
// additional methods to check for equality against a mapped value.
|
||||
final mappedType = converter.dartType.codeString(options);
|
||||
final mappedType = converter.dartTypeCode(options);
|
||||
|
||||
type = 'GeneratedColumnWithTypeConverter<$mappedType, $innerType>';
|
||||
expressionBuffer
|
||||
..write('.withConverter<')
|
||||
|
@ -310,7 +311,16 @@ class TableWriter extends TableOrViewWriter {
|
|||
void _writeConvertersAsStaticFields() {
|
||||
for (final converter in table.converters) {
|
||||
final typeName = converter.converterNameInCode(scope.generationOptions);
|
||||
final code = converter.expression;
|
||||
var 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;');
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,4 +1,5 @@
|
|||
import 'package:drift_dev/src/analyzer/errors.dart';
|
||||
import 'package:drift_dev/src/analyzer/runner/results.dart';
|
||||
import 'package:drift_dev/src/model/table.dart';
|
||||
import 'package:drift_dev/src/model/used_type_converter.dart';
|
||||
import 'package:test/test.dart';
|
||||
|
@ -29,7 +30,8 @@ TypeConverter<Dart, Sql> tc<Dart, Sql>() => throw 'stub';
|
|||
class Users extends Table {
|
||||
TextColumn get wrongSqlType => text().map(tc<int, int>())();
|
||||
TextColumn get illegalNull => text().map(tc<String, String?>())();
|
||||
TextColumn get illegalNonNull => text().map(tc<String, String>()).nullable()();
|
||||
TextColumn get illegalNonNull => text().map(tc<String?, String>()).nullable()();
|
||||
TextColumn get implicitlyNullAware => text().map(tc<String, String>()).nullable()();
|
||||
}
|
||||
''',
|
||||
'a|lib/main.drift': '''
|
||||
|
@ -71,6 +73,8 @@ CREATE TABLE users (
|
|||
|
||||
test('warns about type issues around converters', () async {
|
||||
final result = await state.analyze('package:a/nullability.dart');
|
||||
final table =
|
||||
(result.currentResult as ParsedDartFile).declaredTables.single;
|
||||
|
||||
expect(
|
||||
result.errors.errors,
|
||||
|
@ -85,9 +89,12 @@ CREATE TABLE users (
|
|||
isA<ErrorInDartCode>()
|
||||
.having((e) => e.message, 'message',
|
||||
contains('This column is nullable'))
|
||||
.having((e) => e.span?.text, 'span', 'tc<String, String>()'),
|
||||
.having((e) => e.span?.text, 'span', 'tc<String?, String>()'),
|
||||
],
|
||||
);
|
||||
|
||||
final implicitlyNullAware = table.columns[3];
|
||||
expect(implicitlyNullAware.typeConverter?.skipForNulls, isTrue);
|
||||
});
|
||||
|
||||
test('json converters in drift files', () {
|
||||
|
|
Loading…
Reference in New Issue