drift/drift_dev/test/analyzer/sql_queries/query_handler_test.dart

209 lines
6.3 KiB
Dart

//@dart=2.9
import 'package:drift_dev/moor_generator.dart';
import 'package:drift_dev/src/analyzer/moor/create_table_reader.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/steps.dart';
import 'package:drift_dev/src/analyzer/runner/task.dart';
import 'package:drift_dev/src/analyzer/sql_queries/query_handler.dart';
import 'package:drift_dev/src/analyzer/sql_queries/type_mapping.dart';
import 'package:sqlparser/sqlparser.dart';
import 'package:test/test.dart';
import '../utils.dart';
const createFoo = '''
CREATE TABLE foo (
id INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT,
name VARCHAR
);
''';
const createBar = '''
CREATE TABLE bar (
id INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT,
foo INTEGER NOT NULL REFERENCES foo(id)
);
''';
Future<void> main() async {
final mapper = TypeMapper();
final engine = SqlEngine(EngineOptions(useMoorExtensions: true));
final step = ParseMoorStep(Task(null, null, null),
FoundFile(Uri.parse('file://foo'), FileType.moor), '');
final parsedFoo = engine.parse(createFoo).rootNode as CreateTableStatement;
final foo = await CreateTableReader(parsedFoo, step).extractTable(mapper);
engine.registerTable(mapper.extractStructure(foo));
final parsedBar = engine.parse(createBar).rootNode as CreateTableStatement;
final bar = await CreateTableReader(parsedBar, step).extractTable(mapper);
engine.registerTable(mapper.extractStructure(bar));
SqlQuery parse(String sql) {
final parsed = engine.analyze(sql);
final fakeQuery = DeclaredDartQuery('query', sql);
return QueryHandler(parsed, mapper).handle(fakeQuery);
}
group('detects whether multiple tables are referenced', () {
test('when only selecting from one table', () {
expect(parse('SELECT * FROM foo').hasMultipleTables, isFalse);
});
test('when selecting from multiple tables', () {
expect(
parse('SELECT * FROM bar JOIN foo ON bar.foo = foo.id')
.hasMultipleTables,
isTrue,
);
});
test('when updating a single table', () {
final query = parse('INSERT INTO bar (foo) SELECT id FROM foo');
expect(query.hasMultipleTables, isTrue);
expect((query as UpdatingQuery).updates, hasLength(1));
});
});
test('throws when variable indexes are skipped', () {
expect(() => parse('SELECT ?2'), throwsStateError);
expect(() => parse('SELECT ?1 = ?3'), throwsStateError);
expect(() => parse('SELECT ?1 = ?3 OR ?2'), returnsNormally);
});
test('resolves nested result sets', () async {
final state = TestState.withContent({
'foo|lib/main.moor': r'''
CREATE TABLE points (
id INTEGER NOT NULL PRIMARY KEY,
lat REAL NOT NULL,
long REAL NOT NULL
);
CREATE TABLE routes (
id INTEGER NOT NULL PRIMARY KEY,
"from" INTEGER NOT NULL REFERENCES points (id),
"to" INTEGER NOT NULL REFERENCES points (id)
);
allRoutes: SELECT routes.*, "from".**, "to".**
FROM routes
INNER JOIN points "from" ON "from".id = routes.from
INNER JOIN points "to" ON "to".id = routes."to";
''',
}, enableAnalyzer: false);
final file = await state.analyze('package:foo/main.moor');
final result = file.currentResult as ParsedMoorFile;
state.close();
expect(file.errors.errors, isEmpty);
final query = result.resolvedQueries.single;
final resultSet = (query as SqlSelectQuery).resultSet;
expect(resultSet.columns.map((e) => e.name), ['id', 'from', 'to']);
expect(resultSet.matchingTable, isNull);
expect(
resultSet.nestedResults.cast<NestedResultTable>().map((e) => e.name),
['from', 'to'],
);
expect(
resultSet.nestedResults
.cast<NestedResultTable>()
.map((e) => e.table.displayName),
['points', 'points'],
);
});
test('resolves nullability of aliases in nested result sets', () async {
final state = TestState.withContent({
'foo|lib/main.moor': r'''
CREATE TABLE tableA1 (id INTEGER);
CREATE TABLE tableB1 (id INTEGER);
query: SELECT
tableA1.**,
tableA2.**,
tableB1.**,
tableB2.**
FROM tableA1 -- not nullable
LEFT JOIN tableA1 AS tableA2 -- nullable
ON FALSE
INNER JOIN tableB1 -- not nullable
ON TRUE
LEFT JOIN tableB1 AS tableB2 -- nullable
ON FALSE;
''',
}, enableAnalyzer: false);
final file = await state.analyze('package:foo/main.moor');
final result = file.currentResult as ParsedMoorFile;
state.close();
expect(file.errors.errors, isEmpty);
final query = result.resolvedQueries.single;
final resultSet = (query as SqlSelectQuery).resultSet;
final nested = resultSet.nestedResults;
expect(
nested.cast<NestedResultTable>().map((e) => e.name),
['tableA1', 'tableA2', 'tableB1', 'tableB2'],
);
expect(
nested.cast<NestedResultTable>().map((e) => e.isNullable),
[false, true, false, true],
);
});
test('infers result set for views', () async {
final state = TestState.withContent({
'foo|lib/main.moor': r'''
CREATE VIEW my_view AS SELECT 'foo', 2;
query: SELECT * FROM my_view;
''',
}, enableAnalyzer: false);
final file = await state.analyze('package:foo/main.moor');
expect(file.errors.errors, isEmpty);
final result = file.currentResult as ParsedMoorFile;
final query = result.resolvedQueries.single;
expect(
query.resultSet.matchingTable,
isA<MatchingMoorTable>()
.having((e) => e.table, 'table',
isA<MoorView>().having((e) => e.name, 'name', 'my_view'))
.having((e) => e.effectivelyNoAlias, 'effectivelyNoAlias', isTrue));
});
test('infers nested result set for views', () async {
final state = TestState.withContent({
'foo|lib/main.moor': r'''
CREATE VIEW my_view AS SELECT 'foo', 2;
query: SELECT foo.**, bar.** FROM my_view foo, my_view bar;
''',
}, enableAnalyzer: false);
final file = await state.analyze('package:foo/main.moor');
expect(file.errors.errors, isEmpty);
final result = file.currentResult as ParsedMoorFile;
final query = result.resolvedQueries.single;
expect(query.resultSet.nestedResults, hasLength(2));
expect(
query.resultSet.nestedResults,
everyElement(isA<NestedResultTable>().having(
(e) => e.table.displayName, 'table.displayName', 'my_view')));
});
}