mirror of https://github.com/AMT-Cheif/drift.git
Make aggregate expression builder public
This commit is contained in:
parent
428de9354a
commit
a68ec2a73e
|
@ -1,3 +1,10 @@
|
||||||
|
## 2.18.0-dev
|
||||||
|
|
||||||
|
- Add `AggregateFunctionExpression` to write custom [aggregate function](https://www.sqlite.org/lang_aggfunc.html)
|
||||||
|
invocations in the Dart query builder.
|
||||||
|
- The `json_group_array` and `jsonb_group_array` functions now contain an `orderBy`
|
||||||
|
and `filter` parameter.
|
||||||
|
|
||||||
## 2.17.0
|
## 2.17.0
|
||||||
|
|
||||||
- Adds `companion` entry to `DataClassName` to override the name of the
|
- Adds `companion` entry to `DataClassName` to override the name of the
|
||||||
|
|
|
@ -144,8 +144,13 @@ extension JsonExtensions on Expression<String> {
|
||||||
/// all emails in that folder.
|
/// all emails in that folder.
|
||||||
/// This string could be turned back into a list with
|
/// This string could be turned back into a list with
|
||||||
/// `(json.decode(row.read(subjects)!) as List).cast<String>()`.
|
/// `(json.decode(row.read(subjects)!) as List).cast<String>()`.
|
||||||
Expression<String> jsonGroupArray(Expression value) {
|
Expression<String> jsonGroupArray(
|
||||||
return FunctionCallExpression('json_group_array', [value]);
|
Expression value, {
|
||||||
|
OrderBy? orderBy,
|
||||||
|
Expression<bool>? filter,
|
||||||
|
}) {
|
||||||
|
return AggregateFunctionExpression('json_group_array', [value],
|
||||||
|
orderBy: orderBy, filter: filter);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Returns a binary representation of a JSON array containing the result of
|
/// Returns a binary representation of a JSON array containing the result of
|
||||||
|
@ -153,8 +158,13 @@ Expression<String> jsonGroupArray(Expression value) {
|
||||||
///
|
///
|
||||||
/// See [jsonGroupArray], the variant of this function returning a textual
|
/// See [jsonGroupArray], the variant of this function returning a textual
|
||||||
/// description, for more details and an example.
|
/// description, for more details and an example.
|
||||||
Expression<Uint8List> jsonbGroupArray(Expression value) {
|
Expression<Uint8List> jsonbGroupArray(
|
||||||
return FunctionCallExpression('jsonb_group_array', [value]);
|
Expression value, {
|
||||||
|
OrderBy? orderBy,
|
||||||
|
Expression<bool>? filter,
|
||||||
|
}) {
|
||||||
|
return AggregateFunctionExpression('jsonb_group_array', [value],
|
||||||
|
orderBy: orderBy, filter: filter);
|
||||||
}
|
}
|
||||||
|
|
||||||
List<Expression> _groupObjectArgs(Map<Expression<String>, Expression> values) {
|
List<Expression> _groupObjectArgs(Map<Expression<String>, Expression> values) {
|
||||||
|
|
|
@ -12,7 +12,7 @@ part of '../query_builder.dart';
|
||||||
/// This is equivalent to the `COUNT(*) FILTER (WHERE filter)` sql function. The
|
/// This is equivalent to the `COUNT(*) FILTER (WHERE filter)` sql function. The
|
||||||
/// filter will be omitted if null.
|
/// filter will be omitted if null.
|
||||||
Expression<int> countAll({Expression<bool>? filter}) {
|
Expression<int> countAll({Expression<bool>? filter}) {
|
||||||
return _AggregateExpression('COUNT', const [_StarFunctionParameter()],
|
return AggregateFunctionExpression('COUNT', const [_StarFunctionParameter()],
|
||||||
filter: filter);
|
filter: filter);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -26,7 +26,7 @@ extension BaseAggregate<DT extends Object> on Expression<DT> {
|
||||||
/// counted twice.
|
/// counted twice.
|
||||||
/// {@macro drift_aggregate_filter}
|
/// {@macro drift_aggregate_filter}
|
||||||
Expression<int> count({bool distinct = false, Expression<bool>? filter}) {
|
Expression<int> count({bool distinct = false, Expression<bool>? filter}) {
|
||||||
return _AggregateExpression('COUNT', [this],
|
return AggregateFunctionExpression('COUNT', [this],
|
||||||
filter: filter, distinct: distinct);
|
filter: filter, distinct: distinct);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -35,14 +35,14 @@ extension BaseAggregate<DT extends Object> on Expression<DT> {
|
||||||
/// If there are no non-null values in the group, returns null.
|
/// If there are no non-null values in the group, returns null.
|
||||||
/// {@macro drift_aggregate_filter}
|
/// {@macro drift_aggregate_filter}
|
||||||
Expression<DT> max({Expression<bool>? filter}) =>
|
Expression<DT> max({Expression<bool>? filter}) =>
|
||||||
_AggregateExpression('MAX', [this], filter: filter);
|
AggregateFunctionExpression('MAX', [this], filter: filter);
|
||||||
|
|
||||||
/// Return the minimum of all non-null values in this group.
|
/// Return the minimum of all non-null values in this group.
|
||||||
///
|
///
|
||||||
/// If there are no non-null values in the group, returns null.
|
/// If there are no non-null values in the group, returns null.
|
||||||
/// {@macro drift_aggregate_filter}
|
/// {@macro drift_aggregate_filter}
|
||||||
Expression<DT> min({Expression<bool>? filter}) =>
|
Expression<DT> min({Expression<bool>? filter}) =>
|
||||||
_AggregateExpression('MIN', [this], filter: filter);
|
AggregateFunctionExpression('MIN', [this], filter: filter);
|
||||||
|
|
||||||
/// Returns the concatenation of all non-null values in the current group,
|
/// Returns the concatenation of all non-null values in the current group,
|
||||||
/// joined by the [separator].
|
/// joined by the [separator].
|
||||||
|
@ -71,7 +71,7 @@ extension BaseAggregate<DT extends Object> on Expression<DT> {
|
||||||
'Cannot use groupConcat with distinct: true and a custom separator');
|
'Cannot use groupConcat with distinct: true and a custom separator');
|
||||||
}
|
}
|
||||||
|
|
||||||
return _AggregateExpression(
|
return AggregateFunctionExpression(
|
||||||
'GROUP_CONCAT',
|
'GROUP_CONCAT',
|
||||||
[
|
[
|
||||||
this,
|
this,
|
||||||
|
@ -89,21 +89,21 @@ extension ArithmeticAggregates<DT extends num> on Expression<DT> {
|
||||||
///
|
///
|
||||||
/// {@macro drift_aggregate_filter}
|
/// {@macro drift_aggregate_filter}
|
||||||
Expression<double> avg({Expression<bool>? filter}) =>
|
Expression<double> avg({Expression<bool>? filter}) =>
|
||||||
_AggregateExpression('AVG', [this], filter: filter);
|
AggregateFunctionExpression('AVG', [this], filter: filter);
|
||||||
|
|
||||||
/// Return the maximum of all non-null values in this group.
|
/// Return the maximum of all non-null values in this group.
|
||||||
///
|
///
|
||||||
/// If there are no non-null values in the group, returns null.
|
/// If there are no non-null values in the group, returns null.
|
||||||
/// {@macro drift_aggregate_filter}
|
/// {@macro drift_aggregate_filter}
|
||||||
Expression<DT> max({Expression<bool>? filter}) =>
|
Expression<DT> max({Expression<bool>? filter}) =>
|
||||||
_AggregateExpression('MAX', [this], filter: filter);
|
AggregateFunctionExpression('MAX', [this], filter: filter);
|
||||||
|
|
||||||
/// Return the minimum of all non-null values in this group.
|
/// Return the minimum of all non-null values in this group.
|
||||||
///
|
///
|
||||||
/// If there are no non-null values in the group, returns null.
|
/// If there are no non-null values in the group, returns null.
|
||||||
/// {@macro drift_aggregate_filter}
|
/// {@macro drift_aggregate_filter}
|
||||||
Expression<DT> min({Expression<bool>? filter}) =>
|
Expression<DT> min({Expression<bool>? filter}) =>
|
||||||
_AggregateExpression('MIN', [this], filter: filter);
|
AggregateFunctionExpression('MIN', [this], filter: filter);
|
||||||
|
|
||||||
/// Calculate the sum of all non-null values in the group.
|
/// Calculate the sum of all non-null values in the group.
|
||||||
///
|
///
|
||||||
|
@ -115,7 +115,7 @@ extension ArithmeticAggregates<DT extends num> on Expression<DT> {
|
||||||
/// value and doesn't throw an overflow exception.
|
/// value and doesn't throw an overflow exception.
|
||||||
/// {@macro drift_aggregate_filter}
|
/// {@macro drift_aggregate_filter}
|
||||||
Expression<DT> sum({Expression<bool>? filter}) =>
|
Expression<DT> sum({Expression<bool>? filter}) =>
|
||||||
_AggregateExpression('SUM', [this], filter: filter);
|
AggregateFunctionExpression('SUM', [this], filter: filter);
|
||||||
|
|
||||||
/// Calculate the sum of all non-null values in the group.
|
/// Calculate the sum of all non-null values in the group.
|
||||||
///
|
///
|
||||||
|
@ -123,7 +123,7 @@ extension ArithmeticAggregates<DT extends num> on Expression<DT> {
|
||||||
/// uses floating-point values internally.
|
/// uses floating-point values internally.
|
||||||
/// {@macro drift_aggregate_filter}
|
/// {@macro drift_aggregate_filter}
|
||||||
Expression<double> total({Expression<bool>? filter}) =>
|
Expression<double> total({Expression<bool>? filter}) =>
|
||||||
_AggregateExpression('TOTAL', [this], filter: filter);
|
AggregateFunctionExpression('TOTAL', [this], filter: filter);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Provides aggregate functions that are available for BigInt expressions.
|
/// Provides aggregate functions that are available for BigInt expressions.
|
||||||
|
@ -197,16 +197,41 @@ extension DateTimeAggregate on Expression<DateTime> {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
class _AggregateExpression<D extends Object> extends Expression<D> {
|
/// An expression invoking an [aggregate function](https://www.sqlite.org/lang_aggfunc.html).
|
||||||
|
///
|
||||||
|
/// Aggregate functions, like `count()` or `sum()` collapse the entire data set
|
||||||
|
/// (or a partition of it, if `GROUP BY` is used) into a single value.
|
||||||
|
///
|
||||||
|
/// Drift exposes direct bindings to most aggregate functions (e.g. via
|
||||||
|
/// [BaseAggregate.count]). This class is useful when writing custom aggregate
|
||||||
|
/// function invocations.
|
||||||
|
final class AggregateFunctionExpression<D extends Object>
|
||||||
|
extends Expression<D> {
|
||||||
|
/// The name of the aggregate function to invoke.
|
||||||
final String functionName;
|
final String functionName;
|
||||||
final bool distinct;
|
|
||||||
final List<FunctionParameter> parameter;
|
|
||||||
|
|
||||||
|
/// Whether only distinct rows should be passed to the function.
|
||||||
|
final bool distinct;
|
||||||
|
|
||||||
|
/// The arguments to pass to the function.
|
||||||
|
final List<FunctionParameter> arguments;
|
||||||
|
|
||||||
|
/// The order in which rows of the current group should be passed to the
|
||||||
|
/// aggregate function.
|
||||||
|
final OrderBy? orderBy;
|
||||||
|
|
||||||
|
/// An optional filter clause only passing rows matching this condition into
|
||||||
|
/// the function.
|
||||||
final Where? filter;
|
final Where? filter;
|
||||||
|
|
||||||
_AggregateExpression(this.functionName, this.parameter,
|
/// Creates an aggregate function expression from the syntactic components.
|
||||||
{Expression<bool>? filter, this.distinct = false})
|
AggregateFunctionExpression(
|
||||||
: filter = filter != null ? Where(filter) : null;
|
this.functionName,
|
||||||
|
this.arguments, {
|
||||||
|
Expression<bool>? filter,
|
||||||
|
this.distinct = false,
|
||||||
|
this.orderBy,
|
||||||
|
}) : filter = filter != null ? Where(filter) : null;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
final Precedence precedence = Precedence.primary;
|
final Precedence precedence = Precedence.primary;
|
||||||
|
@ -220,7 +245,11 @@ class _AggregateExpression<D extends Object> extends Expression<D> {
|
||||||
if (distinct) {
|
if (distinct) {
|
||||||
context.buffer.write('DISTINCT ');
|
context.buffer.write('DISTINCT ');
|
||||||
}
|
}
|
||||||
_writeCommaSeparated(context, parameter);
|
_writeCommaSeparated(context, arguments);
|
||||||
|
if (orderBy case final orderBy?) {
|
||||||
|
context.writeWhitespace();
|
||||||
|
orderBy.writeInto(context);
|
||||||
|
}
|
||||||
context.buffer.write(')');
|
context.buffer.write(')');
|
||||||
|
|
||||||
if (filter != null) {
|
if (filter != null) {
|
||||||
|
@ -233,20 +262,20 @@ class _AggregateExpression<D extends Object> extends Expression<D> {
|
||||||
@override
|
@override
|
||||||
int get hashCode {
|
int get hashCode {
|
||||||
return Object.hash(functionName, distinct,
|
return Object.hash(functionName, distinct,
|
||||||
const ListEquality<Object?>().hash(parameter), filter);
|
const ListEquality<Object?>().hash(arguments), orderBy, filter);
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
bool operator ==(Object other) {
|
bool operator ==(Object other) {
|
||||||
if (!identical(this, other) && other.runtimeType != runtimeType) {
|
if (!identical(this, other) && other is! AggregateFunctionExpression<D>) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
// ignore: test_types_in_equals
|
final typedOther = other as AggregateFunctionExpression<D>;
|
||||||
final typedOther = other as _AggregateExpression;
|
|
||||||
return typedOther.functionName == functionName &&
|
return typedOther.functionName == functionName &&
|
||||||
typedOther.distinct == distinct &&
|
typedOther.distinct == distinct &&
|
||||||
const ListEquality<Object?>().equals(typedOther.parameter, parameter) &&
|
const ListEquality<Object?>().equals(typedOther.arguments, arguments) &&
|
||||||
|
typedOther.orderBy == orderBy &&
|
||||||
typedOther.filter == filter;
|
typedOther.filter == filter;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -101,15 +101,17 @@ void main() {
|
||||||
db.todosTable, db.todosTable.category.equalsExp(db.categories.id))
|
db.todosTable, db.todosTable.category.equalsExp(db.categories.id))
|
||||||
]);
|
]);
|
||||||
|
|
||||||
final stringArray = jsonGroupArray(db.todosTable.id);
|
final stringArray = jsonGroupArray(db.todosTable.id,
|
||||||
final binaryArray = jsonbGroupArray(db.todosTable.id).json();
|
orderBy: OrderBy([OrderingTerm.desc(db.todosTable.id)]));
|
||||||
|
final binaryArray = jsonbGroupArray(db.todosTable.id,
|
||||||
|
orderBy: OrderBy([OrderingTerm.asc(db.todosTable.id)])).json();
|
||||||
query
|
query
|
||||||
..groupBy([db.categories.id])
|
..groupBy([db.categories.id])
|
||||||
..addColumns([stringArray, binaryArray]);
|
..addColumns([stringArray, binaryArray]);
|
||||||
|
|
||||||
final row = await query.getSingle();
|
final row = await query.getSingle();
|
||||||
expect(json.decode(row.read(stringArray)!), unorderedEquals([1, 3]));
|
expect(json.decode(row.read(stringArray)!), [3, 1]);
|
||||||
expect(json.decode(row.read(binaryArray)!), unorderedEquals([1, 3]));
|
expect(json.decode(row.read(binaryArray)!), [1, 3]);
|
||||||
});
|
});
|
||||||
|
|
||||||
test('json_group_object', () async {
|
test('json_group_object', () async {
|
||||||
|
|
|
@ -42,6 +42,17 @@ void main() {
|
||||||
|
|
||||||
test('aggregates', () {
|
test('aggregates', () {
|
||||||
expect(jsonGroupArray(column), generates('json_group_array(col)'));
|
expect(jsonGroupArray(column), generates('json_group_array(col)'));
|
||||||
|
expect(
|
||||||
|
jsonGroupArray(
|
||||||
|
column,
|
||||||
|
orderBy: OrderBy([OrderingTerm.desc(column)]),
|
||||||
|
filter: column.length.isBiggerOrEqualValue(10),
|
||||||
|
),
|
||||||
|
generates(
|
||||||
|
'json_group_array(col ORDER BY col DESC) FILTER (WHERE LENGTH(col) >= ?)',
|
||||||
|
[10],
|
||||||
|
),
|
||||||
|
);
|
||||||
expect(
|
expect(
|
||||||
jsonGroupObject({
|
jsonGroupObject({
|
||||||
Variable('foo'): column,
|
Variable('foo'): column,
|
||||||
|
@ -84,6 +95,17 @@ void main() {
|
||||||
|
|
||||||
test('aggregates', () {
|
test('aggregates', () {
|
||||||
expect(jsonbGroupArray(column), generates('jsonb_group_array(col)'));
|
expect(jsonbGroupArray(column), generates('jsonb_group_array(col)'));
|
||||||
|
expect(
|
||||||
|
jsonbGroupArray(
|
||||||
|
column,
|
||||||
|
orderBy: OrderBy([OrderingTerm.desc(column)]),
|
||||||
|
filter: column.length.isBiggerOrEqualValue(10),
|
||||||
|
),
|
||||||
|
generates(
|
||||||
|
'jsonb_group_array(col ORDER BY col DESC) FILTER (WHERE LENGTH(col) >= ?)',
|
||||||
|
[10],
|
||||||
|
),
|
||||||
|
);
|
||||||
expect(
|
expect(
|
||||||
jsonbGroupObject({
|
jsonbGroupObject({
|
||||||
Variable('foo'): column,
|
Variable('foo'): column,
|
||||||
|
|
|
@ -107,7 +107,7 @@ class _GeneratesSqlMatcher extends Matcher {
|
||||||
matches = false;
|
matches = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
final argsMatchState = <String, Object?>{};
|
final argsMatchState = <Object?, Object?>{};
|
||||||
if (_matchVariables != null &&
|
if (_matchVariables != null &&
|
||||||
!_matchVariables.matches(ctx.boundVariables, argsMatchState)) {
|
!_matchVariables.matches(ctx.boundVariables, argsMatchState)) {
|
||||||
matchState['vars'] = ctx.boundVariables;
|
matchState['vars'] = ctx.boundVariables;
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
// Mocks generated by Mockito 5.4.3 from annotations
|
// Mocks generated by Mockito 5.4.4 from annotations
|
||||||
// in drift/test/test_utils/test_utils.dart.
|
// in drift/test/test_utils/test_utils.dart.
|
||||||
// Do not manually edit this file.
|
// Do not manually edit this file.
|
||||||
|
|
||||||
|
|
Loading…
Reference in New Issue