Generate more accurate update rules for triggers

This commit is contained in:
Simon Binder 2020-03-04 20:59:03 +01:00
parent 0b0d5792fd
commit b0b9a0ed47
No known key found for this signature in database
GPG Key ID: 7891917E4147B8C0
11 changed files with 87 additions and 40 deletions

View File

@ -17,6 +17,8 @@
- Experimentally support IndexedDB to store sqlite data on the web - Experimentally support IndexedDB to store sqlite data on the web
- Moor will no longer wait for query stream listeners to receive a done event when closing a database - Moor will no longer wait for query stream listeners to receive a done event when closing a database
or transaction. or transaction.
- Updated stream queries: They now take triggers into account and more accurately detect when an update
is necessary.
## 2.4.1 ## 2.4.1

View File

@ -57,10 +57,11 @@ class WritePropagation extends UpdateRule {
final TableUpdateQuery on; final TableUpdateQuery on;
/// All updates that will be performed by the trigger listening on [on]. /// All updates that will be performed by the trigger listening on [on].
final Set<TableUpdate> result; final List<TableUpdate> result;
/// Default constructor. See [WritePropagation] for details. /// Default constructor. See [WritePropagation] for details.
const WritePropagation(this.on, this.result) : super._(); const WritePropagation({@required this.on, @required this.result})
: super._();
} }
/// Classifies a [TableUpdate] by what kind of write happened - an insert, an /// Classifies a [TableUpdate] by what kind of write happened - an insert, an
@ -90,6 +91,12 @@ class TableUpdate {
/// Default constant constructor. /// Default constant constructor.
const TableUpdate(this.table, {this.kind}); const TableUpdate(this.table, {this.kind});
/// Creates a [TableUpdate] instance based on a [TableInfo] instead of the raw
/// name.
factory TableUpdate.fromTable(TableInfo table, {UpdateKind kind}) {
return TableUpdate(table.actualTableName, kind: kind);
}
@override @override
int get hashCode => $mrjf($mrjc(kind.hashCode, table.hashCode)); int get hashCode => $mrjf($mrjc(kind.hashCode, table.hashCode));
@ -97,6 +104,11 @@ class TableUpdate {
bool operator ==(dynamic other) { bool operator ==(dynamic other) {
return other is TableUpdate && other.kind == kind && other.table == table; return other is TableUpdate && other.kind == kind && other.table == table;
} }
@override
String toString() {
return 'TableUpdate($table, kind: $kind)';
}
} }
/// A table update query describes information to listen for [TableUpdate]s. /// A table update query describes information to listen for [TableUpdate]s.
@ -111,7 +123,7 @@ abstract class TableUpdateQuery {
const factory TableUpdateQuery.any() = AnyUpdateQuery; const factory TableUpdateQuery.any() = AnyUpdateQuery;
/// A query that listens for all updates that match any query in [queries]. /// A query that listens for all updates that match any query in [queries].
const factory TableUpdateQuery.allOf(Set<TableUpdateQuery> queries) = const factory TableUpdateQuery.allOf(List<TableUpdateQuery> queries) =
MultipleUpdateQuery; MultipleUpdateQuery;
/// A query that listens for all updates on a specific [table]. /// A query that listens for all updates on a specific [table].

View File

@ -275,7 +275,7 @@ class AnyUpdateQuery extends TableUpdateQuery {
} }
class MultipleUpdateQuery extends TableUpdateQuery { class MultipleUpdateQuery extends TableUpdateQuery {
final Set<TableUpdateQuery> queries; final List<TableUpdateQuery> queries;
const MultipleUpdateQuery(this.queries); const MultipleUpdateQuery(this.queries);
@ -297,4 +297,14 @@ class SpecificUpdateQuery extends TableUpdateQuery {
limitUpdateKind == null || limitUpdateKind == null ||
update.kind == limitUpdateKind; update.kind == limitUpdateKind;
} }
@override
int get hashCode => $mrjf($mrjc(limitUpdateKind.hashCode, table.hashCode));
@override
bool operator ==(dynamic other) {
return other is SpecificUpdateQuery &&
other.limitUpdateKind == limitUpdateKind &&
other.table == table;
}
} }

