Option to get raw data in result sets (#615)

This commit is contained in:
Simon Binder 2020-07-09 17:38:46 +02:00
parent 7ff91a620a
commit fa1e76f8f3
No known key found for this signature in database
GPG Key ID: 7891917E4147B8C0
13 changed files with 129 additions and 74 deletions

View File

@ -65,6 +65,7 @@ At the moment, moor supports these options:
data classes. data classes.
* `mutable_classes` (defaults to `false`): The fields generated in generated data, companion and result set classes are final * `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`. 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 ## Available extensions

View File

@ -8,6 +8,7 @@ targets:
generate_connect_constructor: true generate_connect_constructor: true
compact_query_methods: true compact_query_methods: true
write_from_json_string_constructor: true write_from_json_string_constructor: true
raw_result_set_data: true
# eagerly_load_dart_ast: true # eagerly_load_dart_ast: true
sqlite_modules: sqlite_modules:
- json1 - json1

View File

@ -1013,6 +1013,7 @@ abstract class _$Database extends GeneratedDatabase {
variables: [], variables: [],
readsFrom: {recipes, ingredientInRecipes}).map((QueryRow row) { readsFrom: {recipes, ingredientInRecipes}).map((QueryRow row) {
return TotalWeightResult( return TotalWeightResult(
row: row,
title: row.readString('title'), title: row.readString('title'),
totalWeight: row.readInt('total_weight'), totalWeight: row.readInt('total_weight'),
); );
@ -1026,13 +1027,14 @@ abstract class _$Database extends GeneratedDatabase {
[categories, recipes, ingredients, ingredientInRecipes]; [categories, recipes, ingredients, ingredientInRecipes];
} }
class TotalWeightResult { class TotalWeightResult extends CustomResultSet {
final String title; final String title;
final int totalWeight; final int totalWeight;
TotalWeightResult({ TotalWeightResult({
@required QueryRow row,
this.title, this.title,
this.totalWeight, this.totalWeight,
}); }) : super(row);
@override @override
int get hashCode => $mrjf($mrjc(title.hashCode, totalWeight.hashCode)); int get hashCode => $mrjf($mrjc(title.hashCode, totalWeight.hashCode));
@override @override

View File

@ -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/connection_pool.dart';
export 'package:moor/src/runtime/executor/executor.dart'; export 'package:moor/src/runtime/executor/executor.dart';
export 'package:moor/src/runtime/executor/transactions.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_verification.dart';
export 'package:moor/src/runtime/data_class.dart'; export 'package:moor/src/runtime/data_class.dart';
export 'package:moor/src/runtime/api/runtime_api.dart'; export 'package:moor/src/runtime/api/runtime_api.dart';

View File

@ -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);
}

View File

@ -1462,6 +1462,7 @@ abstract class _$CustomTablesDb extends GeneratedDatabase {
variables: [], variables: [],
readsFrom: {config}).map((QueryRow row) { readsFrom: {config}).map((QueryRow row) {
return JsonResult( return JsonResult(
row: row,
key: row.readString('key'), key: row.readString('key'),
value: row.readString('value'), value: row.readString('value'),
); );
@ -1474,6 +1475,7 @@ abstract class _$CustomTablesDb extends GeneratedDatabase {
variables: [], variables: [],
readsFrom: {}).map((QueryRow row) { readsFrom: {}).map((QueryRow row) {
return JsonResult( return JsonResult(
row: row,
key: row.readString('key'), key: row.readString('key'),
value: row.readString('value'), value: row.readString('value'),
); );
@ -1487,6 +1489,7 @@ abstract class _$CustomTablesDb extends GeneratedDatabase {
variables: [...generatedpredicate.introducedVariables], variables: [...generatedpredicate.introducedVariables],
readsFrom: {withConstraints, withDefaults}).map((QueryRow row) { readsFrom: {withConstraints, withDefaults}).map((QueryRow row) {
return MultipleResult( return MultipleResult(
row: row,
a: row.readString('a'), a: row.readString('a'),
b: row.readInt('b'), b: row.readInt('b'),
c: withConstraints.mapFromRowOrNull(row, tablePrefix: 'nested_0'), c: withConstraints.mapFromRowOrNull(row, tablePrefix: 'nested_0'),
@ -1508,6 +1511,7 @@ abstract class _$CustomTablesDb extends GeneratedDatabase {
variables: [...generatedexpr.introducedVariables], variables: [...generatedexpr.introducedVariables],
readsFrom: {config}).map((QueryRow row) { readsFrom: {config}).map((QueryRow row) {
return ReadRowIdResult( return ReadRowIdResult(
row: row,
rowid: row.readInt('rowid'), rowid: row.readInt('rowid'),
configKey: row.readString('config_key'), configKey: row.readString('config_key'),
configValue: row.readString('config_value'), 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 key;
final String value; final String value;
JsonResult({ JsonResult({
@required QueryRow row,
this.key, this.key,
this.value, this.value,
}); }) : super(row);
@override @override
int get hashCode => $mrjf($mrjc(key.hashCode, value.hashCode)); int get hashCode => $mrjf($mrjc(key.hashCode, value.hashCode));
@override @override
@ -1587,15 +1592,16 @@ class JsonResult {
} }
} }
class MultipleResult { class MultipleResult extends CustomResultSet {
final String a; final String a;
final int b; final int b;
final WithConstraint c; final WithConstraint c;
MultipleResult({ MultipleResult({
@required QueryRow row,
this.a, this.a,
this.b, this.b,
this.c, this.c,
}); }) : super(row);
@override @override
int get hashCode => $mrjf($mrjc(a.hashCode, $mrjc(b.hashCode, c.hashCode))); int get hashCode => $mrjf($mrjc(a.hashCode, $mrjc(b.hashCode, c.hashCode)));
@override @override
@ -1616,19 +1622,20 @@ class MultipleResult {
} }
} }
class ReadRowIdResult { class ReadRowIdResult extends CustomResultSet {
final int rowid; final int rowid;
final String configKey; final String configKey;
final String configValue; final String configValue;
final SyncType syncState; final SyncType syncState;
final SyncType syncStateImplicit; final SyncType syncStateImplicit;
ReadRowIdResult({ ReadRowIdResult({
@required QueryRow row,
this.rowid, this.rowid,
this.configKey, this.configKey,
this.configValue, this.configValue,
this.syncState, this.syncState,
this.syncStateImplicit, this.syncStateImplicit,
}); }) : super(row);
@override @override
int get hashCode => $mrjf($mrjc( int get hashCode => $mrjf($mrjc(
rowid.hashCode, rowid.hashCode,

View File

@ -1563,6 +1563,7 @@ abstract class _$TodoDb extends GeneratedDatabase {
variables: [], variables: [],
readsFrom: {categories, todosTable}).map((QueryRow row) { readsFrom: {categories, todosTable}).map((QueryRow row) {
return AllTodosWithCategoryResult( return AllTodosWithCategoryResult(
row: row,
id: row.readInt('id'), id: row.readInt('id'),
title: row.readString('title'), title: row.readString('title'),
content: row.readString('content'), content: row.readString('content'),
@ -1628,7 +1629,7 @@ abstract class _$TodoDb extends GeneratedDatabase {
]; ];
} }
class AllTodosWithCategoryResult { class AllTodosWithCategoryResult extends CustomResultSet {
final int id; final int id;
final String title; final String title;
final String content; final String content;
@ -1637,6 +1638,7 @@ class AllTodosWithCategoryResult {
final int catId; final int catId;
final String catDesc; final String catDesc;
AllTodosWithCategoryResult({ AllTodosWithCategoryResult({
@required QueryRow row,
this.id, this.id,
this.title, this.title,
this.content, this.content,
@ -1644,7 +1646,7 @@ class AllTodosWithCategoryResult {
this.category, this.category,
this.catId, this.catId,
this.catDesc, this.catDesc,
}); }) : super(row);
@override @override
int get hashCode => $mrjf($mrjc( int get hashCode => $mrjf($mrjc(
id.hashCode, id.hashCode,

View File

@ -137,15 +137,15 @@ void main() {
final mock = MockExecutor(); final mock = MockExecutor();
final db = CustomTablesDb(mock); final db = CustomTablesDb(mock);
when(mock.runSelect(any, any)).thenAnswer((_) { const row = {
final row = { 'a': 'text for a',
'a': 'text for a', 'b': 42,
'b': 42, 'nested_0.a': 'text',
'nested_0.a': 'text', 'nested_0.b': 1337,
'nested_0.b': 1337, 'nested_0.c': 18.7,
'nested_0.c': 18.7, };
};
when(mock.runSelect(any, any)).thenAnswer((_) {
return Future.value([row]); return Future.value([row]);
}); });
@ -154,6 +154,7 @@ void main() {
expect( expect(
result, result,
MultipleResult( MultipleResult(
row: QueryRow(row, db),
a: 'text for a', a: 'text for a',
b: 42, b: 42,
c: WithConstraint(a: 'text', b: 1337, c: 18.7), c: WithConstraint(a: 'text', b: 1337, c: 18.7),
@ -165,16 +166,16 @@ void main() {
final mock = MockExecutor(); final mock = MockExecutor();
final db = CustomTablesDb(mock); 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((_) { when(mock.runSelect(any, any)).thenAnswer((_) {
return Future.value([ return Future.value([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,
}
]);
}); });
final result = await db.multiple(const Constant(true)).getSingle(); final result = await db.multiple(const Constant(true)).getSingle();
@ -182,6 +183,7 @@ void main() {
expect( expect(
result, result,
MultipleResult( MultipleResult(
row: QueryRow(row, db),
a: 'text for a', a: 'text for a',
b: 42, b: 42,
// Since a non-nullable column in c was null, table should be null // Since a non-nullable column in c was null, table should be null

View File

@ -74,6 +74,11 @@ class MoorOptions {
@JsonKey(name: 'mutable_classes', defaultValue: false) @JsonKey(name: 'mutable_classes', defaultValue: false)
final bool generateMutableClasses; 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. /// Whether the [module] has been enabled in this configuration.
bool hasModule(SqlModule module) => modules.contains(module); bool hasModule(SqlModule module) => modules.contains(module);
@ -89,6 +94,7 @@ class MoorOptions {
this.eagerlyLoadDartAst = false, this.eagerlyLoadDartAst = false,
this.dataClassToCompanions = true, this.dataClassToCompanions = true,
this.generateMutableClasses = false, this.generateMutableClasses = false,
this.rawResultSetData = false,
this.modules = const [], this.modules = const [],
}); });

View File

@ -20,7 +20,8 @@ MoorOptions _$MoorOptionsFromJson(Map<String, dynamic> json) {
'sqlite_modules', 'sqlite_modules',
'eagerly_load_dart_ast', 'eagerly_load_dart_ast',
'data_class_to_companions', 'data_class_to_companions',
'mutable_classes' 'mutable_classes',
'raw_result_set_data'
]); ]);
final val = MoorOptions( final val = MoorOptions(
generateFromJsonStringConstructor: $checkedConvert( generateFromJsonStringConstructor: $checkedConvert(
@ -57,6 +58,9 @@ MoorOptions _$MoorOptionsFromJson(Map<String, dynamic> json) {
true, true,
generateMutableClasses: generateMutableClasses:
$checkedConvert(json, 'mutable_classes', (v) => v as bool) ?? false, $checkedConvert(json, 'mutable_classes', (v) => v as bool) ?? false,
rawResultSetData:
$checkedConvert(json, 'raw_result_set_data', (v) => v as bool) ??
false,
modules: $checkedConvert( modules: $checkedConvert(
json, json,
'sqlite_modules', 'sqlite_modules',
@ -80,7 +84,8 @@ MoorOptions _$MoorOptionsFromJson(Map<String, dynamic> json) {
'eagerlyLoadDartAst': 'eagerly_load_dart_ast', 'eagerlyLoadDartAst': 'eagerly_load_dart_ast',
'dataClassToCompanions': 'data_class_to_companions', 'dataClassToCompanions': 'data_class_to_companions',
'generateMutableClasses': 'mutable_classes', 'generateMutableClasses': 'mutable_classes',
'modules': 'sqlite_modules' 'modules': 'sqlite_modules',
'rawResultSetData': 'raw_result_set_data',
}); });
} }

View File

@ -176,6 +176,7 @@ class InferredResultSet {
/// ///
/// See [NestedResultTable] for further discussion and examples. /// See [NestedResultTable] for further discussion and examples.
final List<NestedResultTable> nestedResults; final List<NestedResultTable> nestedResults;
Map<NestedResultTable, String> _expandedNestedPrefixes;
final List<ResultColumn> columns; final List<ResultColumn> columns;
final Map<ResultColumn, String> _dartNames = {}; final Map<ResultColumn, String> _dartNames = {};
@ -217,6 +218,17 @@ class InferredResultSet {
bool get singleColumn => bool get singleColumn =>
matchingTable == null && nestedResults.isEmpty && columns.length == 1; 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<ResultColumn, String> names) { void forceDartNames(Map<ResultColumn, String> names) {
_dartNames _dartNames
..clear() ..clear()

View File

@ -19,8 +19,6 @@ class QueryWriter {
final SqlQuery query; final SqlQuery query;
final Scope scope; final Scope scope;
final Map<NestedResultTable, String> _expandedNestedPrefixes = {};
SqlSelectQuery get _select => query as SqlSelectQuery; SqlSelectQuery get _select => query as SqlSelectQuery;
UpdatingQuery get _update => query as UpdatingQuery; UpdatingQuery get _update => query as UpdatingQuery;
@ -64,8 +62,6 @@ class QueryWriter {
} }
void _writeSelect() { void _writeSelect() {
_createNamesForNestedResults();
_writeSelectStatementCreator(); _writeSelectStatementCreator();
if (!_newSelectableMode) { 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 /// Writes the function literal that turns a "QueryRow" into the desired
/// custom return type of a select statement. /// custom return type of a select statement.
void _writeMappingLambda() { void _writeMappingLambda() {
if (_select.resultSet.singleColumn) { if (_select.resultSet.singleColumn) {
final column = _select.resultSet.columns.single; final column = _select.resultSet.columns.single;
_buffer.write('(QueryRow row) => ${_readingCode(column)}'); _buffer.write('(QueryRow row) => ${readingCode(column)}');
return;
} else if (_select.resultSet.matchingTable != null) { } 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 match = _select.resultSet.matchingTable;
final table = match.table; final table = match.table;
@ -118,37 +108,35 @@ class QueryWriter {
_buffer.write('})'); _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` /// 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 /// in the same scope, reads the [column] from that row and brings it into a
/// suitable type. /// suitable type.
String _readingCode(ResultColumn column) { static String readingCode(ResultColumn column) {
final readMethod = readFromMethods[column.type]; final readMethod = readFromMethods[column.type];
final dartLiteral = asDartLiteral(column.name); final dartLiteral = asDartLiteral(column.name);
@ -430,7 +418,7 @@ class QueryWriter {
final result = doubleStarColumnToResolvedTable[rewriteTarget]; final result = doubleStarColumnToResolvedTable[rewriteTarget];
if (result == null) continue; if (result == null) continue;
final prefix = _expandedNestedPrefixes[result]; final prefix = _select.resultSet.nestedPrefixFor(result);
final table = rewriteTarget.tableName; final table = rewriteTarget.tableName;
// Convert foo.** to "foo.a" AS "nested_0.a", ... for all columns in foo // Convert foo.** to "foo.a" AS "nested_0.a", ... for all columns in foo

View File

@ -14,12 +14,20 @@ class ResultSetWriter {
final fieldNames = <String>[]; final fieldNames = <String>[];
final into = scope.leaf(); 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; final modifier = scope.options.fieldModifier;
// write fields // write fields
for (final column in query.resultSet.columns) { for (final column in resultSet.columns) {
final name = query.resultSet.dartNameFor(column); final name = resultSet.dartNameFor(column);
final runtimeType = column.dartType; final runtimeType = column.dartType;
into.write('$modifier $runtimeType $name\n;'); into.write('$modifier $runtimeType $name\n;');
@ -27,7 +35,7 @@ class ResultSetWriter {
fieldNames.add(name); fieldNames.add(name);
} }
for (final nested in query.resultSet.nestedResults) { for (final nested in resultSet.nestedResults) {
final typeName = nested.table.dartTypeName; final typeName = nested.table.dartTypeName;
final fieldName = nested.dartFieldName; final fieldName = nested.dartFieldName;
@ -37,11 +45,21 @@ class ResultSetWriter {
} }
// write the constructor // 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) { for (final column in fieldNames) {
into.write('this.$column,'); 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 requested, override hashCode and equals
if (scope.writer.options.overrideHashAndEqualsInResultSets) { if (scope.writer.options.overrideHashAndEqualsInResultSets) {