Support calling join on a JoinedSelectStatement

This commit is contained in:
Simon Binder 2020-01-28 21:32:30 +01:00
parent 727ab4d88a
commit a37a653e43
No known key found for this signature in database
GPG Key ID: 7891917E4147B8C0
7 changed files with 93 additions and 7 deletions

View File

@ -133,7 +133,7 @@ mixin QueryEngine on DatabaseConnectionUser {
bool distinct = false,
}) {
return JoinedSelectStatement<T, R>(
_resolvedEngine, table, const [], distinct, false);
_resolvedEngine, table, [], distinct, false);
}
/// Starts a [DeleteStatement] that can be used to delete rows from a table.

View File

@ -14,4 +14,13 @@ class Where extends Component {
context.buffer.write('WHERE ');
predicate.writeInto(context);
}
@override
int get hashCode => predicate.hashCode * 7;
@override
bool operator ==(dynamic other) {
return identical(this, other) ||
other is Where && other.predicate == predicate;
}
}

View File

@ -96,6 +96,26 @@ class _AggregateExpression<D, S extends SqlType<D>> extends Expression<D, S> {
context.buffer.write(')');
}
}
@override
int get hashCode {
return $mrjf($mrjc(functionName.hashCode,
$mrjc(distinct.hashCode, $mrjc(parameter.hashCode, filter.hashCode))));
}
@override
bool operator ==(dynamic other) {
if (!identical(this, other) && other.runtimeType != runtimeType) {
return false;
}
// ignore: test_types_in_equals
final typedOther = other as _AggregateExpression;
return typedOther.functionName == functionName &&
typedOther.distinct == distinct &&
typedOther.parameter == parameter &&
typedOther.filter == filter;
}
}
class _StarFunctionParameter implements FunctionParameter {

View File

@ -12,6 +12,9 @@ abstract class FunctionParameter implements Component {}
/// Any sql expression that evaluates to some generic value. This does not
/// include queries (which might evaluate to multiple values) but individual
/// columns, functions and operators.
///
/// It's important that all subclasses properly implement [hashCode] and
/// [==].
abstract class Expression<D, T extends SqlType<D>>
implements FunctionParameter {
/// Constant constructor so that subclasses can be constant.

View File

@ -60,6 +60,7 @@ class SimpleSelectStatement<T extends Table, D extends DataClass>
/// ```
///
/// See also:
/// - https://moor.simonbinder.eu/docs/advanced-features/joins/#joins
/// - [innerJoin], [leftOuterJoin] and [crossJoin], which can be used to
/// construct a [Join].
/// - [DatabaseConnectionUser.alias], which can be used to build statements

View File

@ -12,11 +12,7 @@ class JoinedSelectStatement<FirstT extends Table, FirstD extends DataClass>
JoinedSelectStatement(
QueryEngine database, TableInfo<FirstT, FirstD> table, this._joins,
[this.distinct = false, this._includeMainTableInResult = true])
: super(database, table) {
// use all columns across all tables as result column for this query
_selectedColumns.addAll(
_queriedTables(true).expand((t) => t.$columns).cast<Expression>());
}
: super(database, table);
/// Whether to generate a `SELECT DISTINCT` query that will remove duplicate
/// rows from the result set.
@ -60,6 +56,10 @@ class JoinedSelectStatement<FirstT extends Table, FirstD extends DataClass>
@override
void writeStartPart(GenerationContext ctx) {
// use all columns across all tables as result column for this query
_selectedColumns.insertAll(
0, _queriedTables(true).expand((t) => t.$columns).cast<Expression>());
ctx.hasMultipleTables = true;
ctx.buffer..write(_beginOfSelect(distinct))..write(' ');
@ -74,8 +74,8 @@ class JoinedSelectStatement<FirstT extends Table, FirstD extends DataClass>
chosenAlias = '${column.tableName}.${column.$name}';
} else {
chosenAlias = 'c$i';
_columnAliases[column] = chosenAlias;
}
_columnAliases[column] = chosenAlias;
column.writeInto(ctx);
ctx.buffer..write(' AS "')..write(chosenAlias)..write('"');
@ -145,6 +145,23 @@ class JoinedSelectStatement<FirstT extends Table, FirstD extends DataClass>
_selectedColumns.addAll(expressions);
}
/// Adds more joined tables to this [JoinedSelectStatement].
///
/// Always returns the same instance.
///
/// See also:
/// - https://moor.simonbinder.eu/docs/advanced-features/joins/#joins
/// - [SimpleSelectStatement.join], which is used for the first join
/// - [innerJoin], [leftOuterJoin] and [crossJoin], which can be used to
/// construct a [Join].
/// - [DatabaseConnectionUser.alias], which can be used to build statements
/// that refer to the same table multiple times.
// ignore: avoid_returning_this
JoinedSelectStatement join(List<Join> joins) {
_joins.addAll(joins);
return this;
}
/// Groups the result by values in [expressions].
///
/// An optional [having] attribute can be set to exclude certain groups.

View File

@ -225,6 +225,42 @@ void main() {
expect(result, 3.0);
});
test('join on JoinedSelectStatement', () async {
final categories = db.categories;
final todos = db.todosTable;
final query = db.selectOnly(categories).join([
innerJoin(
todos,
todos.category.equalsExp(categories.id),
useColumns: false,
)
]);
query
..addColumns([categories.id, todos.id.count()])
..groupBy([categories.id]);
when(executor.runSelect(any, any)).thenAnswer((_) async {
return [
{
'categories.id': 2,
'c1': 10,
}
];
});
final result = await query.getSingle();
verify(executor.runSelect(
'SELECT categories.id AS "categories.id", COUNT(todos.id) AS "c1" '
'FROM categories INNER JOIN todos ON todos.category = categories.id '
'GROUP BY categories.id;',
[]));
expect(result.read(categories.id), equals(2));
expect(result.read(todos.id.count()), equals(10));
});
test('injects custom error message when a table is used multiple times',
() async {
when(executor.runSelect(any, any)).thenAnswer((_) => Future.error('nah'));