Add caseMatch to build CASE WHEN

This commit is contained in:
Simon Binder 2021-04-04 21:37:32 +02:00
parent a3a64419b8
commit 58fdda482f
No known key found for this signature in database
GPG Key ID: 7891917E4147B8C0
5 changed files with 127 additions and 2 deletions

View File

@ -1,3 +1,7 @@
## 4.3.0-dev
- Add `CASE WHEN` expressions with the `caseMatch` method on `Expression`
## 4.2.1
- Deprecate `readBool`, `readString`, `readInt`, `readDouble`, `readDateTime`

View File

@ -0,0 +1,44 @@
import 'package:meta/meta.dart';
import '../query_builder.dart';
/// A `CASE WHEN` expression in sqlite.
///
/// This class supports when expressions with or without a base expression.
@internal
class CaseWhenExpression<T> extends Expression<T?> {
/// The optional base expression. If it's set, the keys in [whenThen] will be
/// compared to this expression.
final Expression? base;
/// The when entries for this expression. This expression will evaluate to the
/// value of the entry with a matching key.
final List<MapEntry<Expression, Expression>> whenThen;
/// The expression to use if no entry in [whenThen] matched.
final Expression<T?>? orElse;
/// Creates a `CASE WHEN` expression from the independent components.
CaseWhenExpression(this.base, this.whenThen, this.orElse);
@override
void writeInto(GenerationContext context) {
context.buffer.write('CASE ');
base?.writeInto(context);
for (final entry in whenThen) {
context.buffer.write(' WHEN ');
entry.key.writeInto(context);
context.buffer.write(' THEN ');
entry.value.writeInto(context);
}
final orElse = this.orElse;
if (orElse != null) {
context.buffer.write(' ELSE ');
orElse.writeInto(context);
}
context.buffer.write(' END');
}
}

View File

@ -86,9 +86,47 @@ abstract class Expression<D> implements FunctionParameter {
return _InSelectExpression(select, this, true);
}
/// A `CASE WHEN` construct using the current expression as a base.
///
/// The expression on which [caseMatch] is invoked will be used as a base and
/// compared against the keys in [when]. If an equal key is found in the map,
/// the expression returned evaluates to the respective value.
/// If no matching keys are found in [when], the [orElse] expression is
/// evaluated and returned. If no [orElse] expression is provided, `NULL` will
/// be returned instead.
///
/// For example, consider this expression mapping numerical weekdays to their
/// name:
///
/// ```dart
/// final weekday = myTable.createdOnWeekDay;
/// weekday.caseMatch<String>(
/// when: {
/// Constant(1): Constant('Monday'),
/// Constant(2): Constant('Tuesday'),
/// Constant(3): Constant('Wednesday'),
/// Constant(4): Constant('Thursday'),
/// Constant(5): Constant('Friday'),
/// Constant(6): Constant('Saturday'),
/// Constant(7): Constant('Sunday'),
/// },
/// orElse: Constant('(unknown)'),
/// );
/// ```
Expression<T?> caseMatch<T>({
required Map<Expression<D>, Expression<T?>> when,
Expression<T?>? orElse,
}) {
if (when.isEmpty) {
throw ArgumentError.value(when, 'when', 'Must not be empty');
}
return CaseWhenExpression<T>(this, when.entries.toList(), orElse);
}
/// Writes this expression into the [GenerationContext], assuming that there's
/// an outer expression with [precedence]. If the [Expression.precedence] of
/// `this` expression is lower, it will be wrapped in
/// `this` expression is lower, it will be wrap}ped in
///
/// See also:
/// - [Component.writeInto], which doesn't take any precedence relation into

View File

@ -3,6 +3,7 @@
import 'package:collection/collection.dart';
import 'package:meta/meta.dart';
// hidden because of https://github.com/dart-lang/sdk/issues/39262
import 'package:moor/moor.dart'
hide BooleanExpressionOperators, DateTimeExpressions, TableInfoUtils;
@ -11,12 +12,15 @@ import 'package:moor/src/runtime/executor/stream_queries.dart';
import 'package:moor/src/runtime/types/sql_types.dart';
import 'package:moor/src/utils/single_transformer.dart';
// New files should not be part of this mega library, which we're trying to
// split up.
import 'expressions/case_when.dart';
part 'components/group_by.dart';
part 'components/join.dart';
part 'components/limit.dart';
part 'components/order_by.dart';
part 'components/where.dart';
part 'expressions/aggregate.dart';
part 'expressions/algebra.dart';
part 'expressions/bools.dart';

View File

@ -0,0 +1,35 @@
import 'package:moor/moor.dart';
import 'package:test/test.dart';
import '../data/utils/expect_generated.dart';
void main() {
const x = CustomExpression<String>('x');
const y = CustomExpression<int>('y');
test('WHEN without ELSE', () {
expect(
x.caseMatch<int>(when: {
const Constant('a'): const Constant(1),
const Constant('b'): const Constant(2),
}),
generates("CASE x WHEN 'a' THEN 1 WHEN 'b' THEN 2 END"),
);
});
test('WHEN with ELSE', () {
expect(
x.caseMatch<int>(
when: {
const Constant('a'): const Constant(1),
},
orElse: y,
),
generates("CASE x WHEN 'a' THEN 1 ELSE y END"),
);
});
test('does not allow empty WHEN map', () {
expect(() => x.caseMatch(when: const {}), throwsA(isA<ArgumentError>()));
});
}