Support + and - operators on DateTime

This commit is contained in:
Simon Binder 2020-04-17 21:29:12 +02:00
parent 84bac1bf1d
commit a7ac6db55d
No known key found for this signature in database
GPG Key ID: 7891917E4147B8C0
8 changed files with 100 additions and 14 deletions

View File

@ -76,6 +76,13 @@ that you can use operators and comparisons on them.
To obtain the current date or the current time as an expression, use the `currentDate`
and `currentDateAndTime` constants provided by moor.
You can also use the `+` and `-` operators to add or subtract a duration from a time column:
```dart
final toNextWeek = TasksCompanion.custom(dueDate: tasks.dueDate + Duration(weeks: 1));
update(tasks).write(toNextWeek);
```
## `IN` and `NOT IN`
You can check whether an expression is in a list of values by using the `isIn` and `isNotIn`
methods:

View File

@ -44,6 +44,18 @@ extension DateTimeExpressions on Expression<DateTime> {
// for moor, date times are just unix timestamps, so we don't need to rewrite
// anything when converting
Expression<int> get secondsSinceEpoch => dartCast();
/// Adds [duration] from this date.
Expression<DateTime> operator +(Duration duration) {
return _BaseInfixOperator(this, '+', Variable<int>(duration.inSeconds),
precedence: Precedence.plusMinus);
}
/// Subtracts [duration] from this date.
Expression<DateTime> operator -(Duration duration) {
return _BaseInfixOperator(this, '-', Variable<int>(duration.inSeconds),
precedence: Precedence.plusMinus);
}
}
/// Expression that extracts components out of a date time by using the builtin

View File

@ -203,13 +203,13 @@ abstract class _InfixOperator<D> extends Expression<D> {
class _BaseInfixOperator<D> extends _InfixOperator<D> {
@override
final Expression<D> left;
final Expression left;
@override
final String operator;
@override
final Expression<D> right;
final Expression right;
@override
final Precedence precedence;

View File

@ -84,7 +84,10 @@ class IntType extends SqlType<int> {
const IntType();
@override
int mapFromDatabaseResponse(dynamic response) => response as int;
int mapFromDatabaseResponse(dynamic response) {
if (response is int) return response;
return int.parse(response.toString());
}
@override
String mapToSqlConstant(int content) => content?.toString() ?? 'NULL';

View File

@ -4,7 +4,7 @@ import 'package:test/test.dart';
/// Matcher for [Component]-subclasses. Expect that a component generates the
/// matching [sql] and, optionally, the matching [variables].
Matcher generates(dynamic sql, [dynamic variables]) {
final variablesMatcher = variables != null ? wrapMatcher(variables) : null;
final variablesMatcher = variables != null ? wrapMatcher(variables) : isEmpty;
return _GeneratesSqlMatcher(wrapMatcher(sql), variablesMatcher);
}
@ -19,8 +19,9 @@ class _GeneratesSqlMatcher extends Matcher {
description = description.add('generates sql ').addDescriptionOf(_matchSql);
if (_matchVariables != null) {
description =
description.add('and variables').addDescriptionOf(_matchVariables);
description = description
.add(' and variables that ')
.addDescriptionOf(_matchVariables);
}
return description;
}
@ -36,15 +37,15 @@ class _GeneratesSqlMatcher extends Matcher {
mismatchDescription = mismatchDescription.add('generated $sql, which ');
mismatchDescription = _matchSql.describeMismatch(
sql, mismatchDescription, matchState, verbose);
sql, mismatchDescription, matchState['sql_match'] as Map, verbose);
}
if (matchState.containsKey('vars')) {
final vars = matchState['vars'] as List;
mismatchDescription =
mismatchDescription.add('used variables $vars, which ');
mismatchDescription.add('generated variables $vars, which ');
mismatchDescription = _matchVariables.describeMismatch(
vars, mismatchDescription, matchState, verbose);
vars, mismatchDescription, matchState['vars_match'] as Map, verbose);
}
return mismatchDescription;
}
@ -52,7 +53,7 @@ class _GeneratesSqlMatcher extends Matcher {
@override
bool matches(dynamic item, Map matchState) {
if (item is! Component) {
addStateInfo(matchState, {'wrong_type': true});
matchState['wrong_type'] = true;
return false;
}
@ -62,14 +63,18 @@ class _GeneratesSqlMatcher extends Matcher {
var matches = true;
if (!_matchSql.matches(ctx.sql, matchState)) {
addStateInfo(matchState, {'sql': ctx.sql});
final sqlMatchState = {};
if (!_matchSql.matches(ctx.sql, sqlMatchState)) {
matchState['sql'] = ctx.sql;
matchState['sql_match'] = sqlMatchState;
matches = false;
}
final argsMatchState = {};
if (_matchVariables != null &&
!_matchVariables.matches(ctx.boundVariables, matchState)) {
addStateInfo(matchState, {'vars': ctx.boundVariables});
!_matchVariables.matches(ctx.boundVariables, argsMatchState)) {
matchState['vars'] = ctx.boundVariables;
matchState['vars_match'] = argsMatchState;
matches = false;
}

View File

@ -2,6 +2,7 @@ import 'package:moor/moor.dart';
import 'package:test/test.dart';
import '../data/utils/expect_equality.dart';
import '../data/utils/expect_generated.dart';
typedef _Extractor = Expression<int> Function(Expression<DateTime> d);
@ -37,4 +38,13 @@ void main() {
expect(ctx.sql, 'strftime(\'%s\', CURRENT_TIMESTAMP) + 10');
});
test('plus and minus durations', () {
final expr = currentDateAndTime +
const Duration(days: 3) -
const Duration(seconds: 5);
expect(expr,
generates('strftime(\'%s\', CURRENT_TIMESTAMP) + ? - ?', [259200, 5]));
});
}

View File

@ -0,0 +1,35 @@
import 'package:moor/moor.dart';
@TestOn('vm')
import 'package:test/test.dart';
import 'package:moor_ffi/moor_ffi.dart';
import '../data/tables/todos.dart';
void main() {
TodoDb db;
setUp(() async {
db = TodoDb(VmDatabase.memory());
// we selectOnly from users for the lack of a better option. Insert one
// row so that getSingle works
await db.into(db.users).insert(
UsersCompanion.insert(name: 'User name', profilePicture: Uint8List(0)));
});
tearDown(() => db.close());
test('plus and minus on DateTimes', () async {
final nowExpr = currentDateAndTime;
final tomorrow = nowExpr + const Duration(days: 1);
final nowStamp = nowExpr.secondsSinceEpoch;
final tomorrowStamp = tomorrow.secondsSinceEpoch;
final row = await (db.selectOnly(db.users)
..addColumns([nowStamp, tomorrowStamp]))
.getSingle();
expect(row.read(tomorrowStamp) - row.read(nowStamp),
const Duration(days: 1).inSeconds);
});
}

View File

@ -118,6 +118,20 @@ void main() {
});
});
test('can update with custom companions', () async {
await db.update(db.todosTable).replace(TodosTableCompanion.custom(
id: const Variable(4),
content: db.todosTable.content,
targetDate: db.todosTable.targetDate + const Duration(days: 1),
));
verify(executor.runUpdate(
'UPDATE todos SET content = content, target_date = target_date + ? '
'WHERE id = ?;',
argThat(equals([86400, 4])),
));
});
group('custom updates', () {
test('execute the correct sql', () async {
await db.customUpdate('DELETE FROM users');