Make "new" and "old" table available for triggers

This commit is contained in:
Simon Binder 2020-01-01 20:37:34 +01:00
parent 49550c2f74
commit 75cbe74b53
No known key found for this signature in database
GPG Key ID: 7891917E4147B8C0
6 changed files with 129 additions and 9 deletions

View File

@ -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);';

View File

@ -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) {

View File

@ -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;
}

View File

@ -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) {

View File

@ -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;
}

View File

@ -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);
});
});
}