Start generating code with new analyzer

This commit is contained in:
Simon Binder 2022-10-02 23:15:11 +02:00
parent dcdbcb7156
commit ef1b94af1d
No known key found for this signature in database
GPG Key ID: 7891917E4147B8C0
18 changed files with 476 additions and 256 deletions

View File

@ -1 +0,0 @@
export 'src/model/model.dart';

View File

@ -57,7 +57,10 @@ class DartTableResolver extends LocalElementResolver<DiscoveredDartTable> {
customParentClass: dataClassInfo.extending,
baseDartName: element.name,
primaryKeyFromTableConstraint: primaryKey,
uniqueKeysFromTableConstraint: uniqueKeys ?? const [],
tableConstraints: [
for (final uniqueKey in uniqueKeys ?? const <Set<DriftColumn>>[])
UniqueColumns(uniqueKey),
],
withoutRowId: await _overrideWithoutRowId(element) ?? false,
);

View File

@ -53,8 +53,6 @@ class DiscoverStep {
Future<void> discover() async {
final extension = _file.extension;
debugger(when: _file.ownUri.path.endsWith('todos.dart'));
switch (extension) {
case '.dart':
LibraryElement library;

View File

@ -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<T extends DiscoveredElement>
return resolver.driver.typeMapping.newEngineWithTables(references);
}
DriftElement? findInResolved(List<DriftElement> references, String name) {
return references.firstWhereOrNull((e) => e.id.sameName(name));
}
Future<List<DriftElement>> resolveSqlReferences(AstNode stmt) async {
final references =
resolver.driver.newSqlEngine().findReferencedSchemaTables(stmt);

View File

@ -38,6 +38,7 @@ class DriftTableResolver extends LocalElementResolver<DiscoveredDriftTable> {
}
final columns = <DriftColumn>[];
final tableConstraints = <DriftTableConstraint>[];
for (final column in table.resultColumns) {
String? overriddenDartName;
@ -95,6 +96,41 @@ class DriftTableResolver extends LocalElementResolver<DiscoveredDriftTable> {
));
}
if (stmt is CreateTableStatement) {
for (final constraint in stmt.tableConstraints) {
if (constraint is ForeignKeyTableConstraint) {
final otherTable = await resolveSqlReferenceOrReportError<DriftTable>(
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<DiscoveredDriftTable> {
existingRowClass: existingRowClass,
withoutRowId: table.withoutRowId,
strict: table.isStrict,
tableConstraints: tableConstraints,
);
}
}

View File

@ -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)

View File

@ -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;
}
}

View File

@ -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<DriftElement> get references => [if (table != null) table!];
}

View File

@ -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<DriftColumn> columns;
final List<DriftTableConstraint> tableConstraints;
final Set<DriftColumn>? primaryKeyFromTableConstraint;
final List<Set<DriftColumn>> uniqueKeysFromTableConstraint;
@override
final List<DriftElement> 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<DriftColumn> uniqueSet;
UniqueColumns(this.uniqueSet);
}
class ForeignKeyTable extends DriftTableConstraint {
final List<DriftColumn> localColumns;
final DriftTable otherTable;
final List<DriftColumn> otherColumns;
final sql.ReferenceAction? onUpdate;
final sql.ReferenceAction? onDelete;
ForeignKeyTable({
required this.localColumns,
required this.otherTable,
required this.otherColumns,
this.onUpdate,
this.onDelete,
});
}

View File

@ -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<DriftElement> 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);
}

View File

@ -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.
///

View File

@ -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<String, Object?> _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<String, Object?> _serializeTypeConverter(AppliedTypeConverter converter) {
return {
'expression': converter.expression.toJson(),
@ -363,15 +395,6 @@ class ElementDeserializer {
};
}
List<Set<DriftColumn>> 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<DriftColumnConstraint> _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<DriftTableConstraint> _readTableConstraint(
Map json, Map<String, DriftColumn> 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 {

View File

@ -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);
}
}

View File

@ -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 = <UpdateRule>[];
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<UpdateRule> 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<ForeignKeyClause>()) {
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<UpdateRule> 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<UpdateRule> 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)
],
),
);
);
}
}
}

View File

@ -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 = <DriftSchemaEntity, String>{};
final entityGetters = <DriftSchemaElement, String>{};
for (final entity in db.entities) {
for (final entity in db.references.whereType<DriftSchemaElement>()) {
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<TableInfo<Table, dynamic>> 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');
}
}

View File

@ -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?
}
}

View File

@ -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<StringBuffer> _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<Scope> 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<DartScope> 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<String> locals) {
if (locals.contains(dartGetterName)) {
return 'this.$dartGetterName';
}
return dartGetterName;
}
}

View File

@ -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';