View File

@ -32,7 +32,8 @@ class DeleteStatement<T extends Table, D extends DataClass> extends Query<T, D>
final rows = await ctx.executor.runDelete(ctx.sql, ctx.boundVariables); final rows = await ctx.executor.runDelete(ctx.sql, ctx.boundVariables);
if (rows > 0) { if (rows > 0) {
database.markTablesUpdated({table}); database.notifyUpdates(
{TableUpdate.fromTable(table, kind: UpdateKind.delete)});
} }
return rows; return rows;
}); });

View File

@ -36,7 +36,8 @@ class InsertStatement<D extends DataClass> {
return await database.executor.doWhenOpened((e) async { return await database.executor.doWhenOpened((e) async {
final id = await database.executor.runInsert(ctx.sql, ctx.boundVariables); final id = await database.executor.runInsert(ctx.sql, ctx.boundVariables);
database.markTablesUpdated({table}); database.notifyUpdates(
{TableUpdate.fromTable(table, kind: UpdateKind.insert)});
return id; return id;
}); });
} }

View File

@ -1273,11 +1273,17 @@ abstract class _$CustomTablesDb extends GeneratedDatabase {
email email
]; ];
@override @override
StreamQueryUpdateRules get streamUpdateRules => const StreamQueryUpdateRules([ StreamQueryUpdateRules get streamUpdateRules => const StreamQueryUpdateRules(
[
WritePropagation( WritePropagation(
TableUpdateQuery.onTable('config', limitUpdateKind: null), on: TableUpdateQuery.onTable('config',
{TableUpdate('with_defaults', kind: null)}) limitUpdateKind: UpdateKind.insert),
]); result: [
TableUpdate('with_defaults', kind: null),
],
),
],
);
} }
class TableValuedResult { class TableValuedResult {

View File

@ -230,13 +230,24 @@ void main() {
}); });
}); });
// note: There's a trigger on config inserts that updates with_defaults
test('updates streams for updates caused by triggers', () async { test('updates streams for updates caused by triggers', () async {
final db = CustomTablesDb(executor); final db = CustomTablesDb(executor);
db.select(db.withDefaults).watch().listen((_) {}); db.select(db.withDefaults).watch().listen((_) {});
db.markTablesUpdated({db.config}); db.notifyUpdates({const TableUpdate('config', kind: UpdateKind.insert)});
await pumpEventQueue(times: 1); await pumpEventQueue(times: 1);
verify(executor.runSelect(any, any)).called(2); verify(executor.runSelect(any, any)).called(2);
}); });
test('limits trigger propagation to the target type of trigger', () async {
final db = CustomTablesDb(executor);
db.select(db.withDefaults).watch().listen((_) {});
db.notifyUpdates({const TableUpdate('config', kind: UpdateKind.delete)});
await pumpEventQueue(times: 1);
verify(executor.runSelect(any, any)).called(1);
});
} }

View File

