Migrate drift file analysis tests to new analyzer

This commit is contained in:
Simon Binder 2022-10-30 16:09:38 +01:00
parent 292dd9946d
commit 23b0c8a362
No known key found for this signature in database
GPG Key ID: 7891917E4147B8C0
22 changed files with 471 additions and 642 deletions

View File

@ -33,6 +33,17 @@ class DriftAnalysisError {
return DriftAnalysisError(sql.span, message);
}
factory DriftAnalysisError.fromSqlError(sql.AnalysisError error) {
var message = error.message ?? '';
if (error.type == sql.AnalysisErrorType.notSupportedInDesiredVersion) {
message =
'$message\nNote: You can change the assumed sqlite version with build '
'options. See https://drift.simonbinder.eu/options/#assumed-sql-environment for details!';
}
return DriftAnalysisError(error.span, message);
}
@override
String toString() {
final span = this.span;

View File

@ -44,7 +44,7 @@ abstract class DriftElementResolver<T extends DiscoveredElement>
} else if (foundElement is TypeAliasElement) {
final innerType = foundElement.aliasedType;
if (innerType is InterfaceType) {
return FoundDartClass(innerType.element2, innerType.typeArguments);
return FoundDartClass(innerType.element, innerType.typeArguments);
}
}
}
@ -83,7 +83,6 @@ abstract class DriftElementResolver<T extends DiscoveredElement>
}
void reportLint(AnalysisError parserError) {
reportError(
DriftAnalysisError(parserError.span, parserError.message ?? ''));
reportError(DriftAnalysisError.fromSqlError(parserError));
}
}

View File

@ -26,7 +26,7 @@ extension FindDartClass on LocalElementResolver {
} else if (foundElement is TypeAliasElement) {
final innerType = foundElement.aliasedType;
if (innerType is InterfaceType) {
return FoundDartClass(innerType.element2, innerType.typeArguments);
return FoundDartClass(innerType.element, innerType.typeArguments);
}
}
}

View File

@ -2,8 +2,11 @@ import 'package:analyzer/dart/ast/ast.dart' as dart;
import 'package:collection/collection.dart';
import 'package:drift/drift.dart' show DriftSqlType;
import 'package:recase/recase.dart';
import 'package:sqlparser/sqlparser.dart';
import 'package:sqlparser/sqlparser.dart' hide PrimaryKeyColumn, UniqueColumn;
import 'package:sqlparser/sqlparser.dart' as sql;
import 'package:sqlparser/utils/node_to_text.dart';
import '../../../utils/string_escaper.dart';
import '../../backend.dart';
import '../../driver/error.dart';
import '../../results/results.dart';
@ -52,6 +55,7 @@ class DriftTableResolver extends LocalElementResolver<DiscoveredDriftTable> {
final nullable = column.type.nullable != false;
final constraints = <DriftColumnConstraint>[];
AppliedTypeConverter? converter;
AnnotatedDartCode? defaultArgument;
final typeName = column.definition?.typeName;
final enumMatch =
@ -69,8 +73,8 @@ class DriftTableResolver extends LocalElementResolver<DiscoveredDriftTable> {
));
} else {
converter = readEnumConverter(
(msg) =>
DriftAnalysisError.inDriftFile(column.definition ?? stmt, msg),
(msg) => reportError(
DriftAnalysisError.inDriftFile(column.definition ?? stmt, msg)),
dartClass.classElement.thisType,
);
}
@ -87,7 +91,7 @@ class DriftTableResolver extends LocalElementResolver<DiscoveredDriftTable> {
if (converter != null) {
reportError(DriftAnalysisError.inDriftFile(
constraint,
'Multiple type converters applied to this converter, ignoring '
'Multiple type converters applied to this column, ignoring '
'this one.'));
continue;
}
@ -125,6 +129,26 @@ class DriftTableResolver extends LocalElementResolver<DiscoveredDriftTable> {
}
}
}
} else if (constraint is GeneratedAs) {
constraints.add(ColumnGeneratedAs(
AnnotatedDartCode.build((b) => b
..addText('const ')
..addSymbol('CustomExpression', AnnotatedDartCode.drift)
..addText('(')
..addText(asDartLiteral(constraint.expression.toSql()))
..addText(')')),
constraint.stored));
} else if (constraint is Default) {
defaultArgument = AnnotatedDartCode.build((b) => b
..addText('const ')
..addSymbol('CustomExpression', AnnotatedDartCode.drift)
..addText('(')
..addText(asDartLiteral(constraint.expression.toSql()))
..addText(')'));
} else if (constraint is sql.PrimaryKeyColumn) {
constraints.add(PrimaryKeyColumn(constraint.autoIncrement));
} else if (constraint is sql.UniqueColumn) {
constraints.add(UniqueColumn());
}
}
@ -135,6 +159,7 @@ class DriftTableResolver extends LocalElementResolver<DiscoveredDriftTable> {
nameInDart: overriddenDartName ?? ReCase(column.name).camelCase,
constraints: constraints,
typeConverter: converter,
defaultArgument: defaultArgument,
declaration: DriftDeclaration.driftFile(
column.definition?.nameToken ?? stmt,
state.ownId.libraryUri,

View File

@ -53,8 +53,7 @@ class FileAnalyzer {
queries[query.name] = analyzer.analyze(query);
for (final error in analyzer.lints) {
result.analysisErrors.add(DriftAnalysisError(
error.span, 'Error in ${query.name}: ${error.message}'));
result.analysisErrors.add(DriftAnalysisError.fromSqlError(error));
}
}
@ -91,8 +90,7 @@ class FileAnalyzer {
..declaredInDriftFile = true;
for (final error in analyzer.lints) {
result.analysisErrors
.add(DriftAnalysisError(error.span, error.message ?? ''));
result.analysisErrors.add(DriftAnalysisError.fromSqlError(error));
}
} else if (element is DriftView) {
final source = element.source;

View File

@ -229,7 +229,7 @@ AppliedTypeConverter readEnumConverter(
reportError('Not a class: `$enumType`');
}
final creatingClass = enumType.element2;
final creatingClass = enumType.element;
if (creatingClass is! EnumElement) {
reportError('Not an enum: `${creatingClass!.displayName}`');
}

View File

@ -0,0 +1,101 @@
@Tags(['analyzer'])
import 'package:drift/drift.dart' as drift;
import 'package:drift_dev/src/analysis/results/results.dart';
import 'package:sqlparser/sqlparser.dart';
import 'package:test/test.dart';
import '../../test_utils.dart';
void main() {
test('view created', () async {
final state = TestBackend.inTest({
'foo|lib/table.drift': '''
CREATE TABLE t (id INTEGER NOT NULL PRIMARY KEY, name TEXT NOT NULL);
''',
'foo|lib/a.drift': '''
import 'table.drift';
CREATE VIEW random_view AS
SELECT name FROM t WHERE id % 2 = 0;
''',
});
final file = await state.analyze('package:foo/a.drift');
final view = file.analyzedElements.single as DriftView;
expect(view.columns, [
isA<DriftColumn>()
.having((e) => e.sqlType, 'sqlType', drift.DriftSqlType.string)
]);
expect(view.references,
[isA<DriftTable>().having((t) => t.schemaName, 'schemaName', 't')]);
state.expectNoErrors();
});
test('view created from another view', () async {
final state = TestBackend.inTest({
'foo|lib/table.drift': '''
CREATE TABLE t (id INTEGER NOT NULL PRIMARY KEY, name TEXT NOT NULL);
''',
'foo|lib/a.drift': '''
import 'table.drift';
CREATE VIEW parent_view AS
SELECT id, name FROM t WHERE id % 2 = 0;
CREATE VIEW child_view AS
SELECT name FROM parent_view;
''',
});
final file = await state.analyze('package:foo/a.drift');
final parentView =
file.analysis[file.id('parent_view')]!.result as DriftView;
final childView = file.analysis[file.id('child_view')]!.result as DriftView;
expect(parentView.columns, hasLength(2));
expect(childView.columns, [
isA<DriftColumn>()
.having((e) => e.sqlType, 'sqlType', drift.DriftSqlType.string)
]);
expect(parentView.references.map((e) => e.id.name), ['t']);
expect(childView.references, [parentView]);
expect(childView.transitiveTableReferences.map((e) => e.schemaName), ['t']);
state.expectNoErrors();
});
test('view without table', () async {
final state = TestBackend.inTest({
'foo|lib/a.drift': '''
CREATE VIEW random_view AS
SELECT name FROM t WHERE id % 2 = 0;
''',
});
final file = await state.analyze('package:foo/a.drift');
expect(
file.allErrors, contains(isDriftError(contains('Could not find t'))));
});
test('does not allow nested columns', () async {
final state = TestBackend.inTest({
'foo|lib/a.drift': '''
CREATE TABLE foo (bar INTEGER NOT NULL PRIMARY KEY);
CREATE VIEW v AS SELECT foo.** FROM foo;
''',
});
final file = await state.analyze('package:foo/a.drift');
expect(file.allErrors, [
isDriftError(
contains('Nested star columns may only appear in a top-level select '
'query.'))
]);
});
}

View File

@ -1,4 +1,3 @@
import 'package:analyzer/dart/element/type.dart';
import 'package:drift_dev/src/analysis/results/results.dart';
import 'package:test/test.dart';

View File

@ -0,0 +1,40 @@
@Tags(['analyzer'])
import 'package:test/test.dart';
import '../../test_utils.dart';
void main() {
test('reports an error when importing a part file into .drift', () async {
final state = TestBackend.inTest({
'a|lib/base.dart': '''
import 'package:drift/drift.dart';
part 'tables.dart';
''',
'a|lib/tables.dart': '''
part of 'base.dart';
class Events extends Table {
IntColumn get id => integer().autoIncrement()();
RealColumn get sumVal => real().withDefault(Constant(0))();
}
class Records extends Table {
IntColumn get eventId => integer()();
RealColumn get value => real().nullable()();
}
''',
'a|lib/file.drift': '''
import 'tables.dart';
''',
});
final file = await state.analyze('package:a/file.drift');
expect(file.allErrors, [
isDriftError(contains("does not exist or can't be imported."))
.withSpan("import 'tables.dart';")
]);
});
}

View File

@ -1,12 +1,11 @@
import 'package:drift_dev/moor_generator.dart';
import 'package:drift/drift.dart' show DriftSqlType;
import 'package:drift_dev/src/analysis/results/results.dart';
import 'package:drift_dev/src/analyzer/drift/moor_ffi_extension.dart';
import 'package:drift_dev/src/analyzer/errors.dart';
import 'package:drift_dev/src/analyzer/options.dart';
import 'package:drift_dev/src/analyzer/runner/results.dart';
import 'package:sqlparser/sqlparser.dart' hide ResultColumn;
import 'package:test/test.dart';
import '../utils.dart';
import '../../test_utils.dart';
void main() {
late SqlEngine engine;
@ -83,41 +82,38 @@ void main() {
);
});
test('integration tests with moor files and experimental inference',
test('integration tests with drift files and experimental inference',
() async {
final state = TestState.withContent(
final state = TestBackend.inTest(
const {
'foo|lib/a.moor': '''
'foo|lib/a.drift': '''
CREATE TABLE numbers (foo REAL NOT NULL);
query: SELECT pow(oid, foo) FROM numbers;
''',
'foo|lib/b.moor': '''
import 'a.moor';
'foo|lib/b.drift': '''
import 'a.drift';
wrongArgs: SELECT sin(oid, foo) FROM numbers;
'''
},
options: const DriftOptions.defaults(modules: [SqlModule.moor_ffi]),
);
addTearDown(state.close);
final fileA = await state.analyze('package:foo/a.moor');
final fileA = await state.analyze('package:foo/a.drift');
expect(fileA.allErrors, isEmpty);
expect(fileA.errors.errors, isEmpty);
final resultA = fileA.currentResult as ParsedDriftFile;
final queryInA = resultA.resolvedQueries!.single as SqlSelectQuery;
final queryInA =
fileA.fileAnalysis!.resolvedQueries.values.single as SqlSelectQuery;
expect(
queryInA.resultSet.columns.single,
const TypeMatcher<ResultColumn>()
.having((e) => e.type, 'type', DriftSqlType.double),
.having((e) => e.sqlType, 'type', DriftSqlType.double),
);
final fileB = await state.analyze('package:foo/b.moor');
expect(fileB.errors.errors, [
const TypeMatcher<ErrorInDriftFile>()
.having((e) => e.span?.text, 'span.text', 'sin(oid, foo)')
final fileB = await state.analyze('package:foo/b.drift');
expect(fileB.allErrors, [
isDriftError('sin expects 1 arguments, got 2.').withSpan('sin(oid, foo)')
]);
});
}

View File

@ -1,11 +1,11 @@
import 'package:drift_dev/src/analyzer/runner/results.dart';
import 'package:drift_dev/src/analysis/results/results.dart';
import 'package:test/test.dart';
import '../utils.dart';
import '../../test_utils.dart';
void main() {
test('moor files can import original dart source', () async {
final state = TestState.withContent({
test('drift files can import original dart source', () async {
final state = TestBackend.inTest({
'a|lib/base.dart': r'''
import 'package:drift/drift.dart';
@ -29,25 +29,23 @@ class Units extends Table {
IntColumn get id => integer().autoIncrement()();
}
@DriftDatabase(include: {'customizedSQL.moor'})
@DriftDatabase(include: {'customizedSQL.drift'})
class AppDatabase extends _$AppDatabase {
AppDatabase()
: super(FlutterQueryExecutor.inDatabaseFolder(
path: "db.sqlite", logStatements: true));
}
''',
'a|lib/customizedSQL.moor': '''
'a|lib/customizedSQL.drift': '''
import 'base.dart';
create trigger addVal after insert on records when id = NEW.event_id BEGIN update events set sum_val = sum_val + NEW.value; END;
''',
});
addTearDown(state.close);
final file = await state.analyze('package:a/base.dart');
final result = file.currentResult as ParsedDartFile;
final db = result.declaredDatabases.single;
final db = file.fileAnalysis!.resolvedDatabases.values.single;
expect(db.tables, hasLength(3));
expect(db.availableElements.whereType<DriftTable>(), hasLength(3));
});
}

View File

@ -1,13 +1,14 @@
import 'package:drift_dev/src/analysis/results/results.dart';
import 'package:drift_dev/src/analyzer/options.dart';
import 'package:sqlparser/sqlparser.dart';
import 'package:test/test.dart';
import '../utils.dart';
import '../../test_utils.dart';
void main() {
// https://github.com/simolus3/drift/issues/2097#issuecomment-1273008383
test('supports fts5 tables with external content', () async {
final state = TestState.withContent(
test('virtual columns are not required for inserts', () async {
final state = TestBackend.inTest(
{
'foo|lib/a.drift': r'''
CREATE TABLE IF NOT EXISTS nodes (
@ -25,12 +26,13 @@ insertNode: INSERT INTO nodes VALUES(json(?));
final result = await state.analyze('package:foo/a.drift');
expect(result.errors.errors, isEmpty);
final table = result.analysis[result.id('nodes')]!.result as DriftTable;
final table = result.currentResult!.declaredTables.single;
expect(table.sqlName, 'nodes');
expect(table.schemaName, 'nodes');
expect(table.columns, hasLength(2));
expect(table.isColumnRequiredForInsert(table.columns[0]), isFalse);
expect(table.isColumnRequiredForInsert(table.columns[1]), isFalse);
state.expectNoErrors();
});
}

View File

@ -1,13 +1,13 @@
import 'package:drift_dev/src/analyzer/options.dart';
import 'package:test/test.dart';
import '../utils.dart';
import '../../test_utils.dart';
void main() {
// Regression test for https://github.com/simolus3/drift/issues/754
test('supports fts5 tables with external content', () async {
final state = TestState.withContent({
'foo|lib/a.moor': '''
final state = TestBackend.inTest({
'foo|lib/a.drift': '''
CREATE TABLE tbl(a INTEGER PRIMARY KEY, b TEXT, c TEXT);
CREATE VIRTUAL TABLE fts_idx USING fts5(b, c, content='tbl', content_rowid='a');
@ -27,10 +27,11 @@ END;
''',
}, options: const DriftOptions.defaults(modules: [SqlModule.fts5]));
final result = await state.analyze('package:foo/a.moor');
final result = await state.analyze('package:foo/a.drift');
// The generator used to crash while analyzing, so consider the test passed
// if it can analyze the file and sees that there aren't any errors.
expect(result.errors.errors, isEmpty);
state.expectNoErrors();
expect(result.analyzedElements, hasLength(5));
});
}

View File

@ -0,0 +1,53 @@
import 'package:drift_dev/src/analyzer/options.dart';
import 'package:sqlparser/sqlparser.dart';
import 'package:test/test.dart';
import '../../test_utils.dart';
const _content = {
'a|lib/main.drift': '''
CREATE TABLE foo (
id INTEGER NOT NULL PRIMARY KEY,
content TEXT NOT NULL UNIQUE,
content2 TEXT NOT NULL UNIQUE
);
query: INSERT INTO foo VALUES (?, ?, ?)
ON CONFLICT (content) DO NOTHING
ON CONFLICT (content2) DO UPDATE SET content2 = 'duplicate';
''',
};
void main() {
test('does not support newer sqlite features by default', () async {
final state = TestBackend.inTest(_content);
final file = await state.analyze('package:a/main.drift');
expect(
file.allErrors,
[
isDriftError(
allOf(
contains('require sqlite version 3.35 or later'),
contains(
'You can change the assumed sqlite version with build options.'),
),
),
],
);
});
test('supports newer sqlite features', () async {
final state = TestBackend.inTest(
_content,
options: const DriftOptions.defaults(
sqliteAnalysisOptions: SqliteAnalysisOptions(
version: SqliteVersion.v3_35,
),
),
);
await state.analyze('package:a/main.drift');
state.expectNoErrors();
});
}

View File

@ -35,7 +35,7 @@ CREATE TABLE b (
expect(aFoo.sqlType, DriftSqlType.int);
expect(aFoo.nullable, isFalse);
expect(aFoo.constraints, isEmpty);
expect(aFoo.constraints, [isA<PrimaryKeyColumn>()]);
expect(aFoo.customConstraints, isNull);
expect(aBar.sqlType, DriftSqlType.int);
@ -53,4 +53,128 @@ CREATE TABLE b (
expect(bBar.constraints, isEmpty);
expect(bBar.customConstraints, isNull);
});
test('recognizes aliases to rowid', () async {
final state = TestBackend.inTest({
'foo|lib/a.drift': '''
CREATE TABLE users (
id INTEGER PRIMARY KEY,
name TEXT NOT NULL
);
CREATE TABLE users2 (
id INTEGER,
name TEXT NOT NULL,
PRIMARY KEY (id)
);
'''
});
final result = await state.analyze('package:foo/a.drift');
final users1 = result.analysis[result.id('users')]!.result as DriftTable;
final users2 = result.analysis[result.id('users2')]!.result as DriftTable;
expect(users1.isColumnRequiredForInsert(users1.columns[0]), isFalse);
expect(users1.isColumnRequiredForInsert(users1.columns[1]), isTrue);
expect(users2.isColumnRequiredForInsert(users2.columns[0]), isFalse);
expect(users2.isColumnRequiredForInsert(users2.columns[1]), isTrue);
});
test('parses enum columns', () async {
final state = TestBackend.inTest({
'a|lib/a.drift': '''
import 'enum.dart';
CREATE TABLE foo (
fruit ENUM(Fruits) NOT NULL,
another ENUM(DoesNotExist) NOT NULL
);
''',
'a|lib/enum.dart': '''
enum Fruits {
apple, orange, banana
}
''',
});
final file = await state.analyze('package:a/a.drift');
final table = file.analyzedElements.single as DriftTable;
final column = table.columns.singleWhere((c) => c.nameInSql == 'fruit');
expect(column.sqlType, DriftSqlType.int);
expect(
column.typeConverter,
isA<AppliedTypeConverter>()
.having(
(e) => e.expression.toString(),
'expression',
contains('EnumIndexConverter<Fruits>'),
)
.having((e) => e.dartType.getDisplayString(withNullability: true),
'dartType', 'Fruits'),
);
expect(
file.allErrors,
contains(isDriftError(contains('Type DoesNotExist could not be found'))
.withSpan('ENUM(DoesNotExist)')),
);
});
test('does not allow converters for enum columns', () async {
final state = TestBackend.inTest({
'a|lib/a.drift': '''
import 'enum.dart';
CREATE TABLE foo (
fruit ENUM(Fruits) NOT NULL MAPPED BY `MyConverter()`
);
''',
'a|lib/enum.dart': '''
import 'package:drift/drift.dart';
enum Fruits {
apple, orange, banana
}
class MyConverter extends TypeConverter<String, String> {}
''',
});
final file = await state.analyze('package:a/a.drift');
expect(
file.allErrors,
[
isDriftError(
'Multiple type converters applied to this column, ignoring this one.')
.withSpan('MAPPED BY `MyConverter()`')
],
);
});
test('does not allow enum types for non-enums', () async {
final state = TestBackend.inTest({
'a|lib/a.drift': '''
import 'enum.dart';
CREATE TABLE foo (
fruit ENUM(NotAnEnum) NOT NULL
);
''',
'a|lib/enum.dart': '''
class NotAnEnum {}
''',
});
final file = await state.analyze('package:a/a.drift');
expect(file.analyzedElements, hasLength(1));
expect(
file.allErrors,
contains(isDriftError('Not an enum: `NotAnEnum`')),
);
});
}

View File

@ -1,3 +1,5 @@
import 'package:drift/drift.dart';
import 'package:drift_dev/src/analysis/results/results.dart';
import 'package:drift_dev/src/analysis/results/table.dart';
import 'package:test/test.dart';
@ -91,23 +93,82 @@ CREATE TABLE b (
expect(a.references, [b]);
});
test('non-existing', () async {
test('for triggers', () async {
final backend = TestBackend.inTest({
'a|lib/a.drift': '''
import 'b.drift';
CREATE TRIGGER my_trigger AFTER DELETE ON b BEGIN
INSERT INTO deleted_b VALUES (old.bar);
END;
''',
'a|lib/b.drift': '''
CREATE TABLE b (
bar INTEGER NOT NULL
);
CREATE TABLE deleted_b (
bar INTEGER NOT NULL
);
''',
});
final file = await backend.analyze('package:a/a.drift');
backend.expectNoErrors();
final trigger = file.analyzedElements.single as DriftTrigger;
expect(trigger.references, [
isA<DriftTable>().having((e) => e.schemaName, 'schemaName', 'b'),
isA<DriftTable>()
.having((e) => e.schemaName, 'schemaName', 'deleted_b'),
]);
expect(trigger.writes, [
isA<WrittenDriftTable>()
.having((e) => e.table.schemaName, 'table.schemaName', 'deleted_b')
.having((e) => e.kind, 'kind', UpdateKind.insert),
]);
});
test('for indices', () async {});
group('non-existing', () {
test('from table', () async {
final backend = TestBackend.inTest({
'a|lib/a.drift': '''
CREATE TABLE a (
foo INTEGER PRIMARY KEY,
bar INTEGER REFERENCES b (bar)
);
''',
});
final state = await backend.driver
.resolveElements(Uri.parse('package:a/a.drift'));
expect(state.errorsDuringDiscovery, isEmpty);
final resultA = state.analysis.values.single;
expect(resultA.errorsDuringAnalysis,
[isDriftError('`b` could not be found in any import.')]);
});
test('in a trigger', () async {
final backend = TestBackend.inTest(const {
'foo|lib/a.drift': '''
CREATE TRIGGER IF NOT EXISTS foo BEFORE DELETE ON bar BEGIN
END;
''',
});
final state =
await backend.driver.resolveElements(Uri.parse('package:a/a.drift'));
expect(state.errorsDuringDiscovery, isEmpty);
final file = await backend.analyze('package:foo/a.drift');
final resultA = state.analysis.values.single;
expect(resultA.errorsDuringAnalysis,
[isDriftError('This reference could not be found in any import.')]);
expect(
file.allErrors,
contains(
isDriftError(contains('`bar` could not be found in any import'))
.withSpan('bar'),
),
);
});
});
});
}

View File

@ -1,125 +0,0 @@
@Tags(['analyzer'])
import 'package:drift_dev/moor_generator.dart';
import 'package:drift_dev/src/analyzer/errors.dart';
import 'package:test/test.dart';
import '../utils.dart';
void main() {
test('parses enum columns', () async {
final state = TestState.withContent({
'foo|lib/a.moor': '''
import 'enum.dart';
CREATE TABLE foo (
fruit ENUM(Fruits) NOT NULL,
another ENUM(DoesNotExist) NOT NULL
);
''',
'foo|lib/enum.dart': '''
enum Fruits {
apple, orange, banane
}
''',
});
final file = await state.analyze('package:foo/a.moor');
final table = file.currentResult!.declaredTables.single;
final column = table.columns.singleWhere((c) => c.name.name == 'fruit');
state.close();
expect(column.type, DriftSqlType.int);
expect(
column.typeConverter,
isA<UsedTypeConverter>()
.having(
(e) => e.expression,
'expression',
contains('EnumIndexConverter<Fruits>'),
)
.having(
(e) => e.dartType.getDisplayString(withNullability: false),
'mappedType',
'Fruits',
),
);
expect(
file.errors.errors,
contains(
isA<DriftError>().having(
(e) => e.message,
'message',
contains('Type DoesNotExist could not be found'),
),
),
);
});
test('does not allow converters for enum columns', () async {
final state = TestState.withContent({
'foo|lib/a.moor': '''
import 'enum.dart';
CREATE TABLE foo (
fruit ENUM(Fruits) NOT NULL MAPPED BY `MyConverter()`
);
''',
'foo|lib/enum.dart': '''
import 'package:drift/drift.dart';
enum Fruits {
apple, orange, banane
}
class MyConverter extends TypeConverter<String, String> {}
''',
});
final file = await state.analyze('package:foo/a.moor');
state.close();
expect(
file.errors.errors,
contains(
isA<DriftError>().having(
(e) => e.message,
'message',
contains("can't apply another converter"),
),
),
);
});
test('does not allow enum types for non-enums', () async {
final state = TestState.withContent({
'foo|lib/a.moor': '''
import 'enum.dart';
CREATE TABLE foo (
fruit ENUM(NotAnEnum) NOT NULL
);
''',
'foo|lib/enum.dart': '''
class NotAnEnum {}
''',
});
final file = await state.analyze('package:foo/a.moor');
state.close();
expect(
file.errors.errors,
contains(
isA<ErrorInDriftFile>()
.having(
(e) => e.message,
'message',
allOf(contains('NotAnEnum'), contains('Not an enum')),
)
.having((e) => e.span?.text, 'span', 'ENUM(NotAnEnum)'),
),
);
});
}

View File

@ -1,114 +0,0 @@
@Tags(['analyzer'])
import 'package:drift_dev/src/analyzer/errors.dart';
import 'package:drift_dev/src/model/table.dart';
import 'package:sqlparser/sqlparser.dart';
import 'package:test/test.dart';
import '../utils.dart';
void main() {
test('view created', () async {
final state = TestState.withContent({
'foo|lib/table.moor': '''
CREATE TABLE t (id INTEGER NOT NULL PRIMARY KEY, name TEXT NOT NULL);
''',
'foo|lib/a.moor': '''
import 'table.moor';
CREATE VIEW random_view AS
SELECT name FROM t WHERE id % 2 = 0;
''',
});
final file = await state.analyze('package:foo/a.moor');
final view = file.currentResult!.declaredViews.single;
expect(view.parserView!.resolvedColumns.length, equals(1));
final column = view.parserView!.resolvedColumns.single;
state.close();
expect(column.type!.type, BasicType.text);
expect(view.references,
contains(isA<DriftTable>().having((t) => t.sqlName, 'sqlName', 't')));
expect(file.errors.errors, isEmpty);
});
test('view created from another view', () async {
final state = TestState.withContent({
'foo|lib/table.moor': '''
CREATE TABLE t (id INTEGER NOT NULL PRIMARY KEY, name TEXT NOT NULL);
''',
'foo|lib/a.moor': '''
import 'table.moor';
CREATE VIEW parent_view AS
SELECT id, name FROM t WHERE id % 2 = 0;
CREATE VIEW child_view AS
SELECT name FROM parent_view;
''',
});
final file = await state.analyze('package:foo/a.moor');
final parentView = file.currentResult!.declaredViews
.singleWhere((element) => element.name == 'parent_view');
final childView = file.currentResult!.declaredViews
.singleWhere((element) => element.name == 'child_view');
expect(parentView.parserView!.resolvedColumns.length, equals(2));
expect(childView.parserView!.resolvedColumns.length, equals(1));
final column = childView.parserView!.resolvedColumns.single;
state.close();
expect(parentView.references.map((e) => e.displayName), ['t']);
expect(childView.references, [parentView]);
expect(
childView.transitiveTableReferences.map((e) => e.displayName), ['t']);
expect(file.errors.errors, isEmpty);
expect(column.type!.type, BasicType.text);
});
test('view without table', () async {
final state = TestState.withContent({
'foo|lib/a.moor': '''
CREATE VIEW random_view AS
SELECT name FROM t WHERE id % 2 = 0;
''',
});
final file = await state.analyze('package:foo/a.moor');
state.close();
expect(
file.errors.errors,
contains(isA<DriftError>().having(
(e) => e.message,
'message',
contains('Could not find t.'),
)));
});
test('does not allow nested columns', () async {
final state = TestState.withContent({
'foo|lib/a.moor': '''
CREATE TABLE foo (bar INTEGER NOT NULL PRIMARY KEY);
CREATE VIEW v AS SELECT foo.** FROM foo;
''',
});
final file = await state.analyze('package:foo/a.moor');
state.close();
expect(
file.errors.errors,
contains(isA<DriftError>().having(
(e) => e.message,
'message',
contains('Nested star columns may only appear in a top-level select '
'query.'),
)));
});
}

View File

@ -1,149 +0,0 @@
import 'package:drift_dev/moor_generator.dart';
import 'package:test/test.dart';
import '../utils.dart';
void main() {
group('finds referenced tables', () {
const definitions = {
'foo|lib/b.moor': '''
CREATE TABLE users (
id INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT,
name VARCHAR
);
'''
};
TestState? state;
tearDown(() => state?.close());
test('in a foreign key clause', () async {
state = TestState.withContent(const {
'foo|lib/a.moor': '''
import 'b.moor';
CREATE TABLE friendships (
user_a INTEGER REFERENCES users (id),
user_b INTEGER REFERENCES users (id),
PRIMARY KEY(user_a, user_b),
CHECK (user_a != user_b)
);
''',
...definitions,
});
final file = await state!.analyze('package:foo/a.moor');
expect(file.errors.errors, isEmpty);
final table = file.currentResult!.declaredTables.single;
expect(
table.references,
[
const TypeMatcher<DriftTable>()
.having((table) => table.displayName, 'displayName', 'users'),
],
);
});
test('in a trigger', () async {
state = TestState.withContent(const {
'foo|lib/a.moor': '''
import 'b.moor';
CREATE TABLE friendships (
user_a INTEGER REFERENCES users (id),
user_b INTEGER REFERENCES users (id),
PRIMARY KEY(user_a, user_b),
CHECK (user_a != user_b)
);
CREATE TRIGGER my_trigger AFTER DELETE ON users BEGIN
DELETE FROM friendships WHERE user_a = old.id OR user_b = old.id;
END;
''',
...definitions,
});
final file = await state!.analyze('package:foo/a.moor');
expect(file.errors.errors, isEmpty);
final trigger =
file.currentResult!.declaredEntities.whereType<MoorTrigger>().single;
expect(
trigger.references,
{
const TypeMatcher<DriftTable>().having(
(table) => table.displayName, 'displayName', 'friendships'),
const TypeMatcher<DriftTable>()
.having((table) => table.displayName, 'displayName', 'users'),
},
);
expect(trigger.bodyReferences.map((t) => t.sqlName),
{'users', 'friendships'});
expect(trigger.bodyUpdates.map((t) => t.table.sqlName), {'friendships'});
});
test('in an index', () async {
state = TestState.withContent(const {
'foo|lib/a.moor': '''
import 'b.moor';
CREATE INDEX idx ON users (name);
''',
...definitions,
});
final file = await state!.analyze('package:foo/a.moor');
expect(file.errors.errors, isEmpty);
final trigger = file.currentResult!.declaredEntities.single as MoorIndex;
expect(trigger.references, {
const TypeMatcher<DriftTable>()
.having((table) => table.displayName, 'displayName', 'users'),
});
});
});
group('issues error when referencing an unknown table', () {
TestState? state;
tearDown(() => state?.close());
test('in a foreign key clause', () async {
state = TestState.withContent(const {
'foo|lib/a.moor': '''
CREATE TABLE foo (
id INTEGER NOT NULL REFERENCES bar (baz) PRIMARY KEY
);
'''
});
final file = await state!.analyze('package:foo/a.moor');
expect(
file.errors.errors.map((e) => e.message),
contains('Referenced table bar could not befound.'),
);
});
test('in a trigger', () async {
state = TestState.withContent(const {
'foo|lib/a.moor': '''
CREATE TRIGGER IF NOT EXISTS foo BEFORE DELETE ON bar BEGIN
END;
''',
});
final file = await state!.analyze('package:foo/a.moor');
expect(
file.errors.errors.map((e) => e.message),
contains('Target table bar could not be found.'),
);
});
});
}

View File

@ -1,49 +0,0 @@
@Tags(['analyzer'])
import 'package:drift_dev/src/analyzer/errors.dart';
import 'package:test/test.dart';
import '../utils.dart';
void main() {
test('reports an error when importing a part file into .moor', () async {
final state = TestState.withContent({
'a|lib/base.dart': '''
import 'package:drift/drift.dart';
part 'tables.dart';
''',
'a|lib/tables.dart': '''
part of 'base.dart';
class Events extends Table {
IntColumn get id => integer().autoIncrement()();
RealColumn get sumVal => real().withDefault(Constant(0))();
}
class Records extends Table {
IntColumn get eventId => integer()();
RealColumn get value => real().nullable()();
}
''',
'a|lib/file.moor': '''
import 'tables.dart';
''',
});
addTearDown(state.close);
final file = await state.analyze('package:a/file.moor');
expect(file.errors.errors, hasLength(1));
expect(
file.errors.errors.single,
isA<ErrorInDriftFile>()
.having(
(e) => e.message,
'message',
contains('Is it a part file?'),
)
.having((e) => e.span?.text, 'span.text', "import 'tables.dart';"),
);
});
}

View File

@ -1,83 +0,0 @@
import 'package:build/build.dart';
import 'package:drift_dev/src/analyzer/runner/results.dart';
import 'package:drift_dev/src/analyzer/runner/steps.dart';
import 'package:drift_dev/src/analyzer/session.dart';
import 'package:drift_dev/src/model/types.dart';
import 'package:test/test.dart';
import '../../utils/test_backend.dart';
import '../utils.dart';
void main() {
const content = '''
import 'package:my_package/some_file.dart';
import 'relative_file.moor';
CREATE TABLE users(
id INT NOT NULL PRIMARY KEY AUTOINCREMENT,
name VARCHAR NOT NULL CHECK(LENGTH(name) BETWEEN 5 AND 30),
field BOOLEAN,
another DATETIME,
different_json INT JSON KEY myJsonKey
);
usersWithLongName: SELECT * FROM users WHERE LENGTH(name) > 25;
''';
test('parses standalone .moor files', () async {
final asset = AssetId.parse('foo|bar.moor');
final backend = TestBackend({asset: content});
final session = MoorSession(backend);
final task = session.startTask(backend.startTask(asset.uri));
final file = session.registerFile(asset.uri);
final parseStep = ParseMoorStep(task, file, content);
final result = await parseStep.parseFile();
expect(parseStep.errors.errors, isEmpty);
final table = result.declaredTables.single;
expect(table.sqlName, 'users');
expect(table.columns.map((c) => c.name.name),
['id', 'name', 'field', 'another', 'different_json']);
expect(table.columns.map((c) => c.dartGetterName),
['id', 'name', 'field', 'another', 'differentJson']);
expect(table.columns.map((c) => c.dartTypeCode()),
['int', 'String', 'bool?', 'DateTime?', 'int?']);
expect(table.columns.map((c) => c.getJsonKey()),
['id', 'name', 'field', 'another', 'myJsonKey']);
backend.finish();
});
test('recognizes aliases to rowid', () async {
final state = TestState.withContent({
'foo|lib/a.moor': '''
CREATE TABLE users (
id INTEGER PRIMARY KEY,
name TEXT NOT NULL
);
CREATE TABLE users2 (
id INTEGER,
name TEXT NOT NULL,
PRIMARY KEY (id)
);
'''
});
final result = await state.analyze('package:foo/a.moor');
state.close();
final file = result.currentResult as ParsedDriftFile;
final users1 = file.declaredTables.singleWhere((t) => t.sqlName == 'users');
final users2 =
file.declaredTables.singleWhere((t) => t.sqlName == 'users2');
expect(users1.isColumnRequiredForInsert(users1.columns[0]), isFalse);
expect(users1.isColumnRequiredForInsert(users1.columns[1]), isTrue);
expect(users2.isColumnRequiredForInsert(users2.columns[0]), isFalse);
expect(users2.isColumnRequiredForInsert(users2.columns[1]), isTrue);
});
}

View File

@ -1,59 +0,0 @@
import 'package:drift_dev/src/analyzer/errors.dart';
import 'package:drift_dev/src/analyzer/options.dart';
import 'package:sqlparser/sqlparser.dart';
import 'package:test/test.dart';
import '../utils.dart';
const _moorFile = '''
CREATE TABLE foo (
id INTEGER NOT NULL PRIMARY KEY,
content TEXT NOT NULL UNIQUE,
content2 TEXT NOT NULL UNIQUE
);
query: INSERT INTO foo VALUES (?, ?, ?)
ON CONFLICT (content) DO NOTHING
ON CONFLICT (content2) DO UPDATE SET content2 = 'duplicate';
''';
void main() {
test('does not support newer sqlite features by default', () async {
final state = TestState.withContent(
const {
'a|lib/main.moor': _moorFile,
},
);
final file = await state.analyze('package:a/main.moor');
expect(file.errors.errors, hasLength(1));
expect(
file.errors.errors.single,
isA<ErrorInDriftFile>().having(
(e) => e.message,
'message',
allOf(
contains('require sqlite version 3.35 or later'),
contains(
'You can change the assumed sqlite version with build options.'),
),
),
);
});
test('supports newer sqlite features', () async {
final state = TestState.withContent(
const {
'a|lib/main.moor': _moorFile,
},
options: const DriftOptions.defaults(
sqliteAnalysisOptions: SqliteAnalysisOptions(
version: SqliteVersion.v3_35,
),
),
);
final file = await state.analyze('package:a/main.moor');
expect(file.errors.errors, isEmpty);
});
}