Find references of a trigger

This commit is contained in:
Simon Binder 2019-12-31 15:07:08 +01:00
parent 04f75d11d3
commit a3697c6f38
No known key found for this signature in database
GPG Key ID: 7891917E4147B8C0
15 changed files with 234 additions and 30 deletions

View File

@ -14,13 +14,34 @@ class EntityHandler {
EntityHandler(this.step, this.file, this.availableTables);
final Map<CreateTriggerStatement, MoorTrigger> _triggers = {};
final Map<TableInducingStatement, MoorTable> _tables = {};
void handle() {
final referenceResolver = _ReferenceResolvingVisitor(this);
for (final table in file.declaredTables) {
table.references.clear();
final declaration = table.declaration as MoorTableDeclaration;
_tables[declaration.node] = table;
declaration.node.acceptWithoutArg(referenceResolver);
}
file.parseResult.rootNode
?.acceptWithoutArg(_ReferenceResolvingVisitor(this));
for (final trigger in file.declaredEntities.whereType<MoorTrigger>()) {
trigger.on = null;
final declaration = trigger.declaration as MoorTriggerDeclaration;
_triggers[declaration.node] = trigger;
declaration.node.acceptWithoutArg(referenceResolver);
}
}
MoorTable _inducedTable(TableInducingStatement stmt) {
return _tables[stmt];
}
MoorTrigger _inducedTrigger(CreateTriggerStatement stmt) {
return _triggers[stmt];
}
}
@ -29,22 +50,42 @@ class _ReferenceResolvingVisitor extends RecursiveVisitor<void, void> {
_ReferenceResolvingVisitor(this.handler);
MoorTable _resolveTable(TableReference reference) {
return handler.availableTables.singleWhere(
(t) => t.sqlName == reference.tableName,
orElse: () => null);
}
@override
void visitCreateTriggerStatement(CreateTriggerStatement e, void arg) {
final table = _resolveTable(e.onTable);
if (table == null) {
handler.step.reportError(ErrorInMoorFile(
severity: Severity.error,
span: e.onTable.span,
message: 'Target table ${e.onTable.tableName} could not be found.',
));
} else {
final moorTrigger = handler._inducedTrigger(e);
moorTrigger?.on = table;
}
}
@override
void visitForeignKeyClause(ForeignKeyClause clause, void arg) {
final stmt = clause.parents.whereType<CreateTableStatement>().first;
final referencedTable = handler.availableTables.singleWhere(
(t) => t.sqlName == clause.foreignTable.tableName,
orElse: () => null);
final referencedTable = _resolveTable(clause.foreignTable);
if (referencedTable == null) {
handler.step.reportError(ErrorInMoorFile(
severity: Severity.error,
span: clause.span,
message:
'Referenced table ${clause.foreignTable.tableName} could not be'
'found.'));
severity: Severity.error,
span: clause.span,
message:
'Referenced table ${clause.foreignTable.tableName} could not be'
'found.',
));
} else {
final createdTable = handler.file.tableDeclarations[stmt];
final createdTable = handler._inducedTable(stmt);
createdTable?.references?.add(referencedTable);
}

View File

