mirror of https://github.com/AMT-Cheif/drift.git
Make getSingle() and watchSingle() non-nullable
This commit is contained in:
parent
d9cf6660ec
commit
36edcf0ed6
|
@ -1,3 +1,4 @@
|
|||
import 'package:moor/moor.dart';
|
||||
import 'package:test/test.dart';
|
||||
import 'package:tests/database/database.dart';
|
||||
import 'package:tests/suite/suite.dart';
|
||||
|
|
|
@ -1,3 +1,4 @@
|
|||
import 'package:moor/moor.dart' hide isNull;
|
||||
import 'package:test/test.dart';
|
||||
import 'package:tests/database/database.dart';
|
||||
|
||||
|
|
|
@ -1,3 +1,4 @@
|
|||
import 'package:moor/moor.dart';
|
||||
import 'package:test/test.dart';
|
||||
import 'package:tests/data/sample_data.dart' as people;
|
||||
import 'package:tests/database/database.dart';
|
||||
|
|
|
@ -1,3 +1,4 @@
|
|||
import 'package:moor/moor.dart';
|
||||
import 'package:migrations_example/database.dart';
|
||||
import 'package:test/test.dart';
|
||||
import 'package:moor_generator/api/migrations.dart';
|
||||
|
|
|
@ -1,5 +1,7 @@
|
|||
## unreleased (breaking - 4.0)
|
||||
|
||||
- __Breaking__: `getSingle()` and `watchSingle()` are now non-nullable and throw for empty results.
|
||||
Use `getSingleOrNull()` and `watchSingleOrNull()` for the old behavior.
|
||||
- __Breaking__: Changed the `args` parameter in `QueryExecutor` methods to `List<Object?>`
|
||||
- __Breaking__: Removed the second type parameter from `TypedResult.read`
|
||||
- Support null safety
|
||||
|
|
|
@ -265,8 +265,8 @@ mixin QueryEngine on DatabaseConnectionUser {
|
|||
/// query once, use [Selectable.get]. For an auto-updating streams, set the
|
||||
/// set of tables the ready [readsFrom] and use [Selectable.watch]. If you
|
||||
/// know the query will never emit more than one row, you can also use
|
||||
/// [Selectable.getSingle] and [Selectable.watchSingle] which return the item
|
||||
/// directly or wrapping it into a list.
|
||||
/// `getSingle` and `SelectableUtils.watchSingle` which return the item
|
||||
/// directly without wrapping it into a list.
|
||||
///
|
||||
/// If you use variables in your query (for instance with "?"), they will be
|
||||
/// bound to the [variables] you specify on this query.
|
||||
|
@ -280,8 +280,8 @@ mixin QueryEngine on DatabaseConnectionUser {
|
|||
/// query once, use [Selectable.get]. For an auto-updating streams, set the
|
||||
/// set of tables the ready [readsFrom] and use [Selectable.watch]. If you
|
||||
/// know the query will never emit more than one row, you can also use
|
||||
/// [Selectable.getSingle] and [Selectable.watchSingle] which return the item
|
||||
/// directly or wrapping it into a list.
|
||||
/// `getSingle` and `watchSingle` which return the item directly without
|
||||
/// wrapping it into a list.
|
||||
///
|
||||
/// If you use variables in your query (for instance with "?"), they will be
|
||||
/// bound to the [variables] you specify on this query.
|
||||
|
|
|
@ -121,7 +121,7 @@ class Migrator {
|
|||
@experimental
|
||||
Future<void> alterTable(TableMigration migration) async {
|
||||
final foreignKeysEnabled =
|
||||
(await _db.customSelect('PRAGMA foreign_keys').getSingle())!
|
||||
(await _db.customSelect('PRAGMA foreign_keys').getSingle())
|
||||
.readBool('foreign_keys');
|
||||
|
||||
if (foreignKeysEnabled) {
|
||||
|
|
|
@ -75,10 +75,14 @@ abstract class Selectable<T> {
|
|||
/// Creates an auto-updating stream of the result that emits new items
|
||||
/// whenever any table used in this statement changes.
|
||||
Stream<List<T>> watch();
|
||||
}
|
||||
|
||||
/// Defines extensions to only get a single element from a query, or to map
|
||||
/// selectables.
|
||||
extension SelectableUtils<T> on Selectable<T> {
|
||||
/// Executes this statement, like [get], but only returns one value. If the
|
||||
/// result too many values, this method will throw. If no row is returned,
|
||||
/// `null` will be returned instead.
|
||||
/// query returns no or too many rows, the returned future will complete with
|
||||
/// an error.
|
||||
///
|
||||
/// {@template moor_single_query_expl}
|
||||
/// Be aware that this operation won't put a limit clause on this statement,
|
||||
|
@ -96,7 +100,22 @@ abstract class Selectable<T> {
|
|||
/// one row, for instance because you used `limit(1)` or you know the `where`
|
||||
/// clause will only allow one row.
|
||||
/// {@endtemplate}
|
||||
Future<T?> getSingle() async {
|
||||
///
|
||||
/// See also: [getSingleOrNull], which returns `null` instead of throwing if
|
||||
/// the query completes with no rows.
|
||||
Future<T> getSingle() async {
|
||||
return (await get()).single;
|
||||
}
|
||||
|
||||
/// Executes this statement, like [get], but only returns one value. If the
|
||||
/// result too many values, this method will throw. If no row is returned,
|
||||
/// `null` will be returned instead.
|
||||
///
|
||||
/// {@macro moor_single_query_expl}
|
||||
///
|
||||
/// See also: [getSingle], which can be used if the query will never evaluate
|
||||
/// to exactly one row.
|
||||
Future<T?> getSingleOrNull() async {
|
||||
final list = await get();
|
||||
final iterator = list.iterator;
|
||||
|
||||
|
@ -113,14 +132,25 @@ abstract class Selectable<T> {
|
|||
|
||||
/// Creates an auto-updating stream of this statement, similar to [watch].
|
||||
/// However, it is assumed that the query will only emit one result, so
|
||||
/// instead of returning a [Stream<List<T>>], this returns a [Stream<T>]. If
|
||||
/// instead of returning a `Stream<List<T>>`, this returns a `Stream<T>`. If,
|
||||
/// at any point, the query emits no or more than one rows, an error will be
|
||||
/// added to the stream instead.
|
||||
///
|
||||
/// {@macro moor_single_query_expl}
|
||||
Stream<T> watchSingle() {
|
||||
return watch().transform(singleElements());
|
||||
}
|
||||
|
||||
/// Creates an auto-updating stream of this statement, similar to [watch].
|
||||
/// However, it is assumed that the query will only emit one result, so
|
||||
/// instead of returning a `Stream<List<T>>`, this returns a `Stream<T?>`. If
|
||||
/// the query emits more than one row at some point, an error will be emitted
|
||||
/// to the stream instead. If the query emits zero rows at some point, `null`
|
||||
/// will be added to the stream instead.
|
||||
///
|
||||
/// {@macro moor_single_query_expl}
|
||||
Stream<T?> watchSingle() {
|
||||
return watch().transform(singleElements());
|
||||
Stream<T?> watchSingleOrNull() {
|
||||
return watch().transform(singleElementsOrNull());
|
||||
}
|
||||
|
||||
/// Maps this selectable by the [mapper] function.
|
||||
|
|
|
@ -1,8 +1,8 @@
|
|||
import 'dart:async';
|
||||
|
||||
/// Transforms a stream of lists into a stream of single elements, assuming
|
||||
/// that each list is a singleton.
|
||||
StreamTransformer<List<T>, T?> singleElements<T>() {
|
||||
/// that each list is a singleton or empty.
|
||||
StreamTransformer<List<T>, T?> singleElementsOrNull<T>() {
|
||||
return StreamTransformer.fromHandlers(handleData: (data, sink) {
|
||||
try {
|
||||
if (data.isEmpty) {
|
||||
|
@ -16,3 +16,16 @@ StreamTransformer<List<T>, T?> singleElements<T>() {
|
|||
}
|
||||
});
|
||||
}
|
||||
|
||||
/// Transforms a stream of lists into a stream of single elements, assuming
|
||||
/// that each list is a singleton.
|
||||
StreamTransformer<List<T>, T> singleElements<T>() {
|
||||
return StreamTransformer.fromHandlers(handleData: (data, sink) {
|
||||
try {
|
||||
sink.add(data.single);
|
||||
} catch (e) {
|
||||
sink.addError(
|
||||
StateError('Expected exactly one element, but got ${data.length}'));
|
||||
}
|
||||
});
|
||||
}
|
||||
|
|
|
@ -31,6 +31,6 @@ void main() {
|
|||
.equalsExp(Variable.withString('bar')));
|
||||
|
||||
final resultRow = await query.getSingle();
|
||||
expect(resultRow!.read(arrayLengthExpr), 3);
|
||||
expect(resultRow.read(arrayLengthExpr), 3);
|
||||
}, tags: const ['integration']);
|
||||
}
|
||||
|
|
|
@ -42,7 +42,7 @@ void main() {
|
|||
final result = await (db.selectOnly(db.pureDefaults)..addColumns([expr]))
|
||||
.getSingle();
|
||||
|
||||
return result!.read<bool?>(expr)!;
|
||||
return result.read<bool?>(expr)!;
|
||||
}
|
||||
|
||||
expect(
|
||||
|
@ -72,7 +72,7 @@ void main() {
|
|||
final result = await (db.selectOnly(db.pureDefaults)..addColumns([expr]))
|
||||
.getSingle();
|
||||
|
||||
return result!.read<bool?>(expr)!;
|
||||
return result.read<bool?>(expr)!;
|
||||
}
|
||||
|
||||
test('multiLine', () {
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
@TestOn('vm')
|
||||
import 'package:moor/ffi.dart';
|
||||
import 'package:moor/moor.dart';
|
||||
import 'package:sqlite3/sqlite3.dart';
|
||||
import 'package:test/test.dart';
|
||||
|
||||
|
@ -19,7 +20,7 @@ void main() {
|
|||
final db = TodoDb(executor);
|
||||
final row = await db.customSelect('SELECT my_function() AS r;').getSingle();
|
||||
|
||||
expect(row!.readString('r'), 'hello from Dart');
|
||||
expect(row.readString('r'), 'hello from Dart');
|
||||
await db.close();
|
||||
});
|
||||
|
||||
|
@ -31,7 +32,7 @@ void main() {
|
|||
|
||||
final db = TodoDb(executor);
|
||||
final row = await db.customSelect('SELECT my_function() AS r;').getSingle();
|
||||
expect(row!.readString('r'), 'hello from Dart');
|
||||
expect(row.readString('r'), 'hello from Dart');
|
||||
|
||||
final nativeRow = existing.select('SELECT my_function() AS r;').single;
|
||||
expect(nativeRow['r'], 'hello from Dart');
|
||||
|
|
|
@ -123,7 +123,7 @@ void _runTests(
|
|||
test('stream queries work as expected', () async {
|
||||
final initialCompanion = TodosTableCompanion.insert(content: 'my content');
|
||||
|
||||
final stream = database.select(database.todosTable).watchSingle();
|
||||
final stream = database.select(database.todosTable).watchSingleOrNull();
|
||||
|
||||
await expectLater(stream, emits(null));
|
||||
await database.into(database.todosTable).insert(initialCompanion);
|
||||
|
|
|
@ -120,7 +120,7 @@ void main() {
|
|||
});
|
||||
|
||||
test('applies default parameter expressions when not set', () async {
|
||||
await db.readDynamic().getSingle();
|
||||
await db.readDynamic().getSingleOrNull();
|
||||
|
||||
verify(mock.runSelect('SELECT * FROM config WHERE (TRUE)', []));
|
||||
});
|
||||
|
|
|
@ -134,10 +134,17 @@ void main() {
|
|||
test('get once', () {
|
||||
when(executor.runSelect('SELECT * FROM todos;', any))
|
||||
.thenAnswer((_) => Future.value([_dataOfTodoEntry]));
|
||||
|
||||
expect(db.select(db.todosTable).getSingle(), completion(_todoEntry));
|
||||
});
|
||||
|
||||
test('get once without rows', () {
|
||||
when(executor.runSelect('SELECT * FROM todos;', any))
|
||||
.thenAnswer((_) => Future.value([]));
|
||||
|
||||
expect(db.select(db.todosTable).getSingle(), throwsA(anything));
|
||||
expect(db.select(db.todosTable).getSingleOrNull(), completion(isNull));
|
||||
});
|
||||
|
||||
test('get multiple times', () {
|
||||
final resultRows = <List<Map<String, dynamic>>>[
|
||||
[_dataOfTodoEntry],
|
||||
|
@ -150,7 +157,11 @@ void main() {
|
|||
return Future.value(resultRows[_currentRow++]);
|
||||
});
|
||||
|
||||
expectLater(db.select(db.todosTable).watchSingle(),
|
||||
expectLater(
|
||||
db.select(db.todosTable).watchSingle(),
|
||||
emitsInOrder(
|
||||
[_todoEntry, emitsError(anything), emitsError(anything)]));
|
||||
expectLater(db.select(db.todosTable).watchSingleOrNull(),
|
||||
emitsInOrder([_todoEntry, isNull, emitsError(anything)]));
|
||||
|
||||
db
|
||||
|
|
|
@ -56,7 +56,7 @@ void main() {
|
|||
readsFrom: {db.users},
|
||||
)
|
||||
.map((r) => r.readInt('_mocked_'))
|
||||
.watchSingle();
|
||||
.watchSingleOrNull();
|
||||
didSetUpStream.complete();
|
||||
|
||||
await makeUpdate.future;
|
||||
|
|
|
@ -18,9 +18,15 @@ void main() {
|
|||
final controller = StreamController<List<int>>();
|
||||
final stream = controller.stream.transform(singleElements());
|
||||
|
||||
expectLater(stream, emitsInOrder([1, emitsError(anything), 2, null]));
|
||||
expectLater(stream,
|
||||
emitsInOrder([1, emitsError(anything), 2, emitsError(anything)]));
|
||||
|
||||
controller..add([1])..add([2, 3])..add([2])..add([]);
|
||||
controller.close();
|
||||
});
|
||||
|
||||
test('singleElementsOrNull() emits null for empty data', () {
|
||||
final stream = Stream.value([]);
|
||||
expect(stream.transform(singleElementsOrNull()), emits(isNull));
|
||||
});
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue