mirror of https://github.com/AMT-Cheif/drift.git
Option to generate functions for Dart placeholders
This commit is contained in:
parent
e87e4d7a7a
commit
eb362effe8
|
@ -13,6 +13,7 @@ targets:
|
|||
generate_values_in_copy_with: true
|
||||
named_parameters: true
|
||||
new_sql_code_generation: true
|
||||
scoped_dart_components: true
|
||||
sqlite:
|
||||
version: "3.35"
|
||||
modules:
|
||||
|
|
|
@ -1650,11 +1650,11 @@ abstract class _$CustomTablesDb extends GeneratedDatabase {
|
|||
}
|
||||
|
||||
Selectable<Config> readMultiple(List<String> var1,
|
||||
{OrderBy clause = const OrderBy.nothing()}) {
|
||||
{OrderBy Function(ConfigTable config) clause = _$moor$default$0}) {
|
||||
var $arrayStartIndex = 1;
|
||||
final expandedvar1 = $expandVar($arrayStartIndex, var1.length);
|
||||
$arrayStartIndex += var1.length;
|
||||
final generatedclause = $write(clause);
|
||||
final generatedclause = $write(clause(this.config));
|
||||
$arrayStartIndex += generatedclause.amountOfVariables;
|
||||
return customSelect(
|
||||
'SELECT * FROM config WHERE config_key IN ($expandedvar1) ${generatedclause.sql}',
|
||||
|
@ -1668,17 +1668,18 @@ abstract class _$CustomTablesDb extends GeneratedDatabase {
|
|||
}
|
||||
|
||||
Selectable<Config> readDynamic(
|
||||
{Expression<bool> predicate = const CustomExpression('(TRUE)')}) {
|
||||
final generatedpredicate = $write(predicate);
|
||||
{Expression<bool> Function(ConfigTable config) predicate =
|
||||
_$moor$default$1}) {
|
||||
final generatedpredicate = $write(predicate(this.config));
|
||||
return customSelect('SELECT * FROM config WHERE ${generatedpredicate.sql}',
|
||||
variables: [...generatedpredicate.introducedVariables],
|
||||
readsFrom: {config}).map(config.mapFromRow);
|
||||
}
|
||||
|
||||
Selectable<String> typeConverterVar(SyncType? var1, List<SyncType?> var2,
|
||||
{Expression<bool> pred = const CustomExpression('(TRUE)')}) {
|
||||
{Expression<bool> Function(ConfigTable config) pred = _$moor$default$2}) {
|
||||
var $arrayStartIndex = 2;
|
||||
final generatedpred = $write(pred);
|
||||
final generatedpred = $write(pred(this.config));
|
||||
$arrayStartIndex += generatedpred.amountOfVariables;
|
||||
final expandedvar2 = $expandVar($arrayStartIndex, var2.length);
|
||||
$arrayStartIndex += var2.length;
|
||||
|
@ -1721,8 +1722,13 @@ abstract class _$CustomTablesDb extends GeneratedDatabase {
|
|||
});
|
||||
}
|
||||
|
||||
Selectable<MultipleResult> multiple({required Expression<bool> predicate}) {
|
||||
final generatedpredicate = $write(predicate, hasMultipleTables: true);
|
||||
Selectable<MultipleResult> multiple(
|
||||
{required Expression<bool> Function(WithDefaults d, WithConstraints c)
|
||||
predicate}) {
|
||||
final generatedpredicate = $write(
|
||||
predicate(
|
||||
alias(this.withDefaults, 'd'), alias(this.withConstraints, 'c')),
|
||||
hasMultipleTables: true);
|
||||
return customSelect(
|
||||
'SELECT d.*,"c"."a" AS "nested_0.a", "c"."b" AS "nested_0.b", "c"."c" AS "nested_0.c" FROM with_defaults AS d LEFT OUTER JOIN with_constraints AS c ON d.a = c.a AND d.b = c.b WHERE ${generatedpredicate.sql}',
|
||||
variables: [...generatedpredicate.introducedVariables],
|
||||
|
@ -1743,8 +1749,9 @@ abstract class _$CustomTablesDb extends GeneratedDatabase {
|
|||
readsFrom: {email}).map(email.mapFromRow);
|
||||
}
|
||||
|
||||
Selectable<ReadRowIdResult> readRowId({required Expression<int> expr}) {
|
||||
final generatedexpr = $write(expr);
|
||||
Selectable<ReadRowIdResult> readRowId(
|
||||
{required Expression<int> Function(ConfigTable config) expr}) {
|
||||
final generatedexpr = $write(expr(this.config));
|
||||
return customSelect(
|
||||
'SELECT oid, * FROM config WHERE _rowid_ = ${generatedexpr.sql}',
|
||||
variables: [...generatedexpr.introducedVariables],
|
||||
|
@ -1837,6 +1844,12 @@ abstract class _$CustomTablesDb extends GeneratedDatabase {
|
|||
);
|
||||
}
|
||||
|
||||
OrderBy _$moor$default$0(ConfigTable _) => const OrderBy.nothing();
|
||||
Expression<bool> _$moor$default$1(ConfigTable _) =>
|
||||
const CustomExpression('(TRUE)');
|
||||
Expression<bool> _$moor$default$2(ConfigTable _) =>
|
||||
const CustomExpression('(TRUE)');
|
||||
|
||||
class JsonResult extends CustomResultSet {
|
||||
final String key;
|
||||
final String? value;
|
||||
|
|
|
@ -8,7 +8,7 @@ CREATE TABLE no_ids (
|
|||
CREATE TABLE with_defaults (
|
||||
a TEXT DEFAULT 'something',
|
||||
b INT UNIQUE
|
||||
) ;
|
||||
);
|
||||
|
||||
CREATE TABLE with_constraints (
|
||||
a TEXT,
|
||||
|
|
|
@ -103,7 +103,8 @@ void main() {
|
|||
|
||||
test('runs queries with arrays and Dart templates', () async {
|
||||
await db.readMultiple(['a', 'b'],
|
||||
clause: OrderBy([OrderingTerm(expression: db.config.configKey)])).get();
|
||||
clause: (config) =>
|
||||
OrderBy([OrderingTerm(expression: config.configKey)])).get();
|
||||
|
||||
verify(mock.runSelect(
|
||||
'SELECT * FROM config WHERE config_key IN (?1, ?2) '
|
||||
|
@ -118,7 +119,7 @@ void main() {
|
|||
.thenAnswer((_) => Future.value([mockResponse]));
|
||||
|
||||
final parsed = await db
|
||||
.readDynamic(predicate: db.config.configKey.equals('key'))
|
||||
.readDynamic(predicate: (config) => config.configKey.equals('key'))
|
||||
.getSingle();
|
||||
|
||||
verify(
|
||||
|
@ -133,9 +134,9 @@ void main() {
|
|||
});
|
||||
|
||||
test('columns use table names in queries with multiple tables', () async {
|
||||
await db.multiple(predicate: db.withDefaults.a.equals('foo')).get();
|
||||
await db.multiple(predicate: (d, c) => d.a.equals('foo')).get();
|
||||
|
||||
verify(mock.runSelect(argThat(contains('with_defaults.a')), any));
|
||||
verify(mock.runSelect(argThat(contains('d.a = ?')), any));
|
||||
});
|
||||
|
||||
test('order by-params are ignored by default', () async {
|
||||
|
@ -156,8 +157,9 @@ void main() {
|
|||
return Future.value([row]);
|
||||
});
|
||||
|
||||
final result =
|
||||
await db.multiple(predicate: const Constant(true)).getSingle();
|
||||
final result = await db
|
||||
.multiple(predicate: (_, __) => const Constant(true))
|
||||
.getSingle();
|
||||
|
||||
expect(
|
||||
result,
|
||||
|
@ -183,8 +185,9 @@ void main() {
|
|||
return Future.value([row]);
|
||||
});
|
||||
|
||||
final result =
|
||||
await db.multiple(predicate: const Constant(true)).getSingle();
|
||||
final result = await db
|
||||
.multiple(predicate: (_, __) => const Constant(true))
|
||||
.getSingle();
|
||||
|
||||
expect(
|
||||
result,
|
||||
|
|
|
@ -91,6 +91,9 @@ class MoorOptions {
|
|||
@JsonKey(name: 'new_sql_code_generation', defaultValue: false)
|
||||
final bool newSqlCodeGeneration;
|
||||
|
||||
@JsonKey(name: 'scoped_dart_components', defaultValue: false)
|
||||
final bool scopedDartComponents;
|
||||
|
||||
@internal
|
||||
const MoorOptions.defaults({
|
||||
this.generateFromJsonStringConstructor = false,
|
||||
|
@ -108,6 +111,7 @@ class MoorOptions {
|
|||
this.generateValuesInCopyWith = false,
|
||||
this.generateNamedParameters = false,
|
||||
this.newSqlCodeGeneration = false,
|
||||
this.scopedDartComponents = false,
|
||||
this.modules = const [],
|
||||
this.sqliteAnalysisOptions,
|
||||
});
|
||||
|
@ -128,6 +132,7 @@ class MoorOptions {
|
|||
required this.generateValuesInCopyWith,
|
||||
required this.generateNamedParameters,
|
||||
required this.newSqlCodeGeneration,
|
||||
required this.scopedDartComponents,
|
||||
required this.modules,
|
||||
required this.sqliteAnalysisOptions,
|
||||
}) {
|
||||
|
|
|
@ -25,7 +25,8 @@ MoorOptions _$MoorOptionsFromJson(Map json) {
|
|||
'apply_converters_on_variables',
|
||||
'generate_values_in_copy_with',
|
||||
'named_parameters',
|
||||
'new_sql_code_generation'
|
||||
'new_sql_code_generation',
|
||||
'scoped_dart_components'
|
||||
]);
|
||||
final val = MoorOptions(
|
||||
generateFromJsonStringConstructor: $checkedConvert(
|
||||
|
@ -73,6 +74,9 @@ MoorOptions _$MoorOptionsFromJson(Map json) {
|
|||
newSqlCodeGeneration:
|
||||
$checkedConvert(json, 'new_sql_code_generation', (v) => v as bool?) ??
|
||||
false,
|
||||
scopedDartComponents:
|
||||
$checkedConvert(json, 'scoped_dart_components', (v) => v as bool?) ??
|
||||
false,
|
||||
modules: $checkedConvert(
|
||||
json,
|
||||
'sqlite_modules',
|
||||
|
@ -102,6 +106,7 @@ MoorOptions _$MoorOptionsFromJson(Map json) {
|
|||
'generateValuesInCopyWith': 'generate_values_in_copy_with',
|
||||
'generateNamedParameters': 'named_parameters',
|
||||
'newSqlCodeGeneration': 'new_sql_code_generation',
|
||||
'scopedDartComponents': 'scoped_dart_components',
|
||||
'modules': 'sqlite_modules',
|
||||
'sqliteAnalysisOptions': 'sqlite'
|
||||
});
|
||||
|
|
|
@ -278,7 +278,38 @@ class TypeMapper {
|
|||
},
|
||||
);
|
||||
|
||||
return FoundDartPlaceholder(type, name)..astNode = placeholder;
|
||||
final availableResults =
|
||||
placeholder.scope.allOf<ResultSetAvailableInStatement>();
|
||||
final availableMoorResults = <AvailableMoorResultSet>[];
|
||||
for (final available in availableResults) {
|
||||
final aliasedResultSet = available.resultSet.resultSet;
|
||||
final resultSet = aliasedResultSet?.unalias();
|
||||
String name;
|
||||
if (aliasedResultSet is NamedResultSet) {
|
||||
name = aliasedResultSet.name;
|
||||
} else {
|
||||
// If we don't have a name we can't include this result set.
|
||||
continue;
|
||||
}
|
||||
|
||||
MoorEntityWithResultSet moorEntity;
|
||||
|
||||
if (resultSet is Table) {
|
||||
moorEntity = tableToMoor(resultSet);
|
||||
} else if (resultSet is View) {
|
||||
moorEntity = viewToMoor(resultSet);
|
||||
} else {
|
||||
// If this result set is an inner select statement or anything else we
|
||||
// can't represent it in Dart.
|
||||
continue;
|
||||
}
|
||||
|
||||
availableMoorResults
|
||||
.add(AvailableMoorResultSet(name, moorEntity, available));
|
||||
}
|
||||
|
||||
return FoundDartPlaceholder(type, name, availableMoorResults)
|
||||
..astNode = placeholder;
|
||||
}
|
||||
|
||||
MoorTable tableToMoor(Table table) {
|
||||
|
|
|
@ -36,4 +36,9 @@ abstract class MoorEntityWithResultSet extends MoorSchemaEntity {
|
|||
/// The name of the Dart class storing additional properties like type
|
||||
/// converters.
|
||||
String get entityInfoName;
|
||||
|
||||
/// The name of the Dart class storing the right column getters for this type.
|
||||
///
|
||||
/// This class is equal to, or a superclass of, [entityInfoName].
|
||||
String get dslName => entityInfoName;
|
||||
}
|
||||
|
|
|
@ -2,6 +2,7 @@
|
|||
import 'package:collection/collection.dart';
|
||||
import 'package:meta/meta.dart';
|
||||
import 'package:moor/moor.dart' show $mrjf, $mrjc, UpdateKind;
|
||||
import 'package:moor_generator/src/analyzer/options.dart';
|
||||
import 'package:moor_generator/src/analyzer/runner/results.dart';
|
||||
import 'package:moor_generator/src/model/base_entity.dart';
|
||||
import 'package:moor_generator/src/utils/hash.dart';
|
||||
|
@ -599,10 +600,41 @@ class InsertableDartPlaceholderType extends DartPlaceholderType {
|
|||
}
|
||||
}
|
||||
|
||||
/// A Dart placeholder that will be bound at runtime.
|
||||
/// A Dart placeholder that will be bound to a dynamically-generated SQL node
|
||||
/// at runtime.
|
||||
///
|
||||
/// Moor supports injecting expressions, order by terms and clauses and limit
|
||||
/// clauses as placeholders. For insert statements, companions can be used
|
||||
/// as a Dart placeholder too.
|
||||
class FoundDartPlaceholder extends FoundElement {
|
||||
final DartPlaceholderType type;
|
||||
|
||||
/// All result sets that are available for this Dart placeholder.
|
||||
///
|
||||
/// When queries are operating on multiple tables, especially if some of those
|
||||
/// tables have aliases, it may be hard to reflect the name of those tables
|
||||
/// at runtime.
|
||||
/// For instance, consider this query:
|
||||
///
|
||||
/// ```sql
|
||||
/// myQuery: SELECT a.**, b.** FROM users a
|
||||
/// INNER JOIN friends f ON f.a_id = a.id
|
||||
/// INNER JOIN users b ON b.id = f.b_id
|
||||
/// WHERE $expression;
|
||||
/// ```
|
||||
///
|
||||
/// Here `$expression` is a Dart-defined expression evaluating to an sql
|
||||
/// boolean.
|
||||
/// Moor uses to add a `Expression<bool>` parameter to the generated query
|
||||
/// method. Unfortunately, this puts the burden of picking the right table
|
||||
/// name on the user. For instance, they may have to use
|
||||
/// `alias('a', users).someColumn` to avoid getting an runtime exception.
|
||||
/// With a new build option, moor instead generates a
|
||||
/// `Expression<bool> Function(Users a, Users b, Friends f)` function as a
|
||||
/// parameter. This allows users to access the right aliases right away,
|
||||
/// reducing potential for misuse.
|
||||
final List<AvailableMoorResultSet> availableResultSets;
|
||||
|
||||
@override
|
||||
final String name;
|
||||
DartPlaceholder astNode;
|
||||
|
@ -611,26 +643,67 @@ class FoundDartPlaceholder extends FoundElement {
|
|||
type is ExpressionDartPlaceholderType &&
|
||||
(type as ExpressionDartPlaceholderType).defaultValue != null;
|
||||
|
||||
FoundDartPlaceholder(this.type, this.name);
|
||||
FoundDartPlaceholder(this.type, this.name, this.availableResultSets);
|
||||
|
||||
@override
|
||||
String get dartParameterName => name;
|
||||
|
||||
@override
|
||||
int get hashCode => hashAll([type, name]);
|
||||
int get hashCode => hashAll([type, name, ...availableResultSets]);
|
||||
|
||||
@override
|
||||
bool operator ==(dynamic other) {
|
||||
return identical(this, other) ||
|
||||
other is FoundDartPlaceholder &&
|
||||
other.type == type &&
|
||||
other.name == name;
|
||||
other.name == name &&
|
||||
const ListEquality()
|
||||
.equals(other.availableResultSets, availableResultSets);
|
||||
}
|
||||
|
||||
@override
|
||||
String dartTypeCode([GenerationOptions options = const GenerationOptions()]) {
|
||||
return type.parameterTypeCode(options);
|
||||
}
|
||||
|
||||
/// Whether we should write this parameter as a function having available
|
||||
/// result sets as parameters.
|
||||
bool writeAsScopedFunction(MoorOptions options) {
|
||||
return options.scopedDartComponents &&
|
||||
availableResultSets.isNotEmpty &&
|
||||
// Don't generate scoped functions for insertables, where the Dart type
|
||||
// already defines which fields are available
|
||||
type is! InsertableDartPlaceholderType;
|
||||
}
|
||||
}
|
||||
|
||||
/// A table or view that is available in the position of a
|
||||
/// [FoundDartPlaceholder].
|
||||
///
|
||||
/// For more information, see [FoundDartPlaceholder.availableResultSets].
|
||||
class AvailableMoorResultSet {
|
||||
/// The (potentially aliased) name of this result set.
|
||||
final String name;
|
||||
|
||||
/// The table or view that is available.
|
||||
final MoorEntityWithResultSet entity;
|
||||
|
||||
final ResultSetAvailableInStatement source;
|
||||
|
||||
AvailableMoorResultSet(this.name, this.entity, [this.source]);
|
||||
|
||||
/// The argument type of this result set when used in a scoped function.
|
||||
String get argumentType => entity.dslName;
|
||||
|
||||
@override
|
||||
int get hashCode => hashAll([name, entity]);
|
||||
|
||||
@override
|
||||
bool operator ==(Object other) {
|
||||
return other is AvailableMoorResultSet &&
|
||||
other.name == name &&
|
||||
other.entity == entity;
|
||||
}
|
||||
}
|
||||
|
||||
class _ResultColumnEquality implements Equality<ResultColumn> {
|
||||
|
|
|
@ -41,6 +41,9 @@ class MoorTable implements MoorEntityWithResultSet {
|
|||
|
||||
String get _baseName => _overriddenName ?? fromClass.name;
|
||||
|
||||
@override
|
||||
String get dslName => fromClass?.name ?? entityInfoName;
|
||||
|
||||
/// The columns declared in this table.
|
||||
@override
|
||||
final List<MoorColumn> columns;
|
||||
|
|
|
@ -281,6 +281,22 @@ class QueryWriter {
|
|||
void _writeParameters() {
|
||||
final namedElements = <FoundElement>[];
|
||||
|
||||
String typeFor(FoundElement element) {
|
||||
var type = element.dartTypeCode(scope.generationOptions);
|
||||
|
||||
if (element is FoundDartPlaceholder &&
|
||||
element.writeAsScopedFunction(options)) {
|
||||
// Generate a function providing result sets that are in scope as args
|
||||
|
||||
final args = element.availableResultSets
|
||||
.map((e) => '${e.argumentType} ${e.name}')
|
||||
.join(', ');
|
||||
type = '$type Function($args)';
|
||||
}
|
||||
|
||||
return type;
|
||||
}
|
||||
|
||||
var needsComma = false;
|
||||
for (final element in query.elements) {
|
||||
// Placeholders with a default value generate optional (and thus, named)
|
||||
|
@ -294,7 +310,7 @@ class QueryWriter {
|
|||
} else {
|
||||
if (needsComma) _buffer.write(', ');
|
||||
|
||||
final type = element.dartTypeCode(scope.generationOptions);
|
||||
final type = typeFor(element);
|
||||
_buffer.write('$type ${element.dartParameterName}');
|
||||
needsComma = true;
|
||||
}
|
||||
|
@ -325,9 +341,37 @@ class QueryWriter {
|
|||
kind.kind == SimpleDartPlaceholderKind.orderBy) {
|
||||
defaultCode = 'const OrderBy.nothing()';
|
||||
}
|
||||
|
||||
// If the parameter is converted to a scoped function, we also need to
|
||||
// generate a scoped function as a default value. Since defaults have
|
||||
// to be constants, we generate a top-level function which is then
|
||||
// used as a tear-off.
|
||||
if (optional.writeAsScopedFunction(options) && defaultCode != null) {
|
||||
final root = scope.root;
|
||||
final counter = root.counter++;
|
||||
// ignore: prefer_interpolation_to_compose_strings
|
||||
final functionName = r'_$moor$default$' + counter.toString();
|
||||
|
||||
final buffer = root.leaf()
|
||||
..write(optional.dartTypeCode(scope.generationOptions))
|
||||
..write(' ')
|
||||
..write(functionName)
|
||||
..write('(');
|
||||
var i = 0;
|
||||
for (final arg in optional.availableResultSets) {
|
||||
if (i != 0) buffer.write(', ');
|
||||
|
||||
buffer..write(arg.argumentType)..write(' ')..write('_' * (i + 1));
|
||||
}
|
||||
buffer..write(') => ')..write(defaultCode)..write(';');
|
||||
|
||||
// With the function being written, the default code is just a tear-
|
||||
// off of that function
|
||||
defaultCode = functionName;
|
||||
}
|
||||
}
|
||||
|
||||
final type = optional.dartTypeCode(scope.generationOptions);
|
||||
final type = typeFor(optional);
|
||||
|
||||
// No default value, this element is required if it's not nullable
|
||||
var isMarkedAsRequired = false;
|
||||
|
@ -436,6 +480,26 @@ class QueryWriter {
|
|||
} else if (element is FoundDartPlaceholder) {
|
||||
_writeIndexCounterIfNeeded();
|
||||
|
||||
String useExpression() {
|
||||
if (element.writeAsScopedFunction(scope.options)) {
|
||||
// The parameter is a function type that needs to be evaluated first
|
||||
final args = element.availableResultSets.map((e) {
|
||||
final table = 'this.${e.entity.dbGetterName}';
|
||||
final needsAlias = e.name != e.entity.displayName;
|
||||
|
||||
if (needsAlias) {
|
||||
return 'alias($table, ${asDartLiteral(e.name)})';
|
||||
} else {
|
||||
return table;
|
||||
}
|
||||
}).join(', ');
|
||||
return '${element.dartParameterName}($args)';
|
||||
} else {
|
||||
// We can just use the parameter directly
|
||||
return element.dartParameterName;
|
||||
}
|
||||
}
|
||||
|
||||
_buffer
|
||||
..write('final ')
|
||||
..write(placeholderContextName(element))
|
||||
|
@ -449,10 +513,10 @@ class QueryWriter {
|
|||
..write(r'$writeInsertable(this.')
|
||||
..write(table?.dbGetterName)
|
||||
..write(', ')
|
||||
..write(element.dartParameterName)
|
||||
..write(useExpression())
|
||||
..write(');\n');
|
||||
} else {
|
||||
_buffer..write(r'$write(')..write(element.dartParameterName);
|
||||
_buffer..write(r'$write(')..write(useExpression());
|
||||
if (query.hasMultipleTables) {
|
||||
_buffer.write(', hasMultipleTables: true');
|
||||
}
|
||||
|
|
|
@ -54,6 +54,11 @@ class Scope extends _Node {
|
|||
final DartScope scope;
|
||||
final Writer writer;
|
||||
|
||||
/// An arbitrary counter.
|
||||
///
|
||||
/// This can be used to generated methods which must have a unique name-
|
||||
int counter = 0;
|
||||
|
||||
Scope({@required Scope parent, Writer writer})
|
||||
: scope = parent?.scope?.nextLevel ?? DartScope.library,
|
||||
writer = writer ?? parent?.writer,
|
||||
|
|
|
@ -96,10 +96,23 @@ class ProgrammingLanguages extends Table {
|
|||
expect(importQuery.declaredInMoorFile, isFalse);
|
||||
expect(importQuery.hasMultipleTables, isFalse);
|
||||
expect(
|
||||
importQuery.placeholders,
|
||||
contains(equals(FoundDartPlaceholder(
|
||||
importQuery.placeholders,
|
||||
contains(
|
||||
equals(
|
||||
FoundDartPlaceholder(
|
||||
SimpleDartPlaceholderType(SimpleDartPlaceholderKind.orderBy),
|
||||
'o'))));
|
||||
'o',
|
||||
[
|
||||
AvailableMoorResultSet(
|
||||
'programming_languages',
|
||||
database.tables
|
||||
.firstWhere((e) => e.sqlName == 'programming_languages'),
|
||||
)
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
|
||||
final librariesQuery = database.queries
|
||||
.singleWhere((q) => q.name == 'findLibraries') as SqlSelectQuery;
|
||||
|
|
|
@ -34,7 +34,7 @@ totalDurationByArtist:
|
|||
FROM artists a
|
||||
INNER JOIN albums ON albums.artist = a.id
|
||||
INNER JOIN tracks ON tracks.album = albums.id
|
||||
GROUP BY artists.id;
|
||||
GROUP BY a.id;
|
||||
'''
|
||||
}, options: const MoorOptions.defaults());
|
||||
|
||||
|
|
|
@ -88,7 +88,7 @@ CREATE TABLE routes (
|
|||
);
|
||||
|
||||
allRoutes: SELECT routes.*, "from".**, "to".**
|
||||
FROM routes r
|
||||
FROM routes
|
||||
INNER JOIN points "from" ON "from".id = routes.from
|
||||
INNER JOIN points "to" ON "to".id = routes."to";
|
||||
''',
|
||||
|
|
Loading…
Reference in New Issue