@ -20,6 +20,8 @@ class MoorParser {
final queryDeclarations = <DeclaredMoorQuery>[];
final importStatements = <ImportStatement>[];
final createdEntities = <MoorSchemaEntity>[];
for (final parsedStmt in parsedFile.statements) {
if (parsedStmt is ImportStatement) {
final importStmt = parsedStmt;
@ -27,6 +29,9 @@ class MoorParser {
importStatements.add(importStmt);
} else if (parsedStmt is TableInducingStatement) {
createdReaders.add(CreateTableReader(parsedStmt, step));
} else if (parsedStmt is CreateTriggerStatement) {
// the table will be resolved in the analysis step
createdEntities.add(MoorTrigger.fromMoor(parsedStmt, step.file));
} else if (parsedStmt is DeclaredStatement) {
if (parsedStmt.isRegularQuery) {
queryDeclarations.add(DeclaredMoorQuery.fromStatement(parsedStmt));
@ -42,20 +47,18 @@ class MoorParser {
));
}
final createdTables = <MoorTable>[];
final tableDeclarations = <TableInducingStatement, MoorTable>{};
for (final reader in createdReaders) {
final table = reader.extractTable(step.mapper);
createdTables.add(table);
createdEntities.add(table);
tableDeclarations[reader.stmt] = table;
}
final analyzedFile = ParsedMoorFile(
result,
declaredTables: createdTables,
declaredEntities: createdEntities,
queries: queryDeclarations,
imports: importStatements,
tableDeclarations: tableDeclarations,
);
for (final decl in queryDeclarations) {
decl.file = analyzedFile;

View File

@ -42,14 +42,13 @@ class ParsedMoorFile extends FileResult {
final List<PartOfMoorFile> otherComponents;
List<SqlQuery> resolvedQueries;
Map<TableInducingStatement, MoorTable> tableDeclarations;
Map<ImportStatement, FoundFile> resolvedImports;
ParsedMoorFile(this.parseResult,
{List<MoorTable> declaredTables = const [],
this.queries = const [],
this.imports = const [],
this.otherComponents = const [],
this.tableDeclarations = const {}})
: super(declaredTables);
ParsedMoorFile(
this.parseResult, {
List<MoorSchemaEntity> declaredEntities = const [],
this.queries = const [],
this.imports = const [],
this.otherComponents = const [],
}) : super(declaredEntities);
}

View File

@ -1,7 +1,9 @@
import 'package:moor_generator/moor_generator.dart';
/// Some schema entity found.
///
/// Most commonly a table, but it can also be a trigger.
abstract class MoorSchemaEntity {
abstract class MoorSchemaEntity implements HasDeclaration {
/// All entities that have to be created before this entity can be created.
///
/// For tables, this can be contents of a `REFERENCES` clause. For triggers,

View File

@ -6,6 +6,7 @@ import 'package:sqlparser/sqlparser.dart';
part 'columns.dart';
part 'database.dart';
part 'tables.dart';
part 'trigger.dart';
/// Interface for model elements that are declared somewhere.
abstract class HasDeclaration {

View File

@ -0,0 +1,16 @@
part of 'declaration.dart';
abstract class TriggerDeclaration extends Declaration {}
class MoorTriggerDeclaration implements MoorDeclaration, TriggerDeclaration {
@override
final SourceRange declaration;
@override
final CreateTriggerStatement node;
MoorTriggerDeclaration._(this.declaration, this.node);
MoorTriggerDeclaration.fromNodeAndFile(this.node, FoundFile file)
: declaration = SourceRange.fromNodeAndFile(node, file);
}

View File

@ -5,4 +5,5 @@ export 'declarations/declaration.dart';
export 'sources.dart';
export 'sql_query.dart';
export 'table.dart';
export 'trigger.dart';
export 'used_type_converter.dart';

View File

@ -10,7 +10,7 @@ import 'declarations/declaration.dart';
/// A parsed table, declared in code by extending `Table` and referencing that
/// table in `@UseMoor` or `@UseDao`.
class MoorTable implements HasDeclaration, MoorSchemaEntity {
class MoorTable implements MoorSchemaEntity {
/// The [ClassElement] for the class that declares this table or null if
/// the table was inferred from a `CREATE TABLE` statement.
final ClassElement fromClass;

View File

@ -0,0 +1,30 @@
import 'package:moor_generator/src/analyzer/runner/file_graph.dart';
import 'package:sqlparser/sqlparser.dart';
import 'model.dart';
class MoorTrigger implements MoorSchemaEntity {
@override
final String displayName;
@override
final Declaration declaration;
/// The table on which this trigger operates.
///
/// This field can be null in case the table wasn't resolved.
MoorTable on;
MoorTrigger(this.displayName, this.declaration, this.on);
factory MoorTrigger.fromMoor(CreateTriggerStatement stmt, FoundFile file) {
return MoorTrigger(
stmt.triggerName,
MoorTriggerDeclaration.fromNodeAndFile(stmt, file),
null, // must be resolved later
);
}
@override
Iterable<MoorSchemaEntity> get references => [on];
}

View File

@ -0,0 +1,106 @@
import 'package:moor_generator/moor_generator.dart';
import 'package:test/test.dart';
import '../utils.dart';
void main() {
group('finds referenced tables', () {
const definitions = {
'foo|lib/b.moor': '''
CREATE TABLE users (
id INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT,
name VARCHAR
);
'''
};
test('in a foreign key clause', () async {
final state = TestState.withContent(const {
'foo|lib/a.moor': '''
import 'b.moor';
CREATE TABLE friendships (
user_a INTEGER REFERENCES users (id),
user_b INTEGER REFERENCES users (id),
PRIMARY KEY(user_a, user_b),
CHECK (user_a != user_b)
);
''',
...definitions,
});
final file = await state.analyze('package:foo/a.moor');
expect(file.errors.errors, isEmpty);
final table = file.currentResult.declaredTables.single;
expect(
table.references,
[
const TypeMatcher<MoorTable>()
.having((table) => table.displayName, 'displayName', 'users')
],
);
});
test('in a trigger', () async {
final state = TestState.withContent(const {
'foo|lib/a.moor': '''
import 'b.moor';
CREATE TRIGGER my_trigger AFTER DELETE ON users BEGIN
SELECT * FROM users;
END;
''',
...definitions,
});
final file = await state.analyze('package:foo/a.moor');
expect(file.errors.errors, isEmpty);
final trigger = file.currentResult.declaredEntities.single;
expect(
trigger.references,
[
const TypeMatcher<MoorTable>()
.having((table) => table.displayName, 'displayName', 'users')
],
);
});
});
group('issues error when referencing an unknown table', () {
test('in a foreign key clause', () async {
final state = TestState.withContent(const {
'foo|lib/a.moor': '''
CREATE TABLE foo (
id INTEGER NOT NULL REFERENCES bar (baz) PRIMARY KEY
);
'''
});
final file = await state.analyze('package:foo/a.moor');
expect(
file.errors.errors.map((e) => e.message),
contains('Referenced table bar could not befound.'),
);
});
test('in a trigger', () async {
final state = TestState.withContent(const {
'foo|lib/a.moor': '''
CREATE TRIGGER IF NOT EXISTS foo BEFORE DELETE ON bar BEGIN
END;
''',
});
final file = await state.analyze('package:foo/a.moor');
expect(
file.errors.errors.map((e) => e.message),
contains('Target table bar could not be found.'),
);
});
});
}

View File

@ -30,4 +30,9 @@ class TestState {
FoundFile file(String uri) {
return session.registerFile(Uri.parse(uri));
}
Future<FoundFile> analyze(String uri) async {
await runTask(uri);
return file(uri);
}
}

View File

@ -3,6 +3,7 @@
- Added a argument type and argument to the visitor classes
- Experimental new type inference algorithm
- Support `CAST` expressions.
- Support parsing `CREATE TRIGGER` statements
## 0.5.0
- Optionally support the `json1` module

View File

@ -74,7 +74,6 @@ package to generate type-safe methods from sql.
Most on this list is just not supported yet because I didn't found a use case for
them yet. If you need them, just leave an issue and I'll try to implement them soon.
- Some advanced expressions, like `CAST`s aren't supported yet.
- An `UPSERT` clause is not yet supported on insert statements
If you run into parsing errors with what you think is valid sql, please create an issue.

View File

@ -48,7 +48,7 @@ class CreateTriggerStatement extends Statement implements SchemaStatement {
enum TriggerMode { before, after, insteadOf }
abstract class TriggerTarget {
const TriggerTarget();
const TriggerTarget._();
@override
int get hashCode => runtimeType.hashCode;
@ -57,15 +57,15 @@ abstract class TriggerTarget {
}
class DeleteTarget extends TriggerTarget {
const DeleteTarget();
const DeleteTarget() : super._();
}
class InsertTarget extends TriggerTarget {
const InsertTarget();
const InsertTarget() : super._();
}
class UpdateTarget extends TriggerTarget {
final List<Reference> columnNames;
UpdateTarget(this.columnNames);
UpdateTarget(this.columnNames) : super._();
}