diff --git a/drift_dev/lib/src/analysis/resolver/drift/sqlparser/drift_lints.dart b/drift_dev/lib/src/analysis/resolver/drift/sqlparser/drift_lints.dart index f91ecfd2..0e7c9ffd 100644 --- a/drift_dev/lib/src/analysis/resolver/drift/sqlparser/drift_lints.dart +++ b/drift_dev/lib/src/analysis/resolver/drift/sqlparser/drift_lints.dart @@ -198,18 +198,6 @@ class _LintingVisitor extends RecursiveVisitor { relevantNode: e, )); } - - // check that it actually refers to a table - final result = e.resultSet?.unalias(); - if (result is! Table && result is! View) { - linter.sqlParserErrors.add(AnalysisError( - type: AnalysisErrorType.other, - message: 'Nested star columns must refer to a table directly. They ' - "can't refer to a table-valued function or another select " - 'statement.', - relevantNode: e, - )); - } } if (e is NestedQueryColumn) { diff --git a/drift_dev/lib/src/analysis/results/query.dart b/drift_dev/lib/src/analysis/results/query.dart index e153eda0..c7e1ec43 100644 --- a/drift_dev/lib/src/analysis/results/query.dart +++ b/drift_dev/lib/src/analysis/results/query.dart @@ -561,8 +561,11 @@ class InferredResultSet { ) { return switch (column) { ScalarResultColumn() => column, - NestedResultTable() => column.innerResultSet - .mappingToRowClass(column.nameForGeneratedRowClass, options), + NestedResultTable() => StructuredFromNestedColumn( + column, + column.innerResultSet + .mappingToRowClass(column.nameForGeneratedRowClass, options), + ), NestedResultQuery() => MappedNestedListQuery( column, column.query.queryRowType(options), diff --git a/drift_dev/lib/src/writer/queries/query_writer.dart b/drift_dev/lib/src/writer/queries/query_writer.dart index 0b275242..ab38c768 100644 --- a/drift_dev/lib/src/writer/queries/query_writer.dart +++ b/drift_dev/lib/src/writer/queries/query_writer.dart @@ -130,21 +130,40 @@ class QueryWriter { (sqlPrefix: prefix, isNullable: argument.nullable), ); case MappedNestedListQuery(): - final queryRow = _emitter.drift('QueryRow'); - _buffer.write('await '); - _writeCustomSelectStatement(argument.column.query, - includeMappingToDart: false); - _buffer.write('.map('); - _buffer.write('($queryRow row) => '); - _writeArgumentExpression(argument.nestedType, resultSet, context); - _buffer.write(').get()'); + final query = argument.column.query; + _writeCustomSelectStatement(query); + _buffer.write('.get()'); case QueryRowType(): final singleValue = argument.singleValue; if (singleValue != null) { return _writeArgumentExpression(singleValue, resultSet, context); } + if (context.isNullable) { + // If this structed type is nullable, it's coming from an OUTER join + // which means that, even if the individual components making up the + // structure are non-nullable, they might all be null in SQL. We + // detect this case by looking for a non-nullable column and, if it's + // null, return null directly instead of creating the structured type. + for (final arg in argument.positionalArguments + .followedBy(argument.namedArguments.values) + .whereType()) { + if (!arg.nullable) { + final keyInMap = context.applyPrefix(arg.name); + _buffer.write( + 'row.data[${asDartLiteral(keyInMap)}] == null ? null : '); + } + } + } + + final _ArgumentContext childContext = ( + sqlPrefix: context.sqlPrefix, + // Individual fields making up this query row type aren't covered by + // the outer nullability. + isNullable: false, + ); + if (!argument.isRecord) { // We're writing a constructor, so let's start with the class name. _emitter.writeDart(argument.rowType); @@ -159,12 +178,12 @@ class QueryWriter { _buffer.write('('); for (final positional in argument.positionalArguments) { - _writeArgumentExpression(positional, resultSet, context); + _writeArgumentExpression(positional, resultSet, childContext); _buffer.write(', '); } argument.namedArguments.forEach((name, parameter) { _buffer.write('$name: '); - _writeArgumentExpression(parameter, resultSet, context); + _writeArgumentExpression(parameter, resultSet, childContext); _buffer.write(', '); }); @@ -179,7 +198,12 @@ class QueryWriter { final specialName = _transformer.newNameFor(column.sqlParserColumn!); final isNullable = context.isNullable || column.nullable; - final dartLiteral = asDartLiteral(specialName ?? column.name); + var name = specialName ?? column.name; + if (context.sqlPrefix != null) { + name = '${context.sqlPrefix}.$name'; + } + + final dartLiteral = asDartLiteral(name); final method = isNullable ? 'readNullable' : 'read'; final rawDartType = _emitter.dartCode(AnnotatedDartCode([dartTypeNames[column.sqlType]!])); @@ -213,22 +237,20 @@ class QueryWriter { context.isNullable ? 'mapFromRowOrNull' : 'mapFromRow'; final sqlPrefix = context.sqlPrefix; - _emitter.write('${table.dbGetterName}.$mappingMethod(row'); + _emitter.write('await ${table.dbGetterName}.$mappingMethod(row'); if (sqlPrefix != null) { _emitter.write(', tablePrefix: ${asDartLiteral(sqlPrefix)}'); } _emitter.write(')'); } else { - final sqlPrefix = context.sqlPrefix; - // If the entire table can be nullable, we can check whether a non-nullable // column from the table is null. If it is, the entire table is null. This // can happen when the table comes from an outer join. if (context.isNullable) { for (final MapEntry(:key, :value) in match.aliasToColumn.entries) { if (!value.nullable) { - final mapKey = sqlPrefix == null ? key : '$sqlPrefix.$key'; + final mapKey = context.applyPrefix(key); _emitter .write('row.data[${asDartLiteral(mapKey)}] == null ? null : '); @@ -239,13 +261,8 @@ class QueryWriter { _emitter.write('${table.dbGetterName}.mapFromRowWithAlias(row, const {'); for (final alias in match.aliasToColumn.entries) { - var sqlKey = alias.key; - if (sqlPrefix != null) { - sqlKey = '$sqlPrefix.'; - } - _emitter - ..write(asDartLiteral(sqlKey)) + ..write(asDartLiteral(context.applyPrefix(alias.key))) ..write(': ') ..write(asDartLiteral(alias.value.nameInSql)) ..write(', '); @@ -280,23 +297,19 @@ class QueryWriter { _buffer.write(';\n}\n'); } - void _writeCustomSelectStatement(SqlSelectQuery select, - {bool includeMappingToDart = true}) { + void _writeCustomSelectStatement(SqlSelectQuery select) { _buffer.write(' customSelect(${_queryCode(select)}, '); _writeVariables(select); _buffer.write(', '); _writeReadsFrom(select); - if (includeMappingToDart) { - if (select.needsAsyncMapping) { - _buffer.write(').asyncMap('); - } else { - _buffer.write(').map('); - } - - _writeMappingLambda(select); + if (select.needsAsyncMapping) { + _buffer.write(').asyncMap('); + } else { + _buffer.write(').map('); } + _writeMappingLambda(select); _buffer.write(')'); } @@ -811,3 +824,12 @@ String? _defaultForDartPlaceholder( return null; } } + +extension on _ArgumentContext { + String applyPrefix(String originalName) { + return switch (sqlPrefix) { + null => originalName, + var s => '$s.$originalName', + }; + } +} diff --git a/drift_dev/test/analysis/resolver/queries/existing_row_classes_test.dart b/drift_dev/test/analysis/resolver/queries/existing_row_classes_test.dart index 62e94e99..e6a84c9a 100644 --- a/drift_dev/test/analysis/resolver/queries/existing_row_classes_test.dart +++ b/drift_dev/test/analysis/resolver/queries/existing_row_classes_test.dart @@ -1,4 +1,6 @@ +import 'package:drift_dev/src/analysis/options.dart'; import 'package:drift_dev/src/analysis/results/results.dart'; +import 'package:sqlparser/sqlparser.dart'; import 'package:test/test.dart'; import '../../test_utils.dart'; @@ -225,83 +227,124 @@ class MyQueryRow { ); }); - test('nested - single column type', () async { - final state = TestBackend.inTest({ - 'a|lib/a.drift': ''' + group('nested column', () { + test('single column into field', () async { + final state = TestBackend.inTest({ + 'a|lib/a.drift': ''' import 'a.dart'; -foo WITH MyQueryRow: SELECT 1 a, LIST(SELECT 2 AS b) c; +foo WITH MyQueryRow: SELECT 1 AS a, b.** FROM (SELECT 2 AS b) b; ''', - 'a|lib/a.dart': ''' + 'a|lib/a.dart': ''' class MyQueryRow { - MyQueryRow(int a, List c); + MyQueryRow(int a, int b); } ''', + }); + + final file = await state.analyze('package:a/a.drift'); + state.expectNoErrors(); + + final query = file.fileAnalysis!.resolvedQueries.values.single; + expect( + query.resultSet?.existingRowType, + isExistingRowType( + type: 'MyQueryRow', + positional: [ + scalarColumn('a'), + structedFromNested( + isExistingRowType( + singleValue: scalarColumn('b'), + ), + ), + ], + ), + ); }); - final file = await state.analyze('package:a/a.drift'); - state.expectNoErrors(); - - final query = file.fileAnalysis!.resolvedQueries.values.single; - expect( - query.resultSet?.existingRowType, - isExistingRowType( - type: 'MyQueryRow', - positional: [ - scalarColumn('a'), - nestedListQuery( - 'c', - isExistingRowType( - type: 'int', - singleValue: scalarColumn('b'), - ), - ), - ], - ), - ); - }); - - test('nested - table', () async { - final state = TestBackend.inTest({ - 'a|lib/a.drift': ''' + test('single column into single-element record', () async { + final state = TestBackend.inTest({ + 'a|lib/a.drift': ''' import 'a.dart'; -CREATE TABLE tbl (foo TEXT, bar INT); - -foo WITH MyRow: SELECT 1 AS a, b.** FROM tbl - INNER JOIN tbl b ON TRUE; +foo WITH MyQueryRow: SELECT 1 AS a, b.** FROM (SELECT 2 AS b) b; ''', - 'a|lib/a.dart': ''' -class MyRow { - MyRow(int a, TblData b); + 'a|lib/a.dart': ''' +class MyQueryRow { + MyQueryRow(int a, (int) b); } ''', + }); + + final file = await state.analyze('package:a/a.drift'); + state.expectNoErrors(); + + final query = file.fileAnalysis!.resolvedQueries.values.single; + expect( + query.resultSet?.existingRowType, + isExistingRowType( + type: 'MyQueryRow', + positional: [ + scalarColumn('a'), + structedFromNested( + isExistingRowType( + positional: [scalarColumn('b')], + isRecord: isTrue, + ), + ), + ], + ), + ); }); - final file = await state.analyze('package:a/a.drift'); - state.expectNoErrors(); + test('custom result set', () async { + final state = TestBackend.inTest( + { + 'a|lib/a.drift': ''' +import 'a.dart'; - final query = file.fileAnalysis!.resolvedQueries.values.single; - expect( - query.resultSet?.existingRowType, - isExistingRowType( - type: 'MyRow', - positional: [ - scalarColumn('a'), - structedFromNested( - isExistingRowType( - type: 'TblData', - singleValue: isA(), - ), +foo WITH MyQueryRow: SELECT 1 AS id, j.** FROM json_each('') AS j; +''', + 'a|lib/a.dart': ''' +class MyQueryRow { + MyQueryRow(int id, JsonStructure j); +} + +class JsonStructure { + JsonStructure(DriftAny key, DriftAny value, String type); +} +''', + }, + options: const DriftOptions.defaults( + sqliteAnalysisOptions: SqliteAnalysisOptions( + // Make sure json_each is supported + version: SqliteVersion.v3(38), ), - ], - ), - ); - }); + ), + ); - test('nested - table as alternative to row class', () async { - final state = TestBackend.inTest( - { + final file = await state.analyze('package:a/a.drift'); + state.expectNoErrors(); + + final query = file.fileAnalysis!.resolvedQueries.values.single; + expect( + query.resultSet?.existingRowType, + isExistingRowType( + type: 'MyQueryRow', + positional: [ + scalarColumn('id'), + structedFromNested( + isExistingRowType( + type: 'JsonStructure', + ), + ), + ], + ), + ); + }); + + test('table', () async { + final state = TestBackend.inTest({ 'a|lib/a.drift': ''' import 'a.dart'; @@ -311,45 +354,122 @@ foo WITH MyRow: SELECT 1 AS a, b.** FROM tbl INNER JOIN tbl b ON TRUE; ''', 'a|lib/a.dart': ''' +class MyRow { + MyRow(int a, TblData b); +} +''', + }); + + final file = await state.analyze('package:a/a.drift'); + state.expectNoErrors(); + + final query = file.fileAnalysis!.resolvedQueries.values.single; + expect( + query.resultSet?.existingRowType, + isExistingRowType( + type: 'MyRow', + positional: [ + scalarColumn('a'), + structedFromNested( + isExistingRowType( + type: 'TblData', + singleValue: isA(), + ), + ), + ], + ), + ); + }); + + test('table as alternative to row class', () async { + final state = TestBackend.inTest( + { + 'a|lib/a.drift': ''' +import 'a.dart'; + +CREATE TABLE tbl (foo TEXT, bar INT); + +foo WITH MyRow: SELECT 1 AS a, b.** FROM tbl + INNER JOIN tbl b ON TRUE; +''', + 'a|lib/a.dart': ''' class MyRow { MyRow(int a, (String, int) b); } ''', - }, - analyzerExperiments: ['records'], - ); + }, + analyzerExperiments: ['records'], + ); - final file = await state.analyze('package:a/a.drift'); - state.expectNoErrors(); + final file = await state.analyze('package:a/a.drift'); + state.expectNoErrors(); - final query = file.fileAnalysis!.resolvedQueries.values.single; - expect( - query.resultSet?.existingRowType, - isExistingRowType( - type: 'MyRow', - positional: [ - scalarColumn('a'), - structedFromNested( - isExistingRowType( - type: '(String, int)', - positional: [scalarColumn('foo'), scalarColumn('bar')], + final query = file.fileAnalysis!.resolvedQueries.values.single; + expect( + query.resultSet?.existingRowType, + isExistingRowType( + type: 'MyRow', + positional: [ + scalarColumn('a'), + structedFromNested( + isExistingRowType( + type: '(String, int)', + positional: [scalarColumn('foo'), scalarColumn('bar')], + ), ), - ), - ], - ), - ); + ], + ), + ); + }); }); - test('nested - custom result set with class', () async { - final state = TestBackend.inTest({ - 'a|lib/a.drift': ''' + group('nested LIST query', () { + test('single column type', () async { + final state = TestBackend.inTest({ + 'a|lib/a.drift': ''' +import 'a.dart'; + +foo WITH MyQueryRow: SELECT 1 a, LIST(SELECT 2 AS b) c; +''', + 'a|lib/a.dart': ''' +class MyQueryRow { + MyQueryRow(int a, List c); +} +''', + }); + + final file = await state.analyze('package:a/a.drift'); + state.expectNoErrors(); + + final query = file.fileAnalysis!.resolvedQueries.values.single; + expect( + query.resultSet?.existingRowType, + isExistingRowType( + type: 'MyQueryRow', + positional: [ + scalarColumn('a'), + nestedListQuery( + 'c', + isExistingRowType( + type: 'int', + singleValue: scalarColumn('b'), + ), + ), + ], + ), + ); + }); + + test('custom result set with class', () async { + final state = TestBackend.inTest({ + 'a|lib/a.drift': ''' import 'a.dart'; CREATE TABLE tbl (foo TEXT, bar INT); foo WITH MyRow: SELECT 1 AS a, LIST(SELECT * FROM tbl) AS b FROM tbl; ''', - 'a|lib/a.dart': ''' + 'a|lib/a.dart': ''' class MyRow { MyRow(int a, List b); } @@ -358,69 +478,70 @@ class MyNestedTable { MyNestedTable(String foo, int bar) } ''', + }); + + final file = await state.analyze('package:a/a.drift'); + state.expectNoErrors(); + + final query = file.fileAnalysis!.resolvedQueries.values.single; + expect( + query.resultSet?.existingRowType, + isExistingRowType( + type: 'MyRow', + positional: [ + scalarColumn('a'), + nestedListQuery( + 'b', + isExistingRowType( + type: 'MyNestedTable', + positional: [scalarColumn('foo'), scalarColumn('bar')], + ), + ), + ], + ), + ); }); - final file = await state.analyze('package:a/a.drift'); - state.expectNoErrors(); - - final query = file.fileAnalysis!.resolvedQueries.values.single; - expect( - query.resultSet?.existingRowType, - isExistingRowType( - type: 'MyRow', - positional: [ - scalarColumn('a'), - nestedListQuery( - 'b', - isExistingRowType( - type: 'MyNestedTable', - positional: [scalarColumn('foo'), scalarColumn('bar')], - ), - ), - ], - ), - ); - }); - - test('nested - custom result set with record', () async { - final state = TestBackend.inTest( - { - 'a|lib/a.drift': ''' + test('custom result set with record', () async { + final state = TestBackend.inTest( + { + 'a|lib/a.drift': ''' import 'a.dart'; CREATE TABLE tbl (foo TEXT, bar INT); foo WITH MyRow: SELECT 1 AS a, LIST(SELECT * FROM tbl) AS b FROM tbl; ''', - 'a|lib/a.dart': ''' + 'a|lib/a.dart': ''' class MyRow { MyRow(int a, List<(String, int)> b); } ''', - }, - analyzerExperiments: ['records'], - ); + }, + analyzerExperiments: ['records'], + ); - final file = await state.analyze('package:a/a.drift'); - state.expectNoErrors(); + final file = await state.analyze('package:a/a.drift'); + state.expectNoErrors(); - final query = file.fileAnalysis!.resolvedQueries.values.single; - expect( - query.resultSet?.existingRowType, - isExistingRowType( - type: 'MyRow', - positional: [ - scalarColumn('a'), - nestedListQuery( - 'b', - isExistingRowType( - type: '(String, int)', - positional: [scalarColumn('foo'), scalarColumn('bar')], + final query = file.fileAnalysis!.resolvedQueries.values.single; + expect( + query.resultSet?.existingRowType, + isExistingRowType( + type: 'MyRow', + positional: [ + scalarColumn('a'), + nestedListQuery( + 'b', + isExistingRowType( + type: '(String, int)', + positional: [scalarColumn('foo'), scalarColumn('bar')], + ), ), - ), - ], - ), - ); + ], + ), + ); + }); }); test('into record', () async { diff --git a/drift_dev/test/analysis/resolver/queries/linter_test.dart b/drift_dev/test/analysis/resolver/queries/linter_test.dart index d66112cf..a4997ef6 100644 --- a/drift_dev/test/analysis/resolver/queries/linter_test.dart +++ b/drift_dev/test/analysis/resolver/queries/linter_test.dart @@ -75,22 +75,6 @@ q: SELECT * FROM t WHERE i IN ?1; expect(result.allErrors, isEmpty); }); - test('warns when nested results refer to table-valued functions', () async { - final result = await TestBackend.analyzeSingle( - "a: SELECT json_each.** FROM json_each('');", - options: DriftOptions.defaults(modules: [SqlModule.json1]), - ); - - expect( - result.allErrors, - [ - isDriftError( - contains('Nested star columns must refer to a table directly.')) - .withSpan('json_each.**') - ], - ); - }); - test('warns about default values outside of expressions', () async { final state = TestBackend.inTest({ 'foo|lib/a.drift': r''' diff --git a/drift_dev/test/analysis/resolver/queries/query_analyzer_test.dart b/drift_dev/test/analysis/resolver/queries/query_analyzer_test.dart index ee2705c0..0401ac09 100644 --- a/drift_dev/test/analysis/resolver/queries/query_analyzer_test.dart +++ b/drift_dev/test/analysis/resolver/queries/query_analyzer_test.dart @@ -108,6 +108,36 @@ query: SELECT foo.**, bar.** FROM my_view foo, my_view bar; ); }); + test('infers nested result sets for custom result sets', () async { + final state = TestBackend.inTest({ + 'foo|lib/main.drift': r''' +query: SELECT 1 AS a, b.** FROM (SELECT 2 AS b, 3 AS c) AS b; + ''', + }); + + final file = await state.analyze('package:foo/main.drift'); + state.expectNoErrors(); + + final query = file.fileAnalysis!.resolvedQueries.values.single; + + expect( + query.resultSet!.mappingToRowClass('Row', const DriftOptions.defaults()), + isExistingRowType( + type: 'Row', + named: { + 'a': scalarColumn('a'), + 'b': isExistingRowType( + type: 'QueryNestedColumn0', + named: { + 'b': scalarColumn('b'), + 'c': scalarColumn('c'), + }, + ) + }, + ), + ); + }); + for (final dateTimeAsText in [false, true]) { test('analyzing date times (stored as text: $dateTimeAsText)', () async { final state = TestBackend.inTest( diff --git a/drift_dev/test/analysis/resolver/queries/utils.dart b/drift_dev/test/analysis/resolver/queries/utils.dart index 6e2a486f..7f22ebd7 100644 --- a/drift_dev/test/analysis/resolver/queries/utils.dart +++ b/drift_dev/test/analysis/resolver/queries/utils.dart @@ -33,6 +33,7 @@ TypeMatcher isExistingRowType({ Object? singleValue, Object? positional, Object? named, + Object? isRecord, }) { var matcher = isA(); @@ -53,6 +54,9 @@ TypeMatcher isExistingRowType({ if (named != null) { matcher = matcher.having((e) => e.namedArguments, 'namedArguments', named); } + if (isRecord != null) { + matcher = matcher.having((e) => e.isRecord, 'isRecord', isRecord); + } return matcher; } diff --git a/drift_dev/test/writer/queries/query_writer_test.dart b/drift_dev/test/writer/queries/query_writer_test.dart index 69ece4b4..ed630cd9 100644 --- a/drift_dev/test/writer/queries/query_writer_test.dart +++ b/drift_dev/test/writer/queries/query_writer_test.dart @@ -3,6 +3,7 @@ import 'package:drift_dev/src/analysis/options.dart'; import 'package:drift_dev/src/writer/import_manager.dart'; import 'package:drift_dev/src/writer/queries/query_writer.dart'; import 'package:drift_dev/src/writer/writer.dart'; +import 'package:sqlparser/sqlparser.dart'; import 'package:test/test.dart'; import '../../analysis/test_utils.dart'; @@ -14,6 +15,7 @@ void main() { final state = TestBackend.inTest({'a|lib/main.drift': driftFile}, options: options); final file = await state.analyze('package:a/main.drift'); + state.expectNoErrors(); final writer = Writer( const DriftOptions.defaults(generateNamedParameters: true), @@ -55,32 +57,112 @@ void main() { ); }); - test('generates correct name for renamed nested star columns', () async { - final generated = await generateForQueryInDriftFile(''' + group('nested star column', () { + test('get renamed in SQL', () async { + final generated = await generateForQueryInDriftFile(''' CREATE TABLE tbl ( id INTEGER NULL ); query: SELECT t.** AS tableName FROM tbl AS t; '''); - expect( - generated, - allOf( - contains('SELECT"t"."id" AS "nested_0.id"'), - contains('final TblData tableName;'), - ), - ); + expect( + generated, + allOf( + contains('SELECT"t"."id" AS "nested_0.id"'), + contains('final TblData tableName;'), + ), + ); + }); + + test('makes single columns nullable if from outer join', () async { + final generated = await generateForQueryInDriftFile(''' + query: SELECT 1 AS r, joined.** FROM (SELECT 1) + LEFT OUTER JOIN (SELECT 2 AS b) joined; + '''); + + expect( + generated, + allOf( + contains("joined: row.readNullable('nested_0.b')"), + contains('final int? joined;'), + ), + ); + }); + + test('checks for nullable column in nested table', () async { + final generated = await generateForQueryInDriftFile(''' + CREATE TABLE tbl ( + id INTEGER NULL + ); + + query: SELECT 1 AS a, tbl.** FROM (SELECT 1) LEFT OUTER JOIN tbl; + '''); + + expect( + generated, + allOf( + contains( + "tbl: await tbl.mapFromRowOrNull(row, tablePrefix: 'nested_0')"), + contains('final TblData? tbl;'), + ), + ); + }); + + test('checks for nullable column in nested table with alias', () async { + final generated = await generateForQueryInDriftFile(''' + CREATE TABLE tbl ( + id INTEGER NULL, + col TEXT NOT NULL + ); + + query: SELECT 1 AS a, tbl.** FROM (SELECT 1) LEFT OUTER JOIN (SELECT id AS a, col AS b from tbl) tbl; + '''); + + expect( + generated, + allOf( + contains("tbl: row.data['nested_0.b'] == null ? null : " + 'tbl.mapFromRowWithAlias(row'), + contains('final TblData? tbl;'), + ), + ); + }); + + test('checks for nullable column in nested result set', () async { + final generated = await generateForQueryInDriftFile(''' + query: SELECT 1 AS r, joined.** FROM (SELECT 1) + LEFT OUTER JOIN (SELECT NULL AS b, 3 AS c) joined; + '''); + + expect( + generated, + allOf( + contains("joined: row.data['nested_0.c'] == null ? null : " + "QueryNestedColumn0(b: row.readNullable('nested_0.b'), " + "c: row.read('nested_0.c'), )"), + contains('final QueryNestedColumn0? joined;'), + ), + ); + }); }); test('generates correct returning mapping', () async { - final generated = await generateForQueryInDriftFile(''' + final generated = await generateForQueryInDriftFile( + ''' CREATE TABLE tbl ( id INTEGER, text TEXT ); query: INSERT INTO tbl (id, text) VALUES(10, 'test') RETURNING id; - '''); + ''', + options: const DriftOptions.defaults( + sqliteAnalysisOptions: + // Assuming 3.35 because dso that returning works. + SqliteAnalysisOptions(version: SqliteVersion.v3(35)), + ), + ); expect(generated, contains('.toList()')); }); @@ -346,20 +428,19 @@ failQuery: ], readsFrom: { t, - }).asyncMap((i0.QueryRow row) async { - return FailQueryResult( - a: row.readNullable('a'), - b: row.readNullable('b'), - nestedQuery0: await customSelect( - 'SELECT * FROM t AS x WHERE x.b = b OR x.b = ?1', - variables: [ - i0.Variable(inB) - ], - readsFrom: { - t, - }).asyncMap(t.mapFromRow).get(), - ); - }); + }).asyncMap((i0.QueryRow row) async => FailQueryResult( + a: row.readNullable('a'), + b: row.readNullable('b'), + nestedQuery0: await customSelect( + 'SELECT * FROM t AS x WHERE x.b = b OR x.b = ?1', + variables: [ + i0.Variable(inB) + ], + readsFrom: { + t, + }).asyncMap(t.mapFromRow).get(), + )); + } ''')) }, outputs.dartOutputs, outputs); });