Migrate some trigger code to refactorings on develop

This commit is contained in:
Simon Binder 2019-12-30 21:38:24 +01:00
parent ba603f22cc
commit 04f75d11d3
No known key found for this signature in database
GPG Key ID: 7891917E4147B8C0
22 changed files with 107 additions and 80 deletions

View File

@ -31,6 +31,12 @@ abstract class GeneratedDatabase extends DatabaseConnectionUser
/// A list of tables specified in this database.
List<TableInfo> get allTables;
/// A list of all [DatabaseSchemaEntity] that are specified in this database.
///
/// This contains [allTables], but also advanced entities like triggers.
// return allTables for backwards compatibility
List<DatabaseSchemaEntity> get allSchemaEntities => allTables;
/// A [Type] can't be sent across isolates. Instances of this class shouldn't
/// be sent over isolates either, so let's keep a reference to a [Type] that
/// definitely prohibits this.

View File

@ -69,7 +69,7 @@ class Migrator {
/// Creates all tables, triggers, views, indexes and everything else defined
/// in the database, if they don't exist.
Future<void> createAll() async {
for (var entity in _db.allEntities) {
for (final entity in _db.allSchemaEntities) {
if (entity is TableInfo) {
await createTable(entity);
} else if (entity is Trigger) {

View File

@ -28,6 +28,7 @@ part 'expressions/text.dart';
part 'expressions/variables.dart';
part 'schema/columns.dart';
part 'schema/entities.dart';
part 'schema/table_info.dart';
part 'statements/select/custom_select.dart';

View File

@ -1,3 +1,5 @@
part of '../query_builder.dart';
/// Some abstract schema entity that can be stored in a database. This includes
/// tables, triggers, views, indexes, etc.
abstract class DatabaseSchemaEntity {
@ -10,10 +12,11 @@ abstract class DatabaseSchemaEntity {
/// In moor, triggers can only be declared in `.moor` files.
///
/// For more information on triggers, see the [CREATE TRIGGER][sqlite-docs]
/// documentation from sqlite, or the [entry on sqlitetutorial.net][sql-tutorial].
/// documentation from sqlite, or the [entry on sqlitetutorial.net][sql-tut].
///
/// [sqlite-docs]: (https://sqlite.org/lang_createtrigger.html)
/// [sql-tutorial]: (https://www.sqlitetutorial.net/sqlite-trigger/)
///
/// [sqlite-docs]: https://sqlite.org/lang_createtrigger.html
/// [sql-tut]: https://www.sqlitetutorial.net/sqlite-trigger/
class Trigger extends DatabaseSchemaEntity {
/// The `CREATE TRIGGER` sql statement that can be used to create this
/// trigger.

View File

@ -35,7 +35,7 @@ void main() {
final mockExecutor = MockExecutor();
final mockQueryExecutor = MockQueryExecutor();
final db = CustomTablesDb(mockExecutor);
await Migrator(db, mockQueryExecutor).createAllTables();
await Migrator(db, mockQueryExecutor).createAll();
verify(mockQueryExecutor.call(_createNoIds, []));
verify(mockQueryExecutor.call(_createWithDefaults, []));

View File

@ -65,7 +65,7 @@ class Database extends _$Database {
MigrationStrategy get migration {
return MigrationStrategy(
onCreate: (Migrator m) {
return m.createAllTables();
return m.createAll();
},
onUpgrade: (Migrator m, int from, int to) async {
if (from == 1) {

View File

@ -7,12 +7,12 @@ import 'package:sqlparser/sqlparser.dart';
/// Handles `REFERENCES` clauses in tables by resolving their columns and
/// reporting errors if they don't exist. Further, sets the
/// [MoorTable.references] field for tables declared in moor.
class TableHandler {
class EntityHandler {
final AnalyzeMoorStep step;
final ParsedMoorFile file;
final List<MoorTable> availableTables;
TableHandler(this.step, this.file, this.availableTables);
EntityHandler(this.step, this.file, this.availableTables);
void handle() {
for (final table in file.declaredTables) {
@ -25,7 +25,7 @@ class TableHandler {
}
class _ReferenceResolvingVisitor extends RecursiveVisitor<void, void> {
final TableHandler handler;
final EntityHandler handler;
_ReferenceResolvingVisitor(this.handler);

View File

@ -6,9 +6,11 @@ import 'package:moor_generator/src/model/sql_query.dart';
import 'package:sqlparser/sqlparser.dart';
abstract class FileResult {
final List<MoorTable> declaredTables;
final List<MoorSchemaEntity> declaredEntities;
FileResult(this.declaredTables);
Iterable<MoorTable> get declaredTables => declaredEntities.whereType();
FileResult(this.declaredEntities);
}
class ParsedDartFile extends FileResult {

View File

@ -5,7 +5,7 @@ import 'package:moor/moor.dart';
import 'package:moor_generator/moor_generator.dart';
import 'package:moor_generator/src/analyzer/dart/parser.dart';
import 'package:moor_generator/src/analyzer/errors.dart';
import 'package:moor_generator/src/analyzer/moor/table_handler.dart';
import 'package:moor_generator/src/analyzer/moor/entity_handler.dart';
import 'package:moor_generator/src/analyzer/runner/file_graph.dart';
import 'package:moor_generator/src/analyzer/runner/results.dart';
import 'package:moor_generator/src/analyzer/moor/inline_dart_resolver.dart';
@ -14,7 +14,8 @@ import 'package:moor_generator/src/analyzer/sql_queries/sql_parser.dart';
import 'package:moor_generator/src/analyzer/sql_queries/type_mapping.dart';
import 'package:moor_generator/src/analyzer/runner/task.dart';
import 'package:moor_generator/src/model/sql_query.dart';
import 'package:moor_generator/src/utils/table_reference_sorter.dart';
import 'package:moor_generator/src/utils/entity_reference_sorter.dart';
import 'package:source_gen/source_gen.dart';
part 'steps/analyze_dart.dart';
@ -53,8 +54,12 @@ abstract class AnalyzingStep extends Step {
.toList();
}
Iterable<MoorSchemaEntity> _availableEntities(List<FoundFile> imports) {
return imports.expand<MoorSchemaEntity>((file) =>
file.currentResult?.declaredEntities ?? const Iterable.empty());
}
Iterable<MoorTable> _availableTables(List<FoundFile> imports) {
return imports.expand<MoorTable>(
(file) => file.currentResult?.declaredTables ?? const Iterable.empty());
return _availableEntities(imports).whereType();
}
}

View File

@ -10,12 +10,12 @@ class AnalyzeDartStep extends AnalyzingStep {
for (final accessor in parseResult.dbAccessors) {
final transitiveImports = _transitiveImports(accessor.imports);
var availableTables = _availableTables(transitiveImports)
var availableEntities = _availableEntities(transitiveImports)
.followedBy(accessor.declaredTables)
.toList();
try {
availableTables = sortTablesTopologically(availableTables);
availableEntities = sortEntitiesTopologically(availableEntities);
} on CircularReferenceException catch (e) {
final msg = StringBuffer(
'Found a circular reference in your database. This can cause '
@ -39,6 +39,7 @@ class AnalyzeDartStep extends AnalyzingStep {
.whereType<ParsedMoorFile>()
.expand((f) => f.resolvedQueries);
final availableTables = availableEntities.whereType<MoorTable>().toList();
final parser = SqlParser(this, availableTables, accessor.declaredQueries);
parser.parse();

View File

@ -16,7 +16,7 @@ class AnalyzeMoorStep extends AnalyzingStep {
final parser = SqlParser(this, availableTables, parseResult.queries)
..parse();
TableHandler(this, parseResult, availableTables).handle();
EntityHandler(this, parseResult, availableTables).handle();
parseResult.resolvedQueries = parser.foundQueries;
}

View File

@ -0,0 +1,16 @@
/// Some schema entity found.
///
/// Most commonly a table, but it can also be a trigger.
abstract class MoorSchemaEntity {
/// 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,
/// it would be the tables watched.
///
/// The generator will verify that the graph of entities and [references]
/// is acyclic and sort them topologically.
Iterable<MoorSchemaEntity> get references;
/// A human readable name of this entity, like the table name.
String get displayName;
}

View File

@ -1,3 +1,4 @@
export 'base_entity.dart';
export 'column.dart';
export 'database.dart';
export 'declarations/declaration.dart';

View File

@ -1,24 +0,0 @@
import 'package:moor_generator/src/analyzer/sql_queries/meta/declarations.dart';
import 'package:recase/recase.dart';
/// An abstract schema entity that isn't a table.
///
/// This includes triggers or indexes.
abstract class SpecifiedEntity {
final String name;
String get dartFieldName => ReCase(name).camelCase;
SpecifiedEntity(this.name);
}
/// Information about a trigger defined in a `.moor` file.
class SpecifiedTrigger extends SpecifiedEntity {
/// Information on where this trigger was created.
final BaseDeclaration declaration;
/// The `CREATE TRIGGER` sql statement that creates this trigger.
final String sql;
SpecifiedTrigger(String name, this.sql, this.declaration) : super(name);
}

View File

@ -4,12 +4,13 @@ import 'package:moor_generator/src/model/used_type_converter.dart';
import 'package:recase/recase.dart';
import 'package:sqlparser/sqlparser.dart';
import 'base_entity.dart';
import 'column.dart';
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 {
class MoorTable implements HasDeclaration, 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;
@ -80,8 +81,7 @@ class MoorTable implements HasDeclaration {
/// `customConstraints` getter in the table class with this value.
final List<String> overrideTableConstraints;
/// The set of tables referenced somewhere in the declaration of this table,
/// for instance by using a `REFERENCES` column constraint.
@override
final Set<MoorTable> references = {};
/// Returns whether this table was created from a `CREATE VIRTUAL TABLE`
@ -116,6 +116,7 @@ class MoorTable implements HasDeclaration {
Iterable<UsedTypeConverter> get converters =>
columns.map((c) => c.typeConverter).where((t) => t != null);
@override
String get displayName {
if (isFromSql) {
return sqlName;

View File

@ -1,26 +1,27 @@
import 'package:moor_generator/moor_generator.dart';
/// Topologically sorts a list of [MoorTable]s by their
/// [MoorTable.references] relationship: Tables appearing first in the
/// Topologically sorts a list of [MoorSchemaEntity]s by their
/// [MoorSchemaEntity.references] relationship: Tables appearing first in the
/// output have to be created first so the table creation script doesn't crash
/// because of tables not existing.
///
/// If there is a circular reference between [MoorTable]s, an error will
/// be added that contains the name of the tables in question.
List<MoorTable> sortTablesTopologically(Iterable<MoorTable> tables) {
List<MoorSchemaEntity> sortEntitiesTopologically(
Iterable<MoorSchemaEntity> tables) {
final run = _SortRun();
for (final table in tables) {
if (!run.didVisitAlready(table)) {
run.previous[table] = null;
_visit(table, run);
for (final entity in tables) {
if (!run.didVisitAlready(entity)) {
run.previous[entity] = null;
_visit(entity, run);
}
}
return run.result;
}
void _visit(MoorTable table, _SortRun run) {
void _visit(MoorSchemaEntity table, _SortRun run) {
for (final reference in table.references) {
if (run.result.contains(reference)) {
// already handled, nothing to do
@ -38,34 +39,34 @@ void _visit(MoorTable table, _SortRun run) {
}
class _SortRun {
final Map<MoorTable, MoorTable> previous = {};
final List<MoorTable> result = [];
final Map<MoorSchemaEntity, MoorSchemaEntity> previous = {};
final List<MoorSchemaEntity> result = [];
/// Throws a [CircularReferenceException] because the [last] table depends on
/// [first], which (transitively) depends on [last] as well. The path in the
/// thrown exception will go from [first] to [last].
void throwCircularException(MoorTable last, MoorTable first) {
final constructedPath = <MoorTable>[];
void throwCircularException(MoorSchemaEntity last, MoorSchemaEntity first) {
final constructedPath = <MoorSchemaEntity>[];
for (var current = last; current != first; current = previous[current]) {
constructedPath.insert(0, current);
}
constructedPath.insert(0, first);
throw CircularReferenceException(constructedPath);
throw CircularReferenceException._(constructedPath);
}
bool didVisitAlready(MoorTable table) {
bool didVisitAlready(MoorSchemaEntity table) {
return previous[table] != null || result.contains(table);
}
}
/// Thrown by [sortTablesTopologically] when the graph formed by
/// [MoorTable]s and their [MoorTable.references] is not acyclic.
/// Thrown by [sortEntitiesTopologically] when the graph formed by
/// [MoorSchemaEntity.references] is not acyclic.
class CircularReferenceException implements Exception {
/// The list of tables forming a circular reference, so that the first table
/// in this list references the second one and so on. The last table in this
/// list references the first one.
final List<MoorTable> affected;
/// The list of entities forming a circular reference, so that the first
/// entity in this list references the second one and so on. The last entity
/// in this list references the first one, thus forming a cycle.
final List<MoorSchemaEntity> affected;
CircularReferenceException(this.affected);
CircularReferenceException._(this.affected);
}

View File

@ -1,5 +1,5 @@
import 'package:moor_generator/moor_generator.dart';
import 'package:moor_generator/src/utils/table_reference_sorter.dart';
import 'package:moor_generator/src/utils/entity_reference_sorter.dart';
import 'package:test/test.dart';
void main() {
@ -39,14 +39,14 @@ void main() {
a.references.add(b);
b.references.add(c);
final sorted = sortTablesTopologically([a, b, c, d]);
final sorted = sortEntitiesTopologically([a, b, c, d]);
expect(sorted, [c, b, a, d]);
});
}
CircularReferenceException _expectFails(Iterable<MoorTable> table) {
try {
sortTablesTopologically(table);
sortEntitiesTopologically(table);
fail('Expected sortTablesTopologically to throw here');
} on CircularReferenceException catch (e) {
return e;

View File

@ -9,7 +9,9 @@ class Block extends AstNode {
Block(this.statements);
@override
T accept<T>(AstVisitor<T> visitor) => visitor.visitBlock(this);
R accept<A, R>(AstVisitor<A, R> visitor, A arg) {
return visitor.visitBlock(this, arg);
}
@override
Iterable<AstNode> get childNodes => statements;

View File

@ -1,7 +1,7 @@
part of '../ast.dart';
abstract class TableInducingStatement extends Statement
implements PartOfMoorFile, SchemaStatement {
implements SchemaStatement {
final bool ifNotExists;
final String tableName;

View File

@ -1,7 +1,7 @@
part of '../ast.dart';
/// A "CREATE TRIGGER" statement, see https://sqlite.org/lang_createtrigger.html
class CreateTriggerStatement extends Statement with SchemaStatement {
class CreateTriggerStatement extends Statement implements SchemaStatement {
final bool ifNotExists;
final String triggerName;
IdentifierToken triggerNameToken;
@ -24,8 +24,8 @@ class CreateTriggerStatement extends Statement with SchemaStatement {
@required this.action});
@override
T accept<T>(AstVisitor<T> visitor) {
return visitor.visitCreateTriggerStatement(this);
R accept<A, R>(AstVisitor<A, R> visitor, A arg) {
return visitor.visitCreateTriggerStatement(this, arg);
}
@override
@ -53,7 +53,7 @@ abstract class TriggerTarget {
int get hashCode => runtimeType.hashCode;
@override
bool operator ==(other) => other.runtimeType == runtimeType;
bool operator ==(dynamic other) => other.runtimeType == runtimeType;
}
class DeleteTarget extends TriggerTarget {

View File

@ -10,6 +10,7 @@ abstract class AstVisitor<A, R> {
R visitUpdateStatement(UpdateStatement e, A arg);
R visitCreateTableStatement(CreateTableStatement e, A arg);
R visitCreateVirtualTableStatement(CreateVirtualTableStatement e, A arg);
R visitCreateTriggerStatement(CreateTriggerStatement e, A arg);
R visitWithClause(WithClause e, A arg);
R visitCommonTableExpression(CommonTableExpression e, A arg);
@ -50,6 +51,8 @@ abstract class AstVisitor<A, R> {
R visitNumberedVariable(NumberedVariable e, A arg);
R visitNamedVariable(ColonNamedVariable e, A arg);
R visitBlock(Block block, A arg);
R visitMoorFile(MoorFile e, A arg);
R visitMoorImportStatement(ImportStatement e, A arg);
R visitMoorDeclaredStatement(DeclaredStatement e, A arg);
@ -91,12 +94,16 @@ class RecursiveVisitor<A, R> implements AstVisitor<A, R> {
return visitTableInducingStatement(e, arg);
}
@override
@override
R visitCreateVirtualTableStatement(CreateVirtualTableStatement e, A arg) {
return visitTableInducingStatement(e, arg);
}
@override
R visitCreateTriggerStatement(CreateTriggerStatement e, A arg) {
return visitCreateTriggerStatement(e, arg);
}
R visitBaseSelectStatement(BaseSelectStatement stmt, A arg) {
return visitCrudStatement(stmt, arg);
}
@ -204,6 +211,11 @@ class RecursiveVisitor<A, R> implements AstVisitor<A, R> {
return visitChildren(e, arg);
}
@override
R visitBlock(Block e, A arg) {
return visitChildren(e, arg);
}
// Moor-specific additions
@override
R visitMoorFile(MoorFile e, A arg) {

View File

@ -3,7 +3,7 @@ import 'package:test/test.dart';
import 'utils.dart';
final block = Block([
final _block = Block([
UpdateStatement(table: TableReference('tbl'), set: [
SetComponent(
column: Reference(columnName: 'foo'),
@ -36,7 +36,7 @@ void main() {
mode: TriggerMode.after,
target: const DeleteTarget(),
onTable: TableReference('tbl'),
action: block,
action: _block,
),
);
});
@ -61,7 +61,7 @@ void main() {
Reference(columnName: 'bar'),
]),
onTable: TableReference('tbl'),
action: block,
action: _block,
),
);
});
@ -89,7 +89,7 @@ void main() {
Reference(tableName: 'new', columnName: 'foo'),
NullLiteral(token(TokenType.$null)),
),
action: block,
action: _block,
),
);
});