diff --git a/docs/content/en/docs/Advanced Features/builder_options.md b/docs/content/en/docs/Advanced Features/builder_options.md index 1369bf87..24c3df23 100644 --- a/docs/content/en/docs/Advanced Features/builder_options.md +++ b/docs/content/en/docs/Advanced Features/builder_options.md @@ -65,6 +65,7 @@ At the moment, moor supports these options: data classes. * `mutable_classes` (defaults to `false`): The fields generated in generated data, companion and result set classes are final by default. You can make them mutable by setting `mutable_classes: true`. +* `raw_result_set_data`: The generator will expose the underlying `QueryRow` for generated result set classes ## Available extensions diff --git a/moor/build.yaml b/moor/build.yaml index e5c9b33e..0d3b58d9 100644 --- a/moor/build.yaml +++ b/moor/build.yaml @@ -8,6 +8,7 @@ targets: generate_connect_constructor: true compact_query_methods: true write_from_json_string_constructor: true + raw_result_set_data: true # eagerly_load_dart_ast: true sqlite_modules: - json1 diff --git a/moor/example/example.g.dart b/moor/example/example.g.dart index 26bafe5e..7fbf2807 100644 --- a/moor/example/example.g.dart +++ b/moor/example/example.g.dart @@ -1013,6 +1013,7 @@ abstract class _$Database extends GeneratedDatabase { variables: [], readsFrom: {recipes, ingredientInRecipes}).map((QueryRow row) { return TotalWeightResult( + row: row, title: row.readString('title'), totalWeight: row.readInt('total_weight'), ); @@ -1026,13 +1027,14 @@ abstract class _$Database extends GeneratedDatabase { [categories, recipes, ingredients, ingredientInRecipes]; } -class TotalWeightResult { +class TotalWeightResult extends CustomResultSet { final String title; final int totalWeight; TotalWeightResult({ + @required QueryRow row, this.title, this.totalWeight, - }); + }) : super(row); @override int get hashCode => $mrjf($mrjc(title.hashCode, totalWeight.hashCode)); @override diff --git a/moor/lib/moor.dart b/moor/lib/moor.dart index 9a2a0bd3..6fec472d 100644 --- a/moor/lib/moor.dart +++ b/moor/lib/moor.dart @@ -13,6 +13,7 @@ export 'package:moor/src/runtime/query_builder/query_builder.dart'; export 'package:moor/src/runtime/executor/connection_pool.dart'; export 'package:moor/src/runtime/executor/executor.dart'; export 'package:moor/src/runtime/executor/transactions.dart'; +export 'package:moor/src/runtime/custom_result_set.dart'; export 'package:moor/src/runtime/data_verification.dart'; export 'package:moor/src/runtime/data_class.dart'; export 'package:moor/src/runtime/api/runtime_api.dart'; diff --git a/moor/lib/src/runtime/custom_result_set.dart b/moor/lib/src/runtime/custom_result_set.dart new file mode 100644 index 00000000..3f49493d --- /dev/null +++ b/moor/lib/src/runtime/custom_result_set.dart @@ -0,0 +1,10 @@ +import 'package:moor/moor.dart'; + +/// Base class for classes generated by custom queries in `.moor` files. +abstract class CustomResultSet { + /// The raw [QueryRow] from where this result set was extracted. + final QueryRow row; + + /// Default constructor. + CustomResultSet(this.row); +} diff --git a/moor/test/data/tables/custom_tables.g.dart b/moor/test/data/tables/custom_tables.g.dart index 37a2d43e..254920e6 100644 --- a/moor/test/data/tables/custom_tables.g.dart +++ b/moor/test/data/tables/custom_tables.g.dart @@ -1462,6 +1462,7 @@ abstract class _$CustomTablesDb extends GeneratedDatabase { variables: [], readsFrom: {config}).map((QueryRow row) { return JsonResult( + row: row, key: row.readString('key'), value: row.readString('value'), ); @@ -1474,6 +1475,7 @@ abstract class _$CustomTablesDb extends GeneratedDatabase { variables: [], readsFrom: {}).map((QueryRow row) { return JsonResult( + row: row, key: row.readString('key'), value: row.readString('value'), ); @@ -1487,6 +1489,7 @@ abstract class _$CustomTablesDb extends GeneratedDatabase { variables: [...generatedpredicate.introducedVariables], readsFrom: {withConstraints, withDefaults}).map((QueryRow row) { return MultipleResult( + row: row, a: row.readString('a'), b: row.readInt('b'), c: withConstraints.mapFromRowOrNull(row, tablePrefix: 'nested_0'), @@ -1508,6 +1511,7 @@ abstract class _$CustomTablesDb extends GeneratedDatabase { variables: [...generatedexpr.introducedVariables], readsFrom: {config}).map((QueryRow row) { return ReadRowIdResult( + row: row, rowid: row.readInt('rowid'), configKey: row.readString('config_key'), configValue: row.readString('config_value'), @@ -1562,13 +1566,14 @@ abstract class _$CustomTablesDb extends GeneratedDatabase { ); } -class JsonResult { +class JsonResult extends CustomResultSet { final String key; final String value; JsonResult({ + @required QueryRow row, this.key, this.value, - }); + }) : super(row); @override int get hashCode => $mrjf($mrjc(key.hashCode, value.hashCode)); @override @@ -1587,15 +1592,16 @@ class JsonResult { } } -class MultipleResult { +class MultipleResult extends CustomResultSet { final String a; final int b; final WithConstraint c; MultipleResult({ + @required QueryRow row, this.a, this.b, this.c, - }); + }) : super(row); @override int get hashCode => $mrjf($mrjc(a.hashCode, $mrjc(b.hashCode, c.hashCode))); @override @@ -1616,19 +1622,20 @@ class MultipleResult { } } -class ReadRowIdResult { +class ReadRowIdResult extends CustomResultSet { final int rowid; final String configKey; final String configValue; final SyncType syncState; final SyncType syncStateImplicit; ReadRowIdResult({ + @required QueryRow row, this.rowid, this.configKey, this.configValue, this.syncState, this.syncStateImplicit, - }); + }) : super(row); @override int get hashCode => $mrjf($mrjc( rowid.hashCode, diff --git a/moor/test/data/tables/todos.g.dart b/moor/test/data/tables/todos.g.dart index 1f4f8a13..50da7f02 100644 --- a/moor/test/data/tables/todos.g.dart +++ b/moor/test/data/tables/todos.g.dart @@ -1563,6 +1563,7 @@ abstract class _$TodoDb extends GeneratedDatabase { variables: [], readsFrom: {categories, todosTable}).map((QueryRow row) { return AllTodosWithCategoryResult( + row: row, id: row.readInt('id'), title: row.readString('title'), content: row.readString('content'), @@ -1628,7 +1629,7 @@ abstract class _$TodoDb extends GeneratedDatabase { ]; } -class AllTodosWithCategoryResult { +class AllTodosWithCategoryResult extends CustomResultSet { final int id; final String title; final String content; @@ -1637,6 +1638,7 @@ class AllTodosWithCategoryResult { final int catId; final String catDesc; AllTodosWithCategoryResult({ + @required QueryRow row, this.id, this.title, this.content, @@ -1644,7 +1646,7 @@ class AllTodosWithCategoryResult { this.category, this.catId, this.catDesc, - }); + }) : super(row); @override int get hashCode => $mrjf($mrjc( id.hashCode, diff --git a/moor/test/parsed_sql/moor_files_integration_test.dart b/moor/test/parsed_sql/moor_files_integration_test.dart index 47e23eaa..f6b65378 100644 --- a/moor/test/parsed_sql/moor_files_integration_test.dart +++ b/moor/test/parsed_sql/moor_files_integration_test.dart @@ -137,15 +137,15 @@ void main() { final mock = MockExecutor(); final db = CustomTablesDb(mock); - when(mock.runSelect(any, any)).thenAnswer((_) { - final row = { - 'a': 'text for a', - 'b': 42, - 'nested_0.a': 'text', - 'nested_0.b': 1337, - 'nested_0.c': 18.7, - }; + const row = { + 'a': 'text for a', + 'b': 42, + 'nested_0.a': 'text', + 'nested_0.b': 1337, + 'nested_0.c': 18.7, + }; + when(mock.runSelect(any, any)).thenAnswer((_) { return Future.value([row]); }); @@ -154,6 +154,7 @@ void main() { expect( result, MultipleResult( + row: QueryRow(row, db), a: 'text for a', b: 42, c: WithConstraint(a: 'text', b: 1337, c: 18.7), @@ -165,16 +166,16 @@ void main() { final mock = MockExecutor(); final db = CustomTablesDb(mock); + const row = { + 'a': 'text for a', + 'b': 42, + 'nested_0.a': 'text', + 'nested_0.b': null, // note: with_constraints.b is NOT NULL in the db + 'nested_0.c': 18.7, + }; + when(mock.runSelect(any, any)).thenAnswer((_) { - return Future.value([ - { - 'a': 'text for a', - 'b': 42, - 'nested_0.a': 'text', - 'nested_0.b': null, // note: with_constraints.b is NOT NULL in the db - 'nested_0.c': 18.7, - } - ]); + return Future.value([row]); }); final result = await db.multiple(const Constant(true)).getSingle(); @@ -182,6 +183,7 @@ void main() { expect( result, MultipleResult( + row: QueryRow(row, db), a: 'text for a', b: 42, // Since a non-nullable column in c was null, table should be null diff --git a/moor_generator/lib/src/analyzer/options.dart b/moor_generator/lib/src/analyzer/options.dart index e5bda756..3970b0a4 100644 --- a/moor_generator/lib/src/analyzer/options.dart +++ b/moor_generator/lib/src/analyzer/options.dart @@ -74,6 +74,11 @@ class MoorOptions { @JsonKey(name: 'mutable_classes', defaultValue: false) final bool generateMutableClasses; + /// Whether generated query classes should inherit from the `CustomResultSet` + /// and expose their underlying raw `row`. + @JsonKey(name: 'raw_result_set_data', defaultValue: false) + final bool rawResultSetData; + /// Whether the [module] has been enabled in this configuration. bool hasModule(SqlModule module) => modules.contains(module); @@ -89,6 +94,7 @@ class MoorOptions { this.eagerlyLoadDartAst = false, this.dataClassToCompanions = true, this.generateMutableClasses = false, + this.rawResultSetData = false, this.modules = const [], }); diff --git a/moor_generator/lib/src/analyzer/options.g.dart b/moor_generator/lib/src/analyzer/options.g.dart index ba971cd2..7d293d80 100644 --- a/moor_generator/lib/src/analyzer/options.g.dart +++ b/moor_generator/lib/src/analyzer/options.g.dart @@ -20,7 +20,8 @@ MoorOptions _$MoorOptionsFromJson(Map json) { 'sqlite_modules', 'eagerly_load_dart_ast', 'data_class_to_companions', - 'mutable_classes' + 'mutable_classes', + 'raw_result_set_data' ]); final val = MoorOptions( generateFromJsonStringConstructor: $checkedConvert( @@ -57,6 +58,9 @@ MoorOptions _$MoorOptionsFromJson(Map json) { true, generateMutableClasses: $checkedConvert(json, 'mutable_classes', (v) => v as bool) ?? false, + rawResultSetData: + $checkedConvert(json, 'raw_result_set_data', (v) => v as bool) ?? + false, modules: $checkedConvert( json, 'sqlite_modules', @@ -80,7 +84,8 @@ MoorOptions _$MoorOptionsFromJson(Map json) { 'eagerlyLoadDartAst': 'eagerly_load_dart_ast', 'dataClassToCompanions': 'data_class_to_companions', 'generateMutableClasses': 'mutable_classes', - 'modules': 'sqlite_modules' + 'modules': 'sqlite_modules', + 'rawResultSetData': 'raw_result_set_data', }); } diff --git a/moor_generator/lib/src/model/sql_query.dart b/moor_generator/lib/src/model/sql_query.dart index 40e7e8d4..66feecd1 100644 --- a/moor_generator/lib/src/model/sql_query.dart +++ b/moor_generator/lib/src/model/sql_query.dart @@ -176,6 +176,7 @@ class InferredResultSet { /// /// See [NestedResultTable] for further discussion and examples. final List nestedResults; + Map _expandedNestedPrefixes; final List columns; final Map _dartNames = {}; @@ -217,6 +218,17 @@ class InferredResultSet { bool get singleColumn => matchingTable == null && nestedResults.isEmpty && columns.length == 1; + String nestedPrefixFor(NestedResultTable table) { + if (_expandedNestedPrefixes == null) { + var index = 0; + _expandedNestedPrefixes = { + for (final nested in nestedResults) nested: 'nested_${index++}', + }; + } + + return _expandedNestedPrefixes[table]; + } + void forceDartNames(Map names) { _dartNames ..clear() diff --git a/moor_generator/lib/src/writer/queries/query_writer.dart b/moor_generator/lib/src/writer/queries/query_writer.dart index a20dfec5..de1a280d 100644 --- a/moor_generator/lib/src/writer/queries/query_writer.dart +++ b/moor_generator/lib/src/writer/queries/query_writer.dart @@ -19,8 +19,6 @@ class QueryWriter { final SqlQuery query; final Scope scope; - final Map _expandedNestedPrefixes = {}; - SqlSelectQuery get _select => query as SqlSelectQuery; UpdatingQuery get _update => query as UpdatingQuery; @@ -64,8 +62,6 @@ class QueryWriter { } void _writeSelect() { - _createNamesForNestedResults(); - _writeSelectStatementCreator(); if (!_newSelectableMode) { @@ -82,22 +78,16 @@ class QueryWriter { } } - void _createNamesForNestedResults() { - var index = 0; - - for (final nested in _select.resultSet.nestedResults) { - _expandedNestedPrefixes[nested] = 'nested_${index++}'; - } - } - /// Writes the function literal that turns a "QueryRow" into the desired /// custom return type of a select statement. void _writeMappingLambda() { if (_select.resultSet.singleColumn) { final column = _select.resultSet.columns.single; - _buffer.write('(QueryRow row) => ${_readingCode(column)}'); - return; + _buffer.write('(QueryRow row) => ${readingCode(column)}'); } else if (_select.resultSet.matchingTable != null) { + // note that, even if the result set has a matching table, we can't just + // use the mapFromRow() function of that table - the column names might + // be different! final match = _select.resultSet.matchingTable; final table = match.table; @@ -118,37 +108,35 @@ class QueryWriter { _buffer.write('})'); } + } else { + _buffer.write('(QueryRow row) { return ${_select.resultClassName}('); - return; + if (options.rawResultSetData) { + _buffer.write('row: row,\n'); + } + + for (final column in _select.resultSet.columns) { + final fieldName = _select.resultSet.dartNameFor(column); + _buffer.write('$fieldName: ${readingCode(column)},'); + } + for (final nested in _select.resultSet.nestedResults) { + final prefix = _select.resultSet.nestedPrefixFor(nested); + if (prefix == null) continue; + + final fieldName = nested.dartFieldName; + final tableGetter = nested.table.dbGetterName; + + _buffer.write('$fieldName: $tableGetter.mapFromRowOrNull(row, ' + 'tablePrefix: ${asDartLiteral(prefix)}),'); + } + _buffer.write(');\n}'); } - - _buffer.write('(QueryRow row) {\n'); - - // note that, even if the result set has a matching table, we can't just - // use the mapFromRow() function of that table - the column names might - // be different! - _buffer.write('return ${_select.resultClassName}('); - for (final column in _select.resultSet.columns) { - final fieldName = _select.resultSet.dartNameFor(column); - _buffer.write('$fieldName: ${_readingCode(column)},'); - } - for (final nested in _select.resultSet.nestedResults) { - final prefix = _expandedNestedPrefixes[nested]; - if (prefix == null) continue; - - final fieldName = nested.dartFieldName; - final tableGetter = nested.table.dbGetterName; - - _buffer.write('$fieldName: $tableGetter.mapFromRowOrNull(row, ' - 'tablePrefix: ${asDartLiteral(prefix)}),'); - } - _buffer.write(');\n}'); } /// Returns Dart code that, given a variable of type `QueryRow` named `row` /// in the same scope, reads the [column] from that row and brings it into a /// suitable type. - String _readingCode(ResultColumn column) { + static String readingCode(ResultColumn column) { final readMethod = readFromMethods[column.type]; final dartLiteral = asDartLiteral(column.name); @@ -430,7 +418,7 @@ class QueryWriter { final result = doubleStarColumnToResolvedTable[rewriteTarget]; if (result == null) continue; - final prefix = _expandedNestedPrefixes[result]; + final prefix = _select.resultSet.nestedPrefixFor(result); final table = rewriteTarget.tableName; // Convert foo.** to "foo.a" AS "nested_0.a", ... for all columns in foo diff --git a/moor_generator/lib/src/writer/queries/result_set_writer.dart b/moor_generator/lib/src/writer/queries/result_set_writer.dart index ac5d3fdd..0394e941 100644 --- a/moor_generator/lib/src/writer/queries/result_set_writer.dart +++ b/moor_generator/lib/src/writer/queries/result_set_writer.dart @@ -14,12 +14,20 @@ class ResultSetWriter { final fieldNames = []; final into = scope.leaf(); - into.write('class $className {\n'); + final resultSet = query.resultSet; + + into.write('class $className '); + if (scope.options.rawResultSetData) { + into.write('extends CustomResultSet {\n'); + } else { + into.write('{\n'); + } + final modifier = scope.options.fieldModifier; // write fields - for (final column in query.resultSet.columns) { - final name = query.resultSet.dartNameFor(column); + for (final column in resultSet.columns) { + final name = resultSet.dartNameFor(column); final runtimeType = column.dartType; into.write('$modifier $runtimeType $name\n;'); @@ -27,7 +35,7 @@ class ResultSetWriter { fieldNames.add(name); } - for (final nested in query.resultSet.nestedResults) { + for (final nested in resultSet.nestedResults) { final typeName = nested.table.dartTypeName; final fieldName = nested.dartFieldName; @@ -37,11 +45,21 @@ class ResultSetWriter { } // write the constructor - into.write('$className({'); + if (scope.options.rawResultSetData) { + into.write('$className({@required QueryRow row,'); + } else { + into.write('$className({'); + } + for (final column in fieldNames) { into.write('this.$column,'); } - into.write('});\n'); + + if (scope.options.rawResultSetData) { + into.write('}): super(row);\n'); + } else { + into.write('});\n'); + } // if requested, override hashCode and equals if (scope.writer.options.overrideHashAndEqualsInResultSets) {