diff --git a/drift/test/generated/custom_tables.g.dart b/drift/test/generated/custom_tables.g.dart index 012cda42..097a47e8 100644 --- a/drift/test/generated/custom_tables.g.dart +++ b/drift/test/generated/custom_tables.g.dart @@ -1756,7 +1756,7 @@ abstract class _$CustomTablesDb extends GeneratedDatabase { readsFrom: { config, }).map((QueryRow row) => JsonResult( - raw: row, + row: row, key: row.read('key'), value: row.readNullable('value'), )); @@ -1765,7 +1765,7 @@ abstract class _$CustomTablesDb extends GeneratedDatabase { Selectable another() { return customSelect('SELECT \'one\' AS "key", NULLIF(\'two\', \'another\') AS value', variables: [], readsFrom: {}) .map((QueryRow row) => JsonResult( - raw: row, + row: row, key: row.read('key'), value: row.readNullable('value'), )); @@ -1789,10 +1789,11 @@ abstract class _$CustomTablesDb extends GeneratedDatabase { withConstraints, ...generatedpredicate.watchedTables, }).asyncMap((QueryRow row) async => MultipleResult( - raw: row, + row: row, a: row.readNullable('a'), b: row.readNullable('b'), - c: withConstraints.mapFromRow(row), + c: await withConstraints.mapFromRowOrNull(row, + tablePrefix: 'nested_0'), )); } @@ -1821,7 +1822,7 @@ abstract class _$CustomTablesDb extends GeneratedDatabase { config, ...generatedexpr.watchedTables, }).map((QueryRow row) => ReadRowIdResult( - raw: row, + row: row, rowid: row.read('rowid'), configKey: row.read('config_key'), configValue: row.readNullable('config_value'), @@ -1887,8 +1888,8 @@ abstract class _$CustomTablesDb extends GeneratedDatabase { withConstraints, withDefaults, }).asyncMap((QueryRow row) async => NestedResult( - raw: row, - defaults: withDefaults.mapFromRow(row), + row: row, + defaults: await withDefaults.mapFromRow(row, tablePrefix: 'nested_0'), nestedQuery1: await customSelect( 'SELECT * FROM with_constraints AS c WHERE c.b = ?1', variables: [ @@ -1897,7 +1898,7 @@ abstract class _$CustomTablesDb extends GeneratedDatabase { readsFrom: { withConstraints, withDefaults, - }).map((QueryRow row) => withConstraints.mapFromRow(row)).get(), + }).asyncMap(withConstraints.mapFromRow).get(), )); } @@ -1914,8 +1915,8 @@ abstract class _$CustomTablesDb extends GeneratedDatabase { syncState: NullAwareTypeConverter.wrapFromSql( ConfigTable.$convertersyncState, row.readNullable('sync_state')), - config: config.mapFromRow(row, tablePrefix: 'nested_0'), - noIds: noIds.mapFromRow(row, tablePrefix: 'nested_1'), + config: await config.mapFromRow(row, tablePrefix: 'nested_0'), + noIds: await noIds.mapFromRow(row, tablePrefix: 'nested_1'), nested: await customSelect('SELECT * FROM no_ids', variables: [], readsFrom: { diff --git a/drift/test/generated/todos.g.dart b/drift/test/generated/todos.g.dart index 24e03fbd..e2bbd0cf 100644 --- a/drift/test/generated/todos.g.dart +++ b/drift/test/generated/todos.g.dart @@ -1715,7 +1715,7 @@ abstract class _$TodoDb extends GeneratedDatabase { categories, todosTable, }).map((QueryRow row) => AllTodosWithCategoryResult( - raw: row, + row: row, id: row.read('id'), title: row.readNullable('title'), content: row.read('content'), diff --git a/drift_dev/lib/src/analysis/results/query.dart b/drift_dev/lib/src/analysis/results/query.dart index c7e1ec43..1b85ddd1 100644 --- a/drift_dev/lib/src/analysis/results/query.dart +++ b/drift_dev/lib/src/analysis/results/query.dart @@ -161,17 +161,6 @@ abstract class SqlQuery { placeholders = elements.whereType().toList(); } - bool get needsAsyncMapping { - final result = resultSet; - if (result != null) { - // Mapping to tables is asynchronous - if (result.matchingTable != null) return true; - if (result.nestedResults.any((e) => e is NestedResultTable)) return true; - } - - return false; - } - bool get _useResultClassName { final resultSet = this.resultSet!; @@ -245,9 +234,6 @@ class SqlSelectQuery extends SqlQuery { bool get hasNestedQuery => resultSet.nestedResults.any((e) => e is NestedResultQuery); - @override - bool get needsAsyncMapping => hasNestedQuery || super.needsAsyncMapping; - SqlSelectQuery( String name, this.fromContext, @@ -547,7 +533,7 @@ class InferredResultSet { singleValue: null, positionalArguments: const [], namedArguments: { - if (options.rawResultSetData) 'raw': RawQueryRow(), + if (options.rawResultSetData) 'row': RawQueryRow(), for (final column in columns) dartNameFor(column): _columnAsArgument(column, options), }, @@ -597,6 +583,19 @@ class QueryRowType implements ArgumentForQueryRowType { this.isRecord = false, }); + Iterable get allArguments sync* { + if (singleValue != null) { + yield singleValue!; + } else { + yield* positionalArguments; + yield* namedArguments.values; + } + } + + @override + bool get requiresAsynchronousContext => + allArguments.any((arg) => arg.requiresAsynchronousContext); + @override String toString() { return 'ExistingQueryRowType(type: $rowType, singleValue: $singleValue, ' @@ -604,13 +603,20 @@ class QueryRowType implements ArgumentForQueryRowType { } } -sealed class ArgumentForQueryRowType {} +sealed class ArgumentForQueryRowType { + /// Whether the code constructing this argument may need to be in an async + /// context. + bool get requiresAsynchronousContext; +} /// An argument that just maps the raw query row. /// /// This is used for generated query classes which can optionally hold a /// reference to the raw result set. -class RawQueryRow extends ArgumentForQueryRowType {} +class RawQueryRow extends ArgumentForQueryRowType { + @override + bool get requiresAsynchronousContext => false; +} class StructuredFromNestedColumn extends ArgumentForQueryRowType { final NestedResultTable table; @@ -619,6 +625,10 @@ class StructuredFromNestedColumn extends ArgumentForQueryRowType { bool get nullable => table.isNullable; StructuredFromNestedColumn(this.table, this.nestedType); + + @override + bool get requiresAsynchronousContext => + nestedType.requiresAsynchronousContext; } class MappedNestedListQuery extends ArgumentForQueryRowType { @@ -626,6 +636,10 @@ class MappedNestedListQuery extends ArgumentForQueryRowType { final QueryRowType nestedType; MappedNestedListQuery(this.column, this.nestedType); + + // List queries run another statement and always need an asynchronous mapping. + @override + bool get requiresAsynchronousContext => true; } /// Information about a matching table. A table matches a query if a query @@ -638,6 +652,11 @@ class MatchingDriftTable implements ArgumentForQueryRowType { MatchingDriftTable(this.table, this.aliasToColumn); + @override + // Mapping from tables is currently asynchronous because the existing data + // class could be an asynchronous factory. + bool get requiresAsynchronousContext => true; + /// Whether the column alias can be ignored. /// /// This is the case if each result column name maps to a drift column with @@ -680,6 +699,9 @@ final class ScalarResultColumn extends ResultColumn @override bool get isArray => false; + @override + bool get requiresAsynchronousContext => false; + @override String dartGetterName(Iterable existingNames) { return dartNameForSqlColumn(name, existingNames: existingNames); diff --git a/drift_dev/lib/src/writer/queries/query_writer.dart b/drift_dev/lib/src/writer/queries/query_writer.dart index ab38c768..46cb5d56 100644 --- a/drift_dev/lib/src/writer/queries/query_writer.dart +++ b/drift_dev/lib/src/writer/queries/query_writer.dart @@ -84,22 +84,19 @@ class QueryWriter { /// Writes the function literal that turns a "QueryRow" into the desired /// custom return type of a query. - void _writeMappingLambda(SqlQuery query) { - final resultSet = query.resultSet!; - final rowClass = query.queryRowType(options); - + void _writeMappingLambda(InferredResultSet resultSet, QueryRowType rowClass) { final queryRow = _emitter.drift('QueryRow'); - final asyncModifier = query.needsAsyncMapping ? 'async' : ''; + final asyncModifier = rowClass.requiresAsynchronousContext ? 'async' : ''; // We can write every available mapping as a Dart expression via // _writeArgumentExpression. This can be turned into a lambda by appending // it with `(QueryRow row) => $expression`. That's also what we're doing, // but if we'll just call mapFromRow in there, we can just tear that method // off instead. This is just an optimization. - final matchingTable = resultSet.matchingTable; - if (matchingTable != null && matchingTable.effectivelyNoAlias) { + final singleValue = rowClass.singleValue; + if (singleValue is MatchingDriftTable && singleValue.effectivelyNoAlias) { // Tear-off mapFromRow method on table - _emitter.write('${matchingTable.table.dbGetterName}.mapFromRow'); + _emitter.write('${singleValue.table.dbGetterName}.mapFromRow'); } else { // In all other cases, we're off to write the expression. _emitter.write('($queryRow row) $asyncModifier => '); @@ -132,7 +129,7 @@ class QueryWriter { case MappedNestedListQuery(): _buffer.write('await '); final query = argument.column.query; - _writeCustomSelectStatement(query); + _writeCustomSelectStatement(query, argument.nestedType); _buffer.write('.get()'); case QueryRowType(): final singleValue = argument.singleValue; @@ -297,19 +294,23 @@ class QueryWriter { _buffer.write(';\n}\n'); } - void _writeCustomSelectStatement(SqlSelectQuery select) { + void _writeCustomSelectStatement(SqlSelectQuery select, + [QueryRowType? resultType]) { _buffer.write(' customSelect(${_queryCode(select)}, '); _writeVariables(select); _buffer.write(', '); _writeReadsFrom(select); - if (select.needsAsyncMapping) { + final resultSet = select.resultSet; + resultType ??= select.queryRowType(options); + + if (resultType.requiresAsynchronousContext) { _buffer.write(').asyncMap('); } else { _buffer.write(').map('); } - _writeMappingLambda(select); + _writeMappingLambda(resultSet, resultType); _buffer.write(')'); } @@ -335,13 +336,17 @@ class QueryWriter { _writeCommonUpdateParameters(update); _buffer.write(').then((rows) => '); - if (update.needsAsyncMapping) { + + final resultSet = update.resultSet!; + final rowType = update.queryRowType(options); + + if (rowType.requiresAsynchronousContext) { _buffer.write('Future.wait(rows.map('); - _writeMappingLambda(update); + _writeMappingLambda(resultSet, rowType); _buffer.write('))'); } else { _buffer.write('rows.map('); - _writeMappingLambda(update); + _writeMappingLambda(resultSet, rowType); _buffer.write(').toList()'); } _buffer.write(');\n}');