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 ## 2.12.1

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -1,6 +1,6 @@
name: drift_dev name: drift_dev
description: Dev-dependency for users of drift. Contains the generator and development tools. 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 repository: https://github.com/simolus3/drift
homepage: https://drift.simonbinder.eu/ homepage: https://drift.simonbinder.eu/
issue_tracker: https://github.com/simolus3/drift/issues issue_tracker: https://github.com/simolus3/drift/issues
@ -32,7 +32,7 @@ dependencies:
# Drift-specific analysis and apis # Drift-specific analysis and apis
drift: '>=2.13.0 <2.14.0' drift: '>=2.13.0 <2.14.0'
sqlite3: '>=0.1.6 <3.0.0' sqlite3: '>=0.1.6 <3.0.0'
sqlparser: '^0.31.2' sqlparser: '^0.32.0'
# Dart analysis # Dart analysis
analyzer: '>=5.12.0 <7.0.0' 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),
isA<ResolvedType>() isA<ResolvedType>()
.having((e) => e.type, 'type', BasicType.int) .having((e) => e.type, 'type', BasicType.int)
.having((e) => e.hint, 'hint', const IsBoolean()) .having((e) => e.hints, 'hints', [IsBoolean()]).having(
.having((e) => e.nullable, 'nullable', true), (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 { test('supports @create queries in modular generation', () async {
final result = await emulateDriftBuild( final result = await emulateDriftBuild(
inputs: { inputs: {

View File

@ -1,3 +1,7 @@
## 0.32.0-dev
- Turn `ResolvedType.hints` into a list, supporting multiple type hints.
## 0.31.3 ## 0.31.3
- Fix star columns expanding to more columns than they should. - 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]. /// The [hint] will then be reflected in the [type].
void applyTypeHint(TypeHint hint) { 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 /// Whether this column is an alias for the rowid, as defined in

View File

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

View File

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

View File

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

View File

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

View File

@ -1,6 +1,6 @@
name: sqlparser name: sqlparser
description: Parses sqlite statements and performs static analysis on them 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 homepage: https://github.com/simolus3/drift/tree/develop/sqlparser
repository: https://github.com/simolus3/drift repository: https://github.com/simolus3/drift
#homepage: https://drift.simonbinder.eu/ #homepage: https://drift.simonbinder.eu/

View File

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

View File

@ -110,10 +110,10 @@ void main() {
final table = const SchemaFromCreateTable(driftExtensions: true) final table = const SchemaFromCreateTable(driftExtensions: true)
.read(stmt as CreateTableStatement); .read(stmt as CreateTableStatement);
expect(table.resolvedColumns.map((c) => c.type), const [ expect(table.resolvedColumns.map((c) => c.type), const [
ResolvedType(type: BasicType.int, hint: IsBoolean(), nullable: true), ResolvedType(type: BasicType.int, hints: [IsBoolean()], nullable: true),
ResolvedType(type: BasicType.int, hint: IsDateTime(), nullable: true), ResolvedType(type: BasicType.int, hints: [IsDateTime()], nullable: true),
ResolvedType(type: BasicType.int, hint: IsDateTime(), nullable: true), ResolvedType(type: BasicType.int, hints: [IsDateTime()], nullable: true),
ResolvedType(type: BasicType.int, hint: IsBoolean(), nullable: false), 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 (?)': 'SELECT * FROM demo WHERE content IN (?)':
ResolvedType(type: BasicType.text, isArray: false), ResolvedType(type: BasicType.text, isArray: false),
'SELECT * FROM demo JOIN tbl ON demo.id = tbl.id WHERE date = ?': '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)': 'SELECT row_number() OVER (RANGE ? PRECEDING)':
ResolvedType(type: BasicType.int), ResolvedType(type: BasicType.int),
'SELECT ?;': null, 'SELECT ?;': null,
@ -55,9 +55,9 @@ const Map<String, ResolvedType?> _types = {
'SELECT MAX(id, ?) FROM demo': ResolvedType(type: BasicType.int), 'SELECT MAX(id, ?) FROM demo': ResolvedType(type: BasicType.int),
'SELECT SUM(id = 2) = ? FROM demo': ResolvedType(type: BasicType.int), 'SELECT SUM(id = 2) = ? FROM demo': ResolvedType(type: BasicType.int),
"SELECT unixepoch('now') = ?": "SELECT unixepoch('now') = ?":
ResolvedType(type: BasicType.int, nullable: true, hint: IsDateTime()), ResolvedType(type: BasicType.int, nullable: true, hints: [IsDateTime()]),
"SELECT datetime('now') = ?": "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( 'SELECT CAST(NULLIF(1, 2) AS INTEGER) = ?': ResolvedType(
type: BasicType.int, type: BasicType.int,
nullable: true, nullable: true,

View File

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