mirror of https://github.com/AMT-Cheif/drift.git
Make "new" and "old" table available for triggers
This commit is contained in:
parent
49550c2f74
commit
75cbe74b53
|
@ -27,7 +27,9 @@ const _createMyTable = 'CREATE TABLE IF NOT EXISTS mytable ('
|
|||
'somedate INTEGER);';
|
||||
|
||||
const _createMyTrigger =
|
||||
'CREATE TRIGGER my_trigger AFTER INSERT ON config BEGIN\n INSERT INTO with_defaults VALUES (new.config_key, LENGTH(new.config_value));\nEND;';
|
||||
'''CREATE TRIGGER my_trigger AFTER INSERT ON config BEGIN
|
||||
INSERT INTO with_defaults VALUES (new.config_key, LENGTH(new.config_value));
|
||||
END;''';
|
||||
|
||||
const _createEmail = 'CREATE VIRTUAL TABLE IF NOT EXISTS email USING '
|
||||
'fts5(sender, title, body);';
|
||||
|
|
|
@ -92,6 +92,26 @@ class ColumnResolver extends RecursiveVisitor<void, void> {
|
|||
visitChildren(e, arg);
|
||||
}
|
||||
|
||||
@override
|
||||
void visitCreateTriggerStatement(CreateTriggerStatement e, void arg) {
|
||||
final table = _resolveTableReference(e.onTable);
|
||||
if (table == null) {
|
||||
// further analysis is not really possible without knowing the table
|
||||
super.visitCreateTriggerStatement(e, arg);
|
||||
return;
|
||||
}
|
||||
|
||||
final scope = e.scope;
|
||||
if (e.target.introducesNew) {
|
||||
scope.register('new', table);
|
||||
}
|
||||
if (e.target.introducesOld) {
|
||||
scope.register('old', table);
|
||||
}
|
||||
|
||||
visitChildren(e, arg);
|
||||
}
|
||||
|
||||
void _handle(Queryable queryable, List<Column> availableColumns) {
|
||||
queryable.when(
|
||||
isTable: (table) {
|
||||
|
|
|
@ -54,18 +54,41 @@ abstract class TriggerTarget {
|
|||
|
||||
@override
|
||||
bool operator ==(dynamic other) => other.runtimeType == runtimeType;
|
||||
|
||||
/// Whether this target introduces the "new" table reference in the sub-scope
|
||||
/// of the create trigger statement.
|
||||
bool get introducesNew;
|
||||
|
||||
/// Whether this target introduces the "old" table reference in the sub-scope
|
||||
/// of the create trigger statement.
|
||||
bool get introducesOld;
|
||||
}
|
||||
|
||||
class DeleteTarget extends TriggerTarget {
|
||||
const DeleteTarget() : super._();
|
||||
|
||||
@override
|
||||
bool get introducesNew => false;
|
||||
@override
|
||||
bool get introducesOld => true;
|
||||
}
|
||||
|
||||
class InsertTarget extends TriggerTarget {
|
||||
const InsertTarget() : super._();
|
||||
|
||||
@override
|
||||
bool get introducesNew => true;
|
||||
@override
|
||||
bool get introducesOld => false;
|
||||
}
|
||||
|
||||
class UpdateTarget extends TriggerTarget {
|
||||
final List<Reference> columnNames;
|
||||
|
||||
UpdateTarget(this.columnNames) : super._();
|
||||
|
||||
@override
|
||||
bool get introducesNew => true;
|
||||
@override
|
||||
bool get introducesOld => true;
|
||||
}
|
||||
|
|
|
@ -101,7 +101,7 @@ class RecursiveVisitor<A, R> implements AstVisitor<A, R> {
|
|||
|
||||
@override
|
||||
R visitCreateTriggerStatement(CreateTriggerStatement e, A arg) {
|
||||
return visitCreateTriggerStatement(e, arg);
|
||||
return visitSchemaStatement(e, arg);
|
||||
}
|
||||
|
||||
R visitBaseSelectStatement(BaseSelectStatement stmt, A arg) {
|
||||
|
|
|
@ -186,13 +186,11 @@ class SqlEngine {
|
|||
try {
|
||||
AstPreparingVisitor(context: context).start(node);
|
||||
|
||||
if (node is CrudStatement) {
|
||||
node
|
||||
..acceptWithoutArg(ColumnResolver(context))
|
||||
..acceptWithoutArg(ReferenceResolver(context))
|
||||
..acceptWithoutArg(TypeResolvingVisitor(context))
|
||||
..acceptWithoutArg(LintingVisitor(options, context));
|
||||
}
|
||||
node
|
||||
..acceptWithoutArg(ColumnResolver(context))
|
||||
..acceptWithoutArg(ReferenceResolver(context))
|
||||
..acceptWithoutArg(TypeResolvingVisitor(context))
|
||||
..acceptWithoutArg(LintingVisitor(options, context));
|
||||
} catch (_) {
|
||||
rethrow;
|
||||
}
|
||||
|
|
|
@ -0,0 +1,77 @@
|
|||
import 'package:sqlparser/sqlparser.dart';
|
||||
import 'package:test/test.dart';
|
||||
|
||||
import 'data.dart';
|
||||
|
||||
void main() {
|
||||
final engine = SqlEngine();
|
||||
engine.registerTable(demoTable);
|
||||
|
||||
group('CREATE TRIGGER statements', () {
|
||||
group('delete', () {
|
||||
test('can use OLD references', () {
|
||||
final context = engine.analyze('''
|
||||
CREATE TRIGGER my_trigger BEFORE DELETE ON demo BEGIN
|
||||
SELECT * FROM old;
|
||||
END;
|
||||
''');
|
||||
|
||||
expect(context.errors, isEmpty);
|
||||
});
|
||||
|
||||
test("can't use NEW references", () {
|
||||
final context = engine.analyze('''
|
||||
CREATE TRIGGER my_trigger BEFORE DELETE ON demo BEGIN
|
||||
SELECT * FROM new;
|
||||
END;
|
||||
''');
|
||||
|
||||
expect(
|
||||
context.errors,
|
||||
contains(const TypeMatcher<AnalysisError>()
|
||||
.having((e) => e.type, 'type',
|
||||
AnalysisErrorType.referencedUnknownTable)
|
||||
.having((e) => e.span.text, 'span.text', 'new')),
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
group('insert', () {
|
||||
test('can use NEW references', () {
|
||||
final context = engine.analyze('''
|
||||
CREATE TRIGGER my_trigger BEFORE INSERT ON demo BEGIN
|
||||
SELECT * FROM new;
|
||||
END;
|
||||
''');
|
||||
|
||||
expect(context.errors, isEmpty);
|
||||
});
|
||||
|
||||
test("can't use OLD references", () {
|
||||
final context = engine.analyze('''
|
||||
CREATE TRIGGER my_trigger BEFORE INSERT ON demo BEGIN
|
||||
SELECT * FROM old;
|
||||
END;
|
||||
''');
|
||||
|
||||
expect(
|
||||
context.errors,
|
||||
contains(const TypeMatcher<AnalysisError>()
|
||||
.having((e) => e.type, 'type',
|
||||
AnalysisErrorType.referencedUnknownTable)
|
||||
.having((e) => e.span.text, 'span.text', 'old')),
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
test('update can use NEW and OLD references', () {
|
||||
final context = engine.analyze('''
|
||||
CREATE TRIGGER my_trigger BEFORE UPDATE ON demo BEGIN
|
||||
SELECT * FROM new;
|
||||
INSERT INTO old VALUES (1, 'foo');
|
||||
END;
|
||||
''');
|
||||
expect(context.errors, isEmpty);
|
||||
});
|
||||
});
|
||||
}
|
Loading…
Reference in New Issue