mirror of https://github.com/AMT-Cheif/drift.git
Generate more accurate update rules for triggers
This commit is contained in:
parent
0b0d5792fd
commit
b0b9a0ed47
|
@ -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
|
||||||
|
|
||||||
|
|
|
@ -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].
|
||||||
|
|
|
@ -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;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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;
|
||||||
});
|
});
|
||||||
|
|
|
@ -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;
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
|
@ -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 {
|
||||||
|
|
|
@ -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);
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
|
@ -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.
|
||||||
///
|
///
|
||||||
|
|
|
@ -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)
|
||||||
},
|
],
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
|
@ -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('})');
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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')}),
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
Loading…
Reference in New Issue