mirror of https://github.com/AMT-Cheif/drift.git
Start generating code with new analyzer
This commit is contained in:
parent
dcdbcb7156
commit
ef1b94af1d
|
@ -1 +0,0 @@
|
|||
export 'src/model/model.dart';
|
|
@ -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,
|
||||
);
|
||||
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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!];
|
||||
}
|
||||
|
|
|
@ -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,
|
||||
});
|
||||
}
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
|
|
|
@ -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.
|
||||
///
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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)
|
||||
],
|
||||
),
|
||||
);
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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');
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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?
|
||||
}
|
||||
}
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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';
|
Loading…
Reference in New Issue