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.
* `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

View File

@ -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

View File

@ -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

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/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';

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: [],
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,

View File

@ -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,

View File

@ -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

View File

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

View File

@ -20,7 +20,8 @@ MoorOptions _$MoorOptionsFromJson(Map<String, dynamic> 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<String, dynamic> 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<String, dynamic> 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',
});
}

View File

@ -176,6 +176,7 @@ class InferredResultSet {
///
/// See [NestedResultTable] for further discussion and examples.
final List<NestedResultTable> nestedResults;
Map<NestedResultTable, String> _expandedNestedPrefixes;
final List<ResultColumn> columns;
final Map<ResultColumn, String> _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<ResultColumn, String> names) {
_dartNames
..clear()

View File

@ -19,8 +19,6 @@ class QueryWriter {
final SqlQuery query;
final Scope scope;
final Map<NestedResultTable, String> _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

View File

@ -14,12 +14,20 @@ class ResultSetWriter {
final fieldNames = <String>[];
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) {