// ignore_for_file: public_member_api_docs, sort_constructors_first import 'package:drift_dev/src/analysis/results/results.dart'; import 'package:drift_dev/src/writer/writer.dart'; import 'package:recase/recase.dart'; abstract class _Filter { final String filterName; _Filter( this.filterName, ); void writeFilter(TextEmitter leaf); } class _RegularFilter extends _Filter { final String fieldGetter; final String type; _RegularFilter(this.fieldGetter, this.type, super.filterName); @override void writeFilter(TextEmitter leaf) { leaf ..writeDriftRef("ColumnFilters") ..write("<$type> get $filterName =>") ..writeDriftRef("ColumnFilters") ..write("(state.table.$fieldGetter);"); } } class _FilterWithConverter extends _Filter { final String fieldGetter; final String type; final String converterType; _FilterWithConverter( this.fieldGetter, this.type, super.filterName, this.converterType); @override void writeFilter(TextEmitter leaf) { leaf ..writeDriftRef("ColumnWithTypeConverterFilters") ..write("<$converterType,$type> get $filterName =>") ..writeDriftRef("ColumnWithTypeConverterFilters") ..writeln("(state.table.$fieldGetter);"); leaf ..writeDriftRef("ColumnFilters") ..write("<$type> get ${filterName}Value =>") ..writeDriftRef("ColumnFilters") ..writeln("(state.table.$fieldGetter);"); } } class _ReferencedFilter extends _Filter { final String fieldGetter; final _TableNames referencedTable; final _ColumnNames referencedColumn; _ReferencedFilter(this.fieldGetter, super.filterName, this.referencedTable, this.referencedColumn); @override void writeFilter(TextEmitter leaf) { leaf ..writeDriftRef("ComposableFilter") ..write(" $filterName(") ..writeDriftRef("ComposableFilter") ..writeln(" Function( ${referencedTable.filterComposer} f) f) {") ..writeln(''' return referenced( referencedTable: state.db.${referencedTable.tableGetterName}, getCurrentColumn: (f) => f.$fieldGetter, getReferencedColumn: (f) => f.${referencedColumn.fieldGetter}, getReferencedComposer: (db, table) => ${referencedTable.filterComposer}(db, table), builder: f); }'''); } } abstract class _Ordering { final String orderingName; _Ordering(this.orderingName); void writeOrdering(TextEmitter leaf); } class _RegularOrdering extends _Ordering { final String fieldGetter; final String type; _RegularOrdering(this.fieldGetter, this.type, super.orderingName); @override void writeOrdering(TextEmitter leaf) { leaf ..writeDriftRef("ColumnOrderings") ..write(" get $orderingName =>") ..writeDriftRef("ColumnOrderings") ..write("(state.table.$fieldGetter);"); } } class _ReferencedOrdering extends _Ordering { final String fieldGetter; final _TableNames referencedTable; final _ColumnNames referencedColumn; _ReferencedOrdering(this.fieldGetter, super.orderingName, this.referencedTable, this.referencedColumn); @override void writeOrdering(TextEmitter leaf) { leaf ..writeDriftRef("ComposableOrdering") ..write(" $orderingName(") ..writeDriftRef("ComposableOrdering") ..writeln(" Function( ${referencedTable.orderingComposer} o) o) {") ..writeln(''' return referenced( referencedTable: state.db.${referencedTable.tableGetterName}, getCurrentColumn: (f) => f.$fieldGetter, getReferencedColumn: (f) => f.${referencedColumn.fieldGetter}, getReferencedComposer: (db, table) => ${referencedTable.orderingComposer}(db, table), builder: o); }'''); } } class _ColumnNames { /// The getter for the field /// /// E.G `id` final String fieldGetter; final List<_Filter> filters; final List<_Ordering> orderings; _ColumnNames(this.fieldGetter, this.filters, this.orderings); } class _TableNames { /// The current table final DriftTable table; /// The name of the filter composer class /// /// E.G `UserFilterComposer` final String filterComposer; /// The name of the filter composer class /// /// E.G `UserOrderingComposer` final String orderingComposer; /// The name of the processed table manager class /// /// E.G `UserProcessedTableManager` final String processedTableManager; /// The name of the table manager with filtering class /// /// E.G `UserTableManagerWithFiltering` final String tableManagerWithFiltering; /// The name of the table manager with ordering class /// /// E.G `UserTableManagerWithOrdering` final String tableManagerWithOrdering; /// The name of the root table manager class /// /// E.G `UserTableManager` final String rootTableManager; /// Name of the table class that will be generated /// /// E.G `$CategoriesTable` final String tableClassName; /// Name of the getter for the table /// /// E.G `categories` final String tableGetterName; /// Name of the class that cooresponds to a table row /// /// E.G `Category` final String rowClassName; /// Columns with their names, filters and orderings final List<_ColumnNames> columns; /// Filters for back references final List<_ReferencedFilter> backRefFilters; _TableNames(this.table) : filterComposer = '${table.entityInfoName}FilterComposer', orderingComposer = '${table.entityInfoName}OrderingComposer', processedTableManager = '${table.entityInfoName}ProcessedTableManager', tableManagerWithFiltering = '${table.entityInfoName}TableManagerWithFiltering', tableManagerWithOrdering = '${table.entityInfoName}TableManagerWithOrdering', rootTableManager = '${table.entityInfoName}TableManager', rowClassName = table.nameOfRowClass, tableClassName = table.entityInfoName, tableGetterName = table.dbGetterName, backRefFilters = [], columns = []; void _writeFilterComposer(TextEmitter leaf, String dbClassName) { // Write the FilterComposer leaf ..write('class $filterComposer extends ') ..writeDriftRef('FilterComposer') ..writeln('<$dbClassName,$tableClassName> {') ..writeln('$filterComposer(super.db, super.table);'); for (var c in columns) { for (var f in c.filters) { f.writeFilter(leaf); } } for (var f in backRefFilters) { f.writeFilter(leaf); } leaf.writeln('}'); } void _writeOrderingComposer(TextEmitter leaf, String dbClassName) { // Write the OrderingComposer leaf ..write('class $orderingComposer extends ') ..writeDriftRef('OrderingComposer') ..writeln('<$dbClassName,$tableClassName> {') ..writeln('$orderingComposer(super.db, super.table);'); for (var c in columns) { for (var o in c.orderings) { o.writeOrdering(leaf); } } leaf.writeln('}'); } void _writeProcessedTableManager(TextEmitter leaf, String dbClassName) { leaf ..write('class $processedTableManager extends ') ..writeDriftRef('ProcessedTableManager') ..writeln( '<$dbClassName,$tableClassName,$rowClassName,$filterComposer,$orderingComposer> {') ..writeln('const $processedTableManager(super.state);') ..writeln('}'); } void _writeTableManagerWithFiltering(TextEmitter leaf, String dbClassName) { leaf ..write('class $tableManagerWithFiltering extends ') ..writeDriftRef('TableManagerWithFiltering') ..writeln( '<$dbClassName,$tableClassName,$rowClassName,$filterComposer,$orderingComposer,$processedTableManager> {') ..writeln( 'const $tableManagerWithFiltering(super.state,{required super.getChildManager});') ..writeln('}'); } void _writeTableManagerWithOrdering(TextEmitter leaf, String dbClassName) { leaf ..write('class $tableManagerWithOrdering extends ') ..writeDriftRef('TableManagerWithOrdering') ..writeln( '<$dbClassName,$tableClassName,$rowClassName,$filterComposer,$orderingComposer,$processedTableManager>{') ..writeln( 'const $tableManagerWithOrdering(super.state,{required super.getChildManager});') ..writeln('}'); } void _writeRootTable(TextEmitter leaf, String dbClassName) { final companionClassName = leaf.dartCode(leaf.companionType(table)); // final updateCompanionTypedef = StringBuffer( // 'typedef $updateCompanionTypedefName = $companionClassName Function({'); final createInsertableFunctionTypeDef = StringBuffer('$companionClassName Function({'); // final updateCompanionArguments = StringBuffer('({'); final createInsertableFunctionArgs = StringBuffer('({'); // final updateCompanionBody = StringBuffer('=> $companionClassName('); final createInsertableFunctionBody = StringBuffer('=> $companionClassName.insert('); for (final column in table.columns) { 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 // updateCompanionTypedef.write('$value<$typeName> $param,'); // updateCompanionArguments // .write('$value<$typeName> $param = const $value.absent(),'); // updateCompanionBody.write('$param: $param,'); // The insert compantion has some required arguments and some that are defaulted to absent if (!column.isImplicitRowId && table.isColumnRequiredForInsert(column)) { createInsertableFunctionTypeDef.write('required $typeName $param,'); createInsertableFunctionArgs.write('required $typeName $param,'); } else { createInsertableFunctionTypeDef.write('$value<$typeName> $param,'); createInsertableFunctionArgs .write('$value<$typeName> $param = const $value.absent(),'); } createInsertableFunctionBody.write('$param: $param,'); } // Close // updateCompanionTypedef.write('})'); createInsertableFunctionTypeDef.write('})'); createInsertableFunctionArgs.write('})'); createInsertableFunctionBody.write(")"); // leaf.writeln(updateCompanionTypedef); // leaf.writeln(insertCompanionTypedef); // updateCompanionArguments.write('})'); // insertCompanionArguments.write('})'); // updateCompanionBody.write(")"); // insertCompanionBody.write(")"); leaf ..write('class $rootTableManager extends ') ..writeDriftRef('RootTableManager') ..writeln( '<$dbClassName,$tableClassName,$rowClassName,$filterComposer,$orderingComposer,$processedTableManager,$tableManagerWithFiltering,$tableManagerWithOrdering,$createInsertableFunctionTypeDef> {') ..writeln('$rootTableManager($dbClassName db, $tableClassName table)') ..writeln(": super(") ..writeDriftRef("TableManagerState") ..write( """(db: db, table: table, filteringComposer:$filterComposer(db, table),orderingComposer:$orderingComposer(db, table)), getChildManagerWithFiltering: (f) => $tableManagerWithFiltering(f,getChildManager: (f) => $processedTableManager(f)), getChildManagerWithOrdering: (f) => $tableManagerWithOrdering(f,getChildManager: (f) =>$processedTableManager(f)) ,createInsertable: $createInsertableFunctionArgs$createInsertableFunctionBody);""") ..writeln('}'); } void writeManager(TextEmitter leaf, String dbClassName) { _writeFilterComposer(leaf, dbClassName); _writeOrderingComposer(leaf, dbClassName); _writeProcessedTableManager(leaf, dbClassName); _writeTableManagerWithFiltering(leaf, dbClassName); _writeTableManagerWithOrdering(leaf, dbClassName); _writeRootTable(leaf, dbClassName); } void addFiltersAndOrderings(List tables, TextEmitter leaf) { /// First add the filters and orderings for the columns /// of the current table for (var column in table.columns) { final c = _ColumnNames(column.nameInDart, [], []); final innerColumnType = leaf.dartCode(leaf.innerColumnType(column.sqlType)); if (column.typeConverter != null) { final mappedType = leaf.dartCode(leaf.writer.dartType(column)); c.filters.add(_FilterWithConverter( c.fieldGetter, innerColumnType, c.fieldGetter, mappedType)); } else { c.filters .add(_RegularFilter(c.fieldGetter, innerColumnType, c.fieldGetter)); } c.orderings .add(_RegularOrdering(c.fieldGetter, innerColumnType, c.fieldGetter)); final referencedCol = column.constraints .whereType() .firstOrNull ?.otherColumn; final referencedTable = referencedCol?.owner; if (referencedCol != null && referencedTable is DriftTable) { final referencedTableNames = _TableNames(referencedTable); final referencedColumnNames = _ColumnNames(referencedCol.nameInDart, [], []); c.filters.add(_ReferencedFilter(c.fieldGetter, "${c.fieldGetter}Ref", referencedTableNames, referencedColumnNames)); c.orderings.add(_ReferencedOrdering( c.fieldGetter, "${c.fieldGetter}OrderBy", referencedTableNames, referencedColumnNames)); } columns.add(c); } for (var otherTable in tables) { final otherTableNames = _TableNames(otherTable); /// We are adding backrefs now, skip the current table if (otherTableNames.tableClassName == tableClassName) { continue; } for (var otherColumn in otherTable.columns) { final referencedCol = otherColumn.constraints .whereType() .firstOrNull ?.otherColumn; final referencedTable = referencedCol?.owner; if (referencedCol != null && referencedTable is DriftTable) { final referencedTableNames = _TableNames(referencedTable); final referencedColumnNames = _ColumnNames(referencedCol.nameInDart, [], []); // If we are referencing the current table, add a back ref if (referencedTableNames.tableClassName == tableClassName) { backRefFilters.add(_ReferencedFilter( referencedColumnNames.fieldGetter, "${otherTableNames.tableClassName.camelCase}Refs", otherTableNames, referencedColumnNames)); } } } } // Remove the filters and orderings that have duplicates // TODO: Add warnings for duplicate filters and orderings List duplicates(List items) { final seen = {}; final duplicates = []; for (var item in items) { if (!seen.add(item)) { duplicates.add(item); } } return duplicates; } final filterNamesToRemove = duplicates(columns .map((e) => e.filters.map((e) => e.filterName)) .expand((e) => e) .toList() + backRefFilters.map((e) => e.filterName).toList()); final orderingNamesToRemove = duplicates(columns .map((e) => e.orderings.map((e) => e.orderingName)) .expand((e) => e) .toList()); for (var c in columns) { c.filters.removeWhere((e) => filterNamesToRemove.contains(e.filterName)); c.orderings .removeWhere((e) => orderingNamesToRemove.contains(e.orderingName)); } backRefFilters .removeWhere((e) => filterNamesToRemove.contains(e.filterName)); } } class ManagerWriter { final Scope _scope; final String _dbClassName; final List _addedTables; ManagerWriter(this._scope, this._dbClassName) : _addedTables = []; String get managerGetter { return '''$_dbMangerName get managers => $_dbMangerName(this);'''; } void addTable(DriftTable table) { _addedTables.add(table); } String get _dbMangerName => '${_dbClassName}Manager'; void write() { final leaf = _scope.leaf(); final tableNames = <_TableNames>[]; for (var table in _addedTables) { final t = _TableNames(table); t.addFiltersAndOrderings(_addedTables, leaf); t.writeManager(leaf, _dbClassName); tableNames.add(t); } final tableManagerGetters = StringBuffer(); for (var table in tableNames) { tableManagerGetters.writeln( "${table.rootTableManager} get ${table.tableGetterName} => ${table.rootTableManager}(_db, _db.${table.tableGetterName});"); } leaf.write(""" class $_dbMangerName{ final $_dbClassName _db; $_dbMangerName(this._db); $tableManagerGetters } """); } }