From 3ee05bf647c342273339eb0a9a775e127821056d Mon Sep 17 00:00:00 2001 From: Simon Binder Date: Fri, 3 Jan 2020 15:38:13 +0100 Subject: [PATCH] Respect tables in the body of a CREATE TRIGGER statement --- moor/test/data/tables/custom_tables.g.dart | 272 +++++++++--------- moor_generator/CHANGELOG.md | 1 + .../lib/src/analyzer/moor/entity_handler.dart | 8 + moor_generator/lib/src/model/trigger.dart | 3 +- .../analyzer/moor/entity_handler_test.dart | 23 +- 5 files changed, 164 insertions(+), 143 deletions(-) diff --git a/moor/test/data/tables/custom_tables.g.dart b/moor/test/data/tables/custom_tables.g.dart index da69af9b..b7020802 100644 --- a/moor/test/data/tables/custom_tables.g.dart +++ b/moor/test/data/tables/custom_tables.g.dart @@ -174,139 +174,6 @@ class ConfigTable extends Table with TableInfo { bool get dontWriteConstraints => true; } -class NoId extends DataClass implements Insertable { - final Uint8List payload; - NoId({@required this.payload}); - factory NoId.fromData(Map data, GeneratedDatabase db, - {String prefix}) { - final effectivePrefix = prefix ?? ''; - final uint8ListType = db.typeSystem.forDartType(); - return NoId( - payload: uint8ListType - .mapFromDatabaseResponse(data['${effectivePrefix}payload']), - ); - } - factory NoId.fromJson(Map json, - {ValueSerializer serializer}) { - serializer ??= moorRuntimeOptions.defaultSerializer; - return NoId( - payload: serializer.fromJson(json['payload']), - ); - } - factory NoId.fromJsonString(String encodedJson, - {ValueSerializer serializer}) => - NoId.fromJson(DataClass.parseJson(encodedJson) as Map, - serializer: serializer); - @override - Map toJson({ValueSerializer serializer}) { - serializer ??= moorRuntimeOptions.defaultSerializer; - return { - 'payload': serializer.toJson(payload), - }; - } - - @override - NoIdsCompanion createCompanion(bool nullToAbsent) { - return NoIdsCompanion( - payload: payload == null && nullToAbsent - ? const Value.absent() - : Value(payload), - ); - } - - NoId copyWith({Uint8List payload}) => NoId( - payload: payload ?? this.payload, - ); - @override - String toString() { - return (StringBuffer('NoId(')..write('payload: $payload')..write(')')) - .toString(); - } - - @override - int get hashCode => $mrjf(payload.hashCode); - @override - bool operator ==(dynamic other) => - identical(this, other) || - (other is NoId && other.payload == this.payload); -} - -class NoIdsCompanion extends UpdateCompanion { - final Value payload; - const NoIdsCompanion({ - this.payload = const Value.absent(), - }); - NoIdsCompanion.insert({ - @required Uint8List payload, - }) : payload = Value(payload); - NoIdsCompanion copyWith({Value payload}) { - return NoIdsCompanion( - payload: payload ?? this.payload, - ); - } -} - -class NoIds extends Table with TableInfo { - final GeneratedDatabase _db; - final String _alias; - NoIds(this._db, [this._alias]); - final VerificationMeta _payloadMeta = const VerificationMeta('payload'); - GeneratedBlobColumn _payload; - GeneratedBlobColumn get payload => _payload ??= _constructPayload(); - GeneratedBlobColumn _constructPayload() { - return GeneratedBlobColumn('payload', $tableName, false, - $customConstraints: 'NOT NULL PRIMARY KEY'); - } - - @override - List get $columns => [payload]; - @override - NoIds get asDslTable => this; - @override - String get $tableName => _alias ?? 'no_ids'; - @override - final String actualTableName = 'no_ids'; - @override - VerificationContext validateIntegrity(NoIdsCompanion d, - {bool isInserting = false}) { - final context = VerificationContext(); - if (d.payload.present) { - context.handle(_payloadMeta, - payload.isAcceptableValue(d.payload.value, _payloadMeta)); - } else if (isInserting) { - context.missing(_payloadMeta); - } - return context; - } - - @override - Set get $primaryKey => {payload}; - @override - NoId map(Map data, {String tablePrefix}) { - final effectivePrefix = tablePrefix != null ? '$tablePrefix.' : null; - return NoId.fromData(data, _db, prefix: effectivePrefix); - } - - @override - Map entityToSql(NoIdsCompanion d) { - final map = {}; - if (d.payload.present) { - map['payload'] = Variable(d.payload.value); - } - return map; - } - - @override - NoIds createAlias(String alias) { - return NoIds(_db, alias); - } - - @override - bool get withoutRowId => true; - @override - bool get dontWriteConstraints => true; -} - class WithDefault extends DataClass implements Insertable { final String a; final int b; @@ -463,6 +330,139 @@ class WithDefaults extends Table with TableInfo { bool get dontWriteConstraints => true; } +class NoId extends DataClass implements Insertable { + final Uint8List payload; + NoId({@required this.payload}); + factory NoId.fromData(Map data, GeneratedDatabase db, + {String prefix}) { + final effectivePrefix = prefix ?? ''; + final uint8ListType = db.typeSystem.forDartType(); + return NoId( + payload: uint8ListType + .mapFromDatabaseResponse(data['${effectivePrefix}payload']), + ); + } + factory NoId.fromJson(Map json, + {ValueSerializer serializer}) { + serializer ??= moorRuntimeOptions.defaultSerializer; + return NoId( + payload: serializer.fromJson(json['payload']), + ); + } + factory NoId.fromJsonString(String encodedJson, + {ValueSerializer serializer}) => + NoId.fromJson(DataClass.parseJson(encodedJson) as Map, + serializer: serializer); + @override + Map toJson({ValueSerializer serializer}) { + serializer ??= moorRuntimeOptions.defaultSerializer; + return { + 'payload': serializer.toJson(payload), + }; + } + + @override + NoIdsCompanion createCompanion(bool nullToAbsent) { + return NoIdsCompanion( + payload: payload == null && nullToAbsent + ? const Value.absent() + : Value(payload), + ); + } + + NoId copyWith({Uint8List payload}) => NoId( + payload: payload ?? this.payload, + ); + @override + String toString() { + return (StringBuffer('NoId(')..write('payload: $payload')..write(')')) + .toString(); + } + + @override + int get hashCode => $mrjf(payload.hashCode); + @override + bool operator ==(dynamic other) => + identical(this, other) || + (other is NoId && other.payload == this.payload); +} + +class NoIdsCompanion extends UpdateCompanion { + final Value payload; + const NoIdsCompanion({ + this.payload = const Value.absent(), + }); + NoIdsCompanion.insert({ + @required Uint8List payload, + }) : payload = Value(payload); + NoIdsCompanion copyWith({Value payload}) { + return NoIdsCompanion( + payload: payload ?? this.payload, + ); + } +} + +class NoIds extends Table with TableInfo { + final GeneratedDatabase _db; + final String _alias; + NoIds(this._db, [this._alias]); + final VerificationMeta _payloadMeta = const VerificationMeta('payload'); + GeneratedBlobColumn _payload; + GeneratedBlobColumn get payload => _payload ??= _constructPayload(); + GeneratedBlobColumn _constructPayload() { + return GeneratedBlobColumn('payload', $tableName, false, + $customConstraints: 'NOT NULL PRIMARY KEY'); + } + + @override + List get $columns => [payload]; + @override + NoIds get asDslTable => this; + @override + String get $tableName => _alias ?? 'no_ids'; + @override + final String actualTableName = 'no_ids'; + @override + VerificationContext validateIntegrity(NoIdsCompanion d, + {bool isInserting = false}) { + final context = VerificationContext(); + if (d.payload.present) { + context.handle(_payloadMeta, + payload.isAcceptableValue(d.payload.value, _payloadMeta)); + } else if (isInserting) { + context.missing(_payloadMeta); + } + return context; + } + + @override + Set get $primaryKey => {payload}; + @override + NoId map(Map data, {String tablePrefix}) { + final effectivePrefix = tablePrefix != null ? '$tablePrefix.' : null; + return NoId.fromData(data, _db, prefix: effectivePrefix); + } + + @override + Map entityToSql(NoIdsCompanion d) { + final map = {}; + if (d.payload.present) { + map['payload'] = Variable(d.payload.value); + } + return map; + } + + @override + NoIds createAlias(String alias) { + return NoIds(_db, alias); + } + + @override + bool get withoutRowId => true; + @override + bool get dontWriteConstraints => true; +} + class WithConstraint extends DataClass implements Insertable { final String a; final int b; @@ -1089,10 +1089,10 @@ abstract class _$CustomTablesDb extends GeneratedDatabase { _$CustomTablesDb.connect(DatabaseConnection c) : super.connect(c); ConfigTable _config; ConfigTable get config => _config ??= ConfigTable(this); - NoIds _noIds; - NoIds get noIds => _noIds ??= NoIds(this); WithDefaults _withDefaults; WithDefaults get withDefaults => _withDefaults ??= WithDefaults(this); + NoIds _noIds; + NoIds get noIds => _noIds ??= NoIds(this); WithConstraints _withConstraints; WithConstraints get withConstraints => _withConstraints ??= WithConstraints(this); @@ -1214,11 +1214,11 @@ abstract class _$CustomTablesDb extends GeneratedDatabase { @override List get allSchemaEntities => [ config, + withDefaults, Trigger( 'CREATE TRIGGER my_trigger AFTER INSERT ON config BEGIN\n INSERT INTO with_defaults VALUES (new.config_key, LENGTH(new.config_value));\nEND;', 'my_trigger'), noIds, - withDefaults, withConstraints, mytable, email diff --git a/moor_generator/CHANGELOG.md b/moor_generator/CHANGELOG.md index 61925191..b8f7e830 100644 --- a/moor_generator/CHANGELOG.md +++ b/moor_generator/CHANGELOG.md @@ -3,6 +3,7 @@ - Support explicit type arguments for queries in moor files. In `foo(:bar AS TEXT, :baz AS INT): SELECT :bar, :baz;`, the column type can now be inferred. Previously, the query would fail because of an unknown type. +- Support `CREATE TRIGGER` statements in moor files ## 2.2.0 diff --git a/moor_generator/lib/src/analyzer/moor/entity_handler.dart b/moor_generator/lib/src/analyzer/moor/entity_handler.dart index 41af3f9b..00b5d0b2 100644 --- a/moor_generator/lib/src/analyzer/moor/entity_handler.dart +++ b/moor_generator/lib/src/analyzer/moor/entity_handler.dart @@ -2,6 +2,7 @@ import 'package:moor_generator/moor_generator.dart'; import 'package:moor_generator/src/analyzer/errors.dart'; import 'package:moor_generator/src/analyzer/runner/results.dart'; import 'package:moor_generator/src/analyzer/runner/steps.dart'; +import 'package:moor_generator/src/analyzer/sql_queries/affected_tables_visitor.dart'; import 'package:moor_generator/src/analyzer/sql_queries/lints/linter.dart'; import 'package:moor_generator/src/analyzer/sql_queries/query_analyzer.dart'; import 'package:sqlparser/sqlparser.dart'; @@ -33,6 +34,7 @@ class EntityHandler extends BaseAnalyzer { for (final trigger in file.declaredEntities.whereType()) { trigger.on = null; + trigger.bodyReferences.clear(); final declaration = trigger.declaration as MoorTriggerDeclaration; final node = declaration.node; @@ -46,6 +48,12 @@ class EntityHandler extends BaseAnalyzer { final linter = Linter(context, mapper); linter.reportLints(); reportLints(linter.lints, name: trigger.displayName); + + // find additional tables that might be referenced in the body + final tablesFinder = ReferencedTablesVisitor(); + node.action.acceptWithoutArg(tablesFinder); + final tablesFromBody = tablesFinder.foundTables.map(mapper.tableToMoor); + trigger.bodyReferences.addAll(tablesFromBody); } } diff --git a/moor_generator/lib/src/model/trigger.dart b/moor_generator/lib/src/model/trigger.dart index 2807bf34..7e1e0cbd 100644 --- a/moor_generator/lib/src/model/trigger.dart +++ b/moor_generator/lib/src/model/trigger.dart @@ -14,6 +14,7 @@ class MoorTrigger implements MoorSchemaEntity { /// /// This field can be null in case the table wasn't resolved. MoorTable on; + List bodyReferences = []; String _create; @@ -28,7 +29,7 @@ class MoorTrigger implements MoorSchemaEntity { } @override - Iterable get references => [on]; + Iterable get references => {on, ...bodyReferences}; /// The `CREATE TRIGGER` statement that can be used to create this trigger. String get create { diff --git a/moor_generator/test/analyzer/moor/entity_handler_test.dart b/moor_generator/test/analyzer/moor/entity_handler_test.dart index 331020eb..de2d6ffc 100644 --- a/moor_generator/test/analyzer/moor/entity_handler_test.dart +++ b/moor_generator/test/analyzer/moor/entity_handler_test.dart @@ -38,7 +38,7 @@ CREATE TABLE friendships ( table.references, [ const TypeMatcher() - .having((table) => table.displayName, 'displayName', 'users') + .having((table) => table.displayName, 'displayName', 'users'), ], ); }); @@ -48,8 +48,16 @@ CREATE TABLE friendships ( '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) +); + CREATE TRIGGER my_trigger AFTER DELETE ON users BEGIN - SELECT * FROM users; + DELETE FROM friendships WHERE user_a = old.id OR user_b = old.id; END; ''', ...definitions, @@ -58,13 +66,16 @@ END; final file = await state.analyze('package:foo/a.moor'); expect(file.errors.errors, isEmpty); - final trigger = file.currentResult.declaredEntities.single; + final trigger = + file.currentResult.declaredEntities.whereType().single; expect( trigger.references, - [ + { + const TypeMatcher().having( + (table) => table.displayName, 'displayName', 'friendships'), const TypeMatcher() - .having((table) => table.displayName, 'displayName', 'users') - ], + .having((table) => table.displayName, 'displayName', 'users'), + }, ); }); });