diff --git a/drift_dev/lib/moor_generator.dart b/drift_dev/lib/moor_generator.dart deleted file mode 100644 index 1eecbc2d..00000000 --- a/drift_dev/lib/moor_generator.dart +++ /dev/null @@ -1 +0,0 @@ -export 'src/model/model.dart'; diff --git a/drift_dev/lib/src/analysis/resolver/dart/table.dart b/drift_dev/lib/src/analysis/resolver/dart/table.dart index 8fe42cbf..7b907f61 100644 --- a/drift_dev/lib/src/analysis/resolver/dart/table.dart +++ b/drift_dev/lib/src/analysis/resolver/dart/table.dart @@ -57,7 +57,10 @@ class DartTableResolver extends LocalElementResolver { customParentClass: dataClassInfo.extending, baseDartName: element.name, primaryKeyFromTableConstraint: primaryKey, - uniqueKeysFromTableConstraint: uniqueKeys ?? const [], + tableConstraints: [ + for (final uniqueKey in uniqueKeys ?? const >[]) + UniqueColumns(uniqueKey), + ], withoutRowId: await _overrideWithoutRowId(element) ?? false, ); diff --git a/drift_dev/lib/src/analysis/resolver/discover.dart b/drift_dev/lib/src/analysis/resolver/discover.dart index ef3bda3c..fd36ffc7 100644 --- a/drift_dev/lib/src/analysis/resolver/discover.dart +++ b/drift_dev/lib/src/analysis/resolver/discover.dart @@ -53,8 +53,6 @@ class DiscoverStep { Future discover() async { final extension = _file.extension; - debugger(when: _file.ownUri.path.endsWith('todos.dart')); - switch (extension) { case '.dart': LibraryElement library; diff --git a/drift_dev/lib/src/analysis/resolver/drift/element_resolver.dart b/drift_dev/lib/src/analysis/resolver/drift/element_resolver.dart index a7f98211..38e47c7f 100644 --- a/drift_dev/lib/src/analysis/resolver/drift/element_resolver.dart +++ b/drift_dev/lib/src/analysis/resolver/drift/element_resolver.dart @@ -1,3 +1,4 @@ +import 'package:collection/collection.dart'; import 'package:analyzer/dart/element/element.dart'; import 'package:analyzer/dart/element/type.dart'; import 'package:sqlparser/sqlparser.dart'; @@ -54,6 +55,10 @@ abstract class DriftElementResolver return resolver.driver.typeMapping.newEngineWithTables(references); } + DriftElement? findInResolved(List references, String name) { + return references.firstWhereOrNull((e) => e.id.sameName(name)); + } + Future> resolveSqlReferences(AstNode stmt) async { final references = resolver.driver.newSqlEngine().findReferencedSchemaTables(stmt); diff --git a/drift_dev/lib/src/analysis/resolver/drift/table.dart b/drift_dev/lib/src/analysis/resolver/drift/table.dart index 9ad00e19..2bd8e231 100644 --- a/drift_dev/lib/src/analysis/resolver/drift/table.dart +++ b/drift_dev/lib/src/analysis/resolver/drift/table.dart @@ -38,6 +38,7 @@ class DriftTableResolver extends LocalElementResolver { } final columns = []; + final tableConstraints = []; for (final column in table.resultColumns) { String? overriddenDartName; @@ -95,6 +96,41 @@ class DriftTableResolver extends LocalElementResolver { )); } + if (stmt is CreateTableStatement) { + for (final constraint in stmt.tableConstraints) { + if (constraint is ForeignKeyTableConstraint) { + final otherTable = await resolveSqlReferenceOrReportError( + constraint.clause.foreignTable.tableName, + (msg) => DriftAnalysisError.inDriftFile( + constraint.clause.foreignTable.tableNameToken ?? constraint, + msg, + ), + ); + + if (otherTable != null) { + final localColumns = [ + for (final column in constraint.columns) + columns.firstWhere((e) => e.nameInSql == column.columnName) + ]; + + final foreignColumns = [ + for (final column in constraint.clause.columnNames) + otherTable.columns + .firstWhere((e) => e.nameInSql == column.columnName) + ]; + + tableConstraints.add(ForeignKeyTable( + localColumns: localColumns, + otherTable: otherTable, + otherColumns: foreignColumns, + onUpdate: constraint.clause.onUpdate, + onDelete: constraint.clause.onDelete, + )); + } + } + } + } + String? dartTableName, dataClassName; ExistingRowClass? existingRowClass; @@ -143,6 +179,7 @@ class DriftTableResolver extends LocalElementResolver { existingRowClass: existingRowClass, withoutRowId: table.withoutRowId, strict: table.isStrict, + tableConstraints: tableConstraints, ); } } diff --git a/drift_dev/lib/src/analysis/resolver/drift/trigger.dart b/drift_dev/lib/src/analysis/resolver/drift/trigger.dart index 99f28e1e..d936509a 100644 --- a/drift_dev/lib/src/analysis/resolver/drift/trigger.dart +++ b/drift_dev/lib/src/analysis/resolver/drift/trigger.dart @@ -1,5 +1,6 @@ import 'package:collection/collection.dart'; import 'package:drift/drift.dart' as drift; +import 'package:sqlparser/sqlparser.dart'; import 'package:sqlparser/utils/find_referenced_tables.dart'; import '../../driver/state.dart'; @@ -46,9 +47,21 @@ class DriftTriggerResolver } } + drift.UpdateKind onWrite; + + if (stmt.target is DeleteTarget) { + onWrite = drift.UpdateKind.delete; + } else if (stmt.target is UpdateTarget) { + onWrite = drift.UpdateKind.update; + } else { + onWrite = drift.UpdateKind.insert; + } + return DriftTrigger( discovered.ownId, DriftDeclaration.driftFile(stmt, file.ownUri), + on: findInResolved(references, stmt.onTable.tableName) as DriftTable?, + onWrite: onWrite, references: references, createStmt: source.substring(stmt.firstPosition, stmt.lastPosition), writes: findWrittenTables(stmt) diff --git a/drift_dev/lib/src/analysis/results/element.dart b/drift_dev/lib/src/analysis/results/element.dart index 298d0afe..47b2751d 100644 --- a/drift_dev/lib/src/analysis/results/element.dart +++ b/drift_dev/lib/src/analysis/results/element.dart @@ -2,6 +2,7 @@ import 'package:analyzer/dart/element/element.dart'; import 'package:json_annotation/json_annotation.dart'; import 'package:meta/meta.dart'; import 'package:path/path.dart' show url; +import 'package:recase/recase.dart'; import 'package:sqlparser/sqlparser.dart'; part '../../generated/analysis/results/element.g.dart'; @@ -77,4 +78,13 @@ abstract class DriftSchemaElement extends DriftElement { /// The exact, unaliased name of this element in the database's schema. String get schemaName => id.name; + + /// The getter in a generated database accessor referring to this model. + /// + /// Returns null for entities that shouldn't have a getter. + String? get dbGetterName; + + static String dbFieldName(String baseName) { + return ReCase(baseName).camelCase; + } } diff --git a/drift_dev/lib/src/analysis/results/index.dart b/drift_dev/lib/src/analysis/results/index.dart index e77ef0a4..1653536f 100644 --- a/drift_dev/lib/src/analysis/results/index.dart +++ b/drift_dev/lib/src/analysis/results/index.dart @@ -5,7 +5,7 @@ import 'table.dart'; /// /// Currently, indices can only be created through a `CREATE INDEX` statement in /// a `.drift` file. -class DriftIndex extends DriftElement { +class DriftIndex extends DriftSchemaElement { /// The table on which this index is created. /// /// This may be null if the table couldn't be resolved. @@ -25,6 +25,9 @@ class DriftIndex extends DriftElement { required this.createStmt, }); + @override + String get dbGetterName => DriftSchemaElement.dbFieldName(id.name); + @override Iterable get references => [if (table != null) table!]; } diff --git a/drift_dev/lib/src/analysis/results/table.dart b/drift_dev/lib/src/analysis/results/table.dart index fff01862..c63cf382 100644 --- a/drift_dev/lib/src/analysis/results/table.dart +++ b/drift_dev/lib/src/analysis/results/table.dart @@ -1,4 +1,5 @@ import 'package:drift/drift.dart' show DriftSqlType; +import 'package:sqlparser/sqlparser.dart' as sql; import 'dart.dart'; import 'element.dart'; @@ -10,8 +11,9 @@ class DriftTable extends DriftElementWithResultSet { @override final List columns; + final List tableConstraints; + final Set? primaryKeyFromTableConstraint; - final List> uniqueKeysFromTableConstraint; @override final List references; @@ -48,13 +50,16 @@ class DriftTable extends DriftElementWithResultSet { this.withoutRowId = false, this.strict = false, this.primaryKeyFromTableConstraint, - this.uniqueKeysFromTableConstraint = const [], + this.tableConstraints = const [], }) { for (final column in columns) { column.owner = this; } } + @override + String get dbGetterName => DriftSchemaElement.dbFieldName(baseDartName); + /// The primary key for this table, computed by looking at the /// [primaryKeyFromTableConstraint] and primary key constraints applied to /// individiual columns. @@ -108,6 +113,32 @@ class DriftTable extends DriftElementWithResultSet { } return name; } + + static String _tableInfoNameForTableClass(String className) => + '\$${className}Table'; } -String _tableInfoNameForTableClass(String className) => '\$${className}Table'; +abstract class DriftTableConstraint {} + +class UniqueColumns extends DriftTableConstraint { + final Set uniqueSet; + + UniqueColumns(this.uniqueSet); +} + +class ForeignKeyTable extends DriftTableConstraint { + final List localColumns; + final DriftTable otherTable; + final List otherColumns; + + final sql.ReferenceAction? onUpdate; + final sql.ReferenceAction? onDelete; + + ForeignKeyTable({ + required this.localColumns, + required this.otherTable, + required this.otherColumns, + this.onUpdate, + this.onDelete, + }); +} diff --git a/drift_dev/lib/src/analysis/results/trigger.dart b/drift_dev/lib/src/analysis/results/trigger.dart index 63120242..dd560614 100644 --- a/drift_dev/lib/src/analysis/results/trigger.dart +++ b/drift_dev/lib/src/analysis/results/trigger.dart @@ -1,7 +1,16 @@ +import 'package:drift/drift.dart'; + import 'element.dart'; import 'query.dart'; +import 'table.dart'; + +class DriftTrigger extends DriftSchemaElement { + /// The table whose writes trigger this trigger. + final DriftTable? on; + + /// The kind of write (insert, update, delete) causing this trigger to run. + final UpdateKind onWrite; -class DriftTrigger extends DriftElement { @override final List references; @@ -14,8 +23,13 @@ class DriftTrigger extends DriftElement { DriftTrigger( super.id, super.declaration, { + required this.on, + required this.onWrite, required this.references, required this.createStmt, required this.writes, }); + + @override + String get dbGetterName => DriftSchemaElement.dbFieldName(id.name); } diff --git a/drift_dev/lib/src/analysis/results/view.dart b/drift_dev/lib/src/analysis/results/view.dart index 0bbeb553..c29674ff 100644 --- a/drift_dev/lib/src/analysis/results/view.dart +++ b/drift_dev/lib/src/analysis/results/view.dart @@ -39,6 +39,9 @@ class DriftView extends DriftElementWithResultSet { required this.references, }); + @override + String get dbGetterName => DriftSchemaElement.dbFieldName(id.name); + /// Obtains all tables transitively referenced by the declaration of this /// view. /// diff --git a/drift_dev/lib/src/analysis/serializer.dart b/drift_dev/lib/src/analysis/serializer.dart index af1bff69..123958c1 100644 --- a/drift_dev/lib/src/analysis/serializer.dart +++ b/drift_dev/lib/src/analysis/serializer.dart @@ -30,9 +30,9 @@ class ElementSerializer { 'primary_key_table_constraint': element.primaryKeyFromTableConstraint ?.map((e) => e.nameInSql) .toList(), - 'unique_keys_table_constraint': [ - for (final unique in element.uniqueKeysFromTableConstraint) - [for (final column in unique) column.nameInSql] + 'table_constraints': [ + for (final constraint in element.tableConstraints) + _serializeTableConstraint(constraint), ], 'custom_parent_class': element.customParentClass?.toJson(), 'fixed_entity_info_name': element.fixedEntityInfoName, @@ -57,6 +57,8 @@ class ElementSerializer { additionalInformation = { 'type': 'trigger', 'sql': element.createStmt, + if (element.on != null) 'on': _serializeElementReference(element.on!), + 'onWrite': element.onWrite.name, 'writes': [ for (final write in element.writes) { @@ -164,8 +166,8 @@ class ElementSerializer { return { 'type': 'foreign_key', 'column': _serializeColumnReference(constraint.otherColumn), - 'onUpdate': constraint.onUpdate?.name, - 'onDelete': constraint.onDelete?.name, + 'onUpdate': _serializeReferenceAction(constraint.onUpdate), + 'onDelete': _serializeReferenceAction(constraint.onDelete), }; } else if (constraint is ColumnGeneratedAs) { return {'type': 'generated_as', ...constraint.toJson()}; @@ -178,6 +180,36 @@ class ElementSerializer { } } + Map _serializeTableConstraint( + DriftTableConstraint constraint) { + if (constraint is UniqueColumns) { + return { + 'type': 'unique', + 'columns': [for (final column in constraint.uniqueSet) column.nameInSql] + }; + } else if (constraint is ForeignKeyTable) { + return { + 'type': 'foreign', + 'local': [ + for (final column in constraint.localColumns) column.nameInSql, + ], + 'table': _serializeElementReference(constraint.otherTable), + 'foreign': [ + for (final column in constraint.otherColumns) + _serializeColumn(column), + ], + 'onUpdate': _serializeReferenceAction(constraint.onUpdate), + 'onDelete': _serializeReferenceAction(constraint.onDelete), + }; + } else { + throw UnimplementedError('Unsupported table constraint: $constraint'); + } + } + + String? _serializeReferenceAction(ReferenceAction? action) { + return action?.name; + } + Map _serializeTypeConverter(AppliedTypeConverter converter) { return { 'expression': converter.expression.toJson(), @@ -363,15 +395,6 @@ class ElementDeserializer { }; } - List> uniqueKeysFromTableConstraint = const []; - final serializedUnique = json['unique_keys_table_constraint']; - if (serializedUnique != null) { - uniqueKeysFromTableConstraint = [ - for (final entry in serializedUnique) - {for (final column in entry) columnByName[column]!}, - ]; - } - return DriftTable( id, declaration, @@ -381,7 +404,10 @@ class ElementDeserializer { ? ExistingRowClass.fromJson(json['existing_data_class'] as Map) : null, primaryKeyFromTableConstraint: primaryKeyFromTableConstraint, - uniqueKeysFromTableConstraint: uniqueKeysFromTableConstraint, + tableConstraints: [ + for (final constraint in json['table_constraints']) + await _readTableConstraint(constraint as Map, columnByName), + ], customParentClass: json['custom_parent_class'] != null ? AnnotatedDartCode.fromJson(json['custom_parent_class'] as Map) : null, @@ -408,11 +434,19 @@ class ElementDeserializer { resultClassName: json['result_class'] as String?, ); case 'trigger': + DriftTable? on; + + if (json['on'] != null) { + on = await _readElementReference(json['on'] as Map) as DriftTable; + } + return DriftTrigger( id, declaration, references: references, createStmt: json['sql'] as String, + on: on, + onWrite: UpdateKind.values.byName(json['onWrite'] as String), writes: [ for (final write in json['writes']) WrittenDriftTable( @@ -543,6 +577,10 @@ class ElementDeserializer { ); } + ReferenceAction? _readAction(String? value) { + return value == null ? null : ReferenceAction.values.byName(value); + } + Future _readConstraint(Map json) async { final type = json['type'] as String; @@ -552,14 +590,10 @@ class ElementDeserializer { case 'primary': return PrimaryKeyColumn.fromJson(json); case 'foreign_key': - ReferenceAction? readAction(String? value) { - return value == null ? null : ReferenceAction.values.byName(value); - } - return ForeignKeyReference( await _readDriftColumnReference(json['column'] as Map), - readAction(json['onUpdate'] as String?), - readAction(json['onDelete'] as String?), + _readAction(json['onUpdate'] as String?), + _readAction(json['onDelete'] as String?), ); case 'generated_as': return ColumnGeneratedAs.fromJson(json); @@ -571,6 +605,34 @@ class ElementDeserializer { throw UnimplementedError('Unsupported constraint: $type'); } } + + Future _readTableConstraint( + Map json, Map localColumns) async { + final type = json['type'] as String; + + switch (type) { + case 'unique': + return UniqueColumns({ + for (final ref in json['columns']) localColumns[ref]!, + }); + case 'foreign': + return ForeignKeyTable( + localColumns: [ + for (final ref in json['local']) localColumns[ref]!, + ], + otherTable: + await _readDriftElement(json['table'] as Map) as DriftTable, + otherColumns: [ + for (final ref in json['foreign']) + await _readDriftColumnReference(ref as Map) + ], + onUpdate: _readAction(json['onUpdate'] as String?), + onDelete: _readAction(json['onDelete'] as String?), + ); + default: + throw UnimplementedError('Unsupported constraint: $type'); + } + } } class CouldNotDeserializeException implements Exception { diff --git a/drift_dev/lib/src/backends/build/drift_builder.dart b/drift_dev/lib/src/backends/build/drift_builder.dart index e48b5af6..0ed9bd4a 100644 --- a/drift_dev/lib/src/backends/build/drift_builder.dart +++ b/drift_dev/lib/src/backends/build/drift_builder.dart @@ -1,7 +1,12 @@ import 'package:build/build.dart'; +import 'package:dart_style/dart_style.dart'; import '../../analysis/driver/driver.dart'; +import '../../analysis/results/results.dart'; import '../../analyzer/options.dart'; +import '../../writer/database_writer.dart'; +import '../../writer/import_manager.dart'; +import '../../writer/writer.dart'; import 'backend.dart'; class _BuilderFlags { @@ -75,12 +80,30 @@ class DriftBuilder extends Builder { final result = await driver.resolveElements(buildStep.inputId.uri); - final buffer = StringBuffer(); + final generationOptions = GenerationOptions( + imports: ImportManagerForPartFiles(), + ); + final writer = Writer(options, generationOptions: generationOptions); + for (final element in result.analysis.values) { - buffer.writeln('// ${element.ownId}'); + final result = element.result; + + if (result is DriftDatabase) { + DatabaseWriter(result, writer.child()).write(); + } else { + writer.leaf().writeln('// ${element.ownId}'); + } } - await buildStep.writeAsString( - buildStep.allowedOutputs.single, buffer.toString()); + var generated = writer.writeGenerated(); + + try { + generated = DartFormatter().format(generated); + } on FormatterException { + log.warning('Could not format generated source. The generated code is ' + 'probably invalid, and this is most likely a bug in drift_dev.'); + } + + await buildStep.writeAsString(buildStep.allowedOutputs.single, generated); } } diff --git a/drift_dev/lib/src/services/find_stream_update_rules.dart b/drift_dev/lib/src/services/find_stream_update_rules.dart index d16c1ad2..ee14a93b 100644 --- a/drift_dev/lib/src/services/find_stream_update_rules.dart +++ b/drift_dev/lib/src/services/find_stream_update_rules.dart @@ -1,18 +1,18 @@ -import 'package:collection/collection.dart'; -import 'package:drift/drift.dart'; -import 'package:drift_dev/moor_generator.dart'; +import 'package:drift/drift.dart' hide DriftDatabase; import 'package:sqlparser/sqlparser.dart'; +import '../analysis/results/results.dart'; + class FindStreamUpdateRules { - final Database db; + final DriftDatabase db; FindStreamUpdateRules(this.db); StreamQueryUpdateRules identifyRules() { final rules = []; - for (final entity in db.entities) { - if (entity is MoorTrigger) { + for (final entity in db.references) { + if (entity is DriftTrigger) { _writeRulesForTrigger(entity, rules); } else if (entity is DriftTable) { _writeRulesForTable(entity, rules); @@ -23,80 +23,72 @@ class FindStreamUpdateRules { } void _writeRulesForTable(DriftTable table, List rules) { - final declaration = table.declaration; - - // We only know about foreign key clauses from tables in moor files - if (declaration is! DriftTableDeclaration) return; - - if (declaration.node is! CreateTableStatement) return; - - final stmt = declaration.node as CreateTableStatement; - final tableName = table.sqlName; - - for (final fkClause in stmt.allDescendants.whereType()) { - final referencedMoorTable = table.references.firstWhereOrNull( - (tbl) => tbl.sqlName == fkClause.foreignTable.tableName, - ); - - void writeRule(UpdateKind listen, ReferenceAction? action) { - TableUpdate? effect; - switch (action) { - case ReferenceAction.setNull: - case ReferenceAction.setDefault: - effect = TableUpdate(tableName, kind: UpdateKind.update); - break; - case ReferenceAction.cascade: - effect = TableUpdate(tableName, kind: listen); - break; - default: - break; - } - - if (effect != null) { - rules.add( - WritePropagation( - on: TableUpdateQuery.onTableName( - referencedMoorTable!.sqlName, - limitUpdateKind: listen, - ), - result: [effect], - ), - ); - } + void writeRule( + DriftElement referenced, UpdateKind listen, ReferenceAction? action) { + TableUpdate? effect; + switch (action) { + case ReferenceAction.setNull: + case ReferenceAction.setDefault: + effect = TableUpdate(table.id.name, kind: UpdateKind.update); + break; + case ReferenceAction.cascade: + effect = TableUpdate(table.id.name, kind: listen); + break; + default: + break; } - if (referencedMoorTable == null) continue; - writeRule(UpdateKind.delete, fkClause.onDelete); - writeRule(UpdateKind.update, fkClause.onUpdate); + if (effect != null) { + rules.add( + WritePropagation( + on: TableUpdateQuery.onTableName( + referenced.id.name, + limitUpdateKind: listen, + ), + result: [effect], + ), + ); + } + } + + // Write update rules from fk constraints applied to columns + for (final column in table.columns) { + for (final constraint in column.constraints) { + if (constraint is ForeignKeyReference) { + writeRule(constraint.otherColumn.owner, UpdateKind.delete, + constraint.onDelete); + writeRule(constraint.otherColumn.owner, UpdateKind.update, + constraint.onUpdate); + } + } + } + + // And to those declared on the whole table + for (final constraint in table.tableConstraints) { + if (constraint is ForeignKeyTable) { + writeRule( + constraint.otherTable, UpdateKind.delete, constraint.onDelete); + writeRule( + constraint.otherTable, UpdateKind.update, constraint.onUpdate); + } } } - void _writeRulesForTrigger(MoorTrigger trigger, List rules) { - final declaration = trigger.declaration; - - if (declaration is! DriftTriggerDeclaration) return; - - final target = declaration.node.target; - UpdateKind targetKind; - if (target is DeleteTarget) { - targetKind = UpdateKind.delete; - } else if (target is InsertTarget) { - targetKind = UpdateKind.insert; - } else { - targetKind = UpdateKind.update; - } - - rules.add( - WritePropagation( - on: TableUpdateQuery.onTableName( - trigger.on!.sqlName, - limitUpdateKind: targetKind, + void _writeRulesForTrigger(DriftTrigger trigger, List rules) { + final on = trigger.on; + if (on != null) { + rules.add( + WritePropagation( + on: TableUpdateQuery.onTableName( + on.id.name, + limitUpdateKind: trigger.onWrite, + ), + result: [ + for (final update in trigger.writes) + TableUpdate(update.table.id.name, kind: update.kind) + ], ), - result: [ - for (final update in trigger.bodyUpdates) - TableUpdate(update.table.sqlName, kind: update.kind) - ], - ), - ); + ); + } } } diff --git a/drift_dev/lib/src/writer/database_writer.dart b/drift_dev/lib/src/writer/database_writer.dart index c09aaef2..07b93c42 100644 --- a/drift_dev/lib/src/writer/database_writer.dart +++ b/drift_dev/lib/src/writer/database_writer.dart @@ -1,19 +1,18 @@ -import 'package:drift/drift.dart'; +import 'package:drift/drift.dart' as drift; // ignore: implementation_imports import 'package:drift/src/runtime/executor/stream_queries.dart'; -import 'package:drift_dev/moor_generator.dart'; -import 'package:drift_dev/src/services/find_stream_update_rules.dart'; -import 'package:drift_dev/src/utils/string_escaper.dart'; -import 'package:drift_dev/src/utils/type_utils.dart'; -import 'package:drift_dev/writer.dart'; +import 'package:drift_dev/src/writer/utils/memoized_getter.dart'; import 'package:recase/recase.dart'; -import 'tables/view_writer.dart'; +import '../analysis/results/results.dart'; +import '../services/find_stream_update_rules.dart'; +import '../utils/string_escaper.dart'; +import 'writer.dart'; /// Generates the Dart code put into a `.g.dart` file when running the /// generator. class DatabaseWriter { - final Database db; + final DriftDatabase db; final Scope scope; DatabaseWriter(this.db, this.scope); @@ -23,16 +22,17 @@ class DatabaseWriter { return 'DatabaseAtV${scope.generationOptions.forSchema}'; } - return '_\$${db.fromClass!.name}'; + return '_\$${db.id.name}'; } void write() { - // Write referenced tables and views - for (final table in db.tables) { - TableWriter(table, scope.child()).writeInto(); - } - for (final view in db.views) { - ViewWriter(view, scope.child(), this).write(); + // Write data classes, companions and info classes + for (final reference in db.references) { + if (reference is DriftTable) { +// TableWriter(table, scope.child()).writeInto(); + } else if (reference is DriftView) { +// ViewWriter(view, scope.child(), this).write(); + } } // Write the database class @@ -45,18 +45,21 @@ class DatabaseWriter { firstLeaf.write('abstract '); } - firstLeaf.write('class $className extends GeneratedDatabase {\n' - '$className(QueryExecutor e) : ' - 'super(e); \n'); + firstLeaf + ..write('class $className extends ') + ..writeDriftRef('GeneratedDatabase') + ..writeln('{') + ..writeln( + '$className(${firstLeaf.refDrift('QueryExecutor e')}: super(e);'); if (dbScope.options.generateConnectConstructor) { - firstLeaf.write( - '$className.connect(DatabaseConnection c): super.connect(c); \n'); + final conn = firstLeaf.refDrift('DatabaseConnection'); + firstLeaf.write('$className.connect($conn c): super.connect(c); \n'); } - final entityGetters = {}; + final entityGetters = {}; - for (final entity in db.entities) { + for (final entity in db.references.whereType()) { final getterName = entity.dbGetterName; if (getterName != null) { entityGetters[entity] = getterName; @@ -66,30 +69,31 @@ class DatabaseWriter { final tableClassName = entity.entityInfoName; writeMemoizedGetter( - buffer: dbScope.leaf(), + buffer: dbScope.leaf().buffer, getterName: entity.dbGetterName, returnType: tableClassName, code: '$tableClassName(this)', ); - } else if (entity is MoorTrigger) { + } /* else if (entity is DriftTrigger) { writeMemoizedGetter( - buffer: dbScope.leaf(), + buffer: dbScope.leaf().buffer, getterName: entity.dbGetterName, returnType: 'Trigger', code: 'Trigger(${asDartLiteral(entity.createSql(scope.options))}, ' '${asDartLiteral(entity.displayName)})', ); - } else if (entity is MoorIndex) { + } else if (entity is DriftIndex) { writeMemoizedGetter( - buffer: dbScope.leaf(), + buffer: dbScope.leaf().buffer, getterName: entity.dbGetterName, returnType: 'Index', code: 'Index(${asDartLiteral(entity.displayName)}, ' '${asDartLiteral(entity.createSql(scope.options))})', ); - } else if (entity is MoorView) { + } */ + else if (entity is DriftView) { writeMemoizedGetter( - buffer: dbScope.leaf(), + buffer: dbScope.leaf().buffer, getterName: entity.dbGetterName, returnType: entity.entityInfoName, code: '${entity.entityInfoName}(this)', @@ -98,13 +102,13 @@ class DatabaseWriter { } // Write fields to access an dao. We use a lazy getter for that. - for (final dao in db.daos) { - final typeName = dao.codeString(); + for (final dao in db.accessorTypes) { + final typeName = firstLeaf.dartCode(dao); final getterName = ReCase(typeName).camelCase; - final databaseImplName = db.fromClass!.name; + final databaseImplName = db.id.name; writeMemoizedGetter( - buffer: dbScope.leaf(), + buffer: dbScope.leaf().buffer, getterName: getterName, returnType: typeName, code: '$typeName(this as $databaseImplName)', @@ -112,10 +116,11 @@ class DatabaseWriter { } // Write implementation for query methods - db.queries?.forEach((query) => QueryWriter(dbScope.child()).write(query)); +// db.queries?.forEach((query) => QueryWriter(dbScope.child()).write(query)); // Write List of tables final schemaScope = dbScope.leaf(); +/* schemaScope ..write( '@override\nIterable> get allTables => ') @@ -134,6 +139,7 @@ class DatabaseWriter { }).join(', ')) // close list literal and allSchemaEntities getter ..write('];\n'); +*/ final updateRules = FindStreamUpdateRules(db).identifyRules(); if (updateRules.rules.isNotEmpty) { @@ -159,10 +165,12 @@ class DatabaseWriter { if (scope.options.storeDateTimeValuesAsText) { // Override database options to reflect that DateTimes are stored as text. + final options = schemaScope.refDrift('DriftDatabaseOptions'); + schemaScope ..writeln('@override') - ..writeln('DriftDatabaseOptions get options => ' - 'const DriftDatabaseOptions(storeDateTimeAsText: true);'); + ..writeln('$options get options => ' + 'const $options(storeDateTimeAsText: true);'); } // close the class @@ -170,57 +178,74 @@ class DatabaseWriter { } } -const _kindToDartExpr = { - UpdateKind.delete: 'UpdateKind.delete', - UpdateKind.insert: 'UpdateKind.insert', - UpdateKind.update: 'UpdateKind.update', - null: 'null', -}; +extension on drift.UpdateRule { + void writeConstructor(TextEmitter emitter) { + if (this is drift.WritePropagation) { + final write = this as drift.WritePropagation; -extension on UpdateRule { - void writeConstructor(StringBuffer buffer) { - if (this is WritePropagation) { - final write = this as WritePropagation; - - buffer.write('WritePropagation(on: '); - write.on.writeConstructor(buffer); - buffer.write(', result: ['); + emitter + ..writeDriftRef('WritePropagation') + ..write('(on: '); + write.on.writeConstructor(emitter); + emitter.write(', result: ['); for (final update in write.result) { - update.writeConstructor(buffer); - buffer.write(', '); + update.writeConstructor(emitter); + emitter.write(', '); } - buffer.write('],)'); + emitter.write('],)'); } } } -extension on TableUpdate { - void writeConstructor(StringBuffer buffer) { - buffer.write( - 'TableUpdate(${asDartLiteral(table)}, kind: ${_kindToDartExpr[kind]})'); +extension on drift.TableUpdate { + void writeConstructor(TextEmitter emitter) { + emitter + ..writeDriftRef('TableUpdate') + ..write('(${asDartLiteral(table)})'); + + if (kind == null) { + emitter.write(')'); + } else { + emitter.write(', kind: '); + kind!.write(emitter); + } } } -extension on TableUpdateQuery { - void writeConstructor(StringBuffer buffer) { +extension on drift.TableUpdateQuery { + void writeConstructor(TextEmitter emitter) { + emitter.writeDriftRef('TableUpdateQuery'); + if (this is AnyUpdateQuery) { - buffer.write('TableUpdateQuery.any()'); + emitter.write('.any()'); } else if (this is SpecificUpdateQuery) { final query = this as SpecificUpdateQuery; - buffer - .write('TableUpdateQuery.onTableName(${asDartLiteral(query.table)}, ' - 'limitUpdateKind: ${_kindToDartExpr[query.limitUpdateKind]})'); + emitter.write('.onTableName(${asDartLiteral(query.table)} '); + + if (query.limitUpdateKind != null) { + emitter.write(', '); + query.limitUpdateKind!.write(emitter); + } + emitter.write(')'); } else if (this is MultipleUpdateQuery) { final queries = (this as MultipleUpdateQuery).queries; - buffer.write('TableUpdateQuery.allOf(['); + emitter.write('.allOf(['); for (final query in queries) { - query.writeConstructor(buffer); - buffer.write(', '); + query.writeConstructor(emitter); + emitter.write(', '); } - buffer.write('])'); + emitter.write('])'); } } } + +extension on drift.UpdateKind { + void write(TextEmitter emitter) { + emitter + ..writeDriftRef('UpdateKind') + ..write('.$name'); + } +} diff --git a/drift_dev/lib/src/writer/import_manager.dart b/drift_dev/lib/src/writer/import_manager.dart new file mode 100644 index 00000000..a3b68a60 --- /dev/null +++ b/drift_dev/lib/src/writer/import_manager.dart @@ -0,0 +1,10 @@ +abstract class ImportManager { + String? prefixFor(Uri definitionUri, String elementName); +} + +class ImportManagerForPartFiles extends ImportManager { + @override + String? prefixFor(Uri definitionUri, String elementName) { + return null; // todo: Find import alias from existing imports? + } +} diff --git a/drift_dev/lib/src/writer/writer.dart b/drift_dev/lib/src/writer/writer.dart index 78053d8e..d6f91513 100644 --- a/drift_dev/lib/src/writer/writer.dart +++ b/drift_dev/lib/src/writer/writer.dart @@ -1,5 +1,8 @@ -import 'package:drift_dev/moor_generator.dart'; -import 'package:drift_dev/src/analyzer/options.dart'; +import '../analysis/results/results.dart'; +import '../analyzer/options.dart'; +import 'import_manager.dart'; + +Uri _driftImport = Uri.parse('package:drift/drift.dart'); /// Manages a tree structure which we use to generate code. /// @@ -11,11 +14,19 @@ import 'package:drift_dev/src/analyzer/options.dart'; /// passing a [Scope] we will always be able to write code in a parent scope. class Writer { late final Scope _root; + late final TextEmitter _header; + late final TextEmitter _imports; + final DriftOptions options; final GenerationOptions generationOptions; - Writer(this.options, {this.generationOptions = const GenerationOptions()}) { + TextEmitter get header => _header; + TextEmitter get imports => _imports; + + Writer(this.options, {required this.generationOptions}) { _root = Scope(parent: null, writer: this); + _header = leaf(); + _imports = leaf(); } /// Returns the code generated by this [Writer]. @@ -23,7 +34,7 @@ class Writer { Iterable _leafNodes(Scope scope) sync* { for (final child in scope._children) { - if (child is _LeafNode) { + if (child is TextEmitter) { yield child.buffer; } else if (child is Scope) { yield* _leafNodes(child); @@ -32,7 +43,7 @@ class Writer { } Scope child() => _root.child(); - StringBuffer leaf() => _root.leaf(); + TextEmitter leaf() => _root.leaf(); } abstract class _Node { @@ -49,7 +60,6 @@ abstract class _Node { /// we just pass a single [StringBuffer] around, this is annoying to manage. class Scope extends _Node { final List<_Node> _children = []; - final DartScope scope; final Writer writer; /// An arbitrary counter. @@ -58,8 +68,7 @@ class Scope extends _Node { int counter = 0; Scope({required Scope? parent, Writer? writer}) - : scope = parent?.scope.nextLevel ?? DartScope.library, - writer = writer ?? parent!.writer, + : writer = writer ?? parent!.writer, super(parent); DriftOptions get options => writer.options; @@ -74,29 +83,65 @@ class Scope extends _Node { return found; } - Iterable get _thisAndParents sync* { - Scope? scope = this; - do { - yield scope!; - scope = scope.parent; - } while (scope != null); - } - - Scope findScopeOfLevel(DartScope level) { - return _thisAndParents - .firstWhere((scope) => scope.scope.isSuperScope(level)); - } - Scope child() { final child = Scope(parent: this); _children.add(child); return child; } - StringBuffer leaf() { - final child = _LeafNode(this); + TextEmitter leaf() { + final child = TextEmitter(this); _children.add(child); - return child.buffer; + return child; + } +} + +class TextEmitter extends _Node { + final StringBuffer buffer = StringBuffer(); + final Writer writer; + + TextEmitter(Scope super.parent) : writer = parent.writer; + + void write(Object? object) => buffer.write(object); + void writeln(Object? object) => buffer.writeln(object); + + void writeUriRef(Uri definition, String element) { + return write(refUri(definition, element)); + } + + void writeDriftRef(String element) => write(refDrift(element)); + + String refUri(Uri definition, String element) { + final prefix = + writer.generationOptions.imports.prefixFor(definition, element); + + if (prefix == null) { + return element; + } else { + return '$prefix.$element'; + } + } + + String refDrift(String element) => refUri(_driftImport, element); + + String dartCode(AnnotatedDartCode code) { + final buffer = StringBuffer(); + + for (final lexeme in code.elements) { + if (lexeme is DartTopLevelSymbol) { + final uri = lexeme.importUri; + + if (uri != null) { + buffer.write(refUri(uri, lexeme.lexeme)); + } else { + buffer.write(lexeme.lexeme); + } + } else { + buffer.write(lexeme); + } + } + + return buffer.toString(); } } @@ -114,7 +159,10 @@ class GenerationOptions { /// Whether companions should be generated. final bool writeCompanions; + final ImportManager imports; + const GenerationOptions({ + required this.imports, this.forSchema, this.writeDataClasses = true, this.writeCompanions = true, @@ -125,47 +173,6 @@ class GenerationOptions { bool get isGeneratingForSchema => forSchema != null; } -class _LeafNode extends _Node { - final StringBuffer buffer = StringBuffer(); - - _LeafNode(Scope parent) : super(parent); -} - -class DartScope { - static const DartScope library = DartScope._(0); - static const DartScope topLevelMember = DartScope._(1); - static const DartScope inner = DartScope._(2); - - static const List values = [library, topLevelMember, inner]; - - final int _id; - - const DartScope._(this._id); - - DartScope get nextLevel { - if (_id == values.length - 1) { - // already in innermost level - return this; - } - return values[_id + 1]; - } - - bool isSuperScope(DartScope other) { - return other._id >= _id; - } -} - extension WriterUtilsForOptions on DriftOptions { String get fieldModifier => generateMutableClasses ? '' : 'final'; } - -extension WriterUtilsForColumns on DriftColumn { - /// Adds an `this.` prefix is the [dartGetterName] is in [locals]. - String thisIfNeeded(Set locals) { - if (locals.contains(dartGetterName)) { - return 'this.$dartGetterName'; - } - - return dartGetterName; - } -} diff --git a/drift_dev/lib/writer.dart b/drift_dev/lib/writer.dart deleted file mode 100644 index f67ff50a..00000000 --- a/drift_dev/lib/writer.dart +++ /dev/null @@ -1,15 +0,0 @@ -/// Provides access to the [Writer], which can generate Dart code for parsed -/// databases, daos, queries, tables and more. -library writer; - -import 'src/writer/writer.dart' show Writer; - -export 'src/writer/database_writer.dart'; -export 'src/writer/queries/query_writer.dart'; -export 'src/writer/queries/result_set_writer.dart'; -export 'src/writer/tables/data_class_writer.dart'; -export 'src/writer/tables/table_writer.dart'; -export 'src/writer/tables/update_companion_writer.dart'; -export 'src/writer/utils/memoized_getter.dart'; -export 'src/writer/utils/hash_and_equals.dart'; -export 'src/writer/writer.dart';