mirror of https://github.com/AMT-Cheif/drift.git
Test streams, updates and deletes
This commit is contained in:
parent
b7e37857ec
commit
e4f733119d
|
@ -19,8 +19,9 @@ abstract class GeneratedDatabase {
|
|||
|
||||
List<TableInfo> get allTables;
|
||||
|
||||
GeneratedDatabase(this.typeSystem, this.executor,
|
||||
{this.streamQueries = const StreamQueryStore()});
|
||||
GeneratedDatabase(this.typeSystem, this.executor, {this.streamQueries}) {
|
||||
streamQueries ??= StreamQueryStore();
|
||||
}
|
||||
|
||||
/// Creates a migrator with the provided query executor. We sometimes can't
|
||||
/// use the regular [GeneratedDatabase.executor] because migration happens
|
||||
|
|
|
@ -3,9 +3,9 @@ import 'dart:async';
|
|||
import 'package:sally/sally.dart';
|
||||
|
||||
class StreamQueryStore {
|
||||
final List<_QueryStream> _activeStreams = const [];
|
||||
final List<_QueryStream> _activeStreams = [];
|
||||
|
||||
const StreamQueryStore();
|
||||
StreamQueryStore();
|
||||
|
||||
Stream<List<T>> registerStream<T>(SelectStatement<dynamic, T> statement) {
|
||||
final stream = _QueryStream(statement, this);
|
||||
|
|
|
@ -11,7 +11,9 @@ class InsertStatement<DataClass> {
|
|||
InsertStatement(this.database, this.table);
|
||||
|
||||
Future<void> insert(DataClass entity) async {
|
||||
table.validateIntegrity(entity, true);
|
||||
if (!table.validateIntegrity(entity, true)) {
|
||||
throw InvalidDataException('Invalid data: $entity cannot be written into ${table.$tableName}');
|
||||
}
|
||||
|
||||
final map = table.entityToSql(entity)
|
||||
..removeWhere((_, value) => value == null);
|
||||
|
|
|
@ -38,7 +38,9 @@ class UpdateStatement<T, D> extends Query<T, D> {
|
|||
/// explicitly, this method will update all rows in the specific table.
|
||||
Future<int> write(D entity) async {
|
||||
_updateReference = entity;
|
||||
table.validateIntegrity(_updateReference, false);
|
||||
if (!table.validateIntegrity(_updateReference, false)) {
|
||||
throw InvalidDataException('Invalid data: $entity cannot be written into ${table.$tableName}');
|
||||
}
|
||||
|
||||
final ctx = constructQuery();
|
||||
final rows = await ctx.database.executor.runUpdate(ctx.sql, ctx.boundVariables);
|
||||
|
|
|
@ -17,7 +17,7 @@ abstract class TableInfo<TableDsl, DataClass> {
|
|||
/// that it respects all constraints (nullability, text length, etc.).
|
||||
/// During insertion mode, fields that have a default value or are
|
||||
/// auto-incrementing are allowed to be null as they will be set by sqlite.
|
||||
void validateIntegrity(DataClass instance, bool isInserting) => null;
|
||||
bool validateIntegrity(DataClass instance, bool isInserting) => null;
|
||||
|
||||
/// Maps the given data class into a map that can be inserted into sql. The
|
||||
/// keys should represent the column name in sql, the values the corresponding
|
||||
|
|
|
@ -43,7 +43,7 @@ void main() {
|
|||
verify(streamQueries.handleTableUpdates('users'));
|
||||
});
|
||||
|
||||
test('are not issues when no data was changed', () async {
|
||||
test('are not issued when no data was changed', () async {
|
||||
when(executor.runDelete(any, any)).thenAnswer((_) => Future.value(0));
|
||||
|
||||
await db.delete(db.users).go();
|
||||
|
|
|
@ -0,0 +1,52 @@
|
|||
import 'package:test_api/test_api.dart';
|
||||
|
||||
import 'tables/todos.dart';
|
||||
import 'utils/mocks.dart';
|
||||
|
||||
void main() {
|
||||
TodoDb db;
|
||||
MockExecutor executor;
|
||||
setUp(() {
|
||||
executor = MockExecutor();
|
||||
db = TodoDb(executor);
|
||||
});
|
||||
|
||||
test('streams fetch when the first listener attaches', () {
|
||||
final stream = db.select(db.users).watch();
|
||||
|
||||
verifyNever(executor.runSelect(any, any));
|
||||
|
||||
stream.listen((_) {});
|
||||
|
||||
verify(executor.runSelect(any, any)).called(1);
|
||||
});
|
||||
|
||||
test('streams fetch when the underlying data changes', () {
|
||||
db.select(db.users).watch().listen((_) {});
|
||||
|
||||
db.markTableUpdated('users');
|
||||
|
||||
// twice: Once because the listener attached, once because the data changed
|
||||
verify(executor.runSelect(any, any)).called(2);
|
||||
});
|
||||
|
||||
group("streams don't fetch", () {
|
||||
test('when no listeners were attached', () {
|
||||
db.select(db.users).watch();
|
||||
|
||||
db.markTableUpdated('users');
|
||||
|
||||
verifyNever(executor.runSelect(any, any));
|
||||
});
|
||||
|
||||
test('when the data updates after the listener has detached', () {
|
||||
final subscription = db.select(db.users).watch().listen((_) {});
|
||||
clearInteractions(executor);
|
||||
|
||||
subscription.cancel();
|
||||
db.markTableUpdated('users');
|
||||
|
||||
verifyNever(executor.runSelect(any, any));
|
||||
});
|
||||
});
|
||||
}
|
|
@ -55,7 +55,7 @@ class _$TodosTableTable extends TodosTable
|
|||
@override
|
||||
String get $tableName => 'todos';
|
||||
@override
|
||||
void validateIntegrity(TodoEntry instance, bool isInserting) =>
|
||||
bool validateIntegrity(TodoEntry instance, bool isInserting) =>
|
||||
id.isAcceptableValue(instance.id, isInserting) &&
|
||||
title.isAcceptableValue(instance.title, isInserting) &&
|
||||
content.isAcceptableValue(instance.content, isInserting) &&
|
||||
|
@ -124,7 +124,7 @@ class _$CategoriesTable extends Categories
|
|||
@override
|
||||
String get $tableName => 'categories';
|
||||
@override
|
||||
void validateIntegrity(Category instance, bool isInserting) =>
|
||||
bool validateIntegrity(Category instance, bool isInserting) =>
|
||||
id.isAcceptableValue(instance.id, isInserting) &&
|
||||
description.isAcceptableValue(instance.description, isInserting);
|
||||
@override
|
||||
|
@ -192,7 +192,7 @@ class _$UsersTable extends Users implements TableInfo<Users, User> {
|
|||
@override
|
||||
String get $tableName => 'users';
|
||||
@override
|
||||
void validateIntegrity(User instance, bool isInserting) =>
|
||||
bool validateIntegrity(User instance, bool isInserting) =>
|
||||
id.isAcceptableValue(instance.id, isInserting) &&
|
||||
name.isAcceptableValue(instance.name, isInserting) &&
|
||||
isAwesome.isAcceptableValue(instance.isAwesome, isInserting);
|
||||
|
|
|
@ -0,0 +1,65 @@
|
|||
import 'package:sally/sally.dart';
|
||||
import 'package:test_api/test_api.dart';
|
||||
|
||||
import 'tables/todos.dart';
|
||||
import 'utils/mocks.dart';
|
||||
|
||||
void main() {
|
||||
TodoDb db;
|
||||
MockExecutor executor;
|
||||
MockStreamQueries streamQueries;
|
||||
|
||||
setUp(() {
|
||||
executor = MockExecutor();
|
||||
streamQueries = MockStreamQueries();
|
||||
db = TodoDb(executor)..streamQueries = streamQueries;
|
||||
});
|
||||
|
||||
group('generates update statements', () {
|
||||
test('for entire table', () async {
|
||||
await db
|
||||
.update(db.todosTable)
|
||||
.write(TodoEntry(title: 'Updated title', category: 3));
|
||||
|
||||
verify(executor.runUpdate(
|
||||
'UPDATE todos SET title = ? category = ?;', ['Updated title', 3]));
|
||||
});
|
||||
|
||||
test('with WHERE and LIMIT clauses', () async {
|
||||
await (db.update(db.todosTable)
|
||||
..where((t) => t.id.isSmallerThan(50))
|
||||
..limit(10))
|
||||
.write(TodoEntry(title: 'Changed title'));
|
||||
|
||||
verify(executor.runUpdate(
|
||||
'UPDATE todos SET title = ? WHERE id < ? LIMIT 10;',
|
||||
['Changed title', 50]));
|
||||
});
|
||||
});
|
||||
|
||||
test('does not update with invalid data', () {
|
||||
// The length of a title must be between 4 and 16 chars
|
||||
|
||||
expect(() async {
|
||||
await db.into(db.todosTable).insert(TodoEntry(title: 'lol'));
|
||||
}, throwsA(const TypeMatcher<InvalidDataException>()));
|
||||
});
|
||||
|
||||
group('Table updates for delete statements', () {
|
||||
test('are issued when data was changed', () async {
|
||||
when(executor.runUpdate(any, any)).thenAnswer((_) => Future.value(3));
|
||||
|
||||
await db.update(db.todosTable).write(TodoEntry());
|
||||
|
||||
verify(streamQueries.handleTableUpdates('todos'));
|
||||
});
|
||||
|
||||
test('are not issued when no data was changed', () async {
|
||||
when(executor.runDelete(any, any)).thenAnswer((_) => Future.value(0));
|
||||
|
||||
await db.update(db.todosTable).write(TodoEntry());
|
||||
|
||||
verifyNever(streamQueries.handleTableUpdates(any));
|
||||
});
|
||||
});
|
||||
}
|
|
@ -146,7 +146,7 @@ class TableWriter {
|
|||
void _writeValidityCheckMethod(StringBuffer buffer) {
|
||||
final dataClass = table.dartTypeName;
|
||||
|
||||
buffer.write('@override\nvoid validateIntegrity($dataClass instance, bool isInserting) => ');
|
||||
buffer.write('@override\nbool validateIntegrity($dataClass instance, bool isInserting) => ');
|
||||
|
||||
final validationCode = table.columns.map((column) {
|
||||
final getterName = column.dartGetterName;
|
||||
|
|
Loading…
Reference in New Issue