import 'package:drift_dev/src/analysis/driver/state.dart'; import 'package:drift_dev/src/analysis/options.dart'; import 'package:test/test.dart'; import '../../test_utils.dart'; void main() { test('warns when a result column is unresolved', () async { final result = await TestBackend.analyzeSingle('a: SELECT ?;'); expect(result.allErrors, [isDriftError(contains('unknown type')).withSpan('?')]); }); test('warns for skipped variable index', () async { final result = await TestBackend.analyzeSingle(''' q1(?2 AS TEXT): SELECT ?2; q2: SELECT ?1 = ?3; q3: SELECT ?1 = ?3 OR ?2; '''); expect(result.allErrors, [ isDriftError( 'Illegal variable index 2 because no variable at index 1 exists.') .withSpan('?2'), isDriftError( 'Illegal variable index 3 because no variable at index 2 exists.') .withSpan('?3'), ]); }); test('warns for illegal variable after array or placeholder', () async { final result = await TestBackend.analyzeSingle(r''' CREATE TABLE t (i INTEGER PRIMARY KEY); q1: SELECT * FROM t WHERE i IN ? OR i == ?2; ok1: SELECT * FROM t WHERE i == ?1 OR i IN ?; ok2: SELECT * FROM t WHERE i IN ? OR i = ?; q2: SELECT * FROM t WHERE $pred OR ?1; ok3: SELECT * FROM t WHERE i == ?1 OR $pred; ok4: SELECT * FROM t WHERE $pred OR i = ?; ok5: SELECT * FROM t WHERE $pred OR i IN ?; '''); final message = contains('Cannot have have a variable with an index lower'); expect(result.allErrors, [ isDriftError(message).withSpan('?2'), isDriftError(message).withSpan('?1'), ]); }); test('warns about indexed array variable', () async { final result = await TestBackend.analyzeSingle(r''' CREATE TABLE t (i INTEGER PRIMARY KEY); q: SELECT * FROM t WHERE i IN ?1; '''); expect( result.allErrors, [ isDriftError('Cannot use an array variable with an explicit index') .withSpan('?1'), ], ); }); test('no warning for Dart placeholder in column', () async { final result = await TestBackend.analyzeSingle(r"a: SELECT 'string' = $expr;"); expect(result.allErrors, isEmpty); }); test('warns about default values outside of expressions', () async { final state = await TestBackend.inTest({ 'foo|lib/a.drift': r''' CREATE TABLE foo ( id INT NOT NULL PRIMARY KEY, content VARCHAR ); all ($limit = 3): SELECT * FROM foo LIMIT $limit; ''', }); final result = await state.analyze('package:foo/a.drift'); expect( result.allErrors, contains(isDriftError(contains('only supported for expressions'))), ); }); test('warns when placeholder are used in insert with columns', () async { final state = await TestBackend.inTest({ 'foo|lib/a.drift': r''' CREATE TABLE foo ( id INT NOT NULL PRIMARY KEY, content VARCHAR ); in: INSERT INTO foo (id) $placeholder; ''', }); final result = await state.analyze('package:foo/a.drift'); expect( result.allErrors, contains(isDriftError(contains("Dart placeholders can't be used here"))), ); }); test( 'warns when nested results appear in compound statements', () async { final state = await TestBackend.inTest({ 'foo|lib/a.drift': ''' CREATE TABLE foo ( id INT NOT NULL PRIMARY KEY, content VARCHAR ); all: SELECT foo.** FROM foo UNION ALL SELECT foo.** FROM foo; ''', }); final result = await state.analyze('package:foo/a.drift'); expect( result.allErrors, contains(isDriftError( contains('columns may only appear in a top-level select'))), ); }, ); test( 'warns when nested query appear in nested query', () async { final state = await TestBackend.inTest({ 'foo|lib/a.drift': ''' CREATE TABLE foo ( id INT NOT NULL PRIMARY KEY, content VARCHAR ); all: SELECT foo.**, LIST(SELECT *, LIST(SELECT * FROM foo) FROM foo) FROM foo; ''', }); final result = await state.analyze('package:foo/a.drift'); expect( result.allErrors, contains(isDriftError( contains('query may only appear in a top-level select'))), ); }, ); group('warns about insert column count mismatch', () { TestBackend? state; Future expectError() async { final file = await state!.analyze('package:foo/a.drift'); expect( file.allErrors, contains(isDriftError('Expected tuple to have 2 values')), ); } test('in top-level queries', () async { state = await TestBackend.inTest({ 'foo|lib/a.drift': ''' CREATE TABLE foo ( id INT NOT NULL PRIMARY KEY AUTOINCREMENT, context VARCHAR ); test: INSERT INTO foo VALUES (?) ''', }); await expectError(); }); test('in CREATE TRIGGER statements', () async { state = await TestBackend.inTest({ 'foo|lib/a.drift': ''' CREATE TABLE foo ( id INT NOT NULL PRIMARY KEY AUTOINCREMENT, context VARCHAR ); CREATE TRIGGER my_trigger AFTER DELETE ON foo BEGIN INSERT INTO foo VALUES (old.context); END; ''', }); await expectError(); }); test('in @create statements', () async { state = await TestBackend.inTest({ 'foo|lib/a.drift': ''' CREATE TABLE foo ( id INT NOT NULL PRIMARY KEY AUTOINCREMENT, context VARCHAR ); @create: INSERT INTO foo VALUES (old.context); ''', }); await expectError(); }); }); group('warning about comparing textual date times', () { Future handle(String sql, {bool dateTimesAreText = true}) async { final state = await TestBackend.analyzeSingle( options: DriftOptions.defaults(storeDateTimeValuesAsText: dateTimesAreText), ''' CREATE TABLE t ( a DATETIME, b DATETIME, c DATETIME ); q: $sql; ''', ); return state; } test('for BETWEEN', () async { final state = await handle('SELECT a BETWEEN b AND c FROM t'); expect( state.allErrors, contains(isDriftError( contains('This compares two date time values lexicographically'), )), ); }); test('for equality', () async { for (final operator in ['=', '==', '<>', '!=']) { final state = await handle('SELECT a $operator b FROM t'); expect( state.allErrors, contains( isDriftError( contains( 'Semantically equivalent date time values may be formatted ' 'differently', ), ).withSpan(operator), ), ); } }); test('for comparisons', () async { for (final operator in ['<', '<=', '>=', '>']) { final state = await handle('SELECT a $operator b FROM t'); expect( state.allErrors, contains( isDriftError( contains( 'This compares two date time values lexicographically', ), ).withSpan(operator), ), ); } }); test('does not trigger for unix timestamps', () async { expect( (await handle('SELECT a = b FROM t', dateTimesAreText: false)) .allErrors, isEmpty); expect( (await handle('SELECT a BETWEEN b AND c FROM t', dateTimesAreText: false)) .allErrors, isEmpty); expect( (await handle('SELECT a <= c FROM t', dateTimesAreText: false)) .allErrors, isEmpty); }); }); }