Fix converters on `int64` breaking inference

This commit is contained in:
Simon Binder 2023-10-03 20:54:28 +02:00
parent 89b5fbb371
commit eb03ac5ff0
No known key found for this signature in database
GPG Key ID: 7891917E4147B8C0
21 changed files with 151 additions and 94 deletions

View File

@ -1,6 +1,7 @@
## 2.12.2-dev
## 2.13.0-dev
- Fix indices not being created for Dart tables from different tables.
- Fix indices not being created for Dart tables from different files.
- Fix type converters on `int64` columns not propagating properly.
## 2.12.1

View File

@ -35,7 +35,7 @@ class _LintingVisitor extends RecursiveVisitor<void, void> {
final type = result.type;
return type != null &&
type.type == BasicType.text &&
type.hint is IsDateTime;
type.hint<IsDateTime>() != null;
}
@override
@ -124,9 +124,9 @@ class _LintingVisitor extends RecursiveVisitor<void, void> {
@override
void visitNumericLiteral(NumericLiteral e, void arg) {
final type = linter._context.typeOf(e);
final hint = type.type?.hint;
final hint = type.type?.hint<TypeConverterHint>();
if (hint is TypeConverterHint && hint.converter.isDriftEnumTypeConverter) {
if (hint != null && hint.converter.isDriftEnumTypeConverter) {
final enumElement =
(hint.converter.dartType as InterfaceType).element as EnumElement;
final entryCount =
@ -303,9 +303,9 @@ class _LintingVisitor extends RecursiveVisitor<void, void> {
@override
void visitStringLiteral(StringLiteral e, void arg) {
final type = linter._context.typeOf(e);
final hint = type.type?.hint;
final hint = type.type?.hint<TypeConverterHint>();
if (hint is TypeConverterHint && hint.converter.isDriftEnumTypeConverter) {
if (hint != null && hint.converter.isDriftEnumTypeConverter) {
final enumElement =
(hint.converter.dartType as InterfaceType).element as EnumElement;
final field = enumElement.getField(e.value);

View File

@ -73,39 +73,34 @@ class TypeMapping {
}
ResolvedType _columnType(DriftColumn column) {
return _driftTypeToParser(column.sqlType,
overrideHint: column.typeConverter != null
? TypeConverterHint(column.typeConverter!)
: null)
.withNullable(column.nullable);
final type =
_driftTypeToParser(column.sqlType).withNullable(column.nullable);
if (column.typeConverter case final AppliedTypeConverter c) {
return type.addHint(TypeConverterHint(c));
} else {
return type;
}
}
ResolvedType _driftTypeToParser(DriftSqlType type, {TypeHint? overrideHint}) {
switch (type) {
case DriftSqlType.int:
return ResolvedType(type: BasicType.int, hint: overrideHint);
case DriftSqlType.bigInt:
return ResolvedType(
type: BasicType.int, hint: overrideHint ?? const IsBigInt());
case DriftSqlType.string:
return ResolvedType(type: BasicType.text, hint: overrideHint);
case DriftSqlType.bool:
return ResolvedType(
type: BasicType.int, hint: overrideHint ?? const IsBoolean());
case DriftSqlType.dateTime:
return ResolvedType(
ResolvedType _driftTypeToParser(DriftSqlType type) {
return switch (type) {
DriftSqlType.int => const ResolvedType(type: BasicType.int),
DriftSqlType.bigInt =>
const ResolvedType(type: BasicType.int, hints: [IsBigInt()]),
DriftSqlType.string => const ResolvedType(type: BasicType.text),
DriftSqlType.bool =>
const ResolvedType(type: BasicType.int, hints: [IsBoolean()]),
DriftSqlType.dateTime => ResolvedType(
type: driver.options.storeDateTimeValuesAsText
? BasicType.text
: BasicType.int,
hint: overrideHint ?? const IsDateTime(),
);
case DriftSqlType.blob:
return ResolvedType(type: BasicType.blob, hint: overrideHint);
case DriftSqlType.double:
return ResolvedType(type: BasicType.real, hint: overrideHint);
case DriftSqlType.any:
return ResolvedType(type: BasicType.any, hint: overrideHint);
}
hints: const [IsDateTime()],
),
DriftSqlType.blob => const ResolvedType(type: BasicType.blob),
DriftSqlType.double => const ResolvedType(type: BasicType.real),
DriftSqlType.any => const ResolvedType(type: BasicType.any),
};
}
DriftSqlType sqlTypeToDrift(ResolvedType? type) {
@ -118,12 +113,12 @@ class TypeMapping {
case BasicType.nullType:
return DriftSqlType.string;
case BasicType.int:
if (type.hint is IsBoolean) {
if (type.hint<IsBoolean>() != null) {
return DriftSqlType.bool;
} else if (!driver.options.storeDateTimeValuesAsText &&
type.hint is IsDateTime) {
type.hint<IsDateTime>() != null) {
return DriftSqlType.dateTime;
} else if (type.hint is IsBigInt) {
} else if (type.hint<IsBigInt>() != null) {
return DriftSqlType.bigInt;
}
return DriftSqlType.int;
@ -131,7 +126,7 @@ class TypeMapping {
return DriftSqlType.double;
case BasicType.text:
if (driver.options.storeDateTimeValuesAsText &&
type.hint is IsDateTime) {
type.hint<IsDateTime>() != null) {
return DriftSqlType.dateTime;
}
@ -158,14 +153,16 @@ TypeFromText enumColumnFromText(
if (type != null) {
return ResolvedType(
type: isStoredAsName ? BasicType.text : BasicType.int,
hint: TypeConverterHint(
readEnumConverter(
(_) {},
type,
isStoredAsName ? EnumType.textEnum : EnumType.intEnum,
helper,
)..owningColumn = null,
),
hints: [
TypeConverterHint(
readEnumConverter(
(_) {},
type,
isStoredAsName ? EnumType.textEnum : EnumType.intEnum,
helper,
)..owningColumn = null,
),
],
);
}
}

View File

@ -62,8 +62,8 @@ class DriftViewResolver extends DriftElementResolver<DiscoveredDriftView> {
}
}
if (type != null && type.hint is TypeConverterHint) {
converter ??= (type.hint as TypeConverterHint).converter;
if (type?.hint<TypeConverterHint>() case final TypeConverterHint h) {
converter ??= h.converter;
ownsConverter = converter.owningColumn == null;
}

View File

@ -293,8 +293,8 @@ class QueryAnalyzer {
final mappedBy = source?.mappedBy;
AppliedTypeConverter? converter;
if (type?.hint is TypeConverterHint) {
converter = (type!.hint as TypeConverterHint).converter;
if (type?.hint<TypeConverterHint>() case final TypeConverterHint h) {
converter = h.converter;
} else if (mappedBy != null) {
final dartExpression = _resolvedExpressions[mappedBy.mapper.dartCode];
if (dartExpression != null) {
@ -668,9 +668,11 @@ class QueryAnalyzer {
// Recognizing type converters on variables is opt-in since it would
// break existing code.
if (driver.options.applyConvertersOnVariables &&
internalType.type?.hint is TypeConverterHint) {
converter = (internalType.type!.hint as TypeConverterHint).converter;
if (driver.options.applyConvertersOnVariables) {
if (internalType.type?.hint<TypeConverterHint>()
case final TypeConverterHint h) {
converter = h.converter;
}
}
addNewElement(FoundVariable(

View File

@ -134,13 +134,12 @@ class SqlWriter extends NodeSqlBuilder {
);
final type = schema.resolveColumnType(e.typeName);
final hint = type.hint;
String? overriddenTypeName;
if (hint is IsDateTime) {
if (type.hint<IsDateTime>() != null) {
overriddenTypeName = options.storeDateTimeValuesAsText ? 'TEXT' : 'INT';
} else if (hint is IsBoolean) {
} else if (type.hint<IsBoolean>() != null) {
overriddenTypeName = 'INT';
} else {
final enumMatch = FoundReferencesInSql.enumRegex.firstMatch(e.typeName);

View File

@ -1,6 +1,6 @@
name: drift_dev
description: Dev-dependency for users of drift. Contains the generator and development tools.
version: 2.12.1
version: 2.13.0-dev
repository: https://github.com/simolus3/drift
homepage: https://drift.simonbinder.eu/
issue_tracker: https://github.com/simolus3/drift/issues
@ -32,7 +32,7 @@ dependencies:
# Drift-specific analysis and apis
drift: '>=2.13.0 <2.14.0'
sqlite3: '>=0.1.6 <3.0.0'
sqlparser: '^0.31.2'
sqlparser: '^0.32.0'
# Dart analysis
analyzer: '>=5.12.0 <7.0.0'

View File

@ -96,8 +96,8 @@ sqlite:
isA<ResolvedType>().having((e) => e.type, 'type', BasicType.int),
isA<ResolvedType>()
.having((e) => e.type, 'type', BasicType.int)
.having((e) => e.hint, 'hint', const IsBoolean())
.having((e) => e.nullable, 'nullable', true),
.having((e) => e.hints, 'hints', [IsBoolean()]).having(
(e) => e.nullable, 'nullable', true),
]);
});

View File

@ -325,6 +325,44 @@ TypeConverter<Object, int> myConverter() => throw UnimplementedError();
);
});
test('can restore types from multiple hints', () async {
final result = await emulateDriftBuild(
inputs: {
'a|lib/a.drift': '''
import 'table.dart';
CREATE VIEW my_view AS SELECT foo FROM my_table;
''',
'a|lib/table.dart': '''
import 'package:drift/drift.dart';
class MyTable extends Table {
Int64Column get foo => int64().map(myConverter())();
}
enum MyEnum {
foo, bar
}
TypeConverter<Object, BigInt> myConverter() => throw UnimplementedError();
''',
},
modularBuild: true,
logger: loggerThat(neverEmits(anything)),
);
checkOutputs(
{
'a|lib/a.drift.dart': decodedMatches(contains(
'foo: i2.\$MyTableTable.\$converterfoo.fromSql(attachedDatabase.typeMapping\n'
' .read(i0.DriftSqlType.bigInt')),
'a|lib/table.drift.dart': decodedMatches(anything),
},
result.dartOutputs,
result.writer,
);
});
test('supports @create queries in modular generation', () async {
final result = await emulateDriftBuild(
inputs: {

View File

@ -1,3 +1,7 @@
## 0.32.0-dev
- Turn `ResolvedType.hints` into a list, supporting multiple type hints.
## 0.31.3
- Fix star columns expanding to more columns than they should.

View File

@ -88,7 +88,7 @@ class TableColumn extends Column implements ColumnWithType {
///
/// The [hint] will then be reflected in the [type].
void applyTypeHint(TypeHint hint) {
_type = _type.copyWith(hint: hint);
_type = _type.addHint(hint);
}
/// Whether this column is an alias for the rowid, as defined in

View File

@ -180,7 +180,7 @@ class SchemaFromCreateTable {
if (upper.contains('DATE')) {
return ResolvedType(
type: driftUseTextForDateTime ? BasicType.text : BasicType.int,
hint: const IsDateTime(),
hints: const [IsDateTime()],
);
}

View File

@ -1,3 +1,4 @@
import 'package:collection/collection.dart';
import 'package:sqlparser/src/engine/sql_engine.dart';
/// Something that has a type.
@ -19,12 +20,14 @@ enum BasicType {
}
class ResolvedType {
static const _hintEquality = ListEquality<TypeHint>();
final BasicType? type;
/// We set hints for additional information that might be useful for
/// applications but aren't covered by just exposing a [BasicType]. See the
/// comment on [TypeHint] for examples.
final TypeHint? hint;
final List<TypeHint> hints;
/// Whether this type is nullable. A `null` value for [nullable] indicates
/// that nullability is unknown.
@ -34,15 +37,21 @@ class ResolvedType {
final bool isArray;
const ResolvedType(
{this.type, this.hint, this.nullable = false, this.isArray = false});
{this.type,
this.hints = const [],
this.nullable = false,
this.isArray = false});
const ResolvedType.bool({bool? nullable = false})
: this(type: BasicType.int, hint: const IsBoolean(), nullable: nullable);
: this(
type: BasicType.int,
hints: const [IsBoolean()],
nullable: nullable);
ResolvedType get withoutNullabilityInfo {
return nullable == null
? this
: ResolvedType(
type: type, hint: hint, isArray: isArray, nullable: null);
type: type, hints: hints, isArray: isArray, nullable: null);
}
ResolvedType withNullable(bool nullable) {
@ -53,33 +62,42 @@ class ResolvedType {
return copyWith(isArray: array);
}
ResolvedType copyWith({TypeHint? hint, bool? nullable, bool? isArray}) {
ResolvedType copyWith(
{List<TypeHint>? hints, bool? nullable, bool? isArray}) {
return ResolvedType(
type: type,
hint: hint ?? this.hint,
hints: hints ?? this.hints,
nullable: nullable ?? this.nullable,
isArray: isArray ?? this.isArray,
);
}
T? hint<T extends TypeHint>() {
return hints.whereType<T>().firstOrNull;
}
ResolvedType addHint(TypeHint hint) {
return copyWith(hints: [...hints, hint]);
}
@override
bool operator ==(dynamic other) {
return identical(this, other) ||
other is ResolvedType &&
other.type == type &&
other.hint == hint &&
_hintEquality.equals(other.hints, hints) &&
other.nullable == nullable &&
other.isArray == isArray;
}
@override
int get hashCode {
return type.hashCode + hint.hashCode + nullable.hashCode;
return type.hashCode + _hintEquality.hash(hints) + nullable.hashCode;
}
@override
String toString() {
return 'ResolvedType($type, hint: $hint, nullable: $nullable, '
return 'ResolvedType($type, hints: $hints, nullable: $nullable, '
'array: $isArray)';
}
}

View File

@ -284,12 +284,10 @@ extension ResolvedTypeUtils on ResolvedType {
case CastMode.numeric:
case CastMode.numericPreferInt:
if (type == BasicType.int || type == BasicType.real) {
final newHint = dropTypeHint ? null : hint;
if (newHint != hint) {
if (dropTypeHint && hints.isNotEmpty) {
return ResolvedType(
type: type,
hint: newHint,
hints: const [],
nullable: nullable,
isArray: isArray,
);

View File

@ -581,7 +581,7 @@ class TypeResolver extends RecursiveVisitor<TypeExpectation, void> {
case 'timediff':
return _textType;
case 'datetime':
return _textType.copyWith(hint: const IsDateTime(), nullable: true);
return _textType.copyWith(hints: const [IsDateTime()], nullable: true);
case 'changes':
case 'last_insert_rowid':
case 'random':
@ -655,7 +655,7 @@ class TypeResolver extends RecursiveVisitor<TypeExpectation, void> {
return null;
case 'unixepoch':
return const ResolvedType(
type: BasicType.int, nullable: true, hint: IsDateTime());
type: BasicType.int, nullable: true, hints: [IsDateTime()]);
}
final extensionHandler = _functionHandlerFor(e);
@ -710,7 +710,7 @@ class TypeResolver extends RecursiveVisitor<TypeExpectation, void> {
param,
const ExactTypeExpectation(ResolvedType(
type: BasicType.text,
hint: IsDateTime(),
hints: [IsDateTime()],
)));
visited.add(param);
}

View File

@ -28,10 +28,10 @@ class TypeInferenceSession {
if (expectation is ExactTypeExpectation) {
final expectedType = expectation.type;
if (expectedType.hint != null &&
r.hint == null &&
if (expectedType.hints.isNotEmpty &&
r.hints.isEmpty &&
expectedType.type == r.type) {
r = r.copyWith(hint: expectedType.hint);
r = r.copyWith(hints: expectedType.hints);
}
}

View File

@ -1,6 +1,6 @@
name: sqlparser
description: Parses sqlite statements and performs static analysis on them
version: 0.31.3
version: 0.32.0-dev
homepage: https://github.com/simolus3/drift/tree/develop/sqlparser
repository: https://github.com/simolus3/drift
#homepage: https://drift.simonbinder.eu/

View File

@ -20,7 +20,7 @@ final Table demoTable = Table(
final TableColumn anotherId =
TableColumn('id', const ResolvedType(type: BasicType.int));
final TableColumn dateTime = TableColumn(
'date', const ResolvedType(type: BasicType.int, hint: IsDateTime()));
'date', const ResolvedType(type: BasicType.int, hints: [IsDateTime()]));
final Table anotherTable = Table(
name: 'tbl',

View File

@ -110,10 +110,10 @@ void main() {
final table = const SchemaFromCreateTable(driftExtensions: true)
.read(stmt as CreateTableStatement);
expect(table.resolvedColumns.map((c) => c.type), const [
ResolvedType(type: BasicType.int, hint: IsBoolean(), nullable: true),
ResolvedType(type: BasicType.int, hint: IsDateTime(), nullable: true),
ResolvedType(type: BasicType.int, hint: IsDateTime(), nullable: true),
ResolvedType(type: BasicType.int, hint: IsBoolean(), nullable: false),
ResolvedType(type: BasicType.int, hints: [IsBoolean()], nullable: true),
ResolvedType(type: BasicType.int, hints: [IsDateTime()], nullable: true),
ResolvedType(type: BasicType.int, hints: [IsDateTime()], nullable: true),
ResolvedType(type: BasicType.int, hints: [IsBoolean()], nullable: false),
]);
});

View File

@ -25,7 +25,7 @@ const Map<String, ResolvedType?> _types = {
'SELECT * FROM demo WHERE content IN (?)':
ResolvedType(type: BasicType.text, isArray: false),
'SELECT * FROM demo JOIN tbl ON demo.id = tbl.id WHERE date = ?':
ResolvedType(type: BasicType.int, hint: IsDateTime()),
ResolvedType(type: BasicType.int, hints: [IsDateTime()]),
'SELECT row_number() OVER (RANGE ? PRECEDING)':
ResolvedType(type: BasicType.int),
'SELECT ?;': null,
@ -55,9 +55,9 @@ const Map<String, ResolvedType?> _types = {
'SELECT MAX(id, ?) FROM demo': ResolvedType(type: BasicType.int),
'SELECT SUM(id = 2) = ? FROM demo': ResolvedType(type: BasicType.int),
"SELECT unixepoch('now') = ?":
ResolvedType(type: BasicType.int, nullable: true, hint: IsDateTime()),
ResolvedType(type: BasicType.int, nullable: true, hints: [IsDateTime()]),
"SELECT datetime('now') = ?":
ResolvedType(type: BasicType.text, nullable: true, hint: IsDateTime()),
ResolvedType(type: BasicType.text, nullable: true, hints: [IsDateTime()]),
'SELECT CAST(NULLIF(1, 2) AS INTEGER) = ?': ResolvedType(
type: BasicType.int,
nullable: true,

View File

@ -108,7 +108,7 @@ void main() {
test('infers condition', () {
expect(resolveFirstVariable('SELECT IIF(?, 0, 1)'),
const ResolvedType(type: BasicType.int, hint: IsBoolean()));
const ResolvedType(type: BasicType.int, hints: [IsBoolean()]));
});
});
@ -183,7 +183,7 @@ void main() {
expect(resultType, const ResolvedType(type: BasicType.text));
expect(argType,
const ResolvedType(type: BasicType.text, hint: IsDateTime()));
const ResolvedType(type: BasicType.text, hints: [IsDateTime()]));
});
test('octet_length', () {
@ -298,7 +298,7 @@ WITH RECURSIVE
});
test('resolves type hints from between expressions', () {
const dateTime = ResolvedType(type: BasicType.int, hint: IsDateTime());
const dateTime = ResolvedType(type: BasicType.int, hints: [IsDateTime()]);
final session = obtainResolver(
'SELECT 1 WHERE :date BETWEEN :start AND :end',
options: const AnalyzeStatementOptions(