This commit is contained in:
Moshe Dicker 2024-04-03 12:37:37 -04:00
parent f105cdf62e
commit 242ec4d686
2 changed files with 162 additions and 131 deletions

View File

@ -1,7 +1,7 @@
import 'package:drift/drift.dart' as drift; import 'package:drift/drift.dart' as drift;
// ignore: implementation_imports // ignore: implementation_imports
import 'package:drift/src/runtime/executor/stream_queries.dart'; import 'package:drift/src/runtime/executor/stream_queries.dart';
import 'package:drift_dev/src/writer/manager.dart'; import 'package:drift_dev/src/writer/manager_writer.dart';
import 'package:drift_dev/src/writer/utils/memoized_getter.dart'; import 'package:drift_dev/src/writer/utils/memoized_getter.dart';
import 'package:recase/recase.dart'; import 'package:recase/recase.dart';

View File

@ -2,29 +2,32 @@
import 'package:drift_dev/src/analysis/results/results.dart'; import 'package:drift_dev/src/analysis/results/results.dart';
import 'package:drift_dev/src/writer/writer.dart'; import 'package:drift_dev/src/writer/writer.dart';
abstract class _Filter { abstract class _FilterWriter {
/// The getter for the column on this table /// The getter for the column on this table
/// ///
/// E.G `id` /// E.G `id` in `table.id`
final String fieldGetter; final String fieldGetter;
/// The getter for the columns filter /// The getter for the columns filter
/// ///
/// E.G `id` /// E.G `id` in `f.id.equals(5)`
final String filterName; final String filterName;
/// Abstract class for all filters /// An abstract class for all filters
_Filter(this.filterName, {required this.fieldGetter}); _FilterWriter(this.filterName, {required this.fieldGetter});
/// Write the filter to a provider [TextEmitter]
void writeFilter(TextEmitter leaf); void writeFilter(TextEmitter leaf);
} }
class _RegularFilter extends _Filter { class _RegularFilterWriter extends _FilterWriter {
/// The type that this filter is for /// The type that this column is
///
/// E.G `int`, `String`, etc
final String type; final String type;
/// A class for regular filters /// A class used for writing `ColumnFilters` with regular types
_RegularFilter(super.filterName, _RegularFilterWriter(super.filterName,
{required super.fieldGetter, required this.type}); {required super.fieldGetter, required this.type});
@override @override
@ -37,15 +40,19 @@ class _RegularFilter extends _Filter {
} }
} }
class _FilterWithConverter extends _Filter { class _FilterWithConverterWriter extends _FilterWriter {
/// The type that this filter is for /// The type that this column is
///
/// E.G `int`, `String`, etc
final String type; final String type;
/// The type of the converter /// The type of the user provided converter
///
/// E.G `Color` etc
final String converterType; final String converterType;
/// A class for filters with converters /// A class used for writing `ColumnFilters` with custom converters
_FilterWithConverter(super.filterName, _FilterWithConverterWriter(super.filterName,
{required super.fieldGetter, {required super.fieldGetter,
required this.type, required this.type,
required this.converterType}); required this.converterType});
@ -57,16 +64,10 @@ class _FilterWithConverter extends _Filter {
..write("<$converterType,$type> get $filterName =>") ..write("<$converterType,$type> get $filterName =>")
..writeDriftRef("ColumnWithTypeConverterFilters") ..writeDriftRef("ColumnWithTypeConverterFilters")
..writeln("(state.table.$fieldGetter);"); ..writeln("(state.table.$fieldGetter);");
leaf
..writeDriftRef("ColumnFilters")
..write("<$type> get ${filterName}Value =>")
..writeDriftRef("ColumnFilters")
..writeln("(state.table.$fieldGetter);");
} }
} }
/// A class for filters that reference other tables class _ReferencedFilter extends _FilterWriter {
class _ReferencedFilter extends _Filter {
/// The full function used to get the referenced table /// The full function used to get the referenced table
/// ///
/// E.G `state.db.resultSet<$CategoryTable>('categories')` /// E.G `state.db.resultSet<$CategoryTable>('categories')`
@ -75,7 +76,7 @@ class _ReferencedFilter extends _Filter {
/// The getter for the column on the referenced table /// The getter for the column on the referenced table
/// ///
/// E.G `id` /// E.G `id` in `table.id`
final String referencedColumnGetter; final String referencedColumnGetter;
/// The name of the referenced table's filter composer /// The name of the referenced table's filter composer
@ -83,6 +84,7 @@ class _ReferencedFilter extends _Filter {
/// E.G `CategoryFilterComposer` /// E.G `CategoryFilterComposer`
final String referencedFilterComposer; final String referencedFilterComposer;
/// A class used for building filters for referenced tables
_ReferencedFilter( _ReferencedFilter(
super.filterName, { super.filterName, {
required this.referencedTableField, required this.referencedTableField,
@ -109,29 +111,32 @@ return referenced(
} }
} }
abstract class _Ordering { abstract class _OrderingWriter {
/// The getter for the column on this table /// The getter for the column on this table
/// ///
/// E.G `id` /// E.G `id` in `table.id`
final String fieldGetter; final String fieldGetter;
/// The getter for the columns ordering /// The getter for the columns ordering
/// ///
/// E.G `id` /// E.G `id` in `f.id.equals(5)`
final String orderingName; final String orderingName;
/// Abstract class for all orderings /// Abstract class for all orderings
_Ordering(this.orderingName, {required this.fieldGetter}); _OrderingWriter(this.orderingName, {required this.fieldGetter});
/// Write the ordering to a provider [TextEmitter]
void writeOrdering(TextEmitter leaf); void writeOrdering(TextEmitter leaf);
} }
class _RegularOrdering extends _Ordering { class _RegularOrderingWriter extends _OrderingWriter {
/// The type that this ordering is for /// The type that this column is
///
/// E.G `int`, `String`, etc
final String type; final String type;
/// A class for regular orderings /// A class used for writing `ColumnOrderings` with regular types
_RegularOrdering(super.orderingName, _RegularOrderingWriter(super.orderingName,
{required super.fieldGetter, required this.type}); {required super.fieldGetter, required this.type});
@override @override
@ -144,7 +149,7 @@ class _RegularOrdering extends _Ordering {
} }
} }
class _ReferencedOrdering extends _Ordering { class _ReferencedOrderingWriter extends _OrderingWriter {
/// The full function used to get the referenced table /// The full function used to get the referenced table
/// ///
/// E.G `state.db.resultSet<$CategoryTable>('categories')` /// E.G `state.db.resultSet<$CategoryTable>('categories')`
@ -153,7 +158,7 @@ class _ReferencedOrdering extends _Ordering {
/// The getter for the column on the referenced table /// The getter for the column on the referenced table
/// ///
/// E.G `id` /// E.G `id` in `table.id`
final String referencedColumnGetter; final String referencedColumnGetter;
/// The name of the referenced table's ordering composer /// The name of the referenced table's ordering composer
@ -161,7 +166,8 @@ class _ReferencedOrdering extends _Ordering {
/// E.G `CategoryOrderingComposer` /// E.G `CategoryOrderingComposer`
final String referencedOrderingComposer; final String referencedOrderingComposer;
_ReferencedOrdering(super.orderingName, /// A class used for building orderings for referenced tables
_ReferencedOrderingWriter(super.orderingName,
{required this.referencedTableField, {required this.referencedTableField,
required this.referencedColumnGetter, required this.referencedColumnGetter,
required this.referencedOrderingComposer, required this.referencedOrderingComposer,
@ -185,19 +191,25 @@ return referenced(
} }
} }
class _ColumnNames { class _ColumnWriter {
/// The getter for the field /// The getter for the field
/// ///
/// E.G `id` /// E.G `id` in `table.id`
final String fieldGetter; final String fieldGetter;
final List<_Filter> filters;
final List<_Ordering> orderings; /// List of filters for this column
_ColumnNames(this.fieldGetter) final List<_FilterWriter> filters;
/// List of orderings for this column
final List<_OrderingWriter> orderings;
/// A class used for writing filters and orderings for columns
_ColumnWriter(this.fieldGetter)
: filters = [], : filters = [],
orderings = []; orderings = [];
} }
class _TableNames { class _TableWriter {
/// The current table /// The current table
final DriftTable table; final DriftTable table;
@ -255,13 +267,13 @@ class _TableNames {
/// E.G. `i5.$GeneratedDatabase` /// E.G. `i5.$GeneratedDatabase`
final String databaseGenericName; final String databaseGenericName;
/// Columns with their names, filters and orderings /// Writers for the columns of this table
final List<_ColumnNames> columns; final List<_ColumnWriter> columns;
/// Filters for back references /// Filters for back references
final List<_ReferencedFilter> backRefFilters; final List<_ReferencedFilter> backRefFilters;
_TableNames(this.table, this.scope, this.dbScope, this.databaseGenericName) _TableWriter(this.table, this.scope, this.dbScope, this.databaseGenericName)
: backRefFilters = [], : backRefFilters = [],
columns = []; columns = [];
@ -307,52 +319,66 @@ class _TableNames {
..writeln('}'); ..writeln('}');
} }
void _writeRootTable(TextEmitter leaf) { /// Build the builder for a companion class
final companionClassName = leaf.dartCode(leaf.companionType(table)); /// This is used to build the insert and update companions
/// Returns a tuple with the typedef and the builder
/// Use [isUpdate] to determine if the builder is for an update or insert companion
(String, String) _companionBuilder(String typedefName,
{required bool isUpdate}) {
final companionClassName = scope.dartCode(scope.companionType(table));
final updateCompanionBuilderTypeDef = StringBuffer( final companionBuilderTypeDef =
'typedef $updateCompanionBuilderTypeDefName = $companionClassName Function({'); StringBuffer('typedef $typedefName = $companionClassName Function({');
final insertCompanionBuilderTypeDef = StringBuffer(
'typedef $insertCompanionBuilderTypeDefName = $companionClassName Function({');
final updateCompanionBuilderArguments = StringBuffer('({'); final companionBuilderArguments = StringBuffer('({');
final insertCompanionBuilderArguments = StringBuffer('({');
final updateCompanionBuilderBody = StringBuffer('=> $companionClassName('); final StringBuffer companionBuilderBody;
final insertCompanionBuilderBody = if (isUpdate) {
StringBuffer('=> $companionClassName.insert('); companionBuilderBody = StringBuffer('=> $companionClassName(');
} else {
for (final column in table.columns) { companionBuilderBody = StringBuffer('=> $companionClassName.insert(');
final value = leaf.drift('Value');
final param = column.nameInDart;
final typeName = leaf.dartCode(leaf.dartType(column));
// The update companion has no required fields, they are all defaulted to absent
updateCompanionBuilderTypeDef.write('$value<$typeName> $param,');
updateCompanionBuilderArguments
.write('$value<$typeName> $param = const $value.absent(),');
updateCompanionBuilderBody.write('$param: $param,');
// The insert compantion has some required arguments and some that are defaulted to absent
if (!column.isImplicitRowId && table.isColumnRequiredForInsert(column)) {
insertCompanionBuilderTypeDef.write('required $typeName $param,');
insertCompanionBuilderArguments.write('required $typeName $param,');
} else {
insertCompanionBuilderTypeDef.write('$value<$typeName> $param,');
insertCompanionBuilderArguments
.write('$value<$typeName> $param = const $value.absent(),');
}
insertCompanionBuilderBody.write('$param: $param,');
} }
// Close for (final column in table.columns) {
// updateCompanionTypedef.write('})'); final value = scope.drift('Value');
insertCompanionBuilderTypeDef.write('});'); final param = column.nameInDart;
updateCompanionBuilderTypeDef.write('});'); final typeName = scope.dartCode(scope.dartType(column));
insertCompanionBuilderArguments.write('})');
updateCompanionBuilderArguments.write('})'); companionBuilderBody.write('$param: $param,');
insertCompanionBuilderBody.write(")");
updateCompanionBuilderBody.write(")"); if (isUpdate) {
// The update companion has no required fields, they are all defaulted to absent
companionBuilderTypeDef.write('$value<$typeName> $param,');
companionBuilderArguments
.write('$value<$typeName> $param = const $value.absent(),');
} else {
// The insert compantion has some required arguments and some that are defaulted to absent
if (!column.isImplicitRowId &&
table.isColumnRequiredForInsert(column)) {
companionBuilderTypeDef.write('required $typeName $param,');
companionBuilderArguments.write('required $typeName $param,');
} else {
companionBuilderTypeDef.write('$value<$typeName> $param,');
companionBuilderArguments
.write('$value<$typeName> $param = const $value.absent(),');
}
}
}
companionBuilderTypeDef.write('});');
companionBuilderArguments.write('})');
companionBuilderBody.write(")");
return (
companionBuilderTypeDef.toString(),
companionBuilderArguments.toString() + companionBuilderBody.toString()
);
}
void _writeRootTable(TextEmitter leaf) {
final (insertCompanionBuilderTypeDef, insertCompanionBuilder) =
_companionBuilder(insertCompanionBuilderTypeDefName, isUpdate: false);
final (updateCompanionBuilderTypeDef, updateCompanionBuilder) =
_companionBuilder(updateCompanionBuilderTypeDefName, isUpdate: true);
leaf.writeln(insertCompanionBuilderTypeDef); leaf.writeln(insertCompanionBuilderTypeDef);
leaf.writeln(updateCompanionBuilderTypeDef); leaf.writeln(updateCompanionBuilderTypeDef);
@ -367,11 +393,12 @@ class _TableNames {
..writeDriftRef("TableManagerState") ..writeDriftRef("TableManagerState")
..write( ..write(
"""(db: db, table: table, filteringComposer:$filterComposer(db, table),orderingComposer:$orderingComposer(db, table), """(db: db, table: table, filteringComposer:$filterComposer(db, table),orderingComposer:$orderingComposer(db, table),
getChildManagerBuilder :(p0) => $processedTableManager(p0),getUpdateCompanionBuilder: $updateCompanionBuilderArguments$updateCompanionBuilderBody, getChildManagerBuilder :(p0) => $processedTableManager(p0),getUpdateCompanionBuilder: $updateCompanionBuilder,
getInsertCompanionBuilder:$insertCompanionBuilderArguments$insertCompanionBuilderBody));""") getInsertCompanionBuilder:$insertCompanionBuilder));""")
..writeln('}'); ..writeln('}');
} }
/// Write the manager for this table, with all the filters and orderings
void writeManager(TextEmitter leaf) { void writeManager(TextEmitter leaf) {
_writeFilterComposer(leaf); _writeFilterComposer(leaf);
_writeOrderingComposer(leaf); _writeOrderingComposer(leaf);
@ -379,6 +406,7 @@ class _TableNames {
_writeRootTable(leaf); _writeRootTable(leaf);
} }
/// Add filters and orderings for the columns of this table
void addFiltersAndOrderings(List<DriftTable> tables) { void addFiltersAndOrderings(List<DriftTable> tables) {
// Utility function to get the referenced table and column // Utility function to get the referenced table and column
(DriftTable, DriftColumn)? getReferencedTableAndColumn( (DriftTable, DriftColumn)? getReferencedTableAndColumn(
@ -395,10 +423,22 @@ class _TableNames {
return null; return null;
} }
// Utility function to get the duplicates in a list
List<String> duplicates(List<String> items) {
final seen = <String>{};
final duplicates = <String>[];
for (var item in items) {
if (!seen.add(item)) {
duplicates.add(item);
}
}
return duplicates;
}
/// First add the filters and orderings for the columns /// First add the filters and orderings for the columns
/// of the current table /// of the current table
for (var column in table.columns) { for (var column in table.columns) {
final c = _ColumnNames(column.nameInDart); final c = _ColumnWriter(column.nameInDart);
// The type that this column is (int, string, etc) // The type that this column is (int, string, etc)
final innerColumnType = final innerColumnType =
@ -411,31 +451,32 @@ class _TableNames {
// If the column has a type converter, add a filter with a converter // If the column has a type converter, add a filter with a converter
if (column.typeConverter != null) { if (column.typeConverter != null) {
final converterType = scope.dartCode(scope.writer.dartType(column)); final converterType = scope.dartCode(scope.writer.dartType(column));
c.filters.add(_FilterWithConverter(c.fieldGetter, c.filters.add(_RegularFilterWriter("${c.fieldGetter}Value",
type: innerColumnType, fieldGetter: c.fieldGetter));
c.filters.add(_FilterWithConverterWriter(c.fieldGetter,
converterType: converterType, converterType: converterType,
fieldGetter: c.fieldGetter, fieldGetter: c.fieldGetter,
type: innerColumnType)); type: innerColumnType));
} else { } else {
c.filters.add(_RegularFilter( c.filters.add(_RegularFilterWriter(
c.fieldGetter + (isForeignKey ? "Value" : ""), c.fieldGetter + (isForeignKey ? "Id" : ""),
type: innerColumnType, type: innerColumnType,
fieldGetter: c.fieldGetter)); fieldGetter: c.fieldGetter));
} }
// Add the ordering for the column // Add the ordering for the column
c.orderings.add(_RegularOrdering( c.orderings.add(_RegularOrderingWriter(
c.fieldGetter + (isForeignKey ? "Value" : ""), c.fieldGetter + (isForeignKey ? "Id" : ""),
type: innerColumnType, type: innerColumnType,
fieldGetter: c.fieldGetter)); fieldGetter: c.fieldGetter));
/// If this column is a foreign key to another table, add a filter and ordering /// If this column is a foreign key to another table, add a filter and ordering
/// for the referenced table /// for the referenced table
if (referenced != null) { if (referenced != null) {
final (referencedTable, referencedCol) = referenced; final (referencedTable, referencedCol) = referenced;
final referencedTableNames = final referencedTableNames =
_TableNames(referencedTable, scope, dbScope, databaseGenericName); _TableWriter(referencedTable, scope, dbScope, databaseGenericName);
final referencedColumnNames = _ColumnNames(referencedCol.nameInDart); final referencedColumnNames = _ColumnWriter(referencedCol.nameInDart);
final String referencedTableField = scope.generationOptions.isModular final String referencedTableField = scope.generationOptions.isModular
? "state.db.resultSet<${referencedTableNames.tableClassName}>('${referencedTable.schemaName}')" ? "state.db.resultSet<${referencedTableNames.tableClassName}>('${referencedTable.schemaName}')"
: "state.db.${referencedTable.dbGetterName}"; : "state.db.${referencedTable.dbGetterName}";
@ -445,7 +486,7 @@ class _TableNames {
referencedColumnGetter: referencedColumnNames.fieldGetter, referencedColumnGetter: referencedColumnNames.fieldGetter,
referencedFilterComposer: referencedTableNames.filterComposer, referencedFilterComposer: referencedTableNames.filterComposer,
referencedTableField: referencedTableField)); referencedTableField: referencedTableField));
c.orderings.add(_ReferencedOrdering(c.fieldGetter, c.orderings.add(_ReferencedOrderingWriter(c.fieldGetter,
fieldGetter: c.fieldGetter, fieldGetter: c.fieldGetter,
referencedColumnGetter: referencedColumnNames.fieldGetter, referencedColumnGetter: referencedColumnNames.fieldGetter,
referencedOrderingComposer: referencedTableNames.orderingComposer, referencedOrderingComposer: referencedTableNames.orderingComposer,
@ -462,8 +503,8 @@ class _TableNames {
if (reference != null && if (reference != null &&
reference.$1.entityInfoName == table.entityInfoName) { reference.$1.entityInfoName == table.entityInfoName) {
final referencedTableNames = final referencedTableNames =
_TableNames(ot, scope, dbScope, databaseGenericName); _TableWriter(ot, scope, dbScope, databaseGenericName);
final referencedColumnNames = _ColumnNames(oc.nameInDart); final referencedColumnNames = _ColumnWriter(oc.nameInDart);
final String referencedTableField = scope.generationOptions.isModular final String referencedTableField = scope.generationOptions.isModular
? "state.db.resultSet<${referencedTableNames.tableClassName}>('${ot.schemaName}')" ? "state.db.resultSet<${referencedTableNames.tableClassName}>('${ot.schemaName}')"
: "state.db.${ot.dbGetterName}"; : "state.db.${ot.dbGetterName}";
@ -479,38 +520,27 @@ class _TableNames {
} }
} }
} }
// Remove the filters and orderings that have duplicates // Remove the filters and orderings that have duplicates
// TODO: Add warnings for duplicate filters and orderings // TODO: Add warnings for duplicate filters and orderings
List<String> duplicates(List<String> items) { final duplicatedFilterNames = duplicates(columns
final seen = <String>{};
final duplicates = <String>[];
for (var item in items) {
if (!seen.add(item)) {
duplicates.add(item);
}
}
return duplicates;
}
// Gather which filters and orderings are duplicates
final filterNamesToRemove = duplicates(columns
.map((e) => e.filters.map((e) => e.filterName)) .map((e) => e.filters.map((e) => e.filterName))
.expand((e) => e) .expand((e) => e)
.toList() + .toList() +
backRefFilters.map((e) => e.filterName).toList()); backRefFilters.map((e) => e.filterName).toList());
final orderingNamesToRemove = duplicates(columns final duplicatedOrderingNames = duplicates(columns
.map((e) => e.orderings.map((e) => e.orderingName)) .map((e) => e.orderings.map((e) => e.orderingName))
.expand((e) => e) .expand((e) => e)
.toList()); .toList());
// Remove the duplicates // Remove the duplicates
for (var c in columns) { for (var c in columns) {
c.filters.removeWhere((e) => filterNamesToRemove.contains(e.filterName)); c.filters
.removeWhere((e) => duplicatedFilterNames.contains(e.filterName));
c.orderings c.orderings
.removeWhere((e) => orderingNamesToRemove.contains(e.orderingName)); .removeWhere((e) => duplicatedOrderingNames.contains(e.orderingName));
} }
backRefFilters backRefFilters
.removeWhere((e) => filterNamesToRemove.contains(e.filterName)); .removeWhere((e) => duplicatedFilterNames.contains(e.filterName));
} }
} }
@ -520,14 +550,19 @@ class ManagerWriter {
final String _dbClassName; final String _dbClassName;
late final List<DriftTable> _addedTables; late final List<DriftTable> _addedTables;
/// Class used to write a manager for a database
ManagerWriter(this._scope, this._dbScope, this._dbClassName) { ManagerWriter(this._scope, this._dbScope, this._dbClassName) {
_addedTables = []; _addedTables = [];
} }
/// Add a table to the manager
void addTable(DriftTable table) { void addTable(DriftTable table) {
_addedTables.add(table); _addedTables.add(table);
} }
/// The generic of the database that the manager will use
/// Will be `GeneratedDatabase` if modular generation is enabled
/// or the name of the database class if not
String get databaseGenericName { String get databaseGenericName {
if (_scope.generationOptions.isModular) { if (_scope.generationOptions.isModular) {
return _scope.drift("GeneratedDatabase"); return _scope.drift("GeneratedDatabase");
@ -536,37 +571,33 @@ class ManagerWriter {
} }
} }
/// The name of the manager class
String get databaseManagerName => '${_dbClassName}Manager'; String get databaseManagerName => '${_dbClassName}Manager';
/// The getter for the manager that will be added to the database
String get managerGetter { String get managerGetter {
return '$databaseManagerName get managers => $databaseManagerName(this);'; return '$databaseManagerName get managers => $databaseManagerName(this);';
} }
/// Write the manager to a provider [TextEmitter]
void write() { void write() {
final leaf = _scope.leaf(); final leaf = _scope.leaf();
final tableNames = <_TableNames>[]; final tableNames = <_TableWriter>[];
for (var table in _addedTables) { for (var table in _addedTables) {
tableNames.add(_TableNames(table, _scope, _dbScope, databaseGenericName) tableNames.add(_TableWriter(table, _scope, _dbScope, databaseGenericName)
..addFiltersAndOrderings(_addedTables)); ..addFiltersAndOrderings(_addedTables));
} }
final tableManagerGetters = StringBuffer(); final tableManagerGetters = StringBuffer();
for (var table in tableNames) { for (var table in tableNames) {
table.writeManager(leaf); table.writeManager(leaf);
tableManagerGetters.writeln( tableManagerGetters.writeln(
"${table.rootTableManager} get ${table.table.dbGetterName} => ${table.rootTableManager}(_db, _db.${table.table.dbGetterName});"); "${table.rootTableManager} get ${table.table.dbGetterName} => ${table.rootTableManager}(_db, _db.${table.table.dbGetterName});");
} }
leaf
leaf.write(""" ..writeln('class $databaseManagerName{')
class $databaseManagerName{ ..writeln('final $_dbClassName _db;')
final $_dbClassName _db; ..writeln('$databaseManagerName(this._db);')
..writeln(tableManagerGetters)
$databaseManagerName(this._db); ..writeln('}');
$tableManagerGetters
}
""");
} }
} }