@ -8,7 +8,7 @@ class MoorTrigger implements MoorSchemaEntity {
final String displayName; final String displayName;
@override @override
final TriggerDeclaration declaration; final MoorTriggerDeclaration declaration;
/// The table on which this trigger operates. /// The table on which this trigger operates.
/// ///

View File

@ -1,5 +1,6 @@
import 'package:moor/moor.dart'; import 'package:moor/moor.dart';
import 'package:moor_generator/moor_generator.dart'; import 'package:moor_generator/moor_generator.dart';
import 'package:sqlparser/sqlparser.dart';
class FindStreamUpdateRules { class FindStreamUpdateRules {
final Database db; final Database db;
@ -10,13 +11,26 @@ class FindStreamUpdateRules {
final rules = <UpdateRule>[]; final rules = <UpdateRule>[];
for (final trigger in db.entities.whereType<MoorTrigger>()) { for (final trigger in db.entities.whereType<MoorTrigger>()) {
final target = trigger.declaration.node.target;
UpdateKind targetKind;
if (target is DeleteTarget) {
targetKind = UpdateKind.delete;
} else if (target is InsertTarget) {
targetKind = UpdateKind.insert;
} else {
targetKind = UpdateKind.update;
}
rules.add( rules.add(
WritePropagation( WritePropagation(
TableUpdateQuery.onTable(trigger.on.sqlName), on: TableUpdateQuery.onTable(
{ trigger.on.sqlName,
limitUpdateKind: targetKind,
),
result: [
for (final update in trigger.bodyUpdates) for (final update in trigger.bodyUpdates)
TableUpdate(update.sqlName) TableUpdate(update.sqlName)
}, ],
), ),
); );
} }

View File

@ -116,16 +116,12 @@ class DatabaseWriter {
..write('@override\nStreamQueryUpdateRules get streamUpdateRules => ') ..write('@override\nStreamQueryUpdateRules get streamUpdateRules => ')
..write('const StreamQueryUpdateRules(['); ..write('const StreamQueryUpdateRules([');
var isFirst = true;
for (final rule in updateRules.rules) { for (final rule in updateRules.rules) {
if (!isFirst) { rule.writeConstructor(schemaScope);
schemaScope.write(', '); schemaScope.write(', ');
} }
isFirst = false;
rule.writeConstructor(schemaScope);
}
schemaScope.write(']);\n'); schemaScope.write('],);\n');
} }
// close the class // close the class
@ -145,21 +141,16 @@ extension on UpdateRule {
if (this is WritePropagation) { if (this is WritePropagation) {
final write = this as WritePropagation; final write = this as WritePropagation;
buffer.write('WritePropagation('); buffer.write('WritePropagation(on: ');
write.on.writeConstructor(buffer); write.on.writeConstructor(buffer);
buffer.write(', {'); buffer.write(', result: [');
var isFirst = true;
for (final update in write.result) { for (final update in write.result) {
if (!isFirst) { update.writeConstructor(buffer);
buffer.write(', '); buffer.write(', ');
} }
isFirst = false;
update.writeConstructor(buffer); buffer.write('],)');
}
buffer.write('})');
} }
} }
} }
@ -181,18 +172,13 @@ extension on TableUpdateQuery {
'limitUpdateKind: ${_kindToDartExpr[query.limitUpdateKind]})'); 'limitUpdateKind: ${_kindToDartExpr[query.limitUpdateKind]})');
} else if (this is MultipleUpdateQuery) { } else if (this is MultipleUpdateQuery) {
final queries = (this as MultipleUpdateQuery).queries; final queries = (this as MultipleUpdateQuery).queries;
var isFirst = true;
buffer.write('TableUpdateQuery.allOf({'); buffer.write('TableUpdateQuery.allOf([');
for (final query in queries) { for (final query in queries) {
if (!isFirst) { query.writeConstructor(buffer);
buffer.write(', '); buffer.write(', ');
} }
isFirst = false; buffer.write('])');
query.writeConstructor(buffer);
}
buffer.write('})');
} }
} }
} }

View File

@ -39,7 +39,11 @@ class MyDatabase {}
expect( expect(
rules.rules.single, rules.rules.single,
isA<WritePropagation>() isA<WritePropagation>()
.having((e) => e.on, 'on', const TableUpdateQuery.onTable('users')) .having(
(e) => e.on,
'on',
const TableUpdateQuery.onTable('users',
limitUpdateKind: UpdateKind.insert))
.having((e) => e.result, 'result', {const TableUpdate('users')}), .having((e) => e.result, 'result', {const TableUpdate('users')}),
); );
}); });