mirror of https://github.com/AMT-Cheif/drift.git
Find references of a trigger
This commit is contained in:
parent
04f75d11d3
commit
a3697c6f38
|
@ -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);
|
||||
}
|
||||
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -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);
|
||||
}
|
|
@ -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';
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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];
|
||||
}
|
|
@ -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.'),
|
||||
);
|
||||
});
|
||||
});
|
||||
}
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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.
|
||||
|
|
|
@ -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._();
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue