Port more tests over to new analyzer

This commit is contained in:
Simon Binder 2022-10-29 17:30:18 +02:00
parent 49470e8361
commit 292dd9946d
No known key found for this signature in database
GPG Key ID: 7891917E4147B8C0
10 changed files with 214 additions and 254 deletions

View File

@ -44,6 +44,8 @@ class FileState {
.every((e) => elementIsAnalyzed(e.ownId));
}
DriftElementId id(String name) => DriftElementId(ownUri, name);
bool elementIsAnalyzed(DriftElementId id) {
return analysis[id]?.isUpToDate == true;
}

View File

@ -197,24 +197,29 @@ class DriftTableResolver extends LocalElementResolver<DiscoveredDriftTable> {
} else if (stmt is CreateVirtualTableStatement) {
RecognizedVirtualTableModule? recognized;
if (table is Fts5Table) {
final errorLocation = stmt.tableNameToken ?? stmt;
final errorLocation = stmt.arguments
.firstWhereOrNull((e) => e.text.contains('content')) ??
stmt.span;
final contentTable = table.contentTable != null
? await resolveSqlReferenceOrReportError<DriftTable>(
table.contentTable!,
(msg) => DriftAnalysisError.inDriftFile(errorLocation,
(msg) => DriftAnalysisError(errorLocation,
'Could not find referenced content table: $msg'))
: null;
DriftColumn? contentRowId;
if (contentTable != null) {
references.add(contentTable);
final parserContentTable =
resolver.driver.typeMapping.asSqlParserTable(contentTable);
final rowId = parserContentTable.findColumn(table.contentRowId!);
if (rowId == null) {
reportError(DriftAnalysisError.inDriftFile(
errorLocation,
var location = stmt.arguments
.firstWhereOrNull((e) => e.text.contains('content_rowid'));
reportError(DriftAnalysisError(
location ?? errorLocation,
'Invalid content rowid, `${table.contentRowId}` not found '
'in `${contentTable.schemaName}`'));
} else if (rowId is! RowId) {
@ -226,8 +231,11 @@ class DriftTableResolver extends LocalElementResolver<DiscoveredDriftTable> {
// Also, check that all columns referenced in the fts5 table exist in
// the content table.
for (final column in columns) {
var location = stmt.arguments
.firstWhereOrNull((e) => e.text == column.nameInSql);
if (parserContentTable.findColumn(column.nameInSql) == null) {
reportError(DriftAnalysisError.inDriftFile(errorLocation,
reportError(DriftAnalysisError(location ?? errorLocation,
'The content table has no column `${column.nameInSql}`.'));
}
}

View File

@ -157,7 +157,7 @@ class DriftResolver {
if (candidates.isEmpty) {
return InvalidReferenceResult(
InvalidReferenceError.noElementWichSuchName,
'This reference could not be found in any import.',
'`$reference` could not be found in any import.',
);
} else if (candidates.length > 1) {
final description =

View File

@ -0,0 +1,63 @@
import 'package:drift/drift.dart' show DriftSqlType;
import 'package:drift_dev/src/analysis/results/results.dart';
import 'package:test/test.dart';
import '../../test_utils.dart';
void main() {
test('recognizes CTE clause', () async {
final backend = TestBackend.inTest({
'a|lib/test.drift': '''
test:
WITH RECURSIVE
cnt(x) AS (
SELECT 1
UNION ALL
SELECT x+1 FROM cnt
LIMIT 1000000
)
SELECT x FROM cnt;
'''
});
final file = await backend.analyze('package:a/test.drift');
backend.expectNoErrors();
final query =
file.fileAnalysis!.resolvedQueries.values.single as SqlSelectQuery;
expect(query.variables, isEmpty);
expect(query.readsFrom, isEmpty);
final resultSet = query.resultSet;
expect(resultSet.singleColumn, isTrue);
expect(resultSet.needsOwnClass, isFalse);
expect(resultSet.columns.map(resultSet.dartNameFor), ['x']);
expect(resultSet.columns.map((c) => c.sqlType), [DriftSqlType.int]);
});
test('finds the underlying table when aliased through CTE', () async {
final backend = TestBackend.inTest({
'a|lib/test.drift': '''
CREATE TABLE foo (
id INT NOT NULL PRIMARY KEY AUTOINCREMENT,
bar VARCHAR NOT NULL
);
test2:
WITH alias("first", second) AS (SELECT * FROM foo) SELECT * FROM alias;
'''
});
final file = await backend.analyze('package:a/test.drift');
backend.expectNoErrors();
final query =
file.fileAnalysis!.resolvedQueries.values.single as SqlSelectQuery;
final resultSet = query.resultSet;
expect(resultSet.matchingTable, isNotNull);
expect(resultSet.matchingTable!.table.schemaName, 'foo');
expect(resultSet.needsOwnClass, isFalse);
});
}

View File

@ -0,0 +1,100 @@
import 'package:analyzer/dart/element/type.dart';
import 'package:drift_dev/src/analysis/results/results.dart';
import 'package:test/test.dart';
import '../../test_utils.dart';
void main() {
test('can use existing row classes in drift files', () async {
final state = TestBackend.inTest({
'a|lib/db.drift': '''
import 'rows.dart';
CREATE TABLE custom_name (
id INTEGER NOT NULL PRIMARY KEY,
foo TEXT
) AS MyCustomClass;
CREATE TABLE existing (
id INTEGER NOT NULL PRIMARY KEY,
foo TEXT
) WITH ExistingRowClass;
CREATE VIEW existing_view WITH ExistingForView (foo, bar)
AS SELECT 1, 2;
''',
'a|lib/rows.dart': '''
class ExistingRowClass {
ExistingRowClass(int id, String? foo);
}
class ExistingForView {
ExistingForView(int foo, int bar);
}
''',
});
final file = await state.analyze('package:a/db.drift');
state.expectNoErrors();
final customName =
file.analysis[file.id('custom_name')]!.result! as DriftTable;
final existing = file.analysis[file.id('existing')]!.result! as DriftTable;
final existingView =
file.analysis[file.id('existing_view')]!.result! as DriftView;
expect(customName.nameOfRowClass, 'MyCustomClass');
expect(customName.existingRowClass, isNull);
expect(existing.nameOfRowClass, 'ExistingRowClass');
expect(
existing.existingRowClass!.targetClass.toString(), 'ExistingRowClass');
expect(existingView.nameOfRowClass, 'ExistingForView');
expect(existingView.existingRowClass!.targetClass.toString(),
'ExistingForView');
});
test('can use generic row classes', () async {
final state = TestBackend.inTest({
'a|lib/generic.dart': '''
//@dart=2.13
typedef StringRow = GenericRow<String>;
typedef IntRow = GenericRow<int>;
class GenericRow<T> {
final T value;
GenericRow(this.value);
}
''',
'a|lib/generic.drift': '''
import 'generic.dart';
CREATE TABLE drift_strings (
value TEXT NOT NULL
) WITH StringRow;
CREATE TABLE drift_ints (
value INT NOT NULL
) WITH IntRow;
''',
});
final file = await state.analyze('package:a/generic.drift');
state.expectNoErrors();
final strings =
file.analysis[file.id('drift_strings')]!.result! as DriftTable;
final ints = file.analysis[file.id('drift_ints')]!.result! as DriftTable;
expect(
strings.existingRowClass,
isA<ExistingRowClass>().having((e) => e.targetType.toString(),
'targetType', 'GenericRow<String>'));
expect(
ints.existingRowClass,
isA<ExistingRowClass>().having(
(e) => e.targetType.toString(), 'targetType', 'GenericRow<int>'));
});
}

View File

@ -1,77 +1,60 @@
import 'package:drift_dev/src/analyzer/errors.dart';
import 'package:drift_dev/src/analyzer/options.dart';
import 'package:test/test.dart';
import '../utils.dart';
import '../../test_utils.dart';
const _options = DriftOptions.defaults(modules: [SqlModule.fts5]);
void main() {
group('reports error', () {
test('for missing content table', () async {
final state = TestState.withContent({
final state = TestBackend.inTest({
'a|lib/main.drift': '''
CREATE VIRTUAL TABLE fts USING fts5(a, c, content=tbl);
''',
}, options: _options);
addTearDown(state.close);
final result = await state.analyze('package:a/main.drift');
expect(result.errors.errors, [
const TypeMatcher<ErrorInDriftFile>().having(
(e) => e.message,
'message',
contains('Content table `tbl` could not be found'),
),
expect(result.allErrors, [
isDriftError('Could not find referenced content table: '
'`tbl` could not be found in any import.')
.withSpan('content=tbl'),
]);
});
test('for invalid rowid of content table', () async {
final state = TestState.withContent({
final state = TestBackend.inTest({
'a|lib/main.drift': '''
CREATE TABLE tbl (a, b, c, my_pk INTEGER PRIMARY KEY);
CREATE VIRTUAL TABLE fts USING fts5(a, c, content=tbl, content_rowid=d);
''',
}, options: _options);
addTearDown(state.close);
final result = await state.analyze('package:a/main.drift');
expect(result.errors.errors, [
const TypeMatcher<ErrorInDriftFile>().having(
(e) => e.message,
'message',
contains(
'no column `d`, but this fts5 table is declared to use it as a row '
'id',
),
),
expect(result.allErrors, [
isDriftError('Invalid content rowid, `d` not found in `tbl`')
.withSpan('content_rowid=d'),
]);
});
test('when referencing an unknown column', () async {
final state = TestState.withContent({
final state = TestBackend.inTest({
'a|lib/main.drift': '''
CREATE TABLE tbl (a, b, c, d INTEGER PRIMARY KEY);
CREATE VIRTUAL TABLE fts USING fts5(e, c, content=tbl, content_rowid=d);
''',
}, options: _options);
addTearDown(state.close);
final result = await state.analyze('package:a/main.drift');
expect(result.errors.errors, [
const TypeMatcher<ErrorInDriftFile>().having(
(e) => e.message,
'message',
contains('no column `e`, but this fts5 table references it'),
),
]);
expect(result.allErrors,
[isDriftError('The content table has no column `e`.').withSpan('e')]);
});
});
test('finds referenced table', () async {
final state = TestState.withContent({
final state = TestBackend.inTest({
'a|lib/main.drift': '''
CREATE TABLE tbl (a, b, c, d INTEGER PRIMARY KEY);
@ -79,11 +62,10 @@ CREATE VIRTUAL TABLE fts USING fts5(a, c, content=tbl, content_rowid=d);
CREATE VIRTUAL TABLE fts2 USING fts5(a, c, content=tbl, content_rowid=rowid);
''',
}, options: _options);
addTearDown(state.close);
final result = await state.analyze('package:a/main.drift');
expect(result.errors.errors, isEmpty);
final tables = result.currentResult!.declaredTables.toList();
expect(result.allErrors, isEmpty);
final tables = result.analyzedElements.toList();
expect(tables, hasLength(3));
expect(tables[1].references, contains(tables[0]));

View File

@ -1,86 +0,0 @@
import 'package:build/build.dart';
import 'package:drift_dev/moor_generator.dart';
import 'package:drift_dev/src/analyzer/runner/file_graph.dart';
import 'package:drift_dev/src/analyzer/runner/results.dart';
import 'package:drift_dev/src/analyzer/runner/task.dart';
import 'package:drift_dev/src/analyzer/session.dart';
import 'package:test/test.dart';
import '../../utils/test_backend.dart';
void main() {
late TestBackend backend;
late MoorSession session;
late Task task;
setUpAll(() {
backend = TestBackend(
{
AssetId.parse('foo|lib/test.moor'): r'''
CREATE TABLE foo (
id INT NOT NULL PRIMARY KEY AUTOINCREMENT,
bar VARCHAR NOT NULL
);
test:
WITH RECURSIVE
cnt(x) AS (
SELECT 1
UNION ALL
SELECT x+1 FROM cnt
LIMIT 1000000
)
SELECT x FROM cnt;
test2:
WITH alias("first", second) AS (SELECT * FROM foo) SELECT * FROM alias;
''',
},
);
session = MoorSession(backend);
});
setUp(() async {
final backendTask = backend.startTask(Uri.parse('package:foo/test.moor'));
task = session.startTask(backendTask);
await task.runTask();
});
tearDownAll(() {
backend.finish();
});
test('recognizes CFE clause', () {
final file = session.registerFile(Uri.parse('package:foo/test.moor'));
expect(file.state, FileState.analyzed);
expect(file.errors.errors, isEmpty);
final result = file.currentResult as ParsedDriftFile;
final query = result.resolvedQueries!.firstWhere((q) => q.name == 'test')
as SqlSelectQuery;
expect(query.variables, isEmpty);
expect(query.declaredInMoorFile, isTrue);
expect(query.readsFrom, isEmpty);
final resultSet = query.resultSet;
expect(resultSet.singleColumn, isTrue);
expect(resultSet.needsOwnClass, isFalse);
expect(resultSet.columns.map(resultSet.dartNameFor), ['x']);
expect(resultSet.columns.map((c) => c.type), [DriftSqlType.int]);
});
test('finds the underlying table when aliased through CFE', () {
final file = session.registerFile(Uri.parse('package:foo/test.moor'));
final result = file.currentResult as ParsedDriftFile;
final query = result.resolvedQueries!.firstWhere((q) => q.name == 'test2')
as SqlSelectQuery;
final resultSet = query.resultSet;
expect(resultSet.matchingTable, isNotNull);
expect(resultSet.matchingTable!.table.displayName, 'foo');
expect(resultSet.needsOwnClass, isFalse);
});
}

View File

@ -1,127 +0,0 @@
import 'package:analyzer/dart/element/type.dart';
import 'package:drift_dev/moor_generator.dart';
import 'package:drift_dev/src/analyzer/runner/results.dart';
import 'package:test/test.dart';
import '../utils.dart';
void main() {
test('can use existing row classes in moor files', () async {
final state = TestState.withContent({
'a|lib/db.moor': '''
import 'rows.dart';
CREATE TABLE custom_name (
id INTEGER NOT NULL PRIMARY KEY,
foo TEXT
) AS MyCustomClass;
CREATE TABLE existing (
id INTEGER NOT NULL PRIMARY KEY,
foo TEXT
) WITH ExistingRowClass;
CREATE VIEW existing_view WITH ExistingForView (foo, bar)
AS SELECT 1, 2;
''',
'a|lib/rows.dart': '''
class ExistingRowClass {
ExistingRowClass(int id, String? foo);
}
class ExistingForView {
ExistingForView(int foo, int bar);
}
''',
});
addTearDown(state.close);
final file = await state.analyze('package:a/db.moor');
expect(file.errors.errors, isEmpty);
final result = file.currentResult as ParsedDriftFile;
final customName = result.declaredEntities
.singleWhere((e) => e.displayName == 'custom_name') as DriftTable;
final existing = result.declaredEntities
.singleWhere((e) => e.displayName == 'existing') as DriftTable;
final existingView = result.declaredEntities
.singleWhere((e) => e.displayName == 'existing_view') as MoorView;
expect(customName.dartTypeName, 'MyCustomClass');
expect(customName.existingRowClass, isNull);
expect(existing.dartTypeName, 'ExistingRowClass');
expect(existing.existingRowClass!.targetClass.name, 'ExistingRowClass');
expect(existingView.dartTypeName, 'ExistingForView');
expect(existingView.existingRowClass!.targetClass.name, 'ExistingForView');
});
test('can use generic row classes', () async {
final state = TestState.withContent({
'a|lib/generic.dart': '''
//@dart=2.13
typedef StringRow = GenericRow<String>;
typedef IntRow = GenericRow<int>;
class GenericRow<T> {
final T value;
GenericRow(this.value);
}
''',
'a|lib/generic.moor': '''
import 'generic.dart';
CREATE TABLE moor_strings (
value TEXT NOT NULL
) WITH StringRow;
CREATE TABLE moor_ints (
value INT NOT NULL
) WITH IntRow;
''',
});
addTearDown(state.close);
final file = await state.analyze('package:a/generic.moor');
expect(file.errors.errors, isEmpty);
final tables = (file.currentResult as ParsedDriftFile).declaredTables;
final strings = tables.singleWhere((e) => e.sqlName == 'moor_strings');
final ints = tables.singleWhere((e) => e.sqlName == 'moor_ints');
expect(
strings.existingRowClass,
isA<ExistingRowClass>()
.having((e) => e.targetClass.name, 'targetClass.name', 'GenericRow')
.having(
(e) => e.typeInstantiation,
'typeInstantiation',
allOf(
hasLength(1),
anyElement(
isA<DartType>().having(
(e) => e.isDartCoreString, 'isDartCoreString', isTrue),
),
),
),
);
expect(
ints.existingRowClass,
isA<ExistingRowClass>()
.having((e) => e.targetClass.name, 'targetClass.name', 'GenericRow')
.having(
(e) => e.typeInstantiation,
'typeInstantiation',
allOf(
hasLength(1),
anyElement(
isA<DartType>()
.having((e) => e.isDartCoreInt, 'isDartCoreInt', isTrue),
),
),
),
);
});
}

View File

@ -251,7 +251,20 @@ class NodeSqlBuilder extends AstVisitor<void, void> {
void visitCommonTableExpression(CommonTableExpression e, void arg) {
identifier(e.cteTableName);
if (e.columnNames != null) {
symbol('(${e.columnNames!.join(', ')})', spaceAfter: true);
symbol('(', spaceBefore: true);
var first = true;
for (final columnName in e.columnNames!) {
if (!first) {
symbol(',', spaceAfter: true);
}
identifier(columnName, spaceBefore: !first, spaceAfter: false);
first = false;
}
symbol(')', spaceAfter: true);
}
_keyword(TokenType.as);

View File

@ -194,6 +194,11 @@ CREATE UNIQUE INDEX my_idx ON t1 (c1, c2, c3) WHERE c1 < c3;
''');
});
test('escapes CTEs', () {
testFormat('WITH alias("first", second) AS (SELECT * FROM foo) '
'SELECT * FROM alias');
});
test('with materialized CTEs', () {
testFormat('''
WITH