diff --git a/docs/lib/snippets/dart_api/manager.dart b/docs/lib/snippets/dart_api/manager.dart new file mode 100644 index 00000000..65d343d3 --- /dev/null +++ b/docs/lib/snippets/dart_api/manager.dart @@ -0,0 +1,270 @@ +import 'package:drift/drift.dart'; + +part 'manager.g.dart'; + +class TodoItems extends Table { + IntColumn get id => integer().autoIncrement()(); + TextColumn get title => text().withLength(min: 6, max: 32)(); + TextColumn get content => text().named('body')(); + IntColumn get category => + integer().nullable().references(TodoCategory, #id)(); + DateTimeColumn get createdAt => dateTime().nullable()(); +} + +class TodoCategory extends Table { + IntColumn get id => integer().autoIncrement()(); + TextColumn get description => text()(); +} + +// #docregion user_group_tables + +class Users extends Table { + IntColumn get id => integer().autoIncrement()(); + TextColumn get name => text()(); +} + +class Groups extends Table { + IntColumn get id => integer().autoIncrement()(); + TextColumn get name => text()(); + @ReferenceName("administeredGroups") + IntColumn get admin => integer().nullable().references(Users, #id)(); + @ReferenceName("ownedGroups") + IntColumn get owner => integer().references(Users, #id)(); +} + +// #enddocregion user_group_tables + +@DriftDatabase(tables: [TodoItems, TodoCategory, Groups, Users]) +class AppDatabase extends _$AppDatabase { + AppDatabase(super.e); + @override + int get schemaVersion => 1; +} + +extension ManagerExamples on AppDatabase { + // #docregion manager_create + Future createTodoItem() async { + // Create a new item + await managers.todoItems + .create((o) => o(title: 'Title', content: 'Content')); + + // We can also use `mode` and `onConflict` parameters, just + // like in the `[InsertStatement.insert]` method on the table + await managers.todoItems.create( + (o) => o(title: 'Title', content: 'New Content'), + mode: InsertMode.replace); + + // We can also create multiple items at once + await managers.todoItems.bulkCreate( + (o) => [ + o(title: 'Title 1', content: 'Content 1'), + o(title: 'Title 2', content: 'Content 2'), + ], + ); + } + // #enddocregion manager_create + + // #docregion manager_update + Future updateTodoItems() async { + // Update all items + await managers.todoItems.update((o) => o(content: Value('New Content'))); + + // Update multiple items + await managers.todoItems + .filter((f) => f.id.isIn([1, 2, 3])) + .update((o) => o(content: Value('New Content'))); + } + // #enddocregion manager_update + + // #docregion manager_replace + Future replaceTodoItems() async { + // Replace a single item + var obj = await managers.todoItems.filter((o) => o.id(1)).getSingle(); + obj = obj.copyWith(content: 'New Content'); + await managers.todoItems.replace(obj); + + // Replace multiple items + var objs = + await managers.todoItems.filter((o) => o.id.isIn([1, 2, 3])).get(); + objs = objs.map((o) => o.copyWith(content: 'New Content')).toList(); + await managers.todoItems.bulkReplace(objs); + } + // #enddocregion manager_replace + + // #docregion manager_delete + Future deleteTodoItems() async { + // Delete all items + await managers.todoItems.delete(); + + // Delete a single item + await managers.todoItems.filter((f) => f.id(5)).delete(); + } + // #enddocregion manager_delete + + // #docregion manager_select + Future selectTodoItems() async { + // Get all items + managers.todoItems.get(); + + // A stream of all the todo items, updated in real-time + managers.todoItems.watch(); + + // To get a single item, apply a filter and call `getSingle` + await managers.todoItems.filter((f) => f.id(1)).getSingle(); + } + // #enddocregion manager_select + + // #docregion manager_filter + Future filterTodoItems() async { + // All items with a title of "Title" + managers.todoItems.filter((f) => f.title("Title")); + + // All items with a title of "Title" and content of "Content" + managers.todoItems.filter((f) => f.title("Title") & f.content("Content")); + + // All items with a title of "Title" or content that is not null + managers.todoItems.filter((f) => f.title("Title") | f.content.not.isNull()); + } + // #enddocregion manager_filter + + // #docregion manager_type_specific_filter + Future filterWithType() async { + // Filter all items created since 7 days ago + managers.todoItems.filter( + (f) => f.createdAt.isAfter(DateTime.now().subtract(Duration(days: 7)))); + + // Filter all items with a title that starts with "Title" + managers.todoItems.filter((f) => f.title.startsWith('Title')); + } +// #enddocregion manager_type_specific_filter + +// #docregion manager_ordering + Future orderWithType() async { + // Order all items by their creation date in ascending order + managers.todoItems.orderBy((o) => o.createdAt.asc()); + + // Order all items by their title in ascending order and then by their content in ascending order + managers.todoItems.orderBy((o) => o.title.asc() & o.content.asc()); + } +// #enddocregion manager_ordering + +// #docregion manager_count + Future count() async { + // Count all items + await managers.todoItems.count(); + + // Count all items with a title of "Title" + await managers.todoItems.filter((f) => f.title("Title")).count(); + } +// #enddocregion manager_count + +// #docregion manager_exists + Future exists() async { + // Check if any items exist + await managers.todoItems.exists(); + + // Check if any items with a title of "Title" exist + await managers.todoItems.filter((f) => f.title("Title")).exists(); + } +// #enddocregion manager_exists + +// #docregion manager_filter_forward_references + Future relationalFilter() async { + // Get all items with a category description of "School" + managers.todoItems + .filter((f) => f.category((f) => f.description("School"))); + + // These can be combined with other filters + // For example, get all items with a title of "Title" or a category description of "School" + await managers.todoItems + .filter( + (f) => f.title("Title") | f.category((f) => f.description("School")), + ) + .exists(); + } +// #enddocregion manager_filter_forward_references + +// #docregion manager_filter_back_references + Future reverseRelationalFilter() async { + // Get the category that has a todo item with an id of 1 + managers.todoCategory.filter((f) => f.todoItemsRefs((f) => f.id(1))); + + // These can be combined with other filters + // For example, get all categories with a description of "School" or a todo item with an id of 1 + managers.todoCategory.filter( + (f) => f.description("School") | f.todoItemsRefs((f) => f.id(1)), + ); + } +// #enddocregion manager_filter_back_references + +// #docregion manager_filter_custom_back_references + Future reverseNamedRelationalFilter() async { + // Get all users who are administrators of a group with a name containing "Business" + // or who own a group with an id of 1, 2, 4, or 5 + managers.users.filter( + (f) => + f.administeredGroups((f) => f.name.contains("Business")) | + f.ownedGroups((f) => f.id.isIn([1, 2, 4, 5])), + ); + } +// #enddocregion manager_filter_custom_back_references +} + +// #docregion manager_filter_extensions +// Extend drifts built-in filters by combining the existing filters to create a new one +// or by creating a new filter from scratch +extension After2000Filter on ColumnFilters { + // Create a new filter by combining existing filters + ComposableFilter after2000orBefore1900() => + isAfter(DateTime(2000)) | isBefore(DateTime(1900)); + + // Create a new filter from scratch using the `column` property + ComposableFilter filterOnUnixEpoch(int value) => + ComposableFilter(column.unixepoch.equals(value), inverted: inverted); +} + +Future filterWithExtension(AppDatabase db) async { + // Use the custom filters on any column that is of type DateTime + db.managers.todoItems.filter((f) => f.createdAt.after2000orBefore1900()); + + // Use the custom filter on the `unixepoch` column + db.managers.todoItems.filter((f) => f.createdAt.filterOnUnixEpoch(0)); +} +// #enddocregion manager_filter_extensions + +// #docregion manager_ordering_extensions +// Extend drifts built-in orderings by create a new ordering from scratch +extension After2000Ordering on ColumnOrderings { + ComposableOrdering byUnixEpoch() => ColumnOrderings(column.unixepoch).asc(); +} + +Future orderingWithExtension(AppDatabase db) async { + // Use the custom orderings on any column that is of type DateTime + db.managers.todoItems.orderBy((f) => f.createdAt.byUnixEpoch()); +} +// #enddocregion manager_ordering_extensions + +// #docregion manager_custom_filter +// Extend the generated table filter composer to add a custom filter +extension NoContentOrBefore2000FilterX on $$TodoItemsTableFilterComposer { + ComposableFilter noContentOrBefore2000() => + (content.isNull() | createdAt.isBefore(DateTime(2000))); +} + +Future customFilter(AppDatabase db) async { + // Use the custom filter on the `TodoItems` table + db.managers.todoItems.filter((f) => f.noContentOrBefore2000()); +} +// #enddocregion manager_custom_filter + +// #docregion manager_custom_ordering +// Extend the generated table filter composer to add a custom filter +extension ContentThenCreationDataX on $$TodoItemsTableOrderingComposer { + ComposableOrdering contentThenCreatedAt() => content.asc() & createdAt.asc(); +} + +Future customOrdering(AppDatabase db) async { + // Use the custom ordering on the `TodoItems` table + db.managers.todoItems.orderBy((f) => f.contentThenCreatedAt()); +} +// #enddocregion manager_custom_ordering diff --git a/docs/lib/snippets/drift_files/custom_queries.drift.dart b/docs/lib/snippets/drift_files/custom_queries.drift.dart index 6bcbe9e7..651da4ec 100644 --- a/docs/lib/snippets/drift_files/custom_queries.drift.dart +++ b/docs/lib/snippets/drift_files/custom_queries.drift.dart @@ -1,9 +1,11 @@ // ignore_for_file: type=lint import 'package:drift/drift.dart' as i0; import 'package:drift_docs/snippets/_shared/todo_tables.drift.dart' as i1; +import 'package:drift/internal/modular.dart' as i2; abstract class $MyDatabase extends i0.GeneratedDatabase { $MyDatabase(i0.QueryExecutor e) : super(e); + $MyDatabaseManager get managers => $MyDatabaseManager(this); late final i1.$CategoriesTable categories = i1.$CategoriesTable(this); late final i1.$TodoItemsTable todoItems = i1.$TodoItemsTable(this); i0.Selectable categoriesWithCount() { @@ -28,6 +30,227 @@ abstract class $MyDatabase extends i0.GeneratedDatabase { [categories, todoItems]; } +class $$CategoriesTableFilterComposer + extends i0.FilterComposer { + $$CategoriesTableFilterComposer(super.db, super.table); + i0.ColumnFilters get id => i0.ColumnFilters($table.id); + i0.ColumnFilters get name => i0.ColumnFilters($table.name); + i0.ComposableFilter todoItemsRefs( + i0.ComposableFilter Function($$TodoItemsTableFilterComposer f) f) { + return $composeWithJoins( + $db: $db, + $table: $table, + referencedTable: i2.ReadDatabaseContainer($db) + .resultSet('todo_items'), + getCurrentColumn: (f) => f.id, + getReferencedColumn: (f) => f.category, + getReferencedComposer: (db, table) => + $$TodoItemsTableFilterComposer(db, table), + builder: f); + } +} + +class $$CategoriesTableOrderingComposer + extends i0.OrderingComposer { + $$CategoriesTableOrderingComposer(super.db, super.table); + i0.ColumnOrderings get id => i0.ColumnOrderings($table.id); + i0.ColumnOrderings get name => i0.ColumnOrderings($table.name); +} + +class $$CategoriesTableProcessedTableManager extends i0.ProcessedTableManager< + i0.GeneratedDatabase, + i1.$CategoriesTable, + i1.Category, + $$CategoriesTableFilterComposer, + $$CategoriesTableOrderingComposer, + $$CategoriesTableProcessedTableManager, + $$CategoriesTableInsertCompanionBuilder, + $$CategoriesTableUpdateCompanionBuilder> { + const $$CategoriesTableProcessedTableManager(super.$state); +} + +typedef $$CategoriesTableInsertCompanionBuilder = i1.CategoriesCompanion + Function({ + i0.Value id, + required String name, +}); +typedef $$CategoriesTableUpdateCompanionBuilder = i1.CategoriesCompanion + Function({ + i0.Value id, + i0.Value name, +}); + +class $$CategoriesTableTableManager extends i0.RootTableManager< + i0.GeneratedDatabase, + i1.$CategoriesTable, + i1.Category, + $$CategoriesTableFilterComposer, + $$CategoriesTableOrderingComposer, + $$CategoriesTableProcessedTableManager, + $$CategoriesTableInsertCompanionBuilder, + $$CategoriesTableUpdateCompanionBuilder> { + $$CategoriesTableTableManager( + i0.GeneratedDatabase db, i1.$CategoriesTable table) + : super(i0.TableManagerState( + db: db, + table: table, + filteringComposer: $$CategoriesTableFilterComposer(db, table), + orderingComposer: $$CategoriesTableOrderingComposer(db, table), + getChildManagerBuilder: (p0) => + $$CategoriesTableProcessedTableManager(p0), + getUpdateCompanionBuilder: ({ + i0.Value id = const i0.Value.absent(), + i0.Value name = const i0.Value.absent(), + }) => + i1.CategoriesCompanion( + id: id, + name: name, + ), + getInsertCompanionBuilder: ({ + i0.Value id = const i0.Value.absent(), + required String name, + }) => + i1.CategoriesCompanion.insert( + id: id, + name: name, + ))); +} + +class $$TodoItemsTableFilterComposer + extends i0.FilterComposer { + $$TodoItemsTableFilterComposer(super.db, super.table); + i0.ColumnFilters get id => i0.ColumnFilters($table.id); + i0.ColumnFilters get title => i0.ColumnFilters($table.title); + i0.ColumnFilters get content => i0.ColumnFilters($table.content); + i0.ColumnFilters get categoryId => i0.ColumnFilters($table.category); + i0.ComposableFilter category( + i0.ComposableFilter Function($$CategoriesTableFilterComposer f) f) { + return $composeWithJoins( + $db: $db, + $table: $table, + referencedTable: i2.ReadDatabaseContainer($db) + .resultSet('categories'), + getCurrentColumn: (f) => f.category, + getReferencedColumn: (f) => f.id, + getReferencedComposer: (db, table) => + $$CategoriesTableFilterComposer(db, table), + builder: f); + } + + i0.ColumnFilters get dueDate => i0.ColumnFilters($table.dueDate); +} + +class $$TodoItemsTableOrderingComposer + extends i0.OrderingComposer { + $$TodoItemsTableOrderingComposer(super.db, super.table); + i0.ColumnOrderings get id => i0.ColumnOrderings($table.id); + i0.ColumnOrderings get title => i0.ColumnOrderings($table.title); + i0.ColumnOrderings get content => i0.ColumnOrderings($table.content); + i0.ColumnOrderings get categoryId => i0.ColumnOrderings($table.category); + i0.ComposableOrdering category( + i0.ComposableOrdering Function($$CategoriesTableOrderingComposer o) o) { + return $composeWithJoins( + $db: $db, + $table: $table, + referencedTable: i2.ReadDatabaseContainer($db) + .resultSet('categories'), + getCurrentColumn: (f) => f.category, + getReferencedColumn: (f) => f.id, + getReferencedComposer: (db, table) => + $$CategoriesTableOrderingComposer(db, table), + builder: o); + } + + i0.ColumnOrderings get dueDate => + i0.ColumnOrderings($table.dueDate); +} + +class $$TodoItemsTableProcessedTableManager extends i0.ProcessedTableManager< + i0.GeneratedDatabase, + i1.$TodoItemsTable, + i1.TodoItem, + $$TodoItemsTableFilterComposer, + $$TodoItemsTableOrderingComposer, + $$TodoItemsTableProcessedTableManager, + $$TodoItemsTableInsertCompanionBuilder, + $$TodoItemsTableUpdateCompanionBuilder> { + const $$TodoItemsTableProcessedTableManager(super.$state); +} + +typedef $$TodoItemsTableInsertCompanionBuilder = i1.TodoItemsCompanion + Function({ + i0.Value id, + required String title, + required String content, + i0.Value category, + i0.Value dueDate, +}); +typedef $$TodoItemsTableUpdateCompanionBuilder = i1.TodoItemsCompanion + Function({ + i0.Value id, + i0.Value title, + i0.Value content, + i0.Value category, + i0.Value dueDate, +}); + +class $$TodoItemsTableTableManager extends i0.RootTableManager< + i0.GeneratedDatabase, + i1.$TodoItemsTable, + i1.TodoItem, + $$TodoItemsTableFilterComposer, + $$TodoItemsTableOrderingComposer, + $$TodoItemsTableProcessedTableManager, + $$TodoItemsTableInsertCompanionBuilder, + $$TodoItemsTableUpdateCompanionBuilder> { + $$TodoItemsTableTableManager( + i0.GeneratedDatabase db, i1.$TodoItemsTable table) + : super(i0.TableManagerState( + db: db, + table: table, + filteringComposer: $$TodoItemsTableFilterComposer(db, table), + orderingComposer: $$TodoItemsTableOrderingComposer(db, table), + getChildManagerBuilder: (p0) => + $$TodoItemsTableProcessedTableManager(p0), + getUpdateCompanionBuilder: ({ + i0.Value id = const i0.Value.absent(), + i0.Value title = const i0.Value.absent(), + i0.Value content = const i0.Value.absent(), + i0.Value category = const i0.Value.absent(), + i0.Value dueDate = const i0.Value.absent(), + }) => + i1.TodoItemsCompanion( + id: id, + title: title, + content: content, + category: category, + dueDate: dueDate, + ), + getInsertCompanionBuilder: ({ + i0.Value id = const i0.Value.absent(), + required String title, + required String content, + i0.Value category = const i0.Value.absent(), + i0.Value dueDate = const i0.Value.absent(), + }) => + i1.TodoItemsCompanion.insert( + id: id, + title: title, + content: content, + category: category, + dueDate: dueDate, + ))); +} + +class $MyDatabaseManager { + final $MyDatabase _db; + $MyDatabaseManager(this._db); + $$CategoriesTableTableManager get categories => + $$CategoriesTableTableManager(_db, _db.categories); + $$TodoItemsTableTableManager get todoItems => + $$TodoItemsTableTableManager(_db, _db.todoItems); +} + class CategoriesWithCountResult { final int id; final String name; diff --git a/docs/lib/snippets/modular/many_to_many/json.drift.dart b/docs/lib/snippets/modular/many_to_many/json.drift.dart index a7bdd538..e83c4267 100644 --- a/docs/lib/snippets/modular/many_to_many/json.drift.dart +++ b/docs/lib/snippets/modular/many_to_many/json.drift.dart @@ -7,6 +7,7 @@ import 'package:drift_docs/snippets/modular/many_to_many/json.dart' as i3; abstract class $JsonBasedDatabase extends i0.GeneratedDatabase { $JsonBasedDatabase(i0.QueryExecutor e) : super(e); + $JsonBasedDatabaseManager get managers => $JsonBasedDatabaseManager(this); late final i1.$BuyableItemsTable buyableItems = i1.$BuyableItemsTable(this); late final i2.$ShoppingCartsTable shoppingCarts = i2.$ShoppingCartsTable(this); @@ -18,6 +19,177 @@ abstract class $JsonBasedDatabase extends i0.GeneratedDatabase { [buyableItems, shoppingCarts]; } +class $$BuyableItemsTableFilterComposer + extends i0.FilterComposer { + $$BuyableItemsTableFilterComposer(super.db, super.table); + i0.ColumnFilters get id => i0.ColumnFilters($table.id); + i0.ColumnFilters get description => + i0.ColumnFilters($table.description); + i0.ColumnFilters get price => i0.ColumnFilters($table.price); +} + +class $$BuyableItemsTableOrderingComposer + extends i0.OrderingComposer { + $$BuyableItemsTableOrderingComposer(super.db, super.table); + i0.ColumnOrderings get id => i0.ColumnOrderings($table.id); + i0.ColumnOrderings get description => + i0.ColumnOrderings($table.description); + i0.ColumnOrderings get price => i0.ColumnOrderings($table.price); +} + +class $$BuyableItemsTableProcessedTableManager extends i0.ProcessedTableManager< + i0.GeneratedDatabase, + i1.$BuyableItemsTable, + i1.BuyableItem, + $$BuyableItemsTableFilterComposer, + $$BuyableItemsTableOrderingComposer, + $$BuyableItemsTableProcessedTableManager, + $$BuyableItemsTableInsertCompanionBuilder, + $$BuyableItemsTableUpdateCompanionBuilder> { + const $$BuyableItemsTableProcessedTableManager(super.$state); +} + +typedef $$BuyableItemsTableInsertCompanionBuilder = i1.BuyableItemsCompanion + Function({ + i0.Value id, + required String description, + required int price, +}); +typedef $$BuyableItemsTableUpdateCompanionBuilder = i1.BuyableItemsCompanion + Function({ + i0.Value id, + i0.Value description, + i0.Value price, +}); + +class $$BuyableItemsTableTableManager extends i0.RootTableManager< + i0.GeneratedDatabase, + i1.$BuyableItemsTable, + i1.BuyableItem, + $$BuyableItemsTableFilterComposer, + $$BuyableItemsTableOrderingComposer, + $$BuyableItemsTableProcessedTableManager, + $$BuyableItemsTableInsertCompanionBuilder, + $$BuyableItemsTableUpdateCompanionBuilder> { + $$BuyableItemsTableTableManager( + i0.GeneratedDatabase db, i1.$BuyableItemsTable table) + : super(i0.TableManagerState( + db: db, + table: table, + filteringComposer: $$BuyableItemsTableFilterComposer(db, table), + orderingComposer: $$BuyableItemsTableOrderingComposer(db, table), + getChildManagerBuilder: (p0) => + $$BuyableItemsTableProcessedTableManager(p0), + getUpdateCompanionBuilder: ({ + i0.Value id = const i0.Value.absent(), + i0.Value description = const i0.Value.absent(), + i0.Value price = const i0.Value.absent(), + }) => + i1.BuyableItemsCompanion( + id: id, + description: description, + price: price, + ), + getInsertCompanionBuilder: ({ + i0.Value id = const i0.Value.absent(), + required String description, + required int price, + }) => + i1.BuyableItemsCompanion.insert( + id: id, + description: description, + price: price, + ))); +} + +class $$ShoppingCartsTableFilterComposer + extends i0.FilterComposer { + $$ShoppingCartsTableFilterComposer(super.db, super.table); + i0.ColumnFilters get id => i0.ColumnFilters($table.id); + i0.ColumnFilters get entriesValue => i0.ColumnFilters($table.entries); + i0.ColumnWithTypeConverterFilters< + i3.ShoppingCartEntries, + i3.ShoppingCartEntries, + String> get entries => i0.ColumnWithTypeConverterFilters($table.entries); +} + +class $$ShoppingCartsTableOrderingComposer + extends i0.OrderingComposer { + $$ShoppingCartsTableOrderingComposer(super.db, super.table); + i0.ColumnOrderings get id => i0.ColumnOrderings($table.id); + i0.ColumnOrderings get entries => i0.ColumnOrderings($table.entries); +} + +class $$ShoppingCartsTableProcessedTableManager + extends i0.ProcessedTableManager< + i0.GeneratedDatabase, + i2.$ShoppingCartsTable, + i2.ShoppingCart, + $$ShoppingCartsTableFilterComposer, + $$ShoppingCartsTableOrderingComposer, + $$ShoppingCartsTableProcessedTableManager, + $$ShoppingCartsTableInsertCompanionBuilder, + $$ShoppingCartsTableUpdateCompanionBuilder> { + const $$ShoppingCartsTableProcessedTableManager(super.$state); +} + +typedef $$ShoppingCartsTableInsertCompanionBuilder = i2.ShoppingCartsCompanion + Function({ + i0.Value id, + required i3.ShoppingCartEntries entries, +}); +typedef $$ShoppingCartsTableUpdateCompanionBuilder = i2.ShoppingCartsCompanion + Function({ + i0.Value id, + i0.Value entries, +}); + +class $$ShoppingCartsTableTableManager extends i0.RootTableManager< + i0.GeneratedDatabase, + i2.$ShoppingCartsTable, + i2.ShoppingCart, + $$ShoppingCartsTableFilterComposer, + $$ShoppingCartsTableOrderingComposer, + $$ShoppingCartsTableProcessedTableManager, + $$ShoppingCartsTableInsertCompanionBuilder, + $$ShoppingCartsTableUpdateCompanionBuilder> { + $$ShoppingCartsTableTableManager( + i0.GeneratedDatabase db, i2.$ShoppingCartsTable table) + : super(i0.TableManagerState( + db: db, + table: table, + filteringComposer: $$ShoppingCartsTableFilterComposer(db, table), + orderingComposer: $$ShoppingCartsTableOrderingComposer(db, table), + getChildManagerBuilder: (p0) => + $$ShoppingCartsTableProcessedTableManager(p0), + getUpdateCompanionBuilder: ({ + i0.Value id = const i0.Value.absent(), + i0.Value entries = + const i0.Value.absent(), + }) => + i2.ShoppingCartsCompanion( + id: id, + entries: entries, + ), + getInsertCompanionBuilder: ({ + i0.Value id = const i0.Value.absent(), + required i3.ShoppingCartEntries entries, + }) => + i2.ShoppingCartsCompanion.insert( + id: id, + entries: entries, + ))); +} + +class $JsonBasedDatabaseManager { + final $JsonBasedDatabase _db; + $JsonBasedDatabaseManager(this._db); + $$BuyableItemsTableTableManager get buyableItems => + $$BuyableItemsTableTableManager(_db, _db.buyableItems); + $$ShoppingCartsTableTableManager get shoppingCarts => + $$ShoppingCartsTableTableManager(_db, _db.shoppingCarts); +} + class $ShoppingCartsTable extends i3.ShoppingCarts with i0.TableInfo<$ShoppingCartsTable, i2.ShoppingCart> { @override diff --git a/docs/lib/snippets/modular/many_to_many/relational.drift.dart b/docs/lib/snippets/modular/many_to_many/relational.drift.dart index 1d1bab75..100d677e 100644 --- a/docs/lib/snippets/modular/many_to_many/relational.drift.dart +++ b/docs/lib/snippets/modular/many_to_many/relational.drift.dart @@ -4,10 +4,12 @@ import 'package:drift_docs/snippets/modular/many_to_many/shared.drift.dart' as i1; import 'package:drift_docs/snippets/modular/many_to_many/relational.drift.dart' as i2; -import 'package:drift_docs/snippets/modular/many_to_many/relational.dart' as i3; +import 'package:drift/internal/modular.dart' as i3; +import 'package:drift_docs/snippets/modular/many_to_many/relational.dart' as i4; abstract class $RelationalDatabase extends i0.GeneratedDatabase { $RelationalDatabase(i0.QueryExecutor e) : super(e); + $RelationalDatabaseManager get managers => $RelationalDatabaseManager(this); late final i1.$BuyableItemsTable buyableItems = i1.$BuyableItemsTable(this); late final i2.$ShoppingCartsTable shoppingCarts = i2.$ShoppingCartsTable(this); @@ -21,7 +23,334 @@ abstract class $RelationalDatabase extends i0.GeneratedDatabase { [buyableItems, shoppingCarts, shoppingCartEntries]; } -class $ShoppingCartsTable extends i3.ShoppingCarts +class $$BuyableItemsTableFilterComposer + extends i0.FilterComposer { + $$BuyableItemsTableFilterComposer(super.db, super.table); + i0.ColumnFilters get id => i0.ColumnFilters($table.id); + i0.ColumnFilters get description => + i0.ColumnFilters($table.description); + i0.ColumnFilters get price => i0.ColumnFilters($table.price); + i0.ComposableFilter shoppingCartEntriesRefs( + i0.ComposableFilter Function($$ShoppingCartEntriesTableFilterComposer f) + f) { + return $composeWithJoins( + $db: $db, + $table: $table, + referencedTable: i3.ReadDatabaseContainer($db) + .resultSet('shopping_cart_entries'), + getCurrentColumn: (f) => f.id, + getReferencedColumn: (f) => f.item, + getReferencedComposer: (db, table) => + $$ShoppingCartEntriesTableFilterComposer(db, table), + builder: f); + } +} + +class $$BuyableItemsTableOrderingComposer + extends i0.OrderingComposer { + $$BuyableItemsTableOrderingComposer(super.db, super.table); + i0.ColumnOrderings get id => i0.ColumnOrderings($table.id); + i0.ColumnOrderings get description => + i0.ColumnOrderings($table.description); + i0.ColumnOrderings get price => i0.ColumnOrderings($table.price); +} + +class $$BuyableItemsTableProcessedTableManager extends i0.ProcessedTableManager< + i0.GeneratedDatabase, + i1.$BuyableItemsTable, + i1.BuyableItem, + $$BuyableItemsTableFilterComposer, + $$BuyableItemsTableOrderingComposer, + $$BuyableItemsTableProcessedTableManager, + $$BuyableItemsTableInsertCompanionBuilder, + $$BuyableItemsTableUpdateCompanionBuilder> { + const $$BuyableItemsTableProcessedTableManager(super.$state); +} + +typedef $$BuyableItemsTableInsertCompanionBuilder = i1.BuyableItemsCompanion + Function({ + i0.Value id, + required String description, + required int price, +}); +typedef $$BuyableItemsTableUpdateCompanionBuilder = i1.BuyableItemsCompanion + Function({ + i0.Value id, + i0.Value description, + i0.Value price, +}); + +class $$BuyableItemsTableTableManager extends i0.RootTableManager< + i0.GeneratedDatabase, + i1.$BuyableItemsTable, + i1.BuyableItem, + $$BuyableItemsTableFilterComposer, + $$BuyableItemsTableOrderingComposer, + $$BuyableItemsTableProcessedTableManager, + $$BuyableItemsTableInsertCompanionBuilder, + $$BuyableItemsTableUpdateCompanionBuilder> { + $$BuyableItemsTableTableManager( + i0.GeneratedDatabase db, i1.$BuyableItemsTable table) + : super(i0.TableManagerState( + db: db, + table: table, + filteringComposer: $$BuyableItemsTableFilterComposer(db, table), + orderingComposer: $$BuyableItemsTableOrderingComposer(db, table), + getChildManagerBuilder: (p0) => + $$BuyableItemsTableProcessedTableManager(p0), + getUpdateCompanionBuilder: ({ + i0.Value id = const i0.Value.absent(), + i0.Value description = const i0.Value.absent(), + i0.Value price = const i0.Value.absent(), + }) => + i1.BuyableItemsCompanion( + id: id, + description: description, + price: price, + ), + getInsertCompanionBuilder: ({ + i0.Value id = const i0.Value.absent(), + required String description, + required int price, + }) => + i1.BuyableItemsCompanion.insert( + id: id, + description: description, + price: price, + ))); +} + +class $$ShoppingCartsTableFilterComposer + extends i0.FilterComposer { + $$ShoppingCartsTableFilterComposer(super.db, super.table); + i0.ColumnFilters get id => i0.ColumnFilters($table.id); + i0.ComposableFilter shoppingCartEntriesRefs( + i0.ComposableFilter Function($$ShoppingCartEntriesTableFilterComposer f) + f) { + return $composeWithJoins( + $db: $db, + $table: $table, + referencedTable: i3.ReadDatabaseContainer($db) + .resultSet('shopping_cart_entries'), + getCurrentColumn: (f) => f.id, + getReferencedColumn: (f) => f.shoppingCart, + getReferencedComposer: (db, table) => + $$ShoppingCartEntriesTableFilterComposer(db, table), + builder: f); + } +} + +class $$ShoppingCartsTableOrderingComposer + extends i0.OrderingComposer { + $$ShoppingCartsTableOrderingComposer(super.db, super.table); + i0.ColumnOrderings get id => i0.ColumnOrderings($table.id); +} + +class $$ShoppingCartsTableProcessedTableManager + extends i0.ProcessedTableManager< + i0.GeneratedDatabase, + i2.$ShoppingCartsTable, + i2.ShoppingCart, + $$ShoppingCartsTableFilterComposer, + $$ShoppingCartsTableOrderingComposer, + $$ShoppingCartsTableProcessedTableManager, + $$ShoppingCartsTableInsertCompanionBuilder, + $$ShoppingCartsTableUpdateCompanionBuilder> { + const $$ShoppingCartsTableProcessedTableManager(super.$state); +} + +typedef $$ShoppingCartsTableInsertCompanionBuilder = i2.ShoppingCartsCompanion + Function({ + i0.Value id, +}); +typedef $$ShoppingCartsTableUpdateCompanionBuilder = i2.ShoppingCartsCompanion + Function({ + i0.Value id, +}); + +class $$ShoppingCartsTableTableManager extends i0.RootTableManager< + i0.GeneratedDatabase, + i2.$ShoppingCartsTable, + i2.ShoppingCart, + $$ShoppingCartsTableFilterComposer, + $$ShoppingCartsTableOrderingComposer, + $$ShoppingCartsTableProcessedTableManager, + $$ShoppingCartsTableInsertCompanionBuilder, + $$ShoppingCartsTableUpdateCompanionBuilder> { + $$ShoppingCartsTableTableManager( + i0.GeneratedDatabase db, i2.$ShoppingCartsTable table) + : super(i0.TableManagerState( + db: db, + table: table, + filteringComposer: $$ShoppingCartsTableFilterComposer(db, table), + orderingComposer: $$ShoppingCartsTableOrderingComposer(db, table), + getChildManagerBuilder: (p0) => + $$ShoppingCartsTableProcessedTableManager(p0), + getUpdateCompanionBuilder: ({ + i0.Value id = const i0.Value.absent(), + }) => + i2.ShoppingCartsCompanion( + id: id, + ), + getInsertCompanionBuilder: ({ + i0.Value id = const i0.Value.absent(), + }) => + i2.ShoppingCartsCompanion.insert( + id: id, + ))); +} + +class $$ShoppingCartEntriesTableFilterComposer extends i0 + .FilterComposer { + $$ShoppingCartEntriesTableFilterComposer(super.db, super.table); + i0.ColumnFilters get shoppingCartId => + i0.ColumnFilters($table.shoppingCart); + i0.ComposableFilter shoppingCart( + i0.ComposableFilter Function($$ShoppingCartsTableFilterComposer f) f) { + return $composeWithJoins( + $db: $db, + $table: $table, + referencedTable: i3.ReadDatabaseContainer($db) + .resultSet('shopping_carts'), + getCurrentColumn: (f) => f.shoppingCart, + getReferencedColumn: (f) => f.id, + getReferencedComposer: (db, table) => + $$ShoppingCartsTableFilterComposer(db, table), + builder: f); + } + + i0.ColumnFilters get itemId => i0.ColumnFilters($table.item); + i0.ComposableFilter item( + i0.ComposableFilter Function($$BuyableItemsTableFilterComposer f) f) { + return $composeWithJoins( + $db: $db, + $table: $table, + referencedTable: i3.ReadDatabaseContainer($db) + .resultSet('buyable_items'), + getCurrentColumn: (f) => f.item, + getReferencedColumn: (f) => f.id, + getReferencedComposer: (db, table) => + $$BuyableItemsTableFilterComposer(db, table), + builder: f); + } +} + +class $$ShoppingCartEntriesTableOrderingComposer extends i0 + .OrderingComposer { + $$ShoppingCartEntriesTableOrderingComposer(super.db, super.table); + i0.ColumnOrderings get shoppingCartId => + i0.ColumnOrderings($table.shoppingCart); + i0.ComposableOrdering shoppingCart( + i0.ComposableOrdering Function($$ShoppingCartsTableOrderingComposer o) + o) { + return $composeWithJoins( + $db: $db, + $table: $table, + referencedTable: i3.ReadDatabaseContainer($db) + .resultSet('shopping_carts'), + getCurrentColumn: (f) => f.shoppingCart, + getReferencedColumn: (f) => f.id, + getReferencedComposer: (db, table) => + $$ShoppingCartsTableOrderingComposer(db, table), + builder: o); + } + + i0.ColumnOrderings get itemId => i0.ColumnOrderings($table.item); + i0.ComposableOrdering item( + i0.ComposableOrdering Function($$BuyableItemsTableOrderingComposer o) o) { + return $composeWithJoins( + $db: $db, + $table: $table, + referencedTable: i3.ReadDatabaseContainer($db) + .resultSet('buyable_items'), + getCurrentColumn: (f) => f.item, + getReferencedColumn: (f) => f.id, + getReferencedComposer: (db, table) => + $$BuyableItemsTableOrderingComposer(db, table), + builder: o); + } +} + +class $$ShoppingCartEntriesTableProcessedTableManager + extends i0.ProcessedTableManager< + i0.GeneratedDatabase, + i2.$ShoppingCartEntriesTable, + i2.ShoppingCartEntry, + $$ShoppingCartEntriesTableFilterComposer, + $$ShoppingCartEntriesTableOrderingComposer, + $$ShoppingCartEntriesTableProcessedTableManager, + $$ShoppingCartEntriesTableInsertCompanionBuilder, + $$ShoppingCartEntriesTableUpdateCompanionBuilder> { + const $$ShoppingCartEntriesTableProcessedTableManager(super.$state); +} + +typedef $$ShoppingCartEntriesTableInsertCompanionBuilder + = i2.ShoppingCartEntriesCompanion Function({ + required int shoppingCart, + required int item, + i0.Value rowid, +}); +typedef $$ShoppingCartEntriesTableUpdateCompanionBuilder + = i2.ShoppingCartEntriesCompanion Function({ + i0.Value shoppingCart, + i0.Value item, + i0.Value rowid, +}); + +class $$ShoppingCartEntriesTableTableManager extends i0.RootTableManager< + i0.GeneratedDatabase, + i2.$ShoppingCartEntriesTable, + i2.ShoppingCartEntry, + $$ShoppingCartEntriesTableFilterComposer, + $$ShoppingCartEntriesTableOrderingComposer, + $$ShoppingCartEntriesTableProcessedTableManager, + $$ShoppingCartEntriesTableInsertCompanionBuilder, + $$ShoppingCartEntriesTableUpdateCompanionBuilder> { + $$ShoppingCartEntriesTableTableManager( + i0.GeneratedDatabase db, i2.$ShoppingCartEntriesTable table) + : super(i0.TableManagerState( + db: db, + table: table, + filteringComposer: + $$ShoppingCartEntriesTableFilterComposer(db, table), + orderingComposer: + $$ShoppingCartEntriesTableOrderingComposer(db, table), + getChildManagerBuilder: (p0) => + $$ShoppingCartEntriesTableProcessedTableManager(p0), + getUpdateCompanionBuilder: ({ + i0.Value shoppingCart = const i0.Value.absent(), + i0.Value item = const i0.Value.absent(), + i0.Value rowid = const i0.Value.absent(), + }) => + i2.ShoppingCartEntriesCompanion( + shoppingCart: shoppingCart, + item: item, + rowid: rowid, + ), + getInsertCompanionBuilder: ({ + required int shoppingCart, + required int item, + i0.Value rowid = const i0.Value.absent(), + }) => + i2.ShoppingCartEntriesCompanion.insert( + shoppingCart: shoppingCart, + item: item, + rowid: rowid, + ))); +} + +class $RelationalDatabaseManager { + final $RelationalDatabase _db; + $RelationalDatabaseManager(this._db); + $$BuyableItemsTableTableManager get buyableItems => + $$BuyableItemsTableTableManager(_db, _db.buyableItems); + $$ShoppingCartsTableTableManager get shoppingCarts => + $$ShoppingCartsTableTableManager(_db, _db.shoppingCarts); + $$ShoppingCartEntriesTableTableManager get shoppingCartEntries => + $$ShoppingCartEntriesTableTableManager(_db, _db.shoppingCartEntries); +} + +class $ShoppingCartsTable extends i4.ShoppingCarts with i0.TableInfo<$ShoppingCartsTable, i2.ShoppingCart> { @override final i0.GeneratedDatabase attachedDatabase; @@ -163,7 +492,7 @@ class ShoppingCartsCompanion extends i0.UpdateCompanion { } } -class $ShoppingCartEntriesTable extends i3.ShoppingCartEntries +class $ShoppingCartEntriesTable extends i4.ShoppingCartEntries with i0.TableInfo<$ShoppingCartEntriesTable, i2.ShoppingCartEntry> { @override final i0.GeneratedDatabase attachedDatabase; diff --git a/docs/lib/snippets/setup/database.dart b/docs/lib/snippets/setup/database.dart index 3779df69..ed146019 100644 --- a/docs/lib/snippets/setup/database.dart +++ b/docs/lib/snippets/setup/database.dart @@ -22,11 +22,19 @@ class TodoItems extends Table { IntColumn get id => integer().autoIncrement()(); TextColumn get title => text().withLength(min: 6, max: 32)(); TextColumn get content => text().named('body')(); - IntColumn get category => integer().nullable()(); + IntColumn get category => + integer().nullable().references(TodoCategory, #id)(); + DateTimeColumn get createdAt => dateTime().nullable()(); } + +class TodoCategory extends Table { + IntColumn get id => integer().autoIncrement()(); + TextColumn get description => text()(); +} + // #enddocregion table -@DriftDatabase(tables: [TodoItems]) +@DriftDatabase(tables: [TodoItems, TodoCategory]) class AppDatabase extends _$AppDatabase { // #enddocregion before_generation // #enddocregion after_generation diff --git a/docs/pages/docs/Dart API/manager.md b/docs/pages/docs/Dart API/manager.md new file mode 100644 index 00000000..6bf5cfb4 --- /dev/null +++ b/docs/pages/docs/Dart API/manager.md @@ -0,0 +1,125 @@ +--- +data: + title: Manager + description: Use easier bindings for common queries. + weight: 1 + +template: layouts/docs/single +path: /docs/getting-started/manager/ +--- + +{% assign snippets = 'package:drift_docs/snippets/dart_api/manager.dart.excerpt.json' | readString | json_decode %} + +With generated code, drift allows writing SQL queries in type-safe Dart. +While this is provides lots of flexibility, it requires familiarity with SQL. +As a simpler alternative, drift 2.17 introduced a new set of APIs designed to +make common queries much easier to write. + +The examples on this page use the database from the [setup]({{ '../setup.md' | pageUrl }}) +instructions. + +When manager generation is enabled (default), drift will generate a manager for each table in the database. +A collection of these managers are accessed by a getter `managers` on the database class. +Each table will have a manager generated for it unless it uses a custom row class. + +## Select + +The manager simplifies the process of retrieving rows from a table. Use it to read rows from the table or watch +for changes. + +{% include "blocks/snippet" snippets = snippets name = 'manager_select' %} + +The manager provides a really easy to use API for selecting rows from a table. These can be combined with `|` and `&` and parenthesis to construct more complex queries. Use `.not` to negate a condition. + +{% include "blocks/snippet" snippets = snippets name = 'manager_filter' %} + +Every column has filters for equality, inequality and nullability. +Type specific filters for `int`, `double`, `Int64`, `DateTime` and `String` are included out of the box. + +{% include "blocks/snippet" snippets = snippets name = 'manager_type_specific_filter' %} + + +### Filtering across tables +You can filter across references to other tables by using the generated reference filters. You can nest these as deep as you'd like and the manager will take care of adding the aliased joins behind the scenes. + +{% include "blocks/snippet" snippets = snippets name = 'manager_filter_forward_references' %} + +You can also filter across back references. This is useful when you have a one-to-many relationship and want to filter the parent table based on the child table. + +{% include "blocks/snippet" snippets = snippets name = 'manager_filter_back_references' %} + +The code generator will name this filterset using the name of the table that is being referenced. In the above example, the filterset is named `todoItemsRefs`, because the `TodoItems` table is being referenced. +However, you can also specify a custom name for the filterset using the `@ReferenceName(...)` annotation on the foreign key. This may be necessary if you have multiple references to the same table, take the following example: + +{% include "blocks/snippet" snippets = snippets name = 'user_group_tables' %} + +We can now use them in a query like this: + +{% include "blocks/snippet" snippets = snippets name = 'manager_filter_custom_back_references' %} + +In this example, had we not specified a custom name for the reference, the code generator would have named both filtersets `userRefs` for both references to the `User` table. This would have caused a conflict. By specifying a custom name, we can avoid this issue. + + +### Ordering + +You can also order the results of a query using the `orderBy` method. The syntax is similar to the `filter` method. +Use the `&` to combine multiple orderings. Orderings are applied in the order they are added. +You can also use ordering across multiple tables just like with filters. + +{% include "blocks/snippet" snippets = snippets name = 'manager_ordering' %} + + +### Count and exists +The manager makes it easy to check if a row exists or to count the number of rows that match a certain condition. + +{% include "blocks/snippet" snippets = snippets name = 'manager_count' %} + +{% include "blocks/snippet" snippets = snippets name = 'manager_exists' %} + + +## Updates +We can use the manager to update rows in bulk or individual rows that meet a certain condition. + +{% include "blocks/snippet" snippets = snippets name = 'manager_update' %} + +We can also replace an entire row with a new one. Or even replace multiple rows at once. + +{% include "blocks/snippet" snippets = snippets name = 'manager_replace' %} + +## Creating rows +The manager includes a method for quickly inserting rows into a table. +We can insert a single row or multiple rows at once. + +{% include "blocks/snippet" snippets = snippets name = 'manager_create' %} + + +## Deleting rows +We may also delete rows from a table using the manager. +Any rows that meet the specified condition will be deleted. + +{% include "blocks/snippet" snippets = snippets name = 'manager_delete' %} + +## Extensions +The manager provides a set of filters and orderings out of the box for common types, however you can +extend them to add new filters and orderings. + +#### Custom Column Filters +If you want to add new filters for individual columns types, you can extend the `ColumnFilter` class. + +{% include "blocks/snippet" snippets = snippets name = 'manager_filter_extensions' %} + +#### Custom Table Filters +You can also create custom filters that operate on multiple columns by extending generated filtersets. + +{% include "blocks/snippet" snippets = snippets name = 'manager_custom_filter' %} + +#### Custom Column Orderings +You can create new ordering methods for individual columns types by extending the `ColumnOrdering` class. +Use the `ComposableOrdering` class to create complex orderings. + +{% include "blocks/snippet" snippets = snippets name = 'manager_ordering_extensions' %} + +#### Custom Table Filters +You can also create custom filters that operate on multiple columns by extending generated filtersets. + +{% include "blocks/snippet" snippets = snippets name = 'manager_custom_filter' %} \ No newline at end of file diff --git a/docs/pages/docs/Generation options/index.md b/docs/pages/docs/Generation options/index.md index bc6b843a..d22b3862 100644 --- a/docs/pages/docs/Generation options/index.md +++ b/docs/pages/docs/Generation options/index.md @@ -6,7 +6,7 @@ data: template: layouts/docs/list path: docs/advanced-features/builder_options/ aliases: - - "options/" + - "options/" --- The `drift_dev` package supports a range of options that control how code @@ -19,6 +19,7 @@ advice on which options to use. To use the options, create a `build.yaml` file in the root of your project (e.g. next to your `pubspec.yaml`): + ```yaml # build.yaml. This file is quite powerful, see https://pub.dev/packages/build_config @@ -34,59 +35,60 @@ targets: At the moment, drift supports these options: -* `write_from_json_string_constructor`: boolean. Adds a `.fromJsonString` factory - constructor to generated data classes. By default, we only write a `.fromJson` - constructor that takes a `Map`. -* `override_hash_and_equals_in_result_sets`: boolean. When drift generates another class - to hold the result of generated select queries, this flag controls whether drift should - override `operator ==` and `hashCode` in those classes. In recent versions, it will also - override `toString` if this option is enabled. -* `skip_verification_code`: Generated tables contain a significant chunk of code to verify integrity +- `write_from_json_string_constructor`: boolean. Adds a `.fromJsonString` factory + constructor to generated data classes. By default, we only write a `.fromJson` + constructor that takes a `Map`. +- `override_hash_and_equals_in_result_sets`: boolean. When drift generates another class + to hold the result of generated select queries, this flag controls whether drift should + override `operator ==` and `hashCode` in those classes. In recent versions, it will also + override `toString` if this option is enabled. +- `skip_verification_code`: Generated tables contain a significant chunk of code to verify integrity of inserted data and report detailed errors when the integrity is violated. If you're only using inserts with SQL, or don't need this functionality, enabling this flag can help to reduce the amount generated code. -* `use_data_class_name_for_companions`: By default, the name for [companion classes]({{ "../Dart API/writes.md#updates-and-deletes" | pageUrl }}) +- `use_data_class_name_for_companions`: By default, the name for [companion classes]({{ "../Dart API/writes.md#updates-and-deletes" | pageUrl }}) is based on the table name (e.g. a `@DataClassName('Users') class UsersTable extends Table` would generate a `UsersTableCompanion`). With this option, the name is based on the data class (so `UsersCompanion` in this case). -* `use_column_name_as_json_key_when_defined_in_moor_file` (defaults to `true`): When serializing columns declared inside a +- `use_column_name_as_json_key_when_defined_in_moor_file` (defaults to `true`): When serializing columns declared inside a `.drift` file from and to json, use their sql name instead of the generated Dart getter name (so a column named `user_name` would also use `user_name` as a json key instead of `userName`). You can always override the json key by using a `JSON KEY` column constraint (e.g. `user_name VARCHAR NOT NULL JSON KEY userName`). -* `use_sql_column_name_as_json_key` (defaults to false): Uses the column name in SQL as the JSON key for serialization, +- `use_sql_column_name_as_json_key` (defaults to false): Uses the column name in SQL as the JSON key for serialization, regardless of whether the table was defined in a drift file or not. -* `generate_connect_constructor` (deprecated): Generates a named `connect()` constructor on database classes +- `generate_connect_constructor` (deprecated): Generates a named `connect()` constructor on database classes that takes a `DatabaseConnection` instead of a `QueryExecutor`. This option was deprecated in drift 2.5 because `DatabaseConnection` now implements `QueryExecutor`. -* `data_class_to_companions` (defaults to `true`): Controls whether drift will write the `toCompanion` method in generated - data classes. -* `mutable_classes` (defaults to `false`): The fields generated in generated data, companion and result set classes are final +- `data_class_to_companions` (defaults to `true`): Controls whether drift will write the `toCompanion` method in generated + data classes. +- `mutable_classes` (defaults to `false`): The fields generated in generated data, companion and result set classes are final by default. You can make them mutable by setting `mutable_classes: true`. -* `raw_result_set_data`: The generator will expose the underlying `QueryRow` for generated result set classes -* `apply_converters_on_variables` (defaults to `true`): Applies type converters to variables in compiled statements. -* `generate_values_in_copy_with` (defaults to `true`): Generates a `Value` instead of `T?` for nullable columns in `copyWith`. This allows to set +- `raw_result_set_data`: The generator will expose the underlying `QueryRow` for generated result set classes +- `apply_converters_on_variables` (defaults to `true`): Applies type converters to variables in compiled statements. +- `generate_values_in_copy_with` (defaults to `true`): Generates a `Value` instead of `T?` for nullable columns in `copyWith`. This allows to set columns back to null (by using `Value(null)`). Passing `null` was ignored before, making it impossible to set columns to `null`. -* `named_parameters`: Generates named parameters for named variables in SQL queries. -* `named_parameters_always_required`: All named parameters (generated if `named_parameters` option is `true`) will be required in Dart. -* `scoped_dart_components` (defaults to `true`): Generates a function parameter for [Dart placeholders]({{ '../SQL API/drift_files.md#dart-components-in-sql' | pageUrl }}) in SQL. +- `named_parameters`: Generates named parameters for named variables in SQL queries. +- `named_parameters_always_required`: All named parameters (generated if `named_parameters` option is `true`) will be required in Dart. +- `scoped_dart_components` (defaults to `true`): Generates a function parameter for [Dart placeholders]({{ '../SQL API/drift_files.md#dart-components-in-sql' | pageUrl }}) in SQL. The function has a parameter for each table that is available in the query, making it easier to get aliases right when using Dart placeholders. -* `store_date_time_values_as_text`: Whether date-time columns should be stored as ISO 8601 string instead of a unix timestamp. +- `store_date_time_values_as_text`: Whether date-time columns should be stored as ISO 8601 string instead of a unix timestamp. For more information on these modes, see [datetime options]({{ '../Dart API/tables.md#datetime-options' | pageUrl }}). -* `case_from_dart_to_sql` (defaults to `snake_case`): Controls how the table and column names are re-cased from the Dart identifiers. - The possible values are `preserve`, `camelCase`, `CONSTANT_CASE`, `snake_case`, `PascalCase`, `lowercase` and `UPPERCASE` (default: `snake_case`). -* `write_to_columns_mixins`: Whether the `toColumns` method should be written as a mixin instead of being added directly to the data class. - This is useful when using [existing row classes]({{ '../custom_row_classes.md' | pageUrl }}), as the mixin is generated for those as well. -* `has_separate_analyzer`: This option is only relevant when using the `drift_dev:not_shared` builder, which needs to use a less efficient +- `case_from_dart_to_sql` (defaults to `snake_case`): Controls how the table and column names are re-cased from the Dart identifiers. + The possible values are `preserve`, `camelCase`, `CONSTANT_CASE`, `snake_case`, `PascalCase`, `lowercase` and `UPPERCASE` (default: `snake_case`). +- `write_to_columns_mixins`: Whether the `toColumns` method should be written as a mixin instead of being added directly to the data class. + This is useful when using [existing row classes]({{ '../custom_row_classes.md' | pageUrl }}), as the mixin is generated for those as well. +- `has_separate_analyzer`: This option is only relevant when using the `drift_dev:not_shared` builder, which needs to use a less efficient analysis implementation than the other builders by default. After also applying `drift_dev:analyzer` to the same build target, this option can be enabled to speed up builds. This option has no effect with the default or the modular builder. -* `fatal_warnings`: When enabled (defaults to `false`), warnings found by `drift_dev` in the build process (like syntax errors in SQL queries or +- `fatal_warnings`: When enabled (defaults to `false`), warnings found by `drift_dev` in the build process (like syntax errors in SQL queries or unresolved references in your Dart tables) will cause the build to fail. -* `preamble`: This option is useful when using drift [as a standalone part builder](#using-drift-classes-in-other-builders) or when running a +- `preamble`: This option is useful when using drift [as a standalone part builder](#using-drift-classes-in-other-builders) or when running a [modular build](#modular-code-generation). In these setups, the `preamble` option defined by the [source_gen package](https://pub.dev/packages/source_gen#preamble) would have no effect, which is why it has been added as an option for the drift builders. +- `generate_manager`: When enabled (defaults to `true`), managers will be generated for each table in the database. These managers help perform simple actions without boilerplate. ## Assumed SQL environment @@ -151,7 +153,7 @@ targets: ### Available extensions -__Note__: This enables extensions in the analyzer for custom queries only. For instance, when the `json1` extension is +**Note**: This enables extensions in the analyzer for custom queries only. For instance, when the `json1` extension is enabled, the [`json`](https://www.sqlite.org/json1.html) functions can be used in drift files. This doesn't necessarily mean that those functions are supported at runtime! Both extensions are available on iOS 11 or later. On Android, they're only available when using a `NativeDatabase`. @@ -176,7 +178,7 @@ We currently support the following extensions: - [json1](https://www.sqlite.org/json1.html): Support static analysis for `json_` functions in moor files - [fts5](https://www.sqlite.org/fts5.html): Support `CREATE VIRTUAL TABLE` statements for `fts5` tables and the `MATCH` operator. Functions like `highlight` or `bm25` are available as well. -- `rtree`: Static analysis support for the [R*Tree](https://www.sqlite.org/rtree.html) extension. +- `rtree`: Static analysis support for the [R\*Tree](https://www.sqlite.org/rtree.html) extension. Enabling this option is safe when using a `NativeDatabase` with `sqlite3_flutter_libs`, which compiles sqlite3 with the R*Tree extension enabled. - [geopoly](https://www.sqlite.org/geopoly.html), a generalization of the R*Tree module supporting more complex diff --git a/drift/example/main.g.dart b/drift/example/main.g.dart index adb70e1b..32549c1a 100644 --- a/drift/example/main.g.dart +++ b/drift/example/main.g.dart @@ -685,6 +685,7 @@ class $TodoItemWithCategoryNameViewView extends ViewInfo< abstract class _$Database extends GeneratedDatabase { _$Database(QueryExecutor e) : super(e); + _$DatabaseManager get managers => _$DatabaseManager(this); late final $TodoCategoriesTable todoCategories = $TodoCategoriesTable(this); late final $TodoItemsTable todoItems = $TodoItemsTable(this); late final $TodoCategoryItemCountView todoCategoryItemCount = @@ -705,3 +706,212 @@ abstract class _$Database extends GeneratedDatabase { itemTitle ]; } + +class $$TodoCategoriesTableFilterComposer + extends FilterComposer<_$Database, $TodoCategoriesTable> { + $$TodoCategoriesTableFilterComposer(super.db, super.table); + ColumnFilters get id => ColumnFilters($table.id); + ColumnFilters get name => ColumnFilters($table.name); + ComposableFilter todoItemsRefs( + ComposableFilter Function($$TodoItemsTableFilterComposer f) f) { + return $composeWithJoins( + $db: $db, + $table: $table, + referencedTable: $db.todoItems, + getCurrentColumn: (f) => f.id, + getReferencedColumn: (f) => f.categoryId, + getReferencedComposer: (db, table) => + $$TodoItemsTableFilterComposer(db, table), + builder: f); + } +} + +class $$TodoCategoriesTableOrderingComposer + extends OrderingComposer<_$Database, $TodoCategoriesTable> { + $$TodoCategoriesTableOrderingComposer(super.db, super.table); + ColumnOrderings get id => ColumnOrderings($table.id); + ColumnOrderings get name => ColumnOrderings($table.name); +} + +class $$TodoCategoriesTableProcessedTableManager extends ProcessedTableManager< + _$Database, + $TodoCategoriesTable, + TodoCategory, + $$TodoCategoriesTableFilterComposer, + $$TodoCategoriesTableOrderingComposer, + $$TodoCategoriesTableProcessedTableManager, + $$TodoCategoriesTableInsertCompanionBuilder, + $$TodoCategoriesTableUpdateCompanionBuilder> { + const $$TodoCategoriesTableProcessedTableManager(super.$state); +} + +typedef $$TodoCategoriesTableInsertCompanionBuilder = TodoCategoriesCompanion + Function({ + Value id, + required String name, +}); +typedef $$TodoCategoriesTableUpdateCompanionBuilder = TodoCategoriesCompanion + Function({ + Value id, + Value name, +}); + +class $$TodoCategoriesTableTableManager extends RootTableManager< + _$Database, + $TodoCategoriesTable, + TodoCategory, + $$TodoCategoriesTableFilterComposer, + $$TodoCategoriesTableOrderingComposer, + $$TodoCategoriesTableProcessedTableManager, + $$TodoCategoriesTableInsertCompanionBuilder, + $$TodoCategoriesTableUpdateCompanionBuilder> { + $$TodoCategoriesTableTableManager(_$Database db, $TodoCategoriesTable table) + : super(TableManagerState( + db: db, + table: table, + filteringComposer: $$TodoCategoriesTableFilterComposer(db, table), + orderingComposer: $$TodoCategoriesTableOrderingComposer(db, table), + getChildManagerBuilder: (p0) => + $$TodoCategoriesTableProcessedTableManager(p0), + getUpdateCompanionBuilder: ({ + Value id = const Value.absent(), + Value name = const Value.absent(), + }) => + TodoCategoriesCompanion( + id: id, + name: name, + ), + getInsertCompanionBuilder: ({ + Value id = const Value.absent(), + required String name, + }) => + TodoCategoriesCompanion.insert( + id: id, + name: name, + ))); +} + +class $$TodoItemsTableFilterComposer + extends FilterComposer<_$Database, $TodoItemsTable> { + $$TodoItemsTableFilterComposer(super.db, super.table); + ColumnFilters get id => ColumnFilters($table.id); + ColumnFilters get title => ColumnFilters($table.title); + ColumnFilters get content => ColumnFilters($table.content); + ColumnFilters get categoryIdId => ColumnFilters($table.categoryId); + ComposableFilter categoryId( + ComposableFilter Function($$TodoCategoriesTableFilterComposer f) f) { + return $composeWithJoins( + $db: $db, + $table: $table, + referencedTable: $db.todoCategories, + getCurrentColumn: (f) => f.categoryId, + getReferencedColumn: (f) => f.id, + getReferencedComposer: (db, table) => + $$TodoCategoriesTableFilterComposer(db, table), + builder: f); + } + + ColumnFilters get generatedText => + ColumnFilters($table.generatedText); +} + +class $$TodoItemsTableOrderingComposer + extends OrderingComposer<_$Database, $TodoItemsTable> { + $$TodoItemsTableOrderingComposer(super.db, super.table); + ColumnOrderings get id => ColumnOrderings($table.id); + ColumnOrderings get title => ColumnOrderings($table.title); + ColumnOrderings get content => ColumnOrderings($table.content); + ColumnOrderings get categoryIdId => ColumnOrderings($table.categoryId); + ComposableOrdering categoryId( + ComposableOrdering Function($$TodoCategoriesTableOrderingComposer o) o) { + return $composeWithJoins( + $db: $db, + $table: $table, + referencedTable: $db.todoCategories, + getCurrentColumn: (f) => f.categoryId, + getReferencedColumn: (f) => f.id, + getReferencedComposer: (db, table) => + $$TodoCategoriesTableOrderingComposer(db, table), + builder: o); + } + + ColumnOrderings get generatedText => + ColumnOrderings($table.generatedText); +} + +class $$TodoItemsTableProcessedTableManager extends ProcessedTableManager< + _$Database, + $TodoItemsTable, + TodoItem, + $$TodoItemsTableFilterComposer, + $$TodoItemsTableOrderingComposer, + $$TodoItemsTableProcessedTableManager, + $$TodoItemsTableInsertCompanionBuilder, + $$TodoItemsTableUpdateCompanionBuilder> { + const $$TodoItemsTableProcessedTableManager(super.$state); +} + +typedef $$TodoItemsTableInsertCompanionBuilder = TodoItemsCompanion Function({ + Value id, + required String title, + Value content, + required int categoryId, +}); +typedef $$TodoItemsTableUpdateCompanionBuilder = TodoItemsCompanion Function({ + Value id, + Value title, + Value content, + Value categoryId, +}); + +class $$TodoItemsTableTableManager extends RootTableManager< + _$Database, + $TodoItemsTable, + TodoItem, + $$TodoItemsTableFilterComposer, + $$TodoItemsTableOrderingComposer, + $$TodoItemsTableProcessedTableManager, + $$TodoItemsTableInsertCompanionBuilder, + $$TodoItemsTableUpdateCompanionBuilder> { + $$TodoItemsTableTableManager(_$Database db, $TodoItemsTable table) + : super(TableManagerState( + db: db, + table: table, + filteringComposer: $$TodoItemsTableFilterComposer(db, table), + orderingComposer: $$TodoItemsTableOrderingComposer(db, table), + getChildManagerBuilder: (p0) => + $$TodoItemsTableProcessedTableManager(p0), + getUpdateCompanionBuilder: ({ + Value id = const Value.absent(), + Value title = const Value.absent(), + Value content = const Value.absent(), + Value categoryId = const Value.absent(), + }) => + TodoItemsCompanion( + id: id, + title: title, + content: content, + categoryId: categoryId, + ), + getInsertCompanionBuilder: ({ + Value id = const Value.absent(), + required String title, + Value content = const Value.absent(), + required int categoryId, + }) => + TodoItemsCompanion.insert( + id: id, + title: title, + content: content, + categoryId: categoryId, + ))); +} + +class _$DatabaseManager { + final _$Database _db; + _$DatabaseManager(this._db); + $$TodoCategoriesTableTableManager get todoCategories => + $$TodoCategoriesTableTableManager(_db, _db.todoCategories); + $$TodoItemsTableTableManager get todoItems => + $$TodoItemsTableTableManager(_db, _db.todoItems); +} diff --git a/drift/lib/drift.dart b/drift/lib/drift.dart index a844bb07..2e272ab7 100644 --- a/drift/lib/drift.dart +++ b/drift/lib/drift.dart @@ -20,6 +20,8 @@ export 'src/runtime/query_builder/query_builder.dart' hide CaseWhenExpressionWithBase, BaseCaseWhenExpression; export 'src/runtime/types/converters.dart'; export 'src/runtime/types/mapping.dart' hide BaseSqlType, UserDefinedSqlType; +export 'src/runtime/manager/manager.dart' + hide JoinBuilder, HasJoinBuilders, Composer, BaseTableManager; export 'src/utils/lazy_database.dart'; /// A [ListEquality] instance used by generated drift code for the `==` and diff --git a/drift/lib/src/dsl/columns.dart b/drift/lib/src/dsl/columns.dart index 000e948e..32ba59a9 100644 --- a/drift/lib/src/dsl/columns.dart +++ b/drift/lib/src/dsl/columns.dart @@ -409,3 +409,34 @@ class JsonKey { /// generated json. See the documentation for [JsonKey] for details. const JsonKey(this.key); } + +/// Annotation to use on reference columns inside of a [Table] to define the name +/// that the manager will use when refering to this relation in the reverse. +/// +/// Example: +/// ```dart +/// class TodoEntries extends Table { +/// IntColumn get id => integer().autoIncrement()(); +/// TextColumn get body => text()(); +/// @ReferenceName("categories") +/// IntColumn get category => integer().nullable().references(Categories, #id)(); +/// } +/// class Categories extends Table { +/// IntColumn get id => integer().autoIncrement()(); +/// } +/// /// The manager will now use the name `categories` +/// categories.filter((f) => f.categories((f) => f.body("Todo"))) +/// +/// ``` +/// When these aren't specified, the manager will use the referenced tables name followed by `Refs`. +/// If a reference name clashes with other fields on the table, the generator will show a warning, +/// and filters and orderings wont be generated +class ReferenceName { + /// The name that this reference will use when generating filters and ordering in the reverse direction + /// for [ReferenceName] for details. + final String name; + + /// Annotation to use on reference columns inside of a [Table] to define the name + /// of the filters and orderings for the reverse relation. + const ReferenceName(this.name); +} diff --git a/drift/lib/src/runtime/manager/composer.dart b/drift/lib/src/runtime/manager/composer.dart new file mode 100644 index 00000000..086f0247 --- /dev/null +++ b/drift/lib/src/runtime/manager/composer.dart @@ -0,0 +1,71 @@ +// ignore_for_file: public_member_api_docs, sort_constructors_first +part of 'manager.dart'; + +/// Base class for all composers +/// +/// Any class that can be composed using the `&` or `|` operator is called a composable. +/// [ComposableFilter] and [ComposableOrdering] are examples of composable classes. +/// +/// The [Composer] class is a top level manager for this operation. +/// ```dart +/// filter((f) => f.id.equals(1) & f.name.equals('Bob')); +/// ``` +/// `f` in this example is a [Composer] object, and `f.id.equals(1)` returns a [ComposableFilter] object. +/// +/// The [Composer] class is responsible for creating joins between tables, and passing them down to the composable classes. +@internal +sealed class Composer { + /// The database that the query will be executed on + final DB $db; + + /// The table that the query will be executed on + final CT $table; + + Composer(this.$db, this.$table); + + /// Utility for creating a composer which contains the joins needed to + /// execute a query on a table that is referenced by a foreign key. + B $composeWithJoins, + B extends HasJoinBuilders>({ + required DB $db, + required CT $table, + required GeneratedColumn Function(CT) getCurrentColumn, + required RT referencedTable, + required GeneratedColumn Function(RT) getReferencedColumn, + required B Function(QC) builder, + required QC Function(DB db, RT table) getReferencedComposer, + }) { + // The name of the alias will be created using the following logic: + // "currentTableName__currentColumnName__referencedColumnName__referencedTableName" + // This is to ensure that the alias is unique + final currentColumn = getCurrentColumn($table); + final tempReferencedColumn = getReferencedColumn(referencedTable); + final aliasName = + '${currentColumn.tableName}__${currentColumn.name}__${tempReferencedColumn.tableName}__${tempReferencedColumn.name}'; + final aliasedReferencedTable = + $db.alias(referencedTable as TableInfo, aliasName); + final aliasedReferencedColumn = + getReferencedColumn(aliasedReferencedTable as RT); + + // Create a join builder for the referenced table + final joinBuilder = JoinBuilder( + currentTable: $table, + currentColumn: currentColumn, + referencedTable: aliasedReferencedTable, + referencedColumn: aliasedReferencedColumn, + ); + + // Get the query composer for the referenced table, passing in the aliased + // table and all the join builders + final referencedComposer = + getReferencedComposer($db, aliasedReferencedTable); + + // Run the user provided builder with the referencedQueryComposer + // This may return a filter or ordering, but we only enforce that it's + // a HasJoinBuilders + final result = builder(referencedComposer); + result.addJoinBuilders({joinBuilder}); + + return result; + } +} diff --git a/drift/lib/src/runtime/manager/filter.dart b/drift/lib/src/runtime/manager/filter.dart new file mode 100644 index 00000000..9827be99 --- /dev/null +++ b/drift/lib/src/runtime/manager/filter.dart @@ -0,0 +1,318 @@ +part of 'manager.dart'; + +/// Defines a class which is used to wrap a column to only expose filter functions +class ColumnFilters { + /// This class is a wrapper on top of the generated column class + /// + /// It's used to expose filter functions for a column type + /// + /// Use an extention to add more filters to any column type + /// + /// ```dart + /// extension on ColumnFilters{ + /// ComposableFilter after2000() => isAfter(DateTime(2000)); + ///} + /// ``` + const ColumnFilters(this.column, [this.inverted = false]); + + /// Column that this [ColumnFilters] wraps + final Expression column; + + /// If true, all filters will be inverted + final bool inverted; + + /// Returns a copy of these column filters where all the filters are inverted + /// ```dart + /// myColumn.not.equals(5); // All columns that aren't null and have a value that is not equal to 5 + /// ``` + /// Keep in mind that while using inverted filters, null is never returned. + /// + /// If you would like to include them, use the [isNull] filter as well + /// ```dart + /// myColumn.not.equals(5) | myColumn.isNull(); // All columns that are null OR have a value that is not equal to 5 will be returned + /// ``` + ColumnFilters get not => ColumnFilters(column, !inverted); + + /// Create a filter that checks if the column equals a value. + ComposableFilter equals(T value) => + ComposableFilter(column.equals(value), inverted: inverted); + + /// Create a filter that checks if the column is null. + ComposableFilter isNull() => + ComposableFilter(column.isNull(), inverted: inverted); + + /// Create a filter that checks if the column is in a list of values. + ComposableFilter isIn(Iterable values) => + ComposableFilter(column.isIn(values), inverted: inverted); + + /// Shortcut for [equals] + ComposableFilter call(T value) => equals(value); +} + +enum _StringFilterTypes { contains, startsWith, endsWith } + +/// Built in filters for int/double columns +extension StringFilters on ColumnFilters { + /// This function helps handle case insensitivity in like expressions + /// This helps handle all the possible scenarios: + /// 1. If a user hasn't set the database to be case sensitive in like expressions + /// Then we are ok with having the query be performed on upper case values + /// 2. If a user has set the database to be case sensitive in like expressions + /// We still can perform a case insensitive search by default. We have all filters + /// use `{bool caseInsensitive = true}` which will perform a case insensitive search + /// 3. If a user has set the database to be case sensitive in like expressions and wan't + /// to perform a case sensitive search, they can pass `caseInsensitive = false` manually + /// + /// We are using the default of {bool caseInsensitive = true}, so that users who haven't set + /// the database to be case sensitive wont be confues why their like expressions are case insensitive + Expression _buildExpression( + _StringFilterTypes type, String value, bool caseInsensitive) { + final Expression column; + if (caseInsensitive) { + value = value.toUpperCase(); + column = this.column.upper(); + } else { + column = this.column; + } + switch (type) { + case _StringFilterTypes.contains: + return column.like('%$value%'); + case _StringFilterTypes.startsWith: + return column.like('$value%'); + case _StringFilterTypes.endsWith: + return column.like('%$value'); + } + } + + /// Create a filter to check if the this text column contains a substring + /// + /// Setting [caseInsensitive] to false will have no effect unless the database in configured to use + /// case sensitive like expressions. + /// + /// See https://www.sqlitetutorial.net/sqlite-like/ for more information on how + /// to the like expression works. + ComposableFilter contains(T value, {bool caseInsensitive = true}) { + return ComposableFilter( + _buildExpression(_StringFilterTypes.contains, value, caseInsensitive), + inverted: inverted); + } + + /// Create a filter to check if the this text column starts with a substring + /// + /// Setting [caseInsensitive] to false will have no effect unless the database in configured to use + /// case sensitive like expressions. + /// + /// See https://www.sqlitetutorial.net/sqlite-like/ for more information on how + /// to the like expression works. + ComposableFilter startsWith(T value, {bool caseInsensitive = true}) { + return ComposableFilter( + _buildExpression(_StringFilterTypes.startsWith, value, caseInsensitive), + inverted: inverted); + } + + /// Create a filter to check if the this text column ends with a substring + /// + /// Setting [caseInsensitive] to false will have no effect unless the database in configured to use + /// case sensitive like expressions. + /// + /// See https://www.sqlitetutorial.net/sqlite-like/ for more information on how + /// to the like expression works. + ComposableFilter endsWith(T value, {bool caseInsensitive = true}) { + return ComposableFilter( + _buildExpression(_StringFilterTypes.endsWith, value, caseInsensitive), + inverted: inverted); + } +} + +/// Built in filters for bool columns +extension BoolFilters on ColumnFilters { + /// Create a filter to check if the column is bigger than a value + ComposableFilter isTrue() => + ComposableFilter(column.equals(true), inverted: inverted); + + /// Create a filter to check if the column is small than a value + ComposableFilter isFalse() => + ComposableFilter(column.equals(false), inverted: inverted); +} + +/// Built in filters for int/double columns +extension NumFilters on ColumnFilters { + /// Create a filter to check if the column is bigger than a value + ComposableFilter isBiggerThan(T value) => + ComposableFilter(column.isBiggerThanValue(value), inverted: inverted); + + /// Create a filter to check if the column is small than a value + ComposableFilter isSmallerThan(T value) => + ComposableFilter(column.isSmallerThanValue(value), inverted: inverted); + + /// Create a filter to check if the column is bigger or equal to a value + ComposableFilter isBiggerOrEqualTo(T value) => + ComposableFilter(column.isBiggerOrEqualValue(value), inverted: inverted); + + /// Create a filter to check if the column is small or equal to a value + ComposableFilter isSmallerOrEqualTo(T value) => + ComposableFilter(column.isSmallerOrEqualValue(value), inverted: inverted); + + /// Create a filter to check if the column is between two values + /// This is done inclusively, so the column can be equal to the lower or higher value + /// E.G isBetween(1, 3) will return true for 1, 2, and 3 + ComposableFilter isBetween(T lower, T higher) => + ComposableFilter(column.isBetweenValues(lower, higher), + inverted: inverted); +} + +/// Built in filters for BigInt columns +extension BigIntFilters on ColumnFilters { + /// Create a filter to check if the column is bigger than a value + ComposableFilter isBiggerThan(T value) => + ComposableFilter(column.isBiggerThanValue(value), inverted: inverted); + + /// Create a filter to check if the column is small than a value + ComposableFilter isSmallerThan(T value) => + ComposableFilter(column.isSmallerThanValue(value), inverted: inverted); + + /// Create a filter to check if the column is bigger or equal to a value + ComposableFilter isBiggerOrEqualTo(T value) => + ComposableFilter(column.isBiggerOrEqualValue(value), inverted: inverted); + + /// Create a filter to check if the column is small or equal to a value + ComposableFilter isSmallerOrEqualTo(T value) => + ComposableFilter(column.isSmallerOrEqualValue(value), inverted: inverted); + + /// Create a filter to check if the column is between two values + /// This is done inclusively, so the column can be equal to the lower or higher value + /// E.G isBetween(1, 3) will return true for 1, 2, and 3 + ComposableFilter isBetween(T lower, T higher) => + ComposableFilter(column.isBetweenValues(lower, higher), + inverted: inverted); +} + +/// Built in filters for DateTime columns +extension DateFilters on ColumnFilters { + /// Create a filter to check if the column is after a [DateTime] + ComposableFilter isAfter(T value) => + ComposableFilter(column.isBiggerThanValue(value), inverted: inverted); + + /// Create a filter to check if the column is before a [DateTime] + ComposableFilter isBefore(T value) => + ComposableFilter(column.isSmallerThanValue(value), inverted: inverted); + + /// Create a filter to check if the column is on or after a [DateTime] + ComposableFilter isAfterOrOn(T value) => + ComposableFilter(column.isBiggerOrEqualValue(value), inverted: inverted); + + /// Create a filter to check if the column is before or on a [DateTime] + ComposableFilter isBeforeOrOn(T value) => + ComposableFilter(column.isSmallerOrEqualValue(value), inverted: inverted); + + /// Create a filter to check if the column is between 2 [DateTime]s + /// This is done inclusively, so the column can be equal to the lower or higher value + /// E.G isBetween(1, 3) will return true for 1, 2, and 3 + ComposableFilter isBetween(T lower, T higher) => + ComposableFilter(column.isBetweenValues(lower, higher), + inverted: inverted); +} + +/// Defines a class which is used to wrap a column with a type converter to only expose filter functions +// [CustomType] is the type that the user has defined in their type converter +// [CustomTypeNonNullable] is the type that the user has defined in their type converter, but is non-nullable +class ColumnWithTypeConverterFilters { + /// Similar to [ColumnFilters] but for columns with type converters\ + const ColumnWithTypeConverterFilters(this.column, [this.inverted = false]); + + /// If true, all filters will be inverted + final bool inverted; + + /// Column that this [ColumnWithTypeConverterFilters] wraps + final GeneratedColumnWithTypeConverter column; + + /// Returns a copy of these column filters where all the filters are inverted + /// ```dart + /// myColumn.not.equals(5); // All columns that aren't null and have a value that is not equal to 5 + /// ``` + /// Keep in mind that while using inverted filters, null is never returned. + /// + /// If you would like to include them, use the [isNull] filter as well + /// ```dart + /// myColumn.not.equals(5) | myColumn.isNull(); // All columns that are null OR have a value that is not equal to 5 + /// ``` + ColumnWithTypeConverterFilters + get not => ColumnWithTypeConverterFilters(column, !inverted); + + /// Create a filter that checks if the column is null. + ComposableFilter isNull() => + ComposableFilter(column.isNull(), inverted: inverted); + + /// Get the actual value from the custom type + T _customTypeToSql(CustomTypeNonNullable value) { + assert(value != null, + 'The filter value cannot be null. This is likely a bug in the generated code. Please report this issue.'); + final mappedValue = column.converter.toSql(value as CustomType); + + if (mappedValue == null) { + throw ArgumentError( + 'The TypeConverter for this column returned null when converting the type to sql. Ensure that your TypeConverter never returns null when provided a non-null value.'); + } + return mappedValue; + } + + /// Create a filter that checks if the column equals a value. + ComposableFilter equals(CustomTypeNonNullable value) { + return ComposableFilter(column.equals(_customTypeToSql(value)), + inverted: inverted); + } + + /// Shortcut for [equals] + ComposableFilter call(CustomTypeNonNullable value) => equals(value); + + /// Create a filter that checks if the column is in a list of values. + ComposableFilter isIn(Iterable values) => + ComposableFilter(column.isIn(values.map(_customTypeToSql).toList()), + inverted: inverted); +} + +/// This class is wrapper on the expression class +/// +/// It contains the expression, along with any joins that are required +/// to execute the expression. See [HasJoinBuilders] for more information +/// on how joins are stored +class ComposableFilter extends HasJoinBuilders { + @override + final Set joinBuilders; + + /// The expression that will be applied to the query + late final Expression expression; + + /// Create a new [ComposableFilter] for a column without any joins + ComposableFilter(Expression expression, {required bool inverted}) + : joinBuilders = {} { + this.expression = inverted ? expression.not() : expression; + } + + /// Create a new [ComposableFilter] for a column with joins + ComposableFilter._(this.expression, this.joinBuilders); + + /// Combine two filters with an AND + ComposableFilter operator &(ComposableFilter other) { + return ComposableFilter._( + expression & other.expression, + joinBuilders.union(other.joinBuilders), + ); + } + + /// Combine two filters with an OR + ComposableFilter operator |(ComposableFilter other) { + return ComposableFilter._( + expression | other.expression, + joinBuilders.union(other.joinBuilders), + ); + } +} + +/// The class that orchestrates the composition of filtering +class FilterComposer + extends Composer { + /// Create a filter composer with an empty state + FilterComposer(super.$db, super.$table); +} diff --git a/drift/lib/src/runtime/manager/join.dart b/drift/lib/src/runtime/manager/join.dart new file mode 100644 index 00000000..48f41fcc --- /dev/null +++ b/drift/lib/src/runtime/manager/join.dart @@ -0,0 +1,78 @@ +part of 'manager.dart'; + +/// A class that contains the information needed to create a join +@internal +class JoinBuilder { + /// The table that the join is being applied to + final Table currentTable; + + /// The referenced table that will be joined + final Table referencedTable; + + /// The column of the [currentTable] which will be use to create the join + final GeneratedColumn currentColumn; + + /// The column of the [referencedTable] which will be use to create the join + final GeneratedColumn referencedColumn; + + /// Class that describes how a ordering that is being + /// applied to a referenced table + /// should be joined to the current table + JoinBuilder( + {required this.currentTable, + required this.referencedTable, + required this.currentColumn, + required this.referencedColumn}); + + /// The name of the alias that this join will use + String get aliasedName { + return referencedColumn.tableName; + } + + @override + bool operator ==(covariant JoinBuilder other) { + if (identical(this, other)) return true; + + return other.currentColumn == currentColumn && + other.referencedColumn == referencedColumn; + } + + @override + int get hashCode { + return currentColumn.hashCode ^ referencedColumn.hashCode; + } + + /// Build a join from this join builder + Join buildJoin() { + return leftOuterJoin( + referencedTable, currentColumn.equalsExp(referencedColumn), + useColumns: false); + } +} + +/// An interface for classes that hold join builders +/// Typically used by classes whose composition requires joins +/// to be created +/// +/// Example: +/// ```dart +/// categories.filter((f) => f.todos((f) => f.dueDate.isBefore(DateTime.now()))) +/// ``` +/// +/// In the above example, f.todos() returns a [ComposableFilter] object, which +/// is a subclass of [HasJoinBuilders]. +/// This resulting where expression will require a join to be created +/// between the `categories` and `todos` table. +/// +/// This interface is used to ensure that the [ComposableFilter] object will have +/// the information needed to create the join. +@internal +abstract interface class HasJoinBuilders { + /// The join builders that are associated with this class + Set get joinBuilders; + + /// Add a join builder to this class + void addJoinBuilders(Set builders) { + joinBuilders.addAll(builders); + } +} diff --git a/drift/lib/src/runtime/manager/manager.dart b/drift/lib/src/runtime/manager/manager.dart new file mode 100644 index 00000000..ba366487 --- /dev/null +++ b/drift/lib/src/runtime/manager/manager.dart @@ -0,0 +1,579 @@ +import 'dart:async'; + +import 'package:drift/drift.dart'; +import 'package:meta/meta.dart'; + +part 'composer.dart'; +part 'filter.dart'; +part 'join.dart'; +part 'ordering.dart'; + +sealed class _StatementType { + const _StatementType(); +} + +class _SimpleResult + extends _StatementType { + final SimpleSelectStatement statement; + const _SimpleResult(this.statement); +} + +class _JoinedResult + extends _StatementType { + final JoinedSelectStatement statement; + + const _JoinedResult(this.statement); +} + +/// Defines a class that holds the state for a [BaseTableManager] +/// +/// It holds the state for manager of [T] table in [DB] database, used to return [DT] data classes/rows. +/// It holds the [FS] Filters and [OS] Orderings for the manager. +/// +/// It also holds the [CI] and [CU] functions that are used to create companion builders for inserting and updating data. +/// E.G Instead of `CategoriesCompanion.insert(name: "School")` you would use `(f) => f(name: "School")` +/// +/// The [C] generic refers to the type of the child manager that will be created when a filter/ordering is applied +class TableManagerState< + DB extends GeneratedDatabase, + T extends Table, + DT extends DataClass, + FS extends FilterComposer, + OS extends OrderingComposer, + C extends ProcessedTableManager, + CI extends Function, + CU extends Function> { + /// The database that the query will be exeCCted on + final DB db; + + /// The table that the query will be exeCCted on + final T table; + + /// The expression that will be applied to the query + final Expression? filter; + + /// A set of [OrderingBuilder] which will be used to apply + /// [OrderingTerm]s to the statement when it's eventually built + final Set orderingBuilders; + + /// A set of [JoinBuilder] which will be used to create [Join]s + /// that will be applied to the build statement + final Set joinBuilders; + + /// Whether the query should return distinct results + final bool? distinct; + + /// If set, the maximum number of rows that will be returned + final int? limit; + + /// If set, the number of rows that will be skipped + final int? offset; + + /// The [FilterComposer] for this [TableManagerState] + /// This class will be used to create filtering [Expression]s + /// which will be applied to the statement when its eventually created + final FS filteringComposer; + + /// The [OrderingComposer] for this [TableManagerState] + /// This class will be used to create [OrderingTerm]s + /// which will be applied to the statement when its eventually created + final OS orderingComposer; + + /// This function is used internaly to return a new instance of the child manager + final C Function(TableManagerState) + _getChildManagerBuilder; + + /// This function is passed to the user to create a companion + /// for inserting data into the table + final CI _getInsertCompanionBuilder; + + /// This function is passed to the user to create a companion + /// for updating data in the table + final CU _getUpdateCompanionBuilder; + + /// Defines a class which holds the state for a table manager + /// It contains the database instance, the table instance, and any filters/orderings that will be applied to the query + /// This is held in a seperate class than the [BaseTableManager] so that the state can be passed down from the root manager to the lower level managers + const TableManagerState({ + required this.db, + required this.table, + required this.filteringComposer, + required this.orderingComposer, + required C Function(TableManagerState) + getChildManagerBuilder, + required CI getInsertCompanionBuilder, + required CU getUpdateCompanionBuilder, + this.filter, + this.distinct, + this.limit, + this.offset, + this.orderingBuilders = const {}, + this.joinBuilders = const {}, + }) : _getChildManagerBuilder = getChildManagerBuilder, + _getInsertCompanionBuilder = getInsertCompanionBuilder, + _getUpdateCompanionBuilder = getUpdateCompanionBuilder; + + /// Copy this state with the given values + TableManagerState copyWith({ + bool? distinct, + int? limit, + int? offset, + Expression? filter, + Set? orderingBuilders, + Set? joinBuilders, + }) { + return TableManagerState( + db: db, + table: table, + filteringComposer: filteringComposer, + orderingComposer: orderingComposer, + getChildManagerBuilder: _getChildManagerBuilder, + getInsertCompanionBuilder: _getInsertCompanionBuilder, + getUpdateCompanionBuilder: _getUpdateCompanionBuilder, + filter: filter ?? this.filter, + joinBuilders: joinBuilders ?? this.joinBuilders, + orderingBuilders: orderingBuilders ?? this.orderingBuilders, + distinct: distinct ?? this.distinct, + limit: limit ?? this.limit, + offset: offset ?? this.offset, + ); + } + + /// Helper for getting the table that's casted as a TableInfo + /// This is needed due to dart's limitations with generics + TableInfo get _tableAsTableInfo => table as TableInfo; + + /// Builds a select statement with the given target columns, or all columns if none are provided + _StatementType _buildSelectStatement( + {Iterable? targetColumns}) { + final joins = joinBuilders.map((e) => e.buildJoin()).toList(); + + // If there are no joins and we are returning all columns, we can use a simple select statement + if (joins.isEmpty && targetColumns == null) { + final simpleStatement = + db.select(_tableAsTableInfo, distinct: distinct ?? false); + + // Apply the expression to the statement + if (filter != null) { + simpleStatement.where((_) => filter!); + } + // Apply orderings and limits + + simpleStatement + .orderBy(orderingBuilders.map((e) => (_) => e.buildTerm()).toList()); + if (limit != null) { + simpleStatement.limit(limit!, offset: offset); + } + + return _SimpleResult(simpleStatement); + } else { + JoinedSelectStatement joinedStatement; + // If we are only selecting specific columns, we can use a selectOnly statement + if (targetColumns != null) { + joinedStatement = + (db.selectOnly(_tableAsTableInfo, distinct: distinct ?? false) + ..addColumns(targetColumns)); + // Add the joins to the statement + joinedStatement = + joinedStatement.join(joins) as JoinedSelectStatement; + } else { + joinedStatement = db + .select(_tableAsTableInfo, distinct: distinct ?? false) + .join(joins) as JoinedSelectStatement; + } + // Apply the expression to the statement + if (filter != null) { + joinedStatement.where(filter!); + } + // Apply orderings and limits + + joinedStatement + .orderBy(orderingBuilders.map((e) => e.buildTerm()).toList()); + if (limit != null) { + joinedStatement.limit(limit!, offset: offset); + } + + return _JoinedResult(joinedStatement); + } + } + + /// Build a select statement based on the manager state + Selectable
buildSelectStatement() { + final result = _buildSelectStatement(); + return switch (result) { + _SimpleResult() => result.statement, + _JoinedResult() => + result.statement.map((p0) => p0.readTable(_tableAsTableInfo)) + }; + } + + /// Build an update statement based on the manager state + UpdateStatement buildUpdateStatement() { + final UpdateStatement updateStatement; + if (joinBuilders.isEmpty) { + updateStatement = db.update(_tableAsTableInfo); + if (filter != null) { + updateStatement.where((_) => filter!); + } + } else { + updateStatement = db.update(_tableAsTableInfo); + for (var col in _tableAsTableInfo.primaryKey) { + final subquery = + _buildSelectStatement(targetColumns: [col]) as _JoinedResult; + updateStatement.where((tbl) => col.isInQuery(subquery.statement)); + } + } + return updateStatement; + } + + /// Count the number of rows that would be returned by the built statement + Future count() { + final count = countAll(); + final result = + _buildSelectStatement(targetColumns: [count]) as _JoinedResult; + return result.statement + .map((row) => row.read(count)!) + .get() + .then((value) => value.firstOrNull ?? 0); + } + + /// Check if any rows exists using the built statement + Future exists() async { + final result = _buildSelectStatement(); + final BaseSelectStatement statement; + switch (result) { + case _SimpleResult(): + statement = result.statement; + case _JoinedResult(): + statement = result.statement; + } + final query = existsQuery(statement); + final existsStatement = db.selectOnly(_tableAsTableInfo) + ..addColumns([query]); + return (await existsStatement + .map((p0) => p0.read(query)) + .get() + .then((value) { + return value.firstOrNull ?? false; + })); + } + + /// Build a delete statement based on the manager state + DeleteStatement buildDeleteStatement() { + final DeleteStatement deleteStatement; + if (joinBuilders.isEmpty) { + deleteStatement = db.delete(_tableAsTableInfo); + if (filter != null) { + deleteStatement.where((_) => filter!); + } + } else { + deleteStatement = db.delete(_tableAsTableInfo); + for (var col in _tableAsTableInfo.primaryKey) { + final subquery = + _buildSelectStatement(targetColumns: [col]) as _JoinedResult; + deleteStatement.where((tbl) => col.isInQuery(subquery.statement)); + } + } + return deleteStatement; + } +} + +/// Base class for all table managers +/// Most of this classes functionality is kept in a seperate [TableManagerState] class +/// This is so that the state can be passed down to lower level managers +@internal +abstract class BaseTableManager< + DB extends GeneratedDatabase, + T extends Table, + DT extends DataClass, + FS extends FilterComposer, + OS extends OrderingComposer, + C extends ProcessedTableManager, + CI extends Function, + CU extends Function> + implements + MultiSelectable
, + SingleSelectable
, + SingleOrNullSelectable
{ + /// The state for this manager + final TableManagerState $state; + + /// Create a new [BaseTableManager] instance + const BaseTableManager(this.$state); + + /// Add a limit to the statement + C limit(int limit, {int? offset}) { + return $state + ._getChildManagerBuilder($state.copyWith(limit: limit, offset: offset)); + } + + /// Add ordering to the statement + C orderBy(ComposableOrdering Function(OS o) o) { + final orderings = o($state.orderingComposer); + return $state._getChildManagerBuilder($state.copyWith( + orderingBuilders: + $state.orderingBuilders.union(orderings.orderingBuilders), + joinBuilders: $state.joinBuilders.union(orderings.joinBuilders))); + } + + /// Add a filter to the statement + C filter(ComposableFilter Function(FS f) f) { + final filter = f($state.filteringComposer); + return $state._getChildManagerBuilder($state.copyWith( + filter: $state.filter == null + ? filter.expression + : filter.expression & $state.filter!, + joinBuilders: $state.joinBuilders.union(filter.joinBuilders))); + } + + /// Writes all non-null fields from the entity into the columns of all rows + /// that match the [filter] clause. Warning: That also means that, when you're + /// not setting a where clause explicitly, this method will update all rows in + /// the [$state.table]. + /// + /// The fields that are null on the entity object will not be changed by + /// this operation, they will be ignored. + /// + /// Returns the amount of rows that have been affected by this operation. + /// + /// See also: [RootTableManager.replace], which does not require [filter] statements and + /// supports setting fields back to null. + Future update(Insertable
Function(CU o) f) => + $state.buildUpdateStatement().write(f($state._getUpdateCompanionBuilder)); + + /// Return the count of rows matched by the built statement + /// When counting rows, the query will only count distinct rows by default + Future count([bool distinct = true]) { + return $state.copyWith(distinct: true).count(); + } + + /// Checks whether any rows exist + Future exists() => $state.exists(); + + /// Deletes all rows matched by built statement + /// + /// Returns the amount of rows that were deleted by this statement directly + /// (not including additional rows that might be affected through triggers or + /// foreign key constraints). + Future delete() => $state.buildDeleteStatement().go(); + + /// Executes this statement, like [get], but only returns one + /// value. If the query returns no or too many rows, the returned future will + /// complete with an error. + /// + /// Be aware that this operation won't put a limit clause on this statement, + /// if that's needed you would have to do use [limit]: + /// You should only use this method if you know the query won't have more than + /// one row, for instance because you used `limit(1)` or you know the filters + /// you've applied will only match one row. + /// + /// See also: [getSingleOrNull], which returns `null` instead of + /// throwing if the query completes with no rows. + /// + /// Uses the distinct flag to ensure that only distinct rows are returned + @override + Future
getSingle() => + $state.copyWith(distinct: true).buildSelectStatement().getSingle(); + + /// Creates an auto-updating stream of this statement, similar to + /// [watch]. However, it is assumed that the query will only emit + /// one result, so instead of returning a `Stream>`, this returns a + /// `Stream`. If, at any point, the query emits no or more than one rows, + /// an error will be added to the stream instead. + /// + /// Uses the distinct flag to ensure that only distinct rows are returned + @override + Stream
watchSingle() => + $state.copyWith(distinct: true).buildSelectStatement().watchSingle(); + + /// Executes the statement and returns the first all rows as a list. + /// + /// Use [limit] and [offset] to limit the number of rows returned + /// An offset will only be applied if a limit is also set + /// Set [distinct] to true to ensure that only distinct rows are returned + @override + Future> get({bool distinct = false, int? limit, int? offset}) => + $state + .copyWith(distinct: distinct, limit: limit, offset: offset) + .buildSelectStatement() + .get(); + + /// Creates an auto-updating stream of the result that emits new items + /// whenever any table used in this statement changes. + /// + /// Use [limit] and [offset] to limit the number of rows returned + /// An offset will only be applied if a limit is also set + /// Set [distinct] to true to ensure that only distinct rows are returned + @override + Stream> watch({bool distinct = false, int? limit, int? offset}) => + $state + .copyWith(distinct: distinct, limit: limit, offset: offset) + .buildSelectStatement() + .watch(); + + /// Executes this statement, like [get], but only returns one + /// value. If the result too many values, this method will throw. If no + /// row is returned, `null` will be returned instead. + /// + /// See also: [getSingle], which can be used if the query will + /// always evaluate to exactly one row. + /// + /// Uses the distinct flag to ensure that only distinct rows are returned + @override + Future getSingleOrNull() => + $state.copyWith(distinct: true).buildSelectStatement().getSingleOrNull(); + + /// Creates an auto-updating stream of this statement, similar to + /// [watch]. However, it is assumed that the query will only + /// emit one result, so instead of returning a `Stream>`, this + /// returns a `Stream`. If the query emits more than one row at + /// some point, an error will be emitted to the stream instead. + /// If the query emits zero rows at some point, `null` will be added + /// to the stream instead. + /// + /// Uses the distinct flag to ensure that only distinct rows are returned + @override + Stream watchSingleOrNull() => $state + .copyWith(distinct: true) + .buildSelectStatement() + .watchSingleOrNull(); +} + +/// A table manager that exposes methods to a table manager that already has filters/orderings/limit applied +// As of now this is identical to [BaseTableManager] but it's kept seperate for future extensibility +class ProcessedTableManager< + DB extends GeneratedDatabase, + T extends Table, + D extends DataClass, + FS extends FilterComposer, + OS extends OrderingComposer, + C extends ProcessedTableManager, + CI extends Function, + CU extends Function> + extends BaseTableManager + implements + MultiSelectable, + SingleSelectable, + SingleOrNullSelectable { + /// Create a new [ProcessedTableManager] instance + const ProcessedTableManager(super.$state); +} + +/// A table manager with top level function for creating, reading, updating, and deleting items +abstract class RootTableManager< + DB extends GeneratedDatabase, + T extends Table, + D extends DataClass, + FS extends FilterComposer, + OS extends OrderingComposer, + C extends ProcessedTableManager, + CI extends Function, + CU extends Function> extends BaseTableManager { + /// Create a new [RootTableManager] instance + const RootTableManager(super.$state); + + /// Creates a new row in the table using the given function + /// + /// By default, an exception will be thrown if another row with the same + /// primary key already exists. This behavior can be overridden with [mode], + /// for instance by using [InsertMode.replace] or [InsertMode.insertOrIgnore]. + /// + /// To apply a partial or custom update in case of a conflict, you can also + /// use an [upsert clause](https://sqlite.org/lang_UPSERT.html) by using + /// [onConflict]. See [InsertStatement.insert] for more information. + /// + /// By default, the [onConflict] clause will only consider the table's primary + /// key. If you have additional columns with uniqueness constraints, you have + /// to manually add them to the clause's [DoUpdate.target]. + /// + /// Returns the `rowid` of the inserted row. For tables with an auto-increment + /// column, the `rowid` is the generated value of that column. The returned + /// value can be inaccurate when [onConflict] is set and the insert behaved + /// like an update. + /// + /// If the table doesn't have a `rowid`, you can't rely on the return value. + /// Still, the future will always complete with an error if the insert fails. + Future create(Insertable Function(CI o) f, + {InsertMode? mode, UpsertClause? onConflict}) { + return $state.db.into($state._tableAsTableInfo).insert( + f($state._getInsertCompanionBuilder), + mode: mode, + onConflict: onConflict); + } + + /// Inserts a row into the table and returns it. + /// + /// Depending on the [InsertMode] or the [DoUpdate] `onConflict` clause, the + /// insert statement may not actually insert a row into the database. Since + /// this function was declared to return a non-nullable row, it throws an + /// exception in that case. Use [createReturningOrNull] when performing an + /// insert with an insert mode like [InsertMode.insertOrIgnore] or when using + /// a [DoUpdate] with a `where` clause clause. + Future createReturning(Insertable Function(CI o) f, + {InsertMode? mode, UpsertClause? onConflict}) { + return $state.db.into($state._tableAsTableInfo).insertReturning( + f($state._getInsertCompanionBuilder), + mode: mode, + onConflict: onConflict); + } + + /// Inserts a row into the table and returns it. + /// + /// When no row was inserted and no exception was thrown, for instance because + /// [InsertMode.insertOrIgnore] was used or because the upsert clause had a + /// `where` clause that didn't match, `null` is returned instead. + Future createReturningOrNull(Insertable Function(CI o) f, + {InsertMode? mode, UpsertClause? onConflict}) { + return $state.db.into($state._tableAsTableInfo).insertReturningOrNull( + f($state._getInsertCompanionBuilder), + mode: mode, + onConflict: onConflict); + } + + /// Create multiple rows in the table using the given function + /// + /// All fields in a row that don't have a default value or auto-increment + /// must be set and non-null. Otherwise, an [InvalidDataException] will be + /// thrown. + /// By default, an exception will be thrown if another row with the same + /// primary key already exists. This behavior can be overridden with [mode], + /// for instance by using [InsertMode.replace] or [InsertMode.insertOrIgnore]. + /// Using [bulkCreate] will not disable primary keys or any column constraint + /// checks. + /// [onConflict] can be used to create an upsert clause for engines that + /// support it. For details and examples, see [InsertStatement.insert]. + Future bulkCreate(Iterable> Function(CI o) f, + {InsertMode? mode, UpsertClause? onConflict}) { + return $state.db.batch((b) => b.insertAll( + $state._tableAsTableInfo, f($state._getInsertCompanionBuilder), + mode: mode, onConflict: onConflict)); + } + + /// Replaces the old version of [entity] that is stored in the database with + /// the fields of the [entity] provided here. This implicitly applies a + /// [filter] clause to rows with the same primary key as [entity], so that only + /// the row representing outdated data will be replaced. + /// + /// If [entity] has absent values (set to null on the [DataClass] or + /// explicitly to absent on the [UpdateCompanion]), and a default value for + /// the field exists, that default value will be used. Otherwise, the field + /// will be reset to null. This behavior is different to [update], which simply + /// ignores such fields without changing them in the database. + /// + /// Returns true if a row was affected by this operation. + Future replace(Insertable entity) { + return $state.db.update($state._tableAsTableInfo).replace(entity); + } + + /// Replace multiple rows in the table + /// + /// If any of the [entities] has an absent value (set to null on the [DataClass] or + /// explicitly to absent on the [UpdateCompanion]), and a default value for + /// the field exists, that default value will be used. Otherwise, the field + /// will be reset to null. This behavior is different to [update], which simply + /// ignores such fields without changing them in the database. + Future bulkReplace(Iterable> entities) { + return $state.db + .batch((b) => b.replaceAll($state._tableAsTableInfo, entities)); + } +} diff --git a/drift/lib/src/runtime/manager/ordering.dart b/drift/lib/src/runtime/manager/ordering.dart new file mode 100644 index 00000000..ac703254 --- /dev/null +++ b/drift/lib/src/runtime/manager/ordering.dart @@ -0,0 +1,89 @@ +part of 'manager.dart'; + +/// Defines a class which is used to wrap a column to only expose ordering functions +class ColumnOrderings { + /// This class is a wrapper on top of the generated column class + /// + /// It's used to expose ordering functions for a column + + ColumnOrderings(this.column); + + /// Column that this [ColumnOrderings] wraps + Expression column; + + /// Sort this column in ascending order + /// + /// 10 -> 1 | Z -> A | Dec 31 -> Jan 1 + ComposableOrdering asc() => + ComposableOrdering({OrderingBuilder(OrderingMode.asc, column)}); + + /// Sort this column in descending order + /// + /// 1 -> 10 | A -> Z | Jan 1 -> Dec 31 + ComposableOrdering desc() => + ComposableOrdering({OrderingBuilder(OrderingMode.desc, column)}); +} + +/// Defines a class which will hold the information needed to create an ordering +class OrderingBuilder { + /// The mode of the ordering + final OrderingMode mode; + + /// The column that the ordering is applied to + final Expression column; + + /// Create a new ordering builder, will be used by the [TableManagerState] to create [OrderingTerm]s + OrderingBuilder(this.mode, this.column); + + @override + bool operator ==(covariant OrderingBuilder other) { + if (identical(this, other)) return true; + + return other.mode == mode && other.column == column; + } + + @override + int get hashCode => mode.hashCode ^ column.hashCode; + + /// Build a join from this join builder + OrderingTerm buildTerm() { + return OrderingTerm(mode: mode, expression: column); + } +} + +/// Defines a class that can be used to compose orderings for a column +/// +/// Multiple orderings can be composed together using the `&` operator. +/// The orderings will be executed from left to right. +/// See [HasJoinBuilders] for more information +/// on how joins are stored +class ComposableOrdering extends HasJoinBuilders { + /// The orderings that are being composed + final Set orderingBuilders; + @override + final Set joinBuilders; + + /// Create a new [ComposableOrdering] for a column without any joins + ComposableOrdering(this.orderingBuilders) : joinBuilders = {}; + + /// Create a new [ComposableOrdering] for a column with joins + ComposableOrdering._(this.orderingBuilders, this.joinBuilders); + + /// Combine two orderings with THEN + ComposableOrdering operator &(ComposableOrdering other) { + return ComposableOrdering._(orderingBuilders.union(other.orderingBuilders), + joinBuilders.union(other.joinBuilders)); + } + + /// Build a drift [OrderingTerm] from this ordering + List buildTerms() => orderingBuilders + .map((e) => OrderingTerm(mode: e.mode, expression: e.column)) + .toList(); +} + +/// The class that orchestrates the composition of orderings +class OrderingComposer + extends Composer { + /// Create an ordering composer with an empty state + OrderingComposer(super.$db, super.$table); +} diff --git a/drift/pubspec.yaml b/drift/pubspec.yaml index fcbd149e..5557b498 100644 --- a/drift/pubspec.yaml +++ b/drift/pubspec.yaml @@ -40,3 +40,4 @@ dev_dependencies: shelf: ^1.3.0 test_descriptor: ^2.0.1 vm_service: ^14.0.0 + diff --git a/drift/test/generated/custom_tables.g.dart b/drift/test/generated/custom_tables.g.dart index a116dc90..a949b85b 100644 --- a/drift/test/generated/custom_tables.g.dart +++ b/drift/test/generated/custom_tables.g.dart @@ -1657,6 +1657,7 @@ class MyView extends ViewInfo implements HasResultSet { abstract class _$CustomTablesDb extends GeneratedDatabase { _$CustomTablesDb(QueryExecutor e) : super(e); + _$CustomTablesDbManager get managers => _$CustomTablesDbManager(this); late final NoIds noIds = NoIds(this); late final WithDefaults withDefaults = WithDefaults(this); late final WithConstraints withConstraints = WithConstraints(this); @@ -1970,6 +1971,524 @@ abstract class _$CustomTablesDb extends GeneratedDatabase { const DriftDatabaseOptions(storeDateTimeAsText: true); } +class $WithDefaultsFilterComposer + extends FilterComposer<_$CustomTablesDb, WithDefaults> { + $WithDefaultsFilterComposer(super.db, super.table); + ColumnFilters get a => ColumnFilters($table.a); + ColumnFilters get b => ColumnFilters($table.b); +} + +class $WithDefaultsOrderingComposer + extends OrderingComposer<_$CustomTablesDb, WithDefaults> { + $WithDefaultsOrderingComposer(super.db, super.table); + ColumnOrderings get a => ColumnOrderings($table.a); + ColumnOrderings get b => ColumnOrderings($table.b); +} + +class $WithDefaultsProcessedTableManager extends ProcessedTableManager< + _$CustomTablesDb, + WithDefaults, + WithDefault, + $WithDefaultsFilterComposer, + $WithDefaultsOrderingComposer, + $WithDefaultsProcessedTableManager, + $WithDefaultsInsertCompanionBuilder, + $WithDefaultsUpdateCompanionBuilder> { + const $WithDefaultsProcessedTableManager(super.$state); +} + +typedef $WithDefaultsInsertCompanionBuilder = WithDefaultsCompanion Function({ + Value a, + Value b, + Value rowid, +}); +typedef $WithDefaultsUpdateCompanionBuilder = WithDefaultsCompanion Function({ + Value a, + Value b, + Value rowid, +}); + +class $WithDefaultsTableManager extends RootTableManager< + _$CustomTablesDb, + WithDefaults, + WithDefault, + $WithDefaultsFilterComposer, + $WithDefaultsOrderingComposer, + $WithDefaultsProcessedTableManager, + $WithDefaultsInsertCompanionBuilder, + $WithDefaultsUpdateCompanionBuilder> { + $WithDefaultsTableManager(_$CustomTablesDb db, WithDefaults table) + : super(TableManagerState( + db: db, + table: table, + filteringComposer: $WithDefaultsFilterComposer(db, table), + orderingComposer: $WithDefaultsOrderingComposer(db, table), + getChildManagerBuilder: (p0) => + $WithDefaultsProcessedTableManager(p0), + getUpdateCompanionBuilder: ({ + Value a = const Value.absent(), + Value b = const Value.absent(), + Value rowid = const Value.absent(), + }) => + WithDefaultsCompanion( + a: a, + b: b, + rowid: rowid, + ), + getInsertCompanionBuilder: ({ + Value a = const Value.absent(), + Value b = const Value.absent(), + Value rowid = const Value.absent(), + }) => + WithDefaultsCompanion.insert( + a: a, + b: b, + rowid: rowid, + ))); +} + +class $WithConstraintsFilterComposer + extends FilterComposer<_$CustomTablesDb, WithConstraints> { + $WithConstraintsFilterComposer(super.db, super.table); + ColumnFilters get a => ColumnFilters($table.a); + ColumnFilters get b => ColumnFilters($table.b); + ColumnFilters get c => ColumnFilters($table.c); +} + +class $WithConstraintsOrderingComposer + extends OrderingComposer<_$CustomTablesDb, WithConstraints> { + $WithConstraintsOrderingComposer(super.db, super.table); + ColumnOrderings get a => ColumnOrderings($table.a); + ColumnOrderings get b => ColumnOrderings($table.b); + ColumnOrderings get c => ColumnOrderings($table.c); +} + +class $WithConstraintsProcessedTableManager extends ProcessedTableManager< + _$CustomTablesDb, + WithConstraints, + WithConstraint, + $WithConstraintsFilterComposer, + $WithConstraintsOrderingComposer, + $WithConstraintsProcessedTableManager, + $WithConstraintsInsertCompanionBuilder, + $WithConstraintsUpdateCompanionBuilder> { + const $WithConstraintsProcessedTableManager(super.$state); +} + +typedef $WithConstraintsInsertCompanionBuilder = WithConstraintsCompanion + Function({ + Value a, + required int b, + Value c, + Value rowid, +}); +typedef $WithConstraintsUpdateCompanionBuilder = WithConstraintsCompanion + Function({ + Value a, + Value b, + Value c, + Value rowid, +}); + +class $WithConstraintsTableManager extends RootTableManager< + _$CustomTablesDb, + WithConstraints, + WithConstraint, + $WithConstraintsFilterComposer, + $WithConstraintsOrderingComposer, + $WithConstraintsProcessedTableManager, + $WithConstraintsInsertCompanionBuilder, + $WithConstraintsUpdateCompanionBuilder> { + $WithConstraintsTableManager(_$CustomTablesDb db, WithConstraints table) + : super(TableManagerState( + db: db, + table: table, + filteringComposer: $WithConstraintsFilterComposer(db, table), + orderingComposer: $WithConstraintsOrderingComposer(db, table), + getChildManagerBuilder: (p0) => + $WithConstraintsProcessedTableManager(p0), + getUpdateCompanionBuilder: ({ + Value a = const Value.absent(), + Value b = const Value.absent(), + Value c = const Value.absent(), + Value rowid = const Value.absent(), + }) => + WithConstraintsCompanion( + a: a, + b: b, + c: c, + rowid: rowid, + ), + getInsertCompanionBuilder: ({ + Value a = const Value.absent(), + required int b, + Value c = const Value.absent(), + Value rowid = const Value.absent(), + }) => + WithConstraintsCompanion.insert( + a: a, + b: b, + c: c, + rowid: rowid, + ))); +} + +class $ConfigTableFilterComposer + extends FilterComposer<_$CustomTablesDb, ConfigTable> { + $ConfigTableFilterComposer(super.db, super.table); + ColumnFilters get configKey => ColumnFilters($table.configKey); + ColumnFilters get configValue => ColumnFilters($table.configValue); + ColumnFilters get syncStateValue => ColumnFilters($table.syncState); + ColumnWithTypeConverterFilters get syncState => + ColumnWithTypeConverterFilters($table.syncState); + ColumnFilters get syncStateImplicitValue => + ColumnFilters($table.syncStateImplicit); + ColumnWithTypeConverterFilters + get syncStateImplicit => + ColumnWithTypeConverterFilters($table.syncStateImplicit); +} + +class $ConfigTableOrderingComposer + extends OrderingComposer<_$CustomTablesDb, ConfigTable> { + $ConfigTableOrderingComposer(super.db, super.table); + ColumnOrderings get configKey => ColumnOrderings($table.configKey); + ColumnOrderings get configValue => + ColumnOrderings($table.configValue); + ColumnOrderings get syncState => ColumnOrderings($table.syncState); + ColumnOrderings get syncStateImplicit => + ColumnOrderings($table.syncStateImplicit); +} + +class $ConfigTableProcessedTableManager extends ProcessedTableManager< + _$CustomTablesDb, + ConfigTable, + Config, + $ConfigTableFilterComposer, + $ConfigTableOrderingComposer, + $ConfigTableProcessedTableManager, + $ConfigTableInsertCompanionBuilder, + $ConfigTableUpdateCompanionBuilder> { + const $ConfigTableProcessedTableManager(super.$state); +} + +typedef $ConfigTableInsertCompanionBuilder = ConfigCompanion Function({ + required String configKey, + Value configValue, + Value syncState, + Value syncStateImplicit, + Value rowid, +}); +typedef $ConfigTableUpdateCompanionBuilder = ConfigCompanion Function({ + Value configKey, + Value configValue, + Value syncState, + Value syncStateImplicit, + Value rowid, +}); + +class $ConfigTableTableManager extends RootTableManager< + _$CustomTablesDb, + ConfigTable, + Config, + $ConfigTableFilterComposer, + $ConfigTableOrderingComposer, + $ConfigTableProcessedTableManager, + $ConfigTableInsertCompanionBuilder, + $ConfigTableUpdateCompanionBuilder> { + $ConfigTableTableManager(_$CustomTablesDb db, ConfigTable table) + : super(TableManagerState( + db: db, + table: table, + filteringComposer: $ConfigTableFilterComposer(db, table), + orderingComposer: $ConfigTableOrderingComposer(db, table), + getChildManagerBuilder: (p0) => + $ConfigTableProcessedTableManager(p0), + getUpdateCompanionBuilder: ({ + Value configKey = const Value.absent(), + Value configValue = const Value.absent(), + Value syncState = const Value.absent(), + Value syncStateImplicit = const Value.absent(), + Value rowid = const Value.absent(), + }) => + ConfigCompanion( + configKey: configKey, + configValue: configValue, + syncState: syncState, + syncStateImplicit: syncStateImplicit, + rowid: rowid, + ), + getInsertCompanionBuilder: ({ + required String configKey, + Value configValue = const Value.absent(), + Value syncState = const Value.absent(), + Value syncStateImplicit = const Value.absent(), + Value rowid = const Value.absent(), + }) => + ConfigCompanion.insert( + configKey: configKey, + configValue: configValue, + syncState: syncState, + syncStateImplicit: syncStateImplicit, + rowid: rowid, + ))); +} + +class $MytableFilterComposer extends FilterComposer<_$CustomTablesDb, Mytable> { + $MytableFilterComposer(super.db, super.table); + ColumnFilters get someid => ColumnFilters($table.someid); + ColumnFilters get sometext => ColumnFilters($table.sometext); + ColumnFilters get isInserting => ColumnFilters($table.isInserting); + ColumnFilters get somedate => ColumnFilters($table.somedate); +} + +class $MytableOrderingComposer + extends OrderingComposer<_$CustomTablesDb, Mytable> { + $MytableOrderingComposer(super.db, super.table); + ColumnOrderings get someid => ColumnOrderings($table.someid); + ColumnOrderings get sometext => ColumnOrderings($table.sometext); + ColumnOrderings get isInserting => ColumnOrderings($table.isInserting); + ColumnOrderings get somedate => ColumnOrderings($table.somedate); +} + +class $MytableProcessedTableManager extends ProcessedTableManager< + _$CustomTablesDb, + Mytable, + MytableData, + $MytableFilterComposer, + $MytableOrderingComposer, + $MytableProcessedTableManager, + $MytableInsertCompanionBuilder, + $MytableUpdateCompanionBuilder> { + const $MytableProcessedTableManager(super.$state); +} + +typedef $MytableInsertCompanionBuilder = MytableCompanion Function({ + Value someid, + Value sometext, + Value isInserting, + Value somedate, +}); +typedef $MytableUpdateCompanionBuilder = MytableCompanion Function({ + Value someid, + Value sometext, + Value isInserting, + Value somedate, +}); + +class $MytableTableManager extends RootTableManager< + _$CustomTablesDb, + Mytable, + MytableData, + $MytableFilterComposer, + $MytableOrderingComposer, + $MytableProcessedTableManager, + $MytableInsertCompanionBuilder, + $MytableUpdateCompanionBuilder> { + $MytableTableManager(_$CustomTablesDb db, Mytable table) + : super(TableManagerState( + db: db, + table: table, + filteringComposer: $MytableFilterComposer(db, table), + orderingComposer: $MytableOrderingComposer(db, table), + getChildManagerBuilder: (p0) => $MytableProcessedTableManager(p0), + getUpdateCompanionBuilder: ({ + Value someid = const Value.absent(), + Value sometext = const Value.absent(), + Value isInserting = const Value.absent(), + Value somedate = const Value.absent(), + }) => + MytableCompanion( + someid: someid, + sometext: sometext, + isInserting: isInserting, + somedate: somedate, + ), + getInsertCompanionBuilder: ({ + Value someid = const Value.absent(), + Value sometext = const Value.absent(), + Value isInserting = const Value.absent(), + Value somedate = const Value.absent(), + }) => + MytableCompanion.insert( + someid: someid, + sometext: sometext, + isInserting: isInserting, + somedate: somedate, + ))); +} + +class $EmailFilterComposer extends FilterComposer<_$CustomTablesDb, Email> { + $EmailFilterComposer(super.db, super.table); + ColumnFilters get sender => ColumnFilters($table.sender); + ColumnFilters get title => ColumnFilters($table.title); + ColumnFilters get body => ColumnFilters($table.body); +} + +class $EmailOrderingComposer extends OrderingComposer<_$CustomTablesDb, Email> { + $EmailOrderingComposer(super.db, super.table); + ColumnOrderings get sender => ColumnOrderings($table.sender); + ColumnOrderings get title => ColumnOrderings($table.title); + ColumnOrderings get body => ColumnOrderings($table.body); +} + +class $EmailProcessedTableManager extends ProcessedTableManager< + _$CustomTablesDb, + Email, + EMail, + $EmailFilterComposer, + $EmailOrderingComposer, + $EmailProcessedTableManager, + $EmailInsertCompanionBuilder, + $EmailUpdateCompanionBuilder> { + const $EmailProcessedTableManager(super.$state); +} + +typedef $EmailInsertCompanionBuilder = EmailCompanion Function({ + required String sender, + required String title, + required String body, + Value rowid, +}); +typedef $EmailUpdateCompanionBuilder = EmailCompanion Function({ + Value sender, + Value title, + Value body, + Value rowid, +}); + +class $EmailTableManager extends RootTableManager< + _$CustomTablesDb, + Email, + EMail, + $EmailFilterComposer, + $EmailOrderingComposer, + $EmailProcessedTableManager, + $EmailInsertCompanionBuilder, + $EmailUpdateCompanionBuilder> { + $EmailTableManager(_$CustomTablesDb db, Email table) + : super(TableManagerState( + db: db, + table: table, + filteringComposer: $EmailFilterComposer(db, table), + orderingComposer: $EmailOrderingComposer(db, table), + getChildManagerBuilder: (p0) => $EmailProcessedTableManager(p0), + getUpdateCompanionBuilder: ({ + Value sender = const Value.absent(), + Value title = const Value.absent(), + Value body = const Value.absent(), + Value rowid = const Value.absent(), + }) => + EmailCompanion( + sender: sender, + title: title, + body: body, + rowid: rowid, + ), + getInsertCompanionBuilder: ({ + required String sender, + required String title, + required String body, + Value rowid = const Value.absent(), + }) => + EmailCompanion.insert( + sender: sender, + title: title, + body: body, + rowid: rowid, + ))); +} + +class $WeirdTableFilterComposer + extends FilterComposer<_$CustomTablesDb, WeirdTable> { + $WeirdTableFilterComposer(super.db, super.table); + ColumnFilters get sqlClass => ColumnFilters($table.sqlClass); + ColumnFilters get textColumn => ColumnFilters($table.textColumn); +} + +class $WeirdTableOrderingComposer + extends OrderingComposer<_$CustomTablesDb, WeirdTable> { + $WeirdTableOrderingComposer(super.db, super.table); + ColumnOrderings get sqlClass => ColumnOrderings($table.sqlClass); + ColumnOrderings get textColumn => ColumnOrderings($table.textColumn); +} + +class $WeirdTableProcessedTableManager extends ProcessedTableManager< + _$CustomTablesDb, + WeirdTable, + WeirdData, + $WeirdTableFilterComposer, + $WeirdTableOrderingComposer, + $WeirdTableProcessedTableManager, + $WeirdTableInsertCompanionBuilder, + $WeirdTableUpdateCompanionBuilder> { + const $WeirdTableProcessedTableManager(super.$state); +} + +typedef $WeirdTableInsertCompanionBuilder = WeirdTableCompanion Function({ + required int sqlClass, + required String textColumn, + Value rowid, +}); +typedef $WeirdTableUpdateCompanionBuilder = WeirdTableCompanion Function({ + Value sqlClass, + Value textColumn, + Value rowid, +}); + +class $WeirdTableTableManager extends RootTableManager< + _$CustomTablesDb, + WeirdTable, + WeirdData, + $WeirdTableFilterComposer, + $WeirdTableOrderingComposer, + $WeirdTableProcessedTableManager, + $WeirdTableInsertCompanionBuilder, + $WeirdTableUpdateCompanionBuilder> { + $WeirdTableTableManager(_$CustomTablesDb db, WeirdTable table) + : super(TableManagerState( + db: db, + table: table, + filteringComposer: $WeirdTableFilterComposer(db, table), + orderingComposer: $WeirdTableOrderingComposer(db, table), + getChildManagerBuilder: (p0) => + $WeirdTableProcessedTableManager(p0), + getUpdateCompanionBuilder: ({ + Value sqlClass = const Value.absent(), + Value textColumn = const Value.absent(), + Value rowid = const Value.absent(), + }) => + WeirdTableCompanion( + sqlClass: sqlClass, + textColumn: textColumn, + rowid: rowid, + ), + getInsertCompanionBuilder: ({ + required int sqlClass, + required String textColumn, + Value rowid = const Value.absent(), + }) => + WeirdTableCompanion.insert( + sqlClass: sqlClass, + textColumn: textColumn, + rowid: rowid, + ))); +} + +class _$CustomTablesDbManager { + final _$CustomTablesDb _db; + _$CustomTablesDbManager(this._db); + $WithDefaultsTableManager get withDefaults => + $WithDefaultsTableManager(_db, _db.withDefaults); + $WithConstraintsTableManager get withConstraints => + $WithConstraintsTableManager(_db, _db.withConstraints); + $ConfigTableTableManager get config => + $ConfigTableTableManager(_db, _db.config); + $MytableTableManager get mytable => $MytableTableManager(_db, _db.mytable); + $EmailTableManager get email => $EmailTableManager(_db, _db.email); + $WeirdTableTableManager get weirdTable => + $WeirdTableTableManager(_db, _db.weirdTable); +} + typedef ReadMultiple$clause = OrderBy Function(ConfigTable config); typedef ReadDynamic$predicate = Expression Function(ConfigTable config); typedef TypeConverterVar$pred = Expression Function(ConfigTable config); diff --git a/drift/test/generated/todos.dart b/drift/test/generated/todos.dart index a7fa13df..361299fc 100644 --- a/drift/test/generated/todos.dart +++ b/drift/test/generated/todos.dart @@ -23,7 +23,7 @@ class TodosTable extends Table with AutoIncrement { TextColumn get content => text()(); @JsonKey('target_date') DateTimeColumn get targetDate => dateTime().nullable().unique()(); - + @ReferenceName("todos") IntColumn get category => integer().references(Categories, #id).nullable()(); TextColumn get status => textEnum().nullable()(); @@ -87,6 +87,22 @@ class TableWithoutPK extends Table { text().map(const CustomConverter()).clientDefault(_uuid.v4)(); } +class TableWithEveryColumnType extends Table with AutoIncrement { + BoolColumn get aBool => boolean().nullable()(); + DateTimeColumn get aDateTime => dateTime().nullable()(); + TextColumn get aText => text().nullable()(); + IntColumn get anInt => integer().nullable()(); + Int64Column get anInt64 => int64().nullable()(); + RealColumn get aReal => real().nullable()(); + BlobColumn get aBlob => blob().nullable()(); + IntColumn get anIntEnum => intEnum().nullable()(); + TextColumn get aTextWithConverter => text() + .named('insert') + .map(const CustomJsonConverter()) + .nullable() + .nullable()(); +} + class CustomRowClass { final int notReallyAnId; final double anotherName; @@ -248,6 +264,7 @@ const uuidType = DialectAwareSqlType.via( TableWithoutPK, PureDefaults, WithCustomType, + TableWithEveryColumnType ], views: [ CategoryTodoCountView, diff --git a/drift/test/generated/todos.g.dart b/drift/test/generated/todos.g.dart index 4685760b..817eef4f 100644 --- a/drift/test/generated/todos.g.dart +++ b/drift/test/generated/todos.g.dart @@ -1640,6 +1640,538 @@ class WithCustomTypeCompanion extends UpdateCompanion { } } +class $TableWithEveryColumnTypeTable extends TableWithEveryColumnType + with + TableInfo<$TableWithEveryColumnTypeTable, + TableWithEveryColumnTypeData> { + @override + final GeneratedDatabase attachedDatabase; + final String? _alias; + $TableWithEveryColumnTypeTable(this.attachedDatabase, [this._alias]); + static const VerificationMeta _idMeta = const VerificationMeta('id'); + @override + late final GeneratedColumnWithTypeConverter id = GeneratedColumn< + int>('id', aliasedName, false, + hasAutoIncrement: true, + type: DriftSqlType.int, + requiredDuringInsert: false, + defaultConstraints: + GeneratedColumn.constraintIsAlways('PRIMARY KEY AUTOINCREMENT')) + .withConverter($TableWithEveryColumnTypeTable.$converterid); + static const VerificationMeta _aBoolMeta = const VerificationMeta('aBool'); + @override + late final GeneratedColumn aBool = GeneratedColumn( + 'a_bool', aliasedName, true, + type: DriftSqlType.bool, + requiredDuringInsert: false, + defaultConstraints: + GeneratedColumn.constraintIsAlways('CHECK ("a_bool" IN (0, 1))')); + static const VerificationMeta _aDateTimeMeta = + const VerificationMeta('aDateTime'); + @override + late final GeneratedColumn aDateTime = GeneratedColumn( + 'a_date_time', aliasedName, true, + type: DriftSqlType.dateTime, requiredDuringInsert: false); + static const VerificationMeta _aTextMeta = const VerificationMeta('aText'); + @override + late final GeneratedColumn aText = GeneratedColumn( + 'a_text', aliasedName, true, + type: DriftSqlType.string, requiredDuringInsert: false); + static const VerificationMeta _anIntMeta = const VerificationMeta('anInt'); + @override + late final GeneratedColumn anInt = GeneratedColumn( + 'an_int', aliasedName, true, + type: DriftSqlType.int, requiredDuringInsert: false); + static const VerificationMeta _anInt64Meta = + const VerificationMeta('anInt64'); + @override + late final GeneratedColumn anInt64 = GeneratedColumn( + 'an_int64', aliasedName, true, + type: DriftSqlType.bigInt, requiredDuringInsert: false); + static const VerificationMeta _aRealMeta = const VerificationMeta('aReal'); + @override + late final GeneratedColumn aReal = GeneratedColumn( + 'a_real', aliasedName, true, + type: DriftSqlType.double, requiredDuringInsert: false); + static const VerificationMeta _aBlobMeta = const VerificationMeta('aBlob'); + @override + late final GeneratedColumn aBlob = GeneratedColumn( + 'a_blob', aliasedName, true, + type: DriftSqlType.blob, requiredDuringInsert: false); + static const VerificationMeta _anIntEnumMeta = + const VerificationMeta('anIntEnum'); + @override + late final GeneratedColumnWithTypeConverter anIntEnum = + GeneratedColumn('an_int_enum', aliasedName, true, + type: DriftSqlType.int, requiredDuringInsert: false) + .withConverter( + $TableWithEveryColumnTypeTable.$converteranIntEnumn); + static const VerificationMeta _aTextWithConverterMeta = + const VerificationMeta('aTextWithConverter'); + @override + late final GeneratedColumnWithTypeConverter + aTextWithConverter = GeneratedColumn('insert', aliasedName, true, + type: DriftSqlType.string, requiredDuringInsert: false) + .withConverter( + $TableWithEveryColumnTypeTable.$converteraTextWithConvertern); + @override + List get $columns => [ + id, + aBool, + aDateTime, + aText, + anInt, + anInt64, + aReal, + aBlob, + anIntEnum, + aTextWithConverter + ]; + @override + String get aliasedName => _alias ?? actualTableName; + @override + String get actualTableName => $name; + static const String $name = 'table_with_every_column_type'; + @override + VerificationContext validateIntegrity( + Insertable instance, + {bool isInserting = false}) { + final context = VerificationContext(); + final data = instance.toColumns(true); + context.handle(_idMeta, const VerificationResult.success()); + if (data.containsKey('a_bool')) { + context.handle( + _aBoolMeta, aBool.isAcceptableOrUnknown(data['a_bool']!, _aBoolMeta)); + } + if (data.containsKey('a_date_time')) { + context.handle( + _aDateTimeMeta, + aDateTime.isAcceptableOrUnknown( + data['a_date_time']!, _aDateTimeMeta)); + } + if (data.containsKey('a_text')) { + context.handle( + _aTextMeta, aText.isAcceptableOrUnknown(data['a_text']!, _aTextMeta)); + } + if (data.containsKey('an_int')) { + context.handle( + _anIntMeta, anInt.isAcceptableOrUnknown(data['an_int']!, _anIntMeta)); + } + if (data.containsKey('an_int64')) { + context.handle(_anInt64Meta, + anInt64.isAcceptableOrUnknown(data['an_int64']!, _anInt64Meta)); + } + if (data.containsKey('a_real')) { + context.handle( + _aRealMeta, aReal.isAcceptableOrUnknown(data['a_real']!, _aRealMeta)); + } + if (data.containsKey('a_blob')) { + context.handle( + _aBlobMeta, aBlob.isAcceptableOrUnknown(data['a_blob']!, _aBlobMeta)); + } + context.handle(_anIntEnumMeta, const VerificationResult.success()); + context.handle(_aTextWithConverterMeta, const VerificationResult.success()); + return context; + } + + @override + Set get $primaryKey => {id}; + @override + TableWithEveryColumnTypeData map(Map data, + {String? tablePrefix}) { + final effectivePrefix = tablePrefix != null ? '$tablePrefix.' : ''; + return TableWithEveryColumnTypeData( + id: $TableWithEveryColumnTypeTable.$converterid.fromSql(attachedDatabase + .typeMapping + .read(DriftSqlType.int, data['${effectivePrefix}id'])!), + aBool: attachedDatabase.typeMapping + .read(DriftSqlType.bool, data['${effectivePrefix}a_bool']), + aDateTime: attachedDatabase.typeMapping + .read(DriftSqlType.dateTime, data['${effectivePrefix}a_date_time']), + aText: attachedDatabase.typeMapping + .read(DriftSqlType.string, data['${effectivePrefix}a_text']), + anInt: attachedDatabase.typeMapping + .read(DriftSqlType.int, data['${effectivePrefix}an_int']), + anInt64: attachedDatabase.typeMapping + .read(DriftSqlType.bigInt, data['${effectivePrefix}an_int64']), + aReal: attachedDatabase.typeMapping + .read(DriftSqlType.double, data['${effectivePrefix}a_real']), + aBlob: attachedDatabase.typeMapping + .read(DriftSqlType.blob, data['${effectivePrefix}a_blob']), + anIntEnum: $TableWithEveryColumnTypeTable.$converteranIntEnumn.fromSql( + attachedDatabase.typeMapping + .read(DriftSqlType.int, data['${effectivePrefix}an_int_enum'])), + aTextWithConverter: $TableWithEveryColumnTypeTable + .$converteraTextWithConvertern + .fromSql(attachedDatabase.typeMapping + .read(DriftSqlType.string, data['${effectivePrefix}insert'])), + ); + } + + @override + $TableWithEveryColumnTypeTable createAlias(String alias) { + return $TableWithEveryColumnTypeTable(attachedDatabase, alias); + } + + static JsonTypeConverter2 $converterid = + TypeConverter.extensionType(); + static JsonTypeConverter2 $converteranIntEnum = + const EnumIndexConverter(TodoStatus.values); + static JsonTypeConverter2 $converteranIntEnumn = + JsonTypeConverter2.asNullable($converteranIntEnum); + static JsonTypeConverter2> + $converteraTextWithConverter = const CustomJsonConverter(); + static JsonTypeConverter2?> + $converteraTextWithConvertern = + JsonTypeConverter2.asNullable($converteraTextWithConverter); +} + +class TableWithEveryColumnTypeData extends DataClass + implements Insertable { + final RowId id; + final bool? aBool; + final DateTime? aDateTime; + final String? aText; + final int? anInt; + final BigInt? anInt64; + final double? aReal; + final Uint8List? aBlob; + final TodoStatus? anIntEnum; + final MyCustomObject? aTextWithConverter; + const TableWithEveryColumnTypeData( + {required this.id, + this.aBool, + this.aDateTime, + this.aText, + this.anInt, + this.anInt64, + this.aReal, + this.aBlob, + this.anIntEnum, + this.aTextWithConverter}); + @override + Map toColumns(bool nullToAbsent) { + final map = {}; + { + map['id'] = + Variable($TableWithEveryColumnTypeTable.$converterid.toSql(id)); + } + if (!nullToAbsent || aBool != null) { + map['a_bool'] = Variable(aBool); + } + if (!nullToAbsent || aDateTime != null) { + map['a_date_time'] = Variable(aDateTime); + } + if (!nullToAbsent || aText != null) { + map['a_text'] = Variable(aText); + } + if (!nullToAbsent || anInt != null) { + map['an_int'] = Variable(anInt); + } + if (!nullToAbsent || anInt64 != null) { + map['an_int64'] = Variable(anInt64); + } + if (!nullToAbsent || aReal != null) { + map['a_real'] = Variable(aReal); + } + if (!nullToAbsent || aBlob != null) { + map['a_blob'] = Variable(aBlob); + } + if (!nullToAbsent || anIntEnum != null) { + map['an_int_enum'] = Variable( + $TableWithEveryColumnTypeTable.$converteranIntEnumn.toSql(anIntEnum)); + } + if (!nullToAbsent || aTextWithConverter != null) { + map['insert'] = Variable($TableWithEveryColumnTypeTable + .$converteraTextWithConvertern + .toSql(aTextWithConverter)); + } + return map; + } + + TableWithEveryColumnTypeCompanion toCompanion(bool nullToAbsent) { + return TableWithEveryColumnTypeCompanion( + id: Value(id), + aBool: + aBool == null && nullToAbsent ? const Value.absent() : Value(aBool), + aDateTime: aDateTime == null && nullToAbsent + ? const Value.absent() + : Value(aDateTime), + aText: + aText == null && nullToAbsent ? const Value.absent() : Value(aText), + anInt: + anInt == null && nullToAbsent ? const Value.absent() : Value(anInt), + anInt64: anInt64 == null && nullToAbsent + ? const Value.absent() + : Value(anInt64), + aReal: + aReal == null && nullToAbsent ? const Value.absent() : Value(aReal), + aBlob: + aBlob == null && nullToAbsent ? const Value.absent() : Value(aBlob), + anIntEnum: anIntEnum == null && nullToAbsent + ? const Value.absent() + : Value(anIntEnum), + aTextWithConverter: aTextWithConverter == null && nullToAbsent + ? const Value.absent() + : Value(aTextWithConverter), + ); + } + + factory TableWithEveryColumnTypeData.fromJson(Map json, + {ValueSerializer? serializer}) { + serializer ??= driftRuntimeOptions.defaultSerializer; + return TableWithEveryColumnTypeData( + id: $TableWithEveryColumnTypeTable.$converterid + .fromJson(serializer.fromJson(json['id'])), + aBool: serializer.fromJson(json['aBool']), + aDateTime: serializer.fromJson(json['aDateTime']), + aText: serializer.fromJson(json['aText']), + anInt: serializer.fromJson(json['anInt']), + anInt64: serializer.fromJson(json['anInt64']), + aReal: serializer.fromJson(json['aReal']), + aBlob: serializer.fromJson(json['aBlob']), + anIntEnum: $TableWithEveryColumnTypeTable.$converteranIntEnumn + .fromJson(serializer.fromJson(json['anIntEnum'])), + aTextWithConverter: $TableWithEveryColumnTypeTable + .$converteraTextWithConvertern + .fromJson(serializer + .fromJson?>(json['aTextWithConverter'])), + ); + } + factory TableWithEveryColumnTypeData.fromJsonString(String encodedJson, + {ValueSerializer? serializer}) => + TableWithEveryColumnTypeData.fromJson( + DataClass.parseJson(encodedJson) as Map, + serializer: serializer); + @override + Map toJson({ValueSerializer? serializer}) { + serializer ??= driftRuntimeOptions.defaultSerializer; + return { + 'id': serializer + .toJson($TableWithEveryColumnTypeTable.$converterid.toJson(id)), + 'aBool': serializer.toJson(aBool), + 'aDateTime': serializer.toJson(aDateTime), + 'aText': serializer.toJson(aText), + 'anInt': serializer.toJson(anInt), + 'anInt64': serializer.toJson(anInt64), + 'aReal': serializer.toJson(aReal), + 'aBlob': serializer.toJson(aBlob), + 'anIntEnum': serializer.toJson($TableWithEveryColumnTypeTable + .$converteranIntEnumn + .toJson(anIntEnum)), + 'aTextWithConverter': serializer.toJson?>( + $TableWithEveryColumnTypeTable.$converteraTextWithConvertern + .toJson(aTextWithConverter)), + }; + } + + TableWithEveryColumnTypeData copyWith( + {RowId? id, + Value aBool = const Value.absent(), + Value aDateTime = const Value.absent(), + Value aText = const Value.absent(), + Value anInt = const Value.absent(), + Value anInt64 = const Value.absent(), + Value aReal = const Value.absent(), + Value aBlob = const Value.absent(), + Value anIntEnum = const Value.absent(), + Value aTextWithConverter = const Value.absent()}) => + TableWithEveryColumnTypeData( + id: id ?? this.id, + aBool: aBool.present ? aBool.value : this.aBool, + aDateTime: aDateTime.present ? aDateTime.value : this.aDateTime, + aText: aText.present ? aText.value : this.aText, + anInt: anInt.present ? anInt.value : this.anInt, + anInt64: anInt64.present ? anInt64.value : this.anInt64, + aReal: aReal.present ? aReal.value : this.aReal, + aBlob: aBlob.present ? aBlob.value : this.aBlob, + anIntEnum: anIntEnum.present ? anIntEnum.value : this.anIntEnum, + aTextWithConverter: aTextWithConverter.present + ? aTextWithConverter.value + : this.aTextWithConverter, + ); + @override + String toString() { + return (StringBuffer('TableWithEveryColumnTypeData(') + ..write('id: $id, ') + ..write('aBool: $aBool, ') + ..write('aDateTime: $aDateTime, ') + ..write('aText: $aText, ') + ..write('anInt: $anInt, ') + ..write('anInt64: $anInt64, ') + ..write('aReal: $aReal, ') + ..write('aBlob: $aBlob, ') + ..write('anIntEnum: $anIntEnum, ') + ..write('aTextWithConverter: $aTextWithConverter') + ..write(')')) + .toString(); + } + + @override + int get hashCode => Object.hash(id, aBool, aDateTime, aText, anInt, anInt64, + aReal, $driftBlobEquality.hash(aBlob), anIntEnum, aTextWithConverter); + @override + bool operator ==(Object other) => + identical(this, other) || + (other is TableWithEveryColumnTypeData && + other.id == this.id && + other.aBool == this.aBool && + other.aDateTime == this.aDateTime && + other.aText == this.aText && + other.anInt == this.anInt && + other.anInt64 == this.anInt64 && + other.aReal == this.aReal && + $driftBlobEquality.equals(other.aBlob, this.aBlob) && + other.anIntEnum == this.anIntEnum && + other.aTextWithConverter == this.aTextWithConverter); +} + +class TableWithEveryColumnTypeCompanion + extends UpdateCompanion { + final Value id; + final Value aBool; + final Value aDateTime; + final Value aText; + final Value anInt; + final Value anInt64; + final Value aReal; + final Value aBlob; + final Value anIntEnum; + final Value aTextWithConverter; + const TableWithEveryColumnTypeCompanion({ + this.id = const Value.absent(), + this.aBool = const Value.absent(), + this.aDateTime = const Value.absent(), + this.aText = const Value.absent(), + this.anInt = const Value.absent(), + this.anInt64 = const Value.absent(), + this.aReal = const Value.absent(), + this.aBlob = const Value.absent(), + this.anIntEnum = const Value.absent(), + this.aTextWithConverter = const Value.absent(), + }); + TableWithEveryColumnTypeCompanion.insert({ + this.id = const Value.absent(), + this.aBool = const Value.absent(), + this.aDateTime = const Value.absent(), + this.aText = const Value.absent(), + this.anInt = const Value.absent(), + this.anInt64 = const Value.absent(), + this.aReal = const Value.absent(), + this.aBlob = const Value.absent(), + this.anIntEnum = const Value.absent(), + this.aTextWithConverter = const Value.absent(), + }); + static Insertable custom({ + Expression? id, + Expression? aBool, + Expression? aDateTime, + Expression? aText, + Expression? anInt, + Expression? anInt64, + Expression? aReal, + Expression? aBlob, + Expression? anIntEnum, + Expression? aTextWithConverter, + }) { + return RawValuesInsertable({ + if (id != null) 'id': id, + if (aBool != null) 'a_bool': aBool, + if (aDateTime != null) 'a_date_time': aDateTime, + if (aText != null) 'a_text': aText, + if (anInt != null) 'an_int': anInt, + if (anInt64 != null) 'an_int64': anInt64, + if (aReal != null) 'a_real': aReal, + if (aBlob != null) 'a_blob': aBlob, + if (anIntEnum != null) 'an_int_enum': anIntEnum, + if (aTextWithConverter != null) 'insert': aTextWithConverter, + }); + } + + TableWithEveryColumnTypeCompanion copyWith( + {Value? id, + Value? aBool, + Value? aDateTime, + Value? aText, + Value? anInt, + Value? anInt64, + Value? aReal, + Value? aBlob, + Value? anIntEnum, + Value? aTextWithConverter}) { + return TableWithEveryColumnTypeCompanion( + id: id ?? this.id, + aBool: aBool ?? this.aBool, + aDateTime: aDateTime ?? this.aDateTime, + aText: aText ?? this.aText, + anInt: anInt ?? this.anInt, + anInt64: anInt64 ?? this.anInt64, + aReal: aReal ?? this.aReal, + aBlob: aBlob ?? this.aBlob, + anIntEnum: anIntEnum ?? this.anIntEnum, + aTextWithConverter: aTextWithConverter ?? this.aTextWithConverter, + ); + } + + @override + Map toColumns(bool nullToAbsent) { + final map = {}; + if (id.present) { + map['id'] = Variable( + $TableWithEveryColumnTypeTable.$converterid.toSql(id.value)); + } + if (aBool.present) { + map['a_bool'] = Variable(aBool.value); + } + if (aDateTime.present) { + map['a_date_time'] = Variable(aDateTime.value); + } + if (aText.present) { + map['a_text'] = Variable(aText.value); + } + if (anInt.present) { + map['an_int'] = Variable(anInt.value); + } + if (anInt64.present) { + map['an_int64'] = Variable(anInt64.value); + } + if (aReal.present) { + map['a_real'] = Variable(aReal.value); + } + if (aBlob.present) { + map['a_blob'] = Variable(aBlob.value); + } + if (anIntEnum.present) { + map['an_int_enum'] = Variable($TableWithEveryColumnTypeTable + .$converteranIntEnumn + .toSql(anIntEnum.value)); + } + if (aTextWithConverter.present) { + map['insert'] = Variable($TableWithEveryColumnTypeTable + .$converteraTextWithConvertern + .toSql(aTextWithConverter.value)); + } + return map; + } + + @override + String toString() { + return (StringBuffer('TableWithEveryColumnTypeCompanion(') + ..write('id: $id, ') + ..write('aBool: $aBool, ') + ..write('aDateTime: $aDateTime, ') + ..write('aText: $aText, ') + ..write('anInt: $anInt, ') + ..write('anInt64: $anInt64, ') + ..write('aReal: $aReal, ') + ..write('aBlob: $aBlob, ') + ..write('anIntEnum: $anIntEnum, ') + ..write('aTextWithConverter: $aTextWithConverter') + ..write(')')) + .toString(); + } +} + class CategoryTodoCountViewData extends DataClass { final int? categoryId; final String? description; @@ -1864,6 +2396,7 @@ class $TodoWithCategoryViewView abstract class _$TodoDb extends GeneratedDatabase { _$TodoDb(QueryExecutor e) : super(e); + _$TodoDbManager get managers => _$TodoDbManager(this); late final $CategoriesTable categories = $CategoriesTable(this); late final $TodosTableTable todosTable = $TodosTableTable(this); late final $UsersTable users = $UsersTable(this); @@ -1871,6 +2404,8 @@ abstract class _$TodoDb extends GeneratedDatabase { late final $TableWithoutPKTable tableWithoutPK = $TableWithoutPKTable(this); late final $PureDefaultsTable pureDefaults = $PureDefaultsTable(this); late final $WithCustomTypeTable withCustomType = $WithCustomTypeTable(this); + late final $TableWithEveryColumnTypeTable tableWithEveryColumnType = + $TableWithEveryColumnTypeTable(this); late final $CategoryTodoCountViewView categoryTodoCountView = $CategoryTodoCountViewView(this); late final $TodoWithCategoryViewView todoWithCategoryView = @@ -1957,11 +2492,732 @@ abstract class _$TodoDb extends GeneratedDatabase { tableWithoutPK, pureDefaults, withCustomType, + tableWithEveryColumnType, categoryTodoCountView, todoWithCategoryView ]; } +class $$CategoriesTableFilterComposer + extends FilterComposer<_$TodoDb, $CategoriesTable> { + $$CategoriesTableFilterComposer(super.db, super.table); + ColumnFilters get idValue => ColumnFilters($table.id); + ColumnWithTypeConverterFilters get id => + ColumnWithTypeConverterFilters($table.id); + ColumnFilters get description => ColumnFilters($table.description); + ColumnFilters get priorityValue => ColumnFilters($table.priority); + ColumnWithTypeConverterFilters + get priority => ColumnWithTypeConverterFilters($table.priority); + ColumnFilters get descriptionInUpperCase => + ColumnFilters($table.descriptionInUpperCase); + ComposableFilter todos( + ComposableFilter Function($$TodosTableTableFilterComposer f) f) { + return $composeWithJoins( + $db: $db, + $table: $table, + referencedTable: $db.todosTable, + getCurrentColumn: (f) => f.id, + getReferencedColumn: (f) => f.category, + getReferencedComposer: (db, table) => + $$TodosTableTableFilterComposer(db, table), + builder: f); + } +} + +class $$CategoriesTableOrderingComposer + extends OrderingComposer<_$TodoDb, $CategoriesTable> { + $$CategoriesTableOrderingComposer(super.db, super.table); + ColumnOrderings get id => ColumnOrderings($table.id); + ColumnOrderings get description => + ColumnOrderings($table.description); + ColumnOrderings get priority => ColumnOrderings($table.priority); + ColumnOrderings get descriptionInUpperCase => + ColumnOrderings($table.descriptionInUpperCase); +} + +class $$CategoriesTableProcessedTableManager extends ProcessedTableManager< + _$TodoDb, + $CategoriesTable, + Category, + $$CategoriesTableFilterComposer, + $$CategoriesTableOrderingComposer, + $$CategoriesTableProcessedTableManager, + $$CategoriesTableInsertCompanionBuilder, + $$CategoriesTableUpdateCompanionBuilder> { + const $$CategoriesTableProcessedTableManager(super.$state); +} + +typedef $$CategoriesTableInsertCompanionBuilder = CategoriesCompanion Function({ + Value id, + required String description, + Value priority, +}); +typedef $$CategoriesTableUpdateCompanionBuilder = CategoriesCompanion Function({ + Value id, + Value description, + Value priority, +}); + +class $$CategoriesTableTableManager extends RootTableManager< + _$TodoDb, + $CategoriesTable, + Category, + $$CategoriesTableFilterComposer, + $$CategoriesTableOrderingComposer, + $$CategoriesTableProcessedTableManager, + $$CategoriesTableInsertCompanionBuilder, + $$CategoriesTableUpdateCompanionBuilder> { + $$CategoriesTableTableManager(_$TodoDb db, $CategoriesTable table) + : super(TableManagerState( + db: db, + table: table, + filteringComposer: $$CategoriesTableFilterComposer(db, table), + orderingComposer: $$CategoriesTableOrderingComposer(db, table), + getChildManagerBuilder: (p0) => + $$CategoriesTableProcessedTableManager(p0), + getUpdateCompanionBuilder: ({ + Value id = const Value.absent(), + Value description = const Value.absent(), + Value priority = const Value.absent(), + }) => + CategoriesCompanion( + id: id, + description: description, + priority: priority, + ), + getInsertCompanionBuilder: ({ + Value id = const Value.absent(), + required String description, + Value priority = const Value.absent(), + }) => + CategoriesCompanion.insert( + id: id, + description: description, + priority: priority, + ))); +} + +class $$TodosTableTableFilterComposer + extends FilterComposer<_$TodoDb, $TodosTableTable> { + $$TodosTableTableFilterComposer(super.db, super.table); + ColumnFilters get idValue => ColumnFilters($table.id); + ColumnWithTypeConverterFilters get id => + ColumnWithTypeConverterFilters($table.id); + ColumnFilters get title => ColumnFilters($table.title); + ColumnFilters get content => ColumnFilters($table.content); + ColumnFilters get targetDate => ColumnFilters($table.targetDate); + ColumnFilters get categoryId => ColumnFilters($table.category); + ComposableFilter category( + ComposableFilter Function($$CategoriesTableFilterComposer f) f) { + return $composeWithJoins( + $db: $db, + $table: $table, + referencedTable: $db.categories, + getCurrentColumn: (f) => f.category, + getReferencedColumn: (f) => f.id, + getReferencedComposer: (db, table) => + $$CategoriesTableFilterComposer(db, table), + builder: f); + } + + ColumnFilters get statusValue => ColumnFilters($table.status); + ColumnWithTypeConverterFilters get status => + ColumnWithTypeConverterFilters($table.status); +} + +class $$TodosTableTableOrderingComposer + extends OrderingComposer<_$TodoDb, $TodosTableTable> { + $$TodosTableTableOrderingComposer(super.db, super.table); + ColumnOrderings get id => ColumnOrderings($table.id); + ColumnOrderings get title => ColumnOrderings($table.title); + ColumnOrderings get content => ColumnOrderings($table.content); + ColumnOrderings get targetDate => + ColumnOrderings($table.targetDate); + ColumnOrderings get categoryId => ColumnOrderings($table.category); + ComposableOrdering category( + ComposableOrdering Function($$CategoriesTableOrderingComposer o) o) { + return $composeWithJoins( + $db: $db, + $table: $table, + referencedTable: $db.categories, + getCurrentColumn: (f) => f.category, + getReferencedColumn: (f) => f.id, + getReferencedComposer: (db, table) => + $$CategoriesTableOrderingComposer(db, table), + builder: o); + } + + ColumnOrderings get status => ColumnOrderings($table.status); +} + +class $$TodosTableTableProcessedTableManager extends ProcessedTableManager< + _$TodoDb, + $TodosTableTable, + TodoEntry, + $$TodosTableTableFilterComposer, + $$TodosTableTableOrderingComposer, + $$TodosTableTableProcessedTableManager, + $$TodosTableTableInsertCompanionBuilder, + $$TodosTableTableUpdateCompanionBuilder> { + const $$TodosTableTableProcessedTableManager(super.$state); +} + +typedef $$TodosTableTableInsertCompanionBuilder = TodosTableCompanion Function({ + Value id, + Value title, + required String content, + Value targetDate, + Value category, + Value status, +}); +typedef $$TodosTableTableUpdateCompanionBuilder = TodosTableCompanion Function({ + Value id, + Value title, + Value content, + Value targetDate, + Value category, + Value status, +}); + +class $$TodosTableTableTableManager extends RootTableManager< + _$TodoDb, + $TodosTableTable, + TodoEntry, + $$TodosTableTableFilterComposer, + $$TodosTableTableOrderingComposer, + $$TodosTableTableProcessedTableManager, + $$TodosTableTableInsertCompanionBuilder, + $$TodosTableTableUpdateCompanionBuilder> { + $$TodosTableTableTableManager(_$TodoDb db, $TodosTableTable table) + : super(TableManagerState( + db: db, + table: table, + filteringComposer: $$TodosTableTableFilterComposer(db, table), + orderingComposer: $$TodosTableTableOrderingComposer(db, table), + getChildManagerBuilder: (p0) => + $$TodosTableTableProcessedTableManager(p0), + getUpdateCompanionBuilder: ({ + Value id = const Value.absent(), + Value title = const Value.absent(), + Value content = const Value.absent(), + Value targetDate = const Value.absent(), + Value category = const Value.absent(), + Value status = const Value.absent(), + }) => + TodosTableCompanion( + id: id, + title: title, + content: content, + targetDate: targetDate, + category: category, + status: status, + ), + getInsertCompanionBuilder: ({ + Value id = const Value.absent(), + Value title = const Value.absent(), + required String content, + Value targetDate = const Value.absent(), + Value category = const Value.absent(), + Value status = const Value.absent(), + }) => + TodosTableCompanion.insert( + id: id, + title: title, + content: content, + targetDate: targetDate, + category: category, + status: status, + ))); +} + +class $$UsersTableFilterComposer extends FilterComposer<_$TodoDb, $UsersTable> { + $$UsersTableFilterComposer(super.db, super.table); + ColumnFilters get idValue => ColumnFilters($table.id); + ColumnWithTypeConverterFilters get id => + ColumnWithTypeConverterFilters($table.id); + ColumnFilters get name => ColumnFilters($table.name); + ColumnFilters get isAwesome => ColumnFilters($table.isAwesome); + ColumnFilters get profilePicture => + ColumnFilters($table.profilePicture); + ColumnFilters get creationTime => + ColumnFilters($table.creationTime); +} + +class $$UsersTableOrderingComposer + extends OrderingComposer<_$TodoDb, $UsersTable> { + $$UsersTableOrderingComposer(super.db, super.table); + ColumnOrderings get id => ColumnOrderings($table.id); + ColumnOrderings get name => ColumnOrderings($table.name); + ColumnOrderings get isAwesome => ColumnOrderings($table.isAwesome); + ColumnOrderings get profilePicture => + ColumnOrderings($table.profilePicture); + ColumnOrderings get creationTime => + ColumnOrderings($table.creationTime); +} + +class $$UsersTableProcessedTableManager extends ProcessedTableManager< + _$TodoDb, + $UsersTable, + User, + $$UsersTableFilterComposer, + $$UsersTableOrderingComposer, + $$UsersTableProcessedTableManager, + $$UsersTableInsertCompanionBuilder, + $$UsersTableUpdateCompanionBuilder> { + const $$UsersTableProcessedTableManager(super.$state); +} + +typedef $$UsersTableInsertCompanionBuilder = UsersCompanion Function({ + Value id, + required String name, + Value isAwesome, + required Uint8List profilePicture, + Value creationTime, +}); +typedef $$UsersTableUpdateCompanionBuilder = UsersCompanion Function({ + Value id, + Value name, + Value isAwesome, + Value profilePicture, + Value creationTime, +}); + +class $$UsersTableTableManager extends RootTableManager< + _$TodoDb, + $UsersTable, + User, + $$UsersTableFilterComposer, + $$UsersTableOrderingComposer, + $$UsersTableProcessedTableManager, + $$UsersTableInsertCompanionBuilder, + $$UsersTableUpdateCompanionBuilder> { + $$UsersTableTableManager(_$TodoDb db, $UsersTable table) + : super(TableManagerState( + db: db, + table: table, + filteringComposer: $$UsersTableFilterComposer(db, table), + orderingComposer: $$UsersTableOrderingComposer(db, table), + getChildManagerBuilder: (p0) => + $$UsersTableProcessedTableManager(p0), + getUpdateCompanionBuilder: ({ + Value id = const Value.absent(), + Value name = const Value.absent(), + Value isAwesome = const Value.absent(), + Value profilePicture = const Value.absent(), + Value creationTime = const Value.absent(), + }) => + UsersCompanion( + id: id, + name: name, + isAwesome: isAwesome, + profilePicture: profilePicture, + creationTime: creationTime, + ), + getInsertCompanionBuilder: ({ + Value id = const Value.absent(), + required String name, + Value isAwesome = const Value.absent(), + required Uint8List profilePicture, + Value creationTime = const Value.absent(), + }) => + UsersCompanion.insert( + id: id, + name: name, + isAwesome: isAwesome, + profilePicture: profilePicture, + creationTime: creationTime, + ))); +} + +class $$SharedTodosTableFilterComposer + extends FilterComposer<_$TodoDb, $SharedTodosTable> { + $$SharedTodosTableFilterComposer(super.db, super.table); + ColumnFilters get todo => ColumnFilters($table.todo); + ColumnFilters get user => ColumnFilters($table.user); +} + +class $$SharedTodosTableOrderingComposer + extends OrderingComposer<_$TodoDb, $SharedTodosTable> { + $$SharedTodosTableOrderingComposer(super.db, super.table); + ColumnOrderings get todo => ColumnOrderings($table.todo); + ColumnOrderings get user => ColumnOrderings($table.user); +} + +class $$SharedTodosTableProcessedTableManager extends ProcessedTableManager< + _$TodoDb, + $SharedTodosTable, + SharedTodo, + $$SharedTodosTableFilterComposer, + $$SharedTodosTableOrderingComposer, + $$SharedTodosTableProcessedTableManager, + $$SharedTodosTableInsertCompanionBuilder, + $$SharedTodosTableUpdateCompanionBuilder> { + const $$SharedTodosTableProcessedTableManager(super.$state); +} + +typedef $$SharedTodosTableInsertCompanionBuilder = SharedTodosCompanion + Function({ + required int todo, + required int user, + Value rowid, +}); +typedef $$SharedTodosTableUpdateCompanionBuilder = SharedTodosCompanion + Function({ + Value todo, + Value user, + Value rowid, +}); + +class $$SharedTodosTableTableManager extends RootTableManager< + _$TodoDb, + $SharedTodosTable, + SharedTodo, + $$SharedTodosTableFilterComposer, + $$SharedTodosTableOrderingComposer, + $$SharedTodosTableProcessedTableManager, + $$SharedTodosTableInsertCompanionBuilder, + $$SharedTodosTableUpdateCompanionBuilder> { + $$SharedTodosTableTableManager(_$TodoDb db, $SharedTodosTable table) + : super(TableManagerState( + db: db, + table: table, + filteringComposer: $$SharedTodosTableFilterComposer(db, table), + orderingComposer: $$SharedTodosTableOrderingComposer(db, table), + getChildManagerBuilder: (p0) => + $$SharedTodosTableProcessedTableManager(p0), + getUpdateCompanionBuilder: ({ + Value todo = const Value.absent(), + Value user = const Value.absent(), + Value rowid = const Value.absent(), + }) => + SharedTodosCompanion( + todo: todo, + user: user, + rowid: rowid, + ), + getInsertCompanionBuilder: ({ + required int todo, + required int user, + Value rowid = const Value.absent(), + }) => + SharedTodosCompanion.insert( + todo: todo, + user: user, + rowid: rowid, + ))); +} + +class $$PureDefaultsTableFilterComposer + extends FilterComposer<_$TodoDb, $PureDefaultsTable> { + $$PureDefaultsTableFilterComposer(super.db, super.table); + ColumnFilters get txtValue => ColumnFilters($table.txt); + ColumnWithTypeConverterFilters + get txt => ColumnWithTypeConverterFilters($table.txt); +} + +class $$PureDefaultsTableOrderingComposer + extends OrderingComposer<_$TodoDb, $PureDefaultsTable> { + $$PureDefaultsTableOrderingComposer(super.db, super.table); + ColumnOrderings get txt => ColumnOrderings($table.txt); +} + +class $$PureDefaultsTableProcessedTableManager extends ProcessedTableManager< + _$TodoDb, + $PureDefaultsTable, + PureDefault, + $$PureDefaultsTableFilterComposer, + $$PureDefaultsTableOrderingComposer, + $$PureDefaultsTableProcessedTableManager, + $$PureDefaultsTableInsertCompanionBuilder, + $$PureDefaultsTableUpdateCompanionBuilder> { + const $$PureDefaultsTableProcessedTableManager(super.$state); +} + +typedef $$PureDefaultsTableInsertCompanionBuilder = PureDefaultsCompanion + Function({ + Value txt, + Value rowid, +}); +typedef $$PureDefaultsTableUpdateCompanionBuilder = PureDefaultsCompanion + Function({ + Value txt, + Value rowid, +}); + +class $$PureDefaultsTableTableManager extends RootTableManager< + _$TodoDb, + $PureDefaultsTable, + PureDefault, + $$PureDefaultsTableFilterComposer, + $$PureDefaultsTableOrderingComposer, + $$PureDefaultsTableProcessedTableManager, + $$PureDefaultsTableInsertCompanionBuilder, + $$PureDefaultsTableUpdateCompanionBuilder> { + $$PureDefaultsTableTableManager(_$TodoDb db, $PureDefaultsTable table) + : super(TableManagerState( + db: db, + table: table, + filteringComposer: $$PureDefaultsTableFilterComposer(db, table), + orderingComposer: $$PureDefaultsTableOrderingComposer(db, table), + getChildManagerBuilder: (p0) => + $$PureDefaultsTableProcessedTableManager(p0), + getUpdateCompanionBuilder: ({ + Value txt = const Value.absent(), + Value rowid = const Value.absent(), + }) => + PureDefaultsCompanion( + txt: txt, + rowid: rowid, + ), + getInsertCompanionBuilder: ({ + Value txt = const Value.absent(), + Value rowid = const Value.absent(), + }) => + PureDefaultsCompanion.insert( + txt: txt, + rowid: rowid, + ))); +} + +class $$WithCustomTypeTableFilterComposer + extends FilterComposer<_$TodoDb, $WithCustomTypeTable> { + $$WithCustomTypeTableFilterComposer(super.db, super.table); + ColumnFilters get id => ColumnFilters($table.id); +} + +class $$WithCustomTypeTableOrderingComposer + extends OrderingComposer<_$TodoDb, $WithCustomTypeTable> { + $$WithCustomTypeTableOrderingComposer(super.db, super.table); + ColumnOrderings get id => ColumnOrderings($table.id); +} + +class $$WithCustomTypeTableProcessedTableManager extends ProcessedTableManager< + _$TodoDb, + $WithCustomTypeTable, + WithCustomTypeData, + $$WithCustomTypeTableFilterComposer, + $$WithCustomTypeTableOrderingComposer, + $$WithCustomTypeTableProcessedTableManager, + $$WithCustomTypeTableInsertCompanionBuilder, + $$WithCustomTypeTableUpdateCompanionBuilder> { + const $$WithCustomTypeTableProcessedTableManager(super.$state); +} + +typedef $$WithCustomTypeTableInsertCompanionBuilder = WithCustomTypeCompanion + Function({ + required UuidValue id, + Value rowid, +}); +typedef $$WithCustomTypeTableUpdateCompanionBuilder = WithCustomTypeCompanion + Function({ + Value id, + Value rowid, +}); + +class $$WithCustomTypeTableTableManager extends RootTableManager< + _$TodoDb, + $WithCustomTypeTable, + WithCustomTypeData, + $$WithCustomTypeTableFilterComposer, + $$WithCustomTypeTableOrderingComposer, + $$WithCustomTypeTableProcessedTableManager, + $$WithCustomTypeTableInsertCompanionBuilder, + $$WithCustomTypeTableUpdateCompanionBuilder> { + $$WithCustomTypeTableTableManager(_$TodoDb db, $WithCustomTypeTable table) + : super(TableManagerState( + db: db, + table: table, + filteringComposer: $$WithCustomTypeTableFilterComposer(db, table), + orderingComposer: $$WithCustomTypeTableOrderingComposer(db, table), + getChildManagerBuilder: (p0) => + $$WithCustomTypeTableProcessedTableManager(p0), + getUpdateCompanionBuilder: ({ + Value id = const Value.absent(), + Value rowid = const Value.absent(), + }) => + WithCustomTypeCompanion( + id: id, + rowid: rowid, + ), + getInsertCompanionBuilder: ({ + required UuidValue id, + Value rowid = const Value.absent(), + }) => + WithCustomTypeCompanion.insert( + id: id, + rowid: rowid, + ))); +} + +class $$TableWithEveryColumnTypeTableFilterComposer + extends FilterComposer<_$TodoDb, $TableWithEveryColumnTypeTable> { + $$TableWithEveryColumnTypeTableFilterComposer(super.db, super.table); + ColumnFilters get idValue => ColumnFilters($table.id); + ColumnWithTypeConverterFilters get id => + ColumnWithTypeConverterFilters($table.id); + ColumnFilters get aBool => ColumnFilters($table.aBool); + ColumnFilters get aDateTime => ColumnFilters($table.aDateTime); + ColumnFilters get aText => ColumnFilters($table.aText); + ColumnFilters get anInt => ColumnFilters($table.anInt); + ColumnFilters get anInt64 => ColumnFilters($table.anInt64); + ColumnFilters get aReal => ColumnFilters($table.aReal); + ColumnFilters get aBlob => ColumnFilters($table.aBlob); + ColumnFilters get anIntEnumValue => ColumnFilters($table.anIntEnum); + ColumnWithTypeConverterFilters get anIntEnum => + ColumnWithTypeConverterFilters($table.anIntEnum); + ColumnFilters get aTextWithConverterValue => + ColumnFilters($table.aTextWithConverter); + ColumnWithTypeConverterFilters + get aTextWithConverter => + ColumnWithTypeConverterFilters($table.aTextWithConverter); +} + +class $$TableWithEveryColumnTypeTableOrderingComposer + extends OrderingComposer<_$TodoDb, $TableWithEveryColumnTypeTable> { + $$TableWithEveryColumnTypeTableOrderingComposer(super.db, super.table); + ColumnOrderings get id => ColumnOrderings($table.id); + ColumnOrderings get aBool => ColumnOrderings($table.aBool); + ColumnOrderings get aDateTime => ColumnOrderings($table.aDateTime); + ColumnOrderings get aText => ColumnOrderings($table.aText); + ColumnOrderings get anInt => ColumnOrderings($table.anInt); + ColumnOrderings get anInt64 => ColumnOrderings($table.anInt64); + ColumnOrderings get aReal => ColumnOrderings($table.aReal); + ColumnOrderings get aBlob => ColumnOrderings($table.aBlob); + ColumnOrderings get anIntEnum => ColumnOrderings($table.anIntEnum); + ColumnOrderings get aTextWithConverter => + ColumnOrderings($table.aTextWithConverter); +} + +class $$TableWithEveryColumnTypeTableProcessedTableManager + extends ProcessedTableManager< + _$TodoDb, + $TableWithEveryColumnTypeTable, + TableWithEveryColumnTypeData, + $$TableWithEveryColumnTypeTableFilterComposer, + $$TableWithEveryColumnTypeTableOrderingComposer, + $$TableWithEveryColumnTypeTableProcessedTableManager, + $$TableWithEveryColumnTypeTableInsertCompanionBuilder, + $$TableWithEveryColumnTypeTableUpdateCompanionBuilder> { + const $$TableWithEveryColumnTypeTableProcessedTableManager(super.$state); +} + +typedef $$TableWithEveryColumnTypeTableInsertCompanionBuilder + = TableWithEveryColumnTypeCompanion Function({ + Value id, + Value aBool, + Value aDateTime, + Value aText, + Value anInt, + Value anInt64, + Value aReal, + Value aBlob, + Value anIntEnum, + Value aTextWithConverter, +}); +typedef $$TableWithEveryColumnTypeTableUpdateCompanionBuilder + = TableWithEveryColumnTypeCompanion Function({ + Value id, + Value aBool, + Value aDateTime, + Value aText, + Value anInt, + Value anInt64, + Value aReal, + Value aBlob, + Value anIntEnum, + Value aTextWithConverter, +}); + +class $$TableWithEveryColumnTypeTableTableManager extends RootTableManager< + _$TodoDb, + $TableWithEveryColumnTypeTable, + TableWithEveryColumnTypeData, + $$TableWithEveryColumnTypeTableFilterComposer, + $$TableWithEveryColumnTypeTableOrderingComposer, + $$TableWithEveryColumnTypeTableProcessedTableManager, + $$TableWithEveryColumnTypeTableInsertCompanionBuilder, + $$TableWithEveryColumnTypeTableUpdateCompanionBuilder> { + $$TableWithEveryColumnTypeTableTableManager( + _$TodoDb db, $TableWithEveryColumnTypeTable table) + : super(TableManagerState( + db: db, + table: table, + filteringComposer: + $$TableWithEveryColumnTypeTableFilterComposer(db, table), + orderingComposer: + $$TableWithEveryColumnTypeTableOrderingComposer(db, table), + getChildManagerBuilder: (p0) => + $$TableWithEveryColumnTypeTableProcessedTableManager(p0), + getUpdateCompanionBuilder: ({ + Value id = const Value.absent(), + Value aBool = const Value.absent(), + Value aDateTime = const Value.absent(), + Value aText = const Value.absent(), + Value anInt = const Value.absent(), + Value anInt64 = const Value.absent(), + Value aReal = const Value.absent(), + Value aBlob = const Value.absent(), + Value anIntEnum = const Value.absent(), + Value aTextWithConverter = const Value.absent(), + }) => + TableWithEveryColumnTypeCompanion( + id: id, + aBool: aBool, + aDateTime: aDateTime, + aText: aText, + anInt: anInt, + anInt64: anInt64, + aReal: aReal, + aBlob: aBlob, + anIntEnum: anIntEnum, + aTextWithConverter: aTextWithConverter, + ), + getInsertCompanionBuilder: ({ + Value id = const Value.absent(), + Value aBool = const Value.absent(), + Value aDateTime = const Value.absent(), + Value aText = const Value.absent(), + Value anInt = const Value.absent(), + Value anInt64 = const Value.absent(), + Value aReal = const Value.absent(), + Value aBlob = const Value.absent(), + Value anIntEnum = const Value.absent(), + Value aTextWithConverter = const Value.absent(), + }) => + TableWithEveryColumnTypeCompanion.insert( + id: id, + aBool: aBool, + aDateTime: aDateTime, + aText: aText, + anInt: anInt, + anInt64: anInt64, + aReal: aReal, + aBlob: aBlob, + anIntEnum: anIntEnum, + aTextWithConverter: aTextWithConverter, + ))); +} + +class _$TodoDbManager { + final _$TodoDb _db; + _$TodoDbManager(this._db); + $$CategoriesTableTableManager get categories => + $$CategoriesTableTableManager(_db, _db.categories); + $$TodosTableTableTableManager get todosTable => + $$TodosTableTableTableManager(_db, _db.todosTable); + $$UsersTableTableManager get users => + $$UsersTableTableManager(_db, _db.users); + $$SharedTodosTableTableManager get sharedTodos => + $$SharedTodosTableTableManager(_db, _db.sharedTodos); + $$PureDefaultsTableTableManager get pureDefaults => + $$PureDefaultsTableTableManager(_db, _db.pureDefaults); + $$WithCustomTypeTableTableManager get withCustomType => + $$WithCustomTypeTableTableManager(_db, _db.withCustomType); + $$TableWithEveryColumnTypeTableTableManager get tableWithEveryColumnType => + $$TableWithEveryColumnTypeTableTableManager( + _db, _db.tableWithEveryColumnType); +} + class AllTodosWithCategoryResult extends CustomResultSet { final RowId id; final String? title; diff --git a/drift/test/integration_tests/regress_2166_test.g.dart b/drift/test/integration_tests/regress_2166_test.g.dart index 6f16c66d..a25f8da4 100644 --- a/drift/test/integration_tests/regress_2166_test.g.dart +++ b/drift/test/integration_tests/regress_2166_test.g.dart @@ -184,6 +184,7 @@ class _SomeTableCompanion extends UpdateCompanion<_SomeTableData> { abstract class _$_SomeDb extends GeneratedDatabase { _$_SomeDb(QueryExecutor e) : super(e); + _$_SomeDbManager get managers => _$_SomeDbManager(this); late final $_SomeTableTable someTable = $_SomeTableTable(this); @override Iterable> get allTables => @@ -191,3 +192,80 @@ abstract class _$_SomeDb extends GeneratedDatabase { @override List get allSchemaEntities => [someTable]; } + +class $$_SomeTableTableFilterComposer + extends FilterComposer<_$_SomeDb, $_SomeTableTable> { + $$_SomeTableTableFilterComposer(super.db, super.table); + ColumnFilters get id => ColumnFilters($table.id); + ColumnFilters get name => ColumnFilters($table.name); +} + +class $$_SomeTableTableOrderingComposer + extends OrderingComposer<_$_SomeDb, $_SomeTableTable> { + $$_SomeTableTableOrderingComposer(super.db, super.table); + ColumnOrderings get id => ColumnOrderings($table.id); + ColumnOrderings get name => ColumnOrderings($table.name); +} + +class $$_SomeTableTableProcessedTableManager extends ProcessedTableManager< + _$_SomeDb, + $_SomeTableTable, + _SomeTableData, + $$_SomeTableTableFilterComposer, + $$_SomeTableTableOrderingComposer, + $$_SomeTableTableProcessedTableManager, + $$_SomeTableTableInsertCompanionBuilder, + $$_SomeTableTableUpdateCompanionBuilder> { + const $$_SomeTableTableProcessedTableManager(super.$state); +} + +typedef $$_SomeTableTableInsertCompanionBuilder = _SomeTableCompanion Function({ + Value id, + Value name, +}); +typedef $$_SomeTableTableUpdateCompanionBuilder = _SomeTableCompanion Function({ + Value id, + Value name, +}); + +class $$_SomeTableTableTableManager extends RootTableManager< + _$_SomeDb, + $_SomeTableTable, + _SomeTableData, + $$_SomeTableTableFilterComposer, + $$_SomeTableTableOrderingComposer, + $$_SomeTableTableProcessedTableManager, + $$_SomeTableTableInsertCompanionBuilder, + $$_SomeTableTableUpdateCompanionBuilder> { + $$_SomeTableTableTableManager(_$_SomeDb db, $_SomeTableTable table) + : super(TableManagerState( + db: db, + table: table, + filteringComposer: $$_SomeTableTableFilterComposer(db, table), + orderingComposer: $$_SomeTableTableOrderingComposer(db, table), + getChildManagerBuilder: (p0) => + $$_SomeTableTableProcessedTableManager(p0), + getUpdateCompanionBuilder: ({ + Value id = const Value.absent(), + Value name = const Value.absent(), + }) => + _SomeTableCompanion( + id: id, + name: name, + ), + getInsertCompanionBuilder: ({ + Value id = const Value.absent(), + Value name = const Value.absent(), + }) => + _SomeTableCompanion.insert( + id: id, + name: name, + ))); +} + +class _$_SomeDbManager { + final _$_SomeDb _db; + _$_SomeDbManager(this._db); + $$_SomeTableTableTableManager get someTable => + $$_SomeTableTableTableManager(_db, _db.someTable); +} diff --git a/drift/test/manager/manager_filter_test.dart b/drift/test/manager/manager_filter_test.dart new file mode 100644 index 00000000..38775d02 --- /dev/null +++ b/drift/test/manager/manager_filter_test.dart @@ -0,0 +1,540 @@ +import 'package:drift/drift.dart'; +import 'package:test/test.dart'; + +import '../generated/todos.dart'; +import '../test_utils/test_utils.dart'; + +void main() { + late TodoDb db; + + setUp(() { + db = TodoDb(testInMemoryDatabase()); + }); + + tearDown(() => db.close()); + + test('manager - query generic', () async { + await db.managers.tableWithEveryColumnType.create((o) => o( + aText: Value("Get that math homework done"), + anIntEnum: Value(TodoStatus.open), + aReal: Value(5.0), + aDateTime: Value(DateTime.now().add(Duration(days: 1))))); + await db.managers.tableWithEveryColumnType.create((o) => o( + aText: Value("Get that math homework done"), + anIntEnum: Value(TodoStatus.open), + aDateTime: Value(DateTime.now().add(Duration(days: 2))))); + await db.managers.tableWithEveryColumnType.create((o) => o( + aText: Value("Get that math homework done"), + anIntEnum: Value(TodoStatus.open), + aReal: Value(3.0), + aDateTime: Value(DateTime.now().add(Duration(days: 3))))); + + // Equals + expect( + db.managers.tableWithEveryColumnType + .filter((f) => f.aReal.equals(5.0)) + .count(), + completion(1)); + expect( + db.managers.tableWithEveryColumnType + .filter((f) => f.aReal(3.0)) + .count(), + completion(1)); + + // Not Equals - Exclude null (Default behavior) + expect( + db.managers.tableWithEveryColumnType + .filter((f) => f.aReal.not.equals(5.0)) + .count(), + completion(1)); + + // In + expect( + db.managers.tableWithEveryColumnType + .filter((f) => f.aReal.isIn([3.0, 5.0])) + .count(), + completion(2)); + + // Not In + expect( + db.managers.tableWithEveryColumnType + .filter((f) => f.aReal.not.isIn([3.0, 5.0])) + .count(), + completion(0)); + + // Null check + expect( + db.managers.tableWithEveryColumnType + .filter((f) => f.aReal.isNull()) + .count(), + completion(1)); + + expect( + db.managers.tableWithEveryColumnType + .filter((f) => f.aReal.not.isNull()) + .count(), + completion(2)); + }); + + test('manager - query number', () async { + await db.managers.tableWithEveryColumnType.create((o) => o( + aText: Value("Get that math homework done"), + anIntEnum: Value(TodoStatus.open), + aReal: Value(5.0), + aDateTime: Value(DateTime.now().add(Duration(days: 1))))); + await db.managers.tableWithEveryColumnType.create((o) => o( + aText: Value("Get that math homework done"), + anIntEnum: Value(TodoStatus.open), + aDateTime: Value(DateTime.now().add(Duration(days: 2))))); + await db.managers.tableWithEveryColumnType.create((o) => o( + aText: Value("Get that math homework done"), + anIntEnum: Value(TodoStatus.open), + aReal: Value(3.0), + aDateTime: Value(DateTime.now().add(Duration(days: 3))))); + + // More than + expect( + db.managers.tableWithEveryColumnType + .filter((f) => f.aReal.isBiggerThan(3.0)) + .count(), + completion(1)); + expect( + db.managers.tableWithEveryColumnType + .filter((f) => f.aReal.isBiggerOrEqualTo(3.0)) + .count(), + completion(2)); + + // Less than + expect( + db.managers.tableWithEveryColumnType + .filter((f) => f.aReal.isSmallerThan(5.0)) + .count(), + completion(1)); + expect( + db.managers.tableWithEveryColumnType + .filter((f) => f.aReal.isSmallerOrEqualTo(5.0)) + .count(), + completion(2)); + + // Between + expect( + db.managers.tableWithEveryColumnType + .filter((f) => f.aReal.isBetween(3.0, 5.0)) + .count(), + completion(2)); + expect( + db.managers.tableWithEveryColumnType + .filter((f) => f.aReal.not.isBetween(3.0, 5.0)) + .count(), + completion(0)); + }); + + test('manager - query string', () async { + await db.managers.tableWithEveryColumnType.create((o) => o( + aText: Value("Get that math homework done"), + anIntEnum: Value(TodoStatus.open), + )); + await db.managers.tableWithEveryColumnType.create((o) => o( + aText: Value("That homework Done"), + )); + await db.managers.tableWithEveryColumnType.create((o) => o( + aText: Value("that MATH homework"), + anIntEnum: Value(TodoStatus.open), + )); + + // StartsWith + expect( + db.managers.tableWithEveryColumnType + .filter((f) => f.aText.startsWith("that")) + .count(), + completion(2)); + + // EndsWith + expect( + db.managers.tableWithEveryColumnType + .filter((f) => f.aText.endsWith("done")) + .count(), + completion(2)); + + // Contains + expect( + db.managers.tableWithEveryColumnType + .filter((f) => f.aText.contains("math")) + .count(), + completion(2)); + + // Make the database case sensitive + await db.customStatement('PRAGMA case_sensitive_like = ON'); + + // StartsWith + expect( + db.managers.tableWithEveryColumnType + .filter((f) => f.aText.startsWith("that", caseInsensitive: false)) + .count(), + completion(1)); + + // EndsWith + expect( + db.managers.tableWithEveryColumnType + .filter((f) => f.aText.endsWith("done", caseInsensitive: false)) + .count(), + completion(1)); + + // Contains + expect( + db.managers.tableWithEveryColumnType + .filter((f) => f.aText.contains("math", caseInsensitive: false)) + .count(), + completion(1)); + }); + + test('manager - query int64', () async { + await db.managers.tableWithEveryColumnType.create((o) => o( + aText: Value("Get that math homework done"), + anIntEnum: Value(TodoStatus.open), + anInt64: Value(BigInt.from(5.0)), + aDateTime: Value(DateTime.now().add(Duration(days: 1))))); + await db.managers.tableWithEveryColumnType.create((o) => o( + aText: Value("Get that math homework done"), + anIntEnum: Value(TodoStatus.open), + aDateTime: Value(DateTime.now().add(Duration(days: 2))))); + await db.managers.tableWithEveryColumnType.create((o) => o( + aText: Value("Get that math homework done"), + anIntEnum: Value(TodoStatus.open), + anInt64: Value(BigInt.from(3.0)), + aDateTime: Value(DateTime.now().add(Duration(days: 3))))); + + // More than + expect( + db.managers.tableWithEveryColumnType + .filter((f) => f.anInt64.isBiggerThan(BigInt.from(3.0))) + .count(), + completion(1)); + expect( + db.managers.tableWithEveryColumnType + .filter((f) => f.anInt64.isBiggerOrEqualTo(BigInt.from(3.0))) + .count(), + completion(2)); + + // Less than + expect( + db.managers.tableWithEveryColumnType + .filter((f) => f.anInt64.isSmallerThan(BigInt.from(5.0))) + .count(), + completion(1)); + expect( + db.managers.tableWithEveryColumnType + .filter((f) => f.anInt64.isSmallerOrEqualTo(BigInt.from(5.0))) + .count(), + completion(2)); + + // Between + expect( + db.managers.tableWithEveryColumnType + .filter( + (f) => f.anInt64.isBetween(BigInt.from(3.0), BigInt.from(5.0))) + .count(), + completion(2)); + expect( + db.managers.tableWithEveryColumnType + .filter((f) => + f.anInt64.not.isBetween(BigInt.from(3.0), BigInt.from(5.0))) + .count(), + completion(0)); + }); + + test('manager - query bool', () async { + await db.managers.users.create((o) => o( + name: "John Doe", + profilePicture: Uint8List(0), + isAwesome: Value(true), + creationTime: Value(DateTime.now().add(Duration(days: 1))))); + await db.managers.users.create((o) => o( + name: "Jane Doe1", + profilePicture: Uint8List(0), + isAwesome: Value(false), + creationTime: Value(DateTime.now().add(Duration(days: 2))))); + await db.managers.users.create((o) => o( + name: "Jane Doe2", + profilePicture: Uint8List(0), + isAwesome: Value(true), + creationTime: Value(DateTime.now().add(Duration(days: 2))))); + + // False + expect(db.managers.users.filter((f) => f.isAwesome.isFalse()).count(), + completion(1)); + // True + expect(db.managers.users.filter((f) => f.isAwesome.isTrue()).count(), + completion(2)); + }); + + test('manager - query datetime', () async { + final day1 = DateTime.now().add(Duration(days: 1)); + final day2 = DateTime.now().add(Duration(days: 2)); + final day3 = DateTime.now().add(Duration(days: 3)); + await db.managers.tableWithEveryColumnType.create((o) => o( + aText: Value("Get that math homework done"), + anIntEnum: Value(TodoStatus.open), + aReal: Value(5.0), + aDateTime: Value(day1))); + await db.managers.tableWithEveryColumnType.create((o) => o( + aText: Value("Get that math homework done"), + anIntEnum: Value(TodoStatus.open), + aDateTime: Value(day2))); + await db.managers.tableWithEveryColumnType.create((o) => o( + aText: Value("Get that math homework done"), + anIntEnum: Value(TodoStatus.open), + aReal: Value(3.0), + aDateTime: Value(day3))); + + // More than + expect( + db.managers.tableWithEveryColumnType + .filter((f) => f.aDateTime.isAfter(day2)) + .count(), + completion(1)); + expect( + db.managers.tableWithEveryColumnType + .filter((f) => f.aDateTime.isAfterOrOn(day2)) + .count(), + completion(2)); + + // Less than + expect( + db.managers.tableWithEveryColumnType + .filter((f) => f.aDateTime.isBefore(day2)) + .count(), + completion(1)); + expect( + db.managers.tableWithEveryColumnType + .filter((f) => f.aDateTime.isBeforeOrOn(day2)) + .count(), + completion(2)); + + // Between + expect( + db.managers.tableWithEveryColumnType + .filter((f) => f.aDateTime.isBetween(day1, day2)) + .count(), + completion(2)); + expect( + db.managers.tableWithEveryColumnType + .filter((f) => f.aDateTime.not.isBetween(day1, day2)) + .count(), + completion(1)); + }); + + test('manager - query custom column', () async { + await db.managers.tableWithEveryColumnType.create((o) => o( + aText: Value("Get that math homework done"), + anIntEnum: Value(TodoStatus.open))); + await db.managers.tableWithEveryColumnType.create((o) => o( + aText: Value("Get that math homework done"), + anIntEnum: Value(TodoStatus.open))); + await db.managers.tableWithEveryColumnType.create((o) => o( + aText: Value("Get that math homework done"), + anIntEnum: Value(TodoStatus.workInProgress))); + await db.managers.tableWithEveryColumnType.create((o) => o( + aText: Value("Get that math homework done"), + anIntEnum: Value(TodoStatus.done))); + await db.managers.tableWithEveryColumnType + .create((o) => o(aText: Value("Get that math homework done"))); + + // Equals + expect( + db.managers.tableWithEveryColumnType + .filter((f) => f.anIntEnum.equals(TodoStatus.open)) + .count(), + completion(2)); + expect( + db.managers.tableWithEveryColumnType + .filter((f) => f.anIntEnum(TodoStatus.open)) + .count(), + completion(2)); + + expect( + db.managers.tableWithEveryColumnType + .filter((f) => f.anIntEnum.not(TodoStatus.open)) + .count(), + completion(2)); + + // // Not Equals + // expect( + // db.managers.tableWithEveryColumnType + // .filter((f) => f.anIntEnum.not.equals(TodoStatus.open)) + // .count(), + // completion(2)); + + // // In + // expect( + // db.managers.tableWithEveryColumnType + // .filter((f) => + // f.anIntEnum.isIn([TodoStatus.open, TodoStatus.workInProgress])) + // .count(), + // completion(3)); + + // // Not In + // expect( + // db.managers.tableWithEveryColumnType + // .filter((f) => f.anIntEnum.not + // .isIn([TodoStatus.open, TodoStatus.workInProgress])) + // .count(), + // completion(1)); + }); + + test('manager - filter related', () async { + final schoolCategoryId = await db.managers.categories.create((o) => + o(priority: Value(CategoryPriority.high), description: "School")); + final workCategoryId = await db.managers.categories.create( + (o) => o(priority: Value(CategoryPriority.low), description: "Work")); + + // School + await db.managers.todosTable.create((o) => o( + content: "Get that math homework done", + title: Value("Math Homework"), + category: Value(schoolCategoryId), + status: Value(TodoStatus.open), + targetDate: Value(DateTime.now().add(Duration(days: 1, seconds: 10))))); + await db.managers.todosTable.create((o) => o( + content: "Finish that report", + title: Value("Report"), + category: Value(schoolCategoryId), + status: Value(TodoStatus.workInProgress), + targetDate: Value(DateTime.now().add(Duration(days: 2, seconds: 10))))); + await db.managers.todosTable.create((o) => o( + content: "Get that english homework done", + title: Value("English Homework"), + category: Value(schoolCategoryId), + status: Value(TodoStatus.open), + targetDate: Value(DateTime.now().add(Duration(days: 1, seconds: 15))))); + await db.managers.todosTable.create((o) => o( + content: "Finish that Book report", + title: Value("Book Report"), + category: Value(schoolCategoryId), + status: Value(TodoStatus.done), + targetDate: + Value(DateTime.now().subtract(Duration(days: 2, seconds: 15))))); + + // Work + await db.managers.todosTable.create((o) => o( + content: "File those reports", + title: Value("File Reports"), + category: Value(workCategoryId), + status: Value(TodoStatus.open), + targetDate: Value(DateTime.now().add(Duration(days: 1, seconds: 20))))); + await db.managers.todosTable.create((o) => o( + content: "Clean the office", + title: Value("Clean Office"), + category: Value(workCategoryId), + status: Value(TodoStatus.workInProgress), + targetDate: Value(DateTime.now().add(Duration(days: 2, seconds: 20))))); + await db.managers.todosTable.create((o) => o( + content: "Nail that presentation", + title: Value("Presentation"), + category: Value(workCategoryId), + status: Value(TodoStatus.open), + targetDate: Value(DateTime.now().add(Duration(days: 1, seconds: 25))))); + await db.managers.todosTable.create((o) => o( + content: "Take a break", + title: Value("Break"), + category: Value(workCategoryId), + status: Value(TodoStatus.done), + targetDate: + Value(DateTime.now().subtract(Duration(days: 2, seconds: 25))))); + + // Items with no category + await db.managers.todosTable.create((o) => o( + content: "Get Whiteboard", + title: Value("Whiteboard"), + status: Value(TodoStatus.open), + targetDate: Value(DateTime.now().add(Duration(days: 1, seconds: 50))))); + await db.managers.todosTable.create((o) => o( + content: "Drink Water", + title: Value("Water"), + status: Value(TodoStatus.workInProgress), + targetDate: Value(DateTime.now().add(Duration(days: 2, seconds: 50))))); + + // item without title + + // Equals + expect( + db.managers.todosTable + .filter((f) => f.category( + (f) => f.id(RowId(schoolCategoryId)), + )) + .count(), + completion(4)); + + // Not Equals + expect( + db.managers.todosTable + .filter( + (f) => + f.category((f) => f.id.not.equals(RowId(schoolCategoryId))), + ) + .count(), + completion(4)); + + // Multiple filters + expect( + db.managers.todosTable + .filter((f) => f.category( + (f) => f.id(RowId(schoolCategoryId)), + )) + .filter((f) => f.status.equals(TodoStatus.open)) + .count(), + completion(2)); + + // Multiple 2 related filters + expect( + db.managers.todosTable + .filter((f) => f.category( + (f) => + f.priority.equals(CategoryPriority.low) | + f.descriptionInUpperCase.equals("SCHOOL"), + )) + .count(), + completion(8)); + + // Multiple use related filters twice + expect( + db.managers.todosTable + .filter((f) => + f.category( + (f) => f.priority.equals(CategoryPriority.low), + ) | + f.category( + (f) => f.descriptionInUpperCase.equals("SCHOOL"), + )) + .count(), + completion(8)); + // Use .filter multiple times + expect( + db.managers.todosTable + .filter((f) => f.category( + (f) => f.priority.equals(CategoryPriority.high), + )) + .filter((f) => f.category( + (f) => f.descriptionInUpperCase.equals("SCHOOL"), + )) + .count(), + completion(4)); + + // Use backreference + expect( + db.managers.categories + .filter((f) => f.todos((f) => f.title.equals("Math Homework"))) + .getSingle() + .then((value) => value.description), + completion("School")); + + // Nested backreference + expect( + db.managers.categories + .filter((f) => f.todos((f) => f.category( + (f) => f.todos((f) => f.title.equals("Math Homework"))))) + .getSingle() + .then((value) => value.description), + completion("School")); + }); +} diff --git a/drift/test/manager/manager_order_test.dart b/drift/test/manager/manager_order_test.dart new file mode 100644 index 00000000..4f64141e --- /dev/null +++ b/drift/test/manager/manager_order_test.dart @@ -0,0 +1,91 @@ +import 'package:drift/drift.dart'; +import 'package:test/test.dart'; + +import '../generated/todos.dart'; +import '../test_utils/test_utils.dart'; + +void main() { + late TodoDb db; + + setUp(() { + db = TodoDb(testInMemoryDatabase()); + }); + + tearDown(() => db.close()); + + test('manager - order', () async { + await db.managers.tableWithEveryColumnType.create((o) => o( + id: Value(RowId(1)), + aText: Value("Get that math homework done"), + anIntEnum: Value(TodoStatus.open), + aReal: Value(5.0), + aDateTime: Value(DateTime.now().add(Duration(days: 1))))); + await db.managers.tableWithEveryColumnType.create((o) => o( + aText: Value("Get that math homework done"), + anIntEnum: Value(TodoStatus.open), + aDateTime: Value(DateTime.now().add(Duration(days: 2))))); + await db.managers.tableWithEveryColumnType.create((o) => o( + aText: Value("Get that math homework done"), + anIntEnum: Value(TodoStatus.open), + aReal: Value(3.0), + aDateTime: Value(DateTime.now().add(Duration(days: 3))))); + + // Equals + expect( + db.managers.tableWithEveryColumnType + .orderBy((o) => o.aDateTime.desc()) + .get() + .then((value) => value[0].id), + completion(3)); + expect( + db.managers.tableWithEveryColumnType + .orderBy((o) => o.aDateTime.asc()) + .get() + .then((value) => value[0].id), + completion(1)); + }); + + test('manager - order related', () async { + final schoolCategoryId = await db.managers.categories.create((o) => + o(priority: Value(CategoryPriority.high), description: "School")); + final workCategoryId = await db.managers.categories.create( + (o) => o(priority: Value(CategoryPriority.low), description: "Work")); + + await db.managers.todosTable.create((o) => o( + id: Value(RowId(1)), + content: "Get that english homework done", + title: Value("English Homework"), + category: Value(workCategoryId), + status: Value(TodoStatus.open), + targetDate: Value(DateTime.now().add(Duration(days: 1, seconds: 15))))); + await db.managers.todosTable.create((o) => o( + id: Value(RowId(2)), + content: "Finish that Book report", + title: Value("Book Report"), + category: Value(workCategoryId), + status: Value(TodoStatus.done), + targetDate: + Value(DateTime.now().subtract(Duration(days: 2, seconds: 15))))); + await db.managers.todosTable.create((o) => o( + id: Value(RowId(3)), + content: "Get that math homework done", + title: Value("Math Homework"), + category: Value(schoolCategoryId), + status: Value(TodoStatus.open), + targetDate: Value(DateTime.now().add(Duration(days: 1, seconds: 10))))); + await db.managers.todosTable.create((o) => o( + id: Value(RowId(4)), + content: "Finish that report", + title: Value("Report"), + category: Value(schoolCategoryId), + status: Value(TodoStatus.workInProgress), + targetDate: Value(DateTime.now().add(Duration(days: 2, seconds: 10))))); + // Order by related + expect( + db.managers.todosTable + .orderBy((o) => o.category((o) => o.id.asc())) + .get() + .then((value) => value.map((e) => e.id).toList()), + completion([3, 4, 1, 2])); + }); +} diff --git a/drift/test/manager/manager_test.dart b/drift/test/manager/manager_test.dart new file mode 100644 index 00000000..3bfdd0bc --- /dev/null +++ b/drift/test/manager/manager_test.dart @@ -0,0 +1,163 @@ +import 'package:drift/drift.dart'; +import 'package:test/test.dart'; + +import '../generated/todos.dart'; +import '../test_utils/test_utils.dart'; + +void main() { + driftRuntimeOptions.dontWarnAboutMultipleDatabases = true; + late TodoDb db; + + setUp(() { + db = TodoDb(testInMemoryDatabase()); + }); + + tearDown(() => db.close()); + + test('manager - create', () async { + // Initial count should be 0 + expect(db.managers.categories.count(), completion(0)); + + // Creating a row should return the id + final create1 = db.managers.categories.create( + (o) => o(priority: Value(CategoryPriority.high), description: "High")); + expect(create1, completion(1)); + expect(db.managers.categories.count(), completion(1)); + + // Creating another row should increment the id + final create2 = db.managers.categories.create( + (o) => o(priority: Value(CategoryPriority.low), description: "Low")); + expect(create2, completion(2)); + expect(db.managers.categories.count(), completion(2)); + + // Using an existing id should throw an exception + final create3 = db.managers.categories.create((o) => o( + priority: Value(CategoryPriority.medium), + description: "Medium", + id: Value(RowId(1)))); + expect(create3, throwsException); + + // Using on conflict should not throw an exception + // Only using DoNothing test that onConflict is being passed to the create method + final create4 = db.managers.categories.create( + (o) => o( + priority: Value(CategoryPriority.medium), + description: "Medium", + id: Value(RowId(1))), + onConflict: DoNothing()); + // The is incorrect when using onConflict + expect(create4, completion(2)); + expect(db.managers.categories.count(), completion(2)); + + // Likewise, test that mode is passed to the create method + final create5 = db.managers.categories.create( + (o) => o( + priority: Value(CategoryPriority.medium), + description: "Medium", + id: Value(RowId(1))), + mode: InsertMode.insertOrIgnore); + + // The is incorrect when using mode + expect(create5, completion(2)); + expect(db.managers.categories.count(), completion(2)); + + // Test the other create methods + final create6 = db.managers.categories.createReturning((o) => + o(priority: Value(CategoryPriority.high), description: "Other High")); + expect(create6, completion(isA())); + expect(db.managers.categories.count(), completion(3)); + + // Will return null because the description is not unique + final create7 = db.managers.categories.createReturningOrNull( + (o) => o( + priority: Value(CategoryPriority.high), + description: "High", + ), + mode: InsertMode.insertOrIgnore); + expect(create7, completion(null)); + + // Test batch create + await db.managers.categories.bulkCreate((o) => [ + o(priority: Value(CategoryPriority.high), description: "Super High"), + o(priority: Value(CategoryPriority.low), description: "Super Low"), + o( + priority: Value(CategoryPriority.medium), + description: "Super Medium") + ]); + expect(db.managers.categories.count(), completion(6)); + }); + + test('manager - update', () async { + // Create a row + final obj1 = await db.managers.categories.createReturning( + (o) => o(priority: Value(CategoryPriority.low), description: "Low")); + final obj2 = await db.managers.categories.createReturning((o) => + o(priority: Value(CategoryPriority.low), description: "Other Low")); + + // Replace the row + final update1 = + db.managers.categories.replace(obj1.copyWith(description: "Hello")); + expect(update1, completion(true)); + expect( + db.managers.categories + .filter(((f) => f.id(obj1.id))) + .getSingle() + .then((value) => value.description), + completion("Hello")); + + // Bulk Replace + await db.managers.categories.bulkReplace([ + obj1.copyWith(description: "Hello"), + obj2.copyWith(description: "World") + ]); + expect( + db.managers.categories + .filter(((f) => f.id(obj1.id))) + .getSingle() + .then((value) => value.description), + completion("Hello")); + expect( + db.managers.categories + .filter(((f) => f.id(obj2.id))) + .getSingle() + .then((value) => value.description), + completion("World")); + + // Update All Rows + final update2 = db.managers.categories + .update((o) => o(priority: Value(CategoryPriority.high))); + expect(update2, completion(2)); + + // Update a single row + final update3 = db.managers.categories + .filter(((f) => f.id(obj2.id))) + .update((o) => o(description: Value("World"))); + expect(update3, completion(1)); + expect( + db.managers.categories + .filter(((f) => f.id(obj2.id))) + .getSingle() + .then((value) => value.description), + completion("World")); + }); + + test('manager - delete', () async { + // Create a row + final obj1 = await db.managers.categories.createReturning( + (o) => o(priority: Value(CategoryPriority.low), description: "Low")); + // ignore: unused_local_variable + final obj2 = await db.managers.categories.createReturning((o) => + o(priority: Value(CategoryPriority.low), description: "Other Low")); + + // Delete a single row + final delete1 = + db.managers.categories.filter(((f) => f.id(obj1.id))).delete(); + expect(delete1, completion(1)); + expect(db.managers.categories.count(), completion(1)); + + // Delete all rows + final delete2 = db.managers.categories.delete(); + expect(delete2, completion(1)); + expect(db.managers.categories.count(), completion(0)); + }); +} diff --git a/drift/test/manager/processed_manager_test.dart b/drift/test/manager/processed_manager_test.dart new file mode 100644 index 00000000..8cf01e2b --- /dev/null +++ b/drift/test/manager/processed_manager_test.dart @@ -0,0 +1,87 @@ +import 'package:drift/drift.dart'; +import 'package:test/test.dart'; + +import '../generated/todos.dart'; +import '../test_utils/test_utils.dart'; + +void main() { + late TodoDb db; + + setUp(() { + db = TodoDb(testInMemoryDatabase()); + }); + + tearDown(() => db.close()); + + test('processed manager', () async { + await db.managers.tableWithEveryColumnType.create((o) => o( + aText: Value("Get that math homework done"), + anIntEnum: Value(TodoStatus.open), + aReal: Value(5.0), + aDateTime: Value(DateTime.now().add(Duration(days: 1))))); + await db.managers.tableWithEveryColumnType.create((o) => o( + aText: Value("Get that math homework done"), + anIntEnum: Value(TodoStatus.open), + aDateTime: Value(DateTime.now().add(Duration(days: 2))))); + await db.managers.tableWithEveryColumnType.create((o) => o( + aText: Value("Get that math homework done"), + anIntEnum: Value(TodoStatus.open), + aReal: Value(3.0), + aDateTime: Value(DateTime.now().add(Duration(days: 3))))); + // Test count + expect(db.managers.tableWithEveryColumnType.count(), completion(3)); + // Test get + expect( + db.managers.tableWithEveryColumnType + .get() + .then((value) => value.length), + completion(3)); + // Test getSingle with limit + expect( + db.managers.tableWithEveryColumnType + .limit(1, offset: 1) + .getSingle() + .then((value) => value.id), + completion(2)); + // Test filtered delete + expect( + db.managers.tableWithEveryColumnType + .filter((f) => f.id(RowId(2))) + .delete(), + completion(1)); + + // Test filtered update + expect( + db.managers.tableWithEveryColumnType + .filter((f) => f.id(RowId(1))) + .update((o) => o(aReal: Value(10.0))), + completion(1)); + expect( + db.managers.tableWithEveryColumnType + .filter((f) => f.id(RowId(1))) + .getSingle() + .then((value) => value.aReal), + completion(10.0)); + // Test filtered exists + expect( + db.managers.tableWithEveryColumnType + .filter((f) => f.id(RowId(1))) + .exists(), + completion(true)); + + // Test filtered count + expect( + db.managers.tableWithEveryColumnType + .filter((f) => f.id(RowId(1))) + .count(), + completion(1)); + // Test delte all + expect(db.managers.tableWithEveryColumnType.delete(), completion(2)); + // Test exists - false + expect( + db.managers.tableWithEveryColumnType + .filter((f) => f.id(RowId(1))) + .exists(), + completion(false)); + }); +} diff --git a/drift_dev/lib/src/analysis/options.dart b/drift_dev/lib/src/analysis/options.dart index 88b17514..9c537aee 100644 --- a/drift_dev/lib/src/analysis/options.dart +++ b/drift_dev/lib/src/analysis/options.dart @@ -61,6 +61,10 @@ class DriftOptions { @JsonKey(name: 'generate_connect_constructor', defaultValue: false) final bool generateConnectConstructor; + /// Generate managers to assist with common database operations. + @JsonKey(name: 'generate_manager', defaultValue: true) + final bool generateManager; + @JsonKey(name: 'sqlite_modules', defaultValue: []) @Deprecated('Use effectiveModules instead') final List modules; @@ -128,6 +132,7 @@ class DriftOptions { this.useColumnNameAsJsonKeyWhenDefinedInMoorFile = true, this.useSqlColumnNameAsJsonKey = false, this.generateConnectConstructor = false, + this.generateManager = true, this.dataClassToCompanions = true, this.generateMutableClasses = false, this.rawResultSetData = false, @@ -156,6 +161,7 @@ class DriftOptions { required this.useColumnNameAsJsonKeyWhenDefinedInMoorFile, required this.useSqlColumnNameAsJsonKey, required this.generateConnectConstructor, + required this.generateManager, required this.dataClassToCompanions, required this.generateMutableClasses, required this.rawResultSetData, diff --git a/drift_dev/lib/src/analysis/resolver/dart/column.dart b/drift_dev/lib/src/analysis/resolver/dart/column.dart index 32a9adbc..0a9f7321 100644 --- a/drift_dev/lib/src/analysis/resolver/dart/column.dart +++ b/drift_dev/lib/src/analysis/resolver/dart/column.dart @@ -457,7 +457,6 @@ class ColumnParser { customConstraints: foundCustomConstraint, sourceForCustomConstraints: customConstraintSource, )); - return PendingColumnInformation( DriftColumn( sqlType: columnType, @@ -472,6 +471,7 @@ class ColumnParser { documentationComment: docString, constraints: foundConstraints, customConstraints: foundCustomConstraint, + referenceName: _readReferenceName(element), ), referencesColumnInSameTable: referencesColumnInSameTable, ); @@ -507,6 +507,22 @@ class ColumnParser { return object.computeConstantValue()!.getField('key')!.toStringValue(); } + String? _readReferenceName(Element getter) { + final annotations = getter.metadata; + final object = annotations.firstWhereOrNull((e) { + final value = e.computeConstantValue(); + final valueType = value?.type; + + return valueType is InterfaceType && + isFromDrift(valueType) && + valueType.element.name == 'ReferenceName'; + }); + + if (object == null) return null; + + return object.computeConstantValue()!.getField('name')!.toStringValue(); + } + Future> _driftConstraintsFromCustomConstraints({ required bool isNullable, String? customConstraints, diff --git a/drift_dev/lib/src/analysis/results/column.dart b/drift_dev/lib/src/analysis/results/column.dart index bbd150aa..14f23d35 100644 --- a/drift_dev/lib/src/analysis/results/column.dart +++ b/drift_dev/lib/src/analysis/results/column.dart @@ -69,6 +69,10 @@ class DriftColumn implements HasType { /// set. final AnnotatedDartCode? clientDefaultCode; + /// If this column references another column, this column will contain the name + /// that the filters and orderings should use to denote the reverse direction + final String? referenceName; + @override final AppliedTypeConverter? typeConverter; @@ -92,6 +96,7 @@ class DriftColumn implements HasType { this.documentationComment, this.constraints = const [], this.customConstraints, + this.referenceName, bool foreignConverter = false, }) { if (typeConverter != null && !foreignConverter) { diff --git a/drift_dev/lib/src/analysis/serializer.dart b/drift_dev/lib/src/analysis/serializer.dart index 2b30dc32..6fa94a51 100644 --- a/drift_dev/lib/src/analysis/serializer.dart +++ b/drift_dev/lib/src/analysis/serializer.dart @@ -226,6 +226,7 @@ class ElementSerializer { 'clientDefaultCode': column.clientDefaultCode?.toJson(), 'defaultArgument': column.defaultArgument?.toJson(), 'overriddenJsonName': column.overriddenJsonName, + 'referenceName': column.referenceName, 'documentationComment': column.documentationComment, 'constraints': [ for (final constraint in column.constraints) @@ -773,6 +774,7 @@ class ElementDeserializer { ? AnnotatedDartCode.fromJson(json['defaultArgument'] as Map) : null, overriddenJsonName: json['overriddenJsonName'] as String?, + referenceName: json['referenceName'] as String?, documentationComment: json['documentationComment'] as String?, constraints: [ for (final rawConstraint in json['constraints'] as List) diff --git a/drift_dev/lib/src/cli/commands/schema/generate_utils.dart b/drift_dev/lib/src/cli/commands/schema/generate_utils.dart index ccbdf120..f9786d3e 100644 --- a/drift_dev/lib/src/cli/commands/schema/generate_utils.dart +++ b/drift_dev/lib/src/cli/commands/schema/generate_utils.dart @@ -93,6 +93,7 @@ class GenerateUtilsCommand extends Command { final options = DriftOptions.fromJson({ ...cli.project.moorOptions.toJson(), ...schema.options, + 'generate_manager': false, }); final writer = Writer( diff --git a/drift_dev/lib/src/generated/analysis/options.g.dart b/drift_dev/lib/src/generated/analysis/options.g.dart index 0608fdfb..25ee3f54 100644 --- a/drift_dev/lib/src/generated/analysis/options.g.dart +++ b/drift_dev/lib/src/generated/analysis/options.g.dart @@ -20,6 +20,7 @@ DriftOptions _$DriftOptionsFromJson(Map json) => $checkedCreate( 'use_column_name_as_json_key_when_defined_in_moor_file', 'use_sql_column_name_as_json_key', 'generate_connect_constructor', + 'generate_manager', 'sqlite_modules', 'sqlite', 'sql', @@ -57,6 +58,8 @@ DriftOptions _$DriftOptionsFromJson(Map json) => $checkedCreate( 'use_sql_column_name_as_json_key', (v) => v as bool? ?? false), generateConnectConstructor: $checkedConvert( 'generate_connect_constructor', (v) => v as bool? ?? false), + generateManager: + $checkedConvert('generate_manager', (v) => v as bool? ?? true), dataClassToCompanions: $checkedConvert( 'data_class_to_companions', (v) => v as bool? ?? true), generateMutableClasses: @@ -116,6 +119,7 @@ DriftOptions _$DriftOptionsFromJson(Map json) => $checkedCreate( 'use_column_name_as_json_key_when_defined_in_moor_file', 'useSqlColumnNameAsJsonKey': 'use_sql_column_name_as_json_key', 'generateConnectConstructor': 'generate_connect_constructor', + 'generateManager': 'generate_manager', 'dataClassToCompanions': 'data_class_to_companions', 'generateMutableClasses': 'mutable_classes', 'rawResultSetData': 'raw_result_set_data', @@ -149,6 +153,7 @@ Map _$DriftOptionsToJson(DriftOptions instance) => instance.useColumnNameAsJsonKeyWhenDefinedInMoorFile, 'use_sql_column_name_as_json_key': instance.useSqlColumnNameAsJsonKey, 'generate_connect_constructor': instance.generateConnectConstructor, + 'generate_manager': instance.generateManager, 'sqlite_modules': instance.modules.map((e) => _$SqlModuleEnumMap[e]!).toList(), 'sqlite': instance.sqliteAnalysisOptions?.toJson(), diff --git a/drift_dev/lib/src/writer/database_writer.dart b/drift_dev/lib/src/writer/database_writer.dart index ef550a47..1d1b00e4 100644 --- a/drift_dev/lib/src/writer/database_writer.dart +++ b/drift_dev/lib/src/writer/database_writer.dart @@ -1,6 +1,7 @@ import 'package:drift/drift.dart' as drift; // ignore: implementation_imports import 'package:drift/src/runtime/executor/stream_queries.dart'; +import 'package:drift_dev/src/writer/manager_writer.dart'; import 'package:drift_dev/src/writer/utils/memoized_getter.dart'; import 'package:recase/recase.dart'; @@ -147,6 +148,17 @@ class DatabaseWriter { } } + // Write the main database manager & all the managers for tables + if (scope.options.generateManager) { + final managerWriter = ManagerWriter(scope.child(), dbScope, dbClassName); + for (var table in elements.whereType()) { + managerWriter.addTable(table); + } + managerWriter.write(); + // Add getter for the manager to the database class + firstLeaf.writeln(managerWriter.managerGetter); + } + // Write implementation for query methods for (final query in input.availableRegularQueries) { QueryWriter(dbScope.child()).write(query); diff --git a/drift_dev/lib/src/writer/manager_writer.dart b/drift_dev/lib/src/writer/manager_writer.dart new file mode 100644 index 00000000..2aa2f319 --- /dev/null +++ b/drift_dev/lib/src/writer/manager_writer.dart @@ -0,0 +1,638 @@ +// 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/modules.dart'; +import 'package:drift_dev/src/writer/tables/update_companion_writer.dart'; +import 'package:drift_dev/src/writer/writer.dart'; + +abstract class _FilterWriter { + /// The getter for the column on this table + /// + /// E.G `id` in `table.id` + final String fieldGetter; + + /// The getter for the columns filter + /// + /// E.G `id` in `f.id.equals(5)` + final String filterName; + + /// An abstract class for all filters + _FilterWriter(this.filterName, {required this.fieldGetter}); + + /// Write the filter to a provider [TextEmitter] + void writeFilter(TextEmitter leaf); +} + +class _RegularFilterWriter extends _FilterWriter { + /// The type that this column is + /// + /// E.G `int`, `String`, etc + final String type; + + /// A class used for writing `ColumnFilters` with regular types + _RegularFilterWriter(super.filterName, + {required super.fieldGetter, required this.type}); + + @override + void writeFilter(TextEmitter leaf) { + leaf + ..writeDriftRef("ColumnFilters") + ..write("<$type> get $filterName =>") + ..writeDriftRef("ColumnFilters") + ..write("(\$table.$fieldGetter);"); + } +} + +class _FilterWithConverterWriter extends _FilterWriter { + /// The type that this column is + /// + /// E.G `int`, `String`, etc + final String type; + + /// The type of the user provided converter + /// + /// E.G `Color` etc + final String converterType; + + /// A class used for writing `ColumnFilters` with custom converters + _FilterWithConverterWriter(super.filterName, + {required super.fieldGetter, + required this.type, + required this.converterType}); + + @override + void writeFilter(TextEmitter leaf) { + final nonNullableConverterType = converterType.replaceFirst("?", ""); + leaf + ..writeDriftRef("ColumnWithTypeConverterFilters") + ..write( + "<$converterType,$nonNullableConverterType,$type> get $filterName =>") + ..writeDriftRef("ColumnWithTypeConverterFilters") + ..writeln("(\$table.$fieldGetter);"); + } +} + +class _ReferencedFilterWriter extends _FilterWriter { + /// The full function used to get the referenced table + /// + /// E.G `\$db.resultSet<$CategoryTable>('categories')` + /// or `\$db.categories` + final String referencedTableField; + + /// The getter for the column on the referenced table + /// + /// E.G `id` in `table.id` + final String referencedColumnGetter; + + /// The name of the referenced table's filter composer + /// + /// E.G `CategoryFilterComposer` + final String referencedFilterComposer; + + /// A class used for building filters for referenced tables + _ReferencedFilterWriter( + super.filterName, { + required this.referencedTableField, + required this.referencedColumnGetter, + required this.referencedFilterComposer, + required super.fieldGetter, + }); + + @override + void writeFilter(TextEmitter leaf) { + leaf + ..writeDriftRef("ComposableFilter") + ..write(" $filterName(") + ..writeDriftRef("ComposableFilter") + ..writeln(" Function( $referencedFilterComposer f) f) {") + ..write("return \$composeWithJoins(") + ..writeln("\$db: \$db,") + ..writeln("\$table: \$table,") + ..writeln("referencedTable: $referencedTableField,") + ..writeln("getCurrentColumn: (f) => f.$fieldGetter,") + ..writeln("getReferencedColumn: (f) => f.$referencedColumnGetter,") + ..writeln( + "getReferencedComposer: (db, table) => $referencedFilterComposer(db, table),") + ..writeln("builder: f);") + ..writeln("}"); + } +} + +abstract class _OrderingWriter { + /// The getter for the column on this table + /// + /// E.G `id` in `table.id` + final String fieldGetter; + + /// The getter for the columns ordering + /// + /// E.G `id` in `f.id.equals(5)` + final String orderingName; + + /// Abstract class for all orderings + _OrderingWriter(this.orderingName, {required this.fieldGetter}); + + /// Write the ordering to a provider [TextEmitter] + void writeOrdering(TextEmitter leaf); +} + +class _RegularOrderingWriter extends _OrderingWriter { + /// The type that this column is + /// + /// E.G `int`, `String`, etc + final String type; + + /// A class used for writing `ColumnOrderings` with regular types + _RegularOrderingWriter(super.orderingName, + {required super.fieldGetter, required this.type}); + + @override + void writeOrdering(TextEmitter leaf) { + leaf + ..writeDriftRef("ColumnOrderings") + ..write("<$type> get $orderingName =>") + ..writeDriftRef("ColumnOrderings") + ..write("(\$table.$fieldGetter);"); + } +} + +class _ReferencedOrderingWriter extends _OrderingWriter { + /// The full function used to get the referenced table + /// + /// E.G `\$db.resultSet<$CategoryTable>('categories')` + /// or `\$db.categories` + final String referencedTableField; + + /// The getter for the column on the referenced table + /// + /// E.G `id` in `table.id` + final String referencedColumnGetter; + + /// The name of the referenced table's ordering composer + /// + /// E.G `CategoryOrderingComposer` + final String referencedOrderingComposer; + + /// A class used for building orderings for referenced tables + _ReferencedOrderingWriter(super.orderingName, + {required this.referencedTableField, + required this.referencedColumnGetter, + required this.referencedOrderingComposer, + required super.fieldGetter}); + + @override + void writeOrdering(TextEmitter leaf) { + leaf + ..writeDriftRef("ComposableOrdering") + ..write(" $orderingName(") + ..writeDriftRef("ComposableOrdering") + ..writeln(" Function( $referencedOrderingComposer o) o) {") + ..write("return \$composeWithJoins(") + ..writeln("\$db: \$db,") + ..writeln("\$table: \$table,") + ..writeln("referencedTable: $referencedTableField,") + ..writeln("getCurrentColumn: (f) => f.$fieldGetter,") + ..writeln("getReferencedColumn: (f) => f.$referencedColumnGetter,") + ..writeln( + "getReferencedComposer: (db, table) => $referencedOrderingComposer(db, table),") + ..writeln("builder: o);") + ..writeln("}"); + } +} + +class _ColumnManagerWriter { + /// The getter for the field + /// + /// E.G `id` in `table.id` + final String fieldGetter; + + /// List of filters for this column + final List<_FilterWriter> filters; + + /// List of orderings for this column + final List<_OrderingWriter> orderings; + + /// A class used for writing filters and orderings for columns + _ColumnManagerWriter(this.fieldGetter) + : filters = [], + orderings = []; +} + +class _TableManagerWriter { + /// The current table + final DriftTable table; + + /// Generation Scope + final Scope scope; + + /// Generation Scope for the entire database + final Scope dbScope; + + /// The name of the filter composer class + /// + /// E.G `UserFilterComposer` + String get filterComposer => '\$${table.entityInfoName}FilterComposer'; + + /// The name of the filter composer class + /// + /// E.G `UserOrderingComposer` + String get orderingComposer => '\$${table.entityInfoName}OrderingComposer'; + + /// The name of the processed table manager class + /// + /// E.G `UserProcessedTableManager` + String get processedTableManager => + '\$${table.entityInfoName}ProcessedTableManager'; + + /// The name of the root table manager class + /// + /// E.G `UserTableManager` + String get rootTableManager => '\$${table.entityInfoName}TableManager'; + + /// Name of the typedef for the insertCompanionBuilder + /// + /// E.G. `insertCompanionBuilder` + String get insertCompanionBuilderTypeDefName => + '\$${table.entityInfoName}InsertCompanionBuilder'; + + /// Name of the arguments for the updateCompanionBuilder + /// + /// E.G. `updateCompanionBuilderTypeDef` + String get updateCompanionBuilderTypeDefName => + '\$${table.entityInfoName}UpdateCompanionBuilder'; + + /// Table class name, this may be different from the entity name + /// if modular generation is enabled + /// E.G. `i5.$CategoriesTable` + String get tableClassName => dbScope.dartCode(dbScope.entityInfoType(table)); + + /// Row class name, this may be different from the entity name + /// if modular generation is enabled + /// E.G. `i5.$Category` + String get rowClassName => dbScope.dartCode(dbScope.writer.rowType(table)); + + /// Whether this table has a custom row class + /// We use this row to determine if we should generate a manager for this table + bool get hasCustomRowClass => table.existingRowClass != null; + + /// The name of the database class + /// + /// E.G. `i5.$GeneratedDatabase` + final String databaseGenericName; + + /// Writers for the columns of this table + final List<_ColumnManagerWriter> columns; + + /// Filters for back references + final List<_ReferencedFilterWriter> backRefFilters; + + _TableManagerWriter( + this.table, this.scope, this.dbScope, this.databaseGenericName) + : backRefFilters = [], + columns = []; + + void _writeFilterComposer(TextEmitter leaf) { + leaf + ..write('class $filterComposer extends ') + ..writeDriftRef('FilterComposer') + ..writeln('<$databaseGenericName,$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) { + // Write the OrderingComposer + leaf + ..write('class $orderingComposer extends ') + ..writeDriftRef('OrderingComposer') + ..writeln('<$databaseGenericName,$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) { + leaf + ..write('class $processedTableManager extends ') + ..writeDriftRef('ProcessedTableManager') + ..writeln( + '<$databaseGenericName,$tableClassName,$rowClassName,$filterComposer,$orderingComposer,$processedTableManager,$insertCompanionBuilderTypeDefName,$updateCompanionBuilderTypeDefName> {') + ..writeln('const $processedTableManager(super.\$state);') + ..writeln('}'); + } + + /// Build the builder for a companion class + /// 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 companionBuilderTypeDef = + StringBuffer('typedef $typedefName = $companionClassName Function({'); + + final companionBuilderArguments = StringBuffer('({'); + + final StringBuffer companionBuilderBody; + if (isUpdate) { + companionBuilderBody = StringBuffer('=> $companionClassName('); + } else { + companionBuilderBody = StringBuffer('=> $companionClassName.insert('); + } + + for (final column in UpdateCompanionWriter(table, scope).columns) { + final value = scope.drift('Value'); + final param = column.nameInDart; + final typeName = scope.dartCode(scope.dartType(column)); + + companionBuilderBody.write('$param: $param,'); + + 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(updateCompanionBuilderTypeDef); + + leaf + ..write('class $rootTableManager extends ') + ..writeDriftRef('RootTableManager') + ..writeln( + '<$databaseGenericName,$tableClassName,$rowClassName,$filterComposer,$orderingComposer,$processedTableManager,$insertCompanionBuilderTypeDefName,$updateCompanionBuilderTypeDefName> {') + ..writeln( + '$rootTableManager($databaseGenericName db, $tableClassName table)') + ..writeln(": super(") + ..writeDriftRef("TableManagerState") + ..write( + """(db: db, table: table, filteringComposer:$filterComposer(db, table),orderingComposer:$orderingComposer(db, table), + getChildManagerBuilder :(p0) => $processedTableManager(p0),getUpdateCompanionBuilder: $updateCompanionBuilder, + getInsertCompanionBuilder:$insertCompanionBuilder));""") + ..writeln('}'); + } + + /// Write the manager for this table, with all the filters and orderings + void writeManager(TextEmitter leaf) { + _writeFilterComposer(leaf); + _writeOrderingComposer(leaf); + _writeProcessedTableManager(leaf); + _writeRootTable(leaf); + } + + String _referenceTable(DriftTable table) { + if (scope.generationOptions.isModular) { + final extension = scope.refUri( + ModularAccessorWriter.modularSupport, 'ReadDatabaseContainer'); + final type = scope.dartCode(scope.entityInfoType(table)); + return "$extension(\$db).resultSet<$type>('${table.schemaName}')"; + } else { + return '\$db.${table.dbGetterName}'; + } + } + + /// Add filters and orderings for the columns of this table + void addFiltersAndOrderings(List tables) { + // Utility function to get the referenced table and column + (DriftTable, DriftColumn)? getReferencedTableAndColumn( + DriftColumn column, List tables) { + final referencedCol = column.constraints + .whereType() + .firstOrNull + ?.otherColumn; + if (referencedCol != null && referencedCol.owner is DriftTable) { + final referencedTable = tables.firstWhere( + (t) => t.entityInfoName == referencedCol.owner.entityInfoName); + return (referencedTable, referencedCol); + } + return null; + } + + // Utility function to get the duplicates in a list + List duplicates(List items) { + final seen = {}; + final duplicates = []; + for (var item in items) { + if (!seen.add(item)) { + duplicates.add(item); + } + } + return duplicates; + } + + /// First add the filters and orderings for the columns + /// of the current table + for (var column in table.columns) { + final c = _ColumnManagerWriter(column.nameInDart); + + // The type that this column is (int, string, etc) + final innerColumnType = + scope.dartCode(scope.innerColumnType(column.sqlType)); + + // Get the referenced table and column if this column is a foreign key + final referenced = getReferencedTableAndColumn(column, tables); + final isForeignKey = referenced != null; + + // If the column has a type converter, add a filter with a converter + if (column.typeConverter != null) { + final converterType = scope.dartCode(scope.writer.dartType(column)); + c.filters.add(_RegularFilterWriter("${c.fieldGetter}Value", + type: innerColumnType, fieldGetter: c.fieldGetter)); + c.filters.add(_FilterWithConverterWriter(c.fieldGetter, + converterType: converterType, + fieldGetter: c.fieldGetter, + type: innerColumnType)); + } else { + c.filters.add(_RegularFilterWriter( + c.fieldGetter + (isForeignKey ? "Id" : ""), + type: innerColumnType, + fieldGetter: c.fieldGetter)); + } + + // Add the ordering for the column + c.orderings.add(_RegularOrderingWriter( + c.fieldGetter + (isForeignKey ? "Id" : ""), + type: innerColumnType, + fieldGetter: c.fieldGetter)); + + /// If this column is a foreign key to another table, add a filter and ordering + /// for the referenced table + if (referenced != null) { + final (referencedTable, referencedCol) = referenced; + final referencedTableNames = _TableManagerWriter( + referencedTable, scope, dbScope, databaseGenericName); + final referencedColumnNames = + _ColumnManagerWriter(referencedCol.nameInDart); + final referencedTableField = _referenceTable(referencedTable); + + c.filters.add(_ReferencedFilterWriter(c.fieldGetter, + fieldGetter: c.fieldGetter, + referencedColumnGetter: referencedColumnNames.fieldGetter, + referencedFilterComposer: referencedTableNames.filterComposer, + referencedTableField: referencedTableField)); + c.orderings.add(_ReferencedOrderingWriter(c.fieldGetter, + fieldGetter: c.fieldGetter, + referencedColumnGetter: referencedColumnNames.fieldGetter, + referencedOrderingComposer: referencedTableNames.orderingComposer, + referencedTableField: referencedTableField)); + } + columns.add(c); + } + + // Iterate over all other tables to find back references + for (var ot in tables) { + for (var oc in ot.columns) { + // Check if the column is a foreign key to the current table + final reference = getReferencedTableAndColumn(oc, tables); + if (reference != null && + reference.$1.entityInfoName == table.entityInfoName) { + final referencedTableNames = + _TableManagerWriter(ot, scope, dbScope, databaseGenericName); + final referencedColumnNames = _ColumnManagerWriter(oc.nameInDart); + final referencedTableField = _referenceTable(ot); + + final filterName = oc.referenceName ?? + "${referencedTableNames.table.dbGetterName}Refs"; + + backRefFilters.add(_ReferencedFilterWriter(filterName, + fieldGetter: reference.$2.nameInDart, + referencedColumnGetter: referencedColumnNames.fieldGetter, + referencedFilterComposer: referencedTableNames.filterComposer, + referencedTableField: referencedTableField)); + } + } + } + + // Remove the filters and orderings that have duplicates + final duplicatedFilterNames = duplicates(columns + .map((e) => e.filters.map((e) => e.filterName)) + .expand((e) => e) + .toList() + + backRefFilters.map((e) => e.filterName).toList()); + final duplicatedOrderingNames = duplicates(columns + .map((e) => e.orderings.map((e) => e.orderingName)) + .expand((e) => e) + .toList()); + if (duplicatedFilterNames.isNotEmpty || + duplicatedOrderingNames.isNotEmpty) { + print( + "The code generator encountered an issue while attempting to create filters/orderings for $tableClassName manager. The following filters/orderings were not created ${(duplicatedFilterNames + duplicatedOrderingNames).toSet()}. Use the @ReferenceName() annotation to resolve this issue."); + // Remove the duplicates + for (var c in columns) { + c.filters + .removeWhere((e) => duplicatedFilterNames.contains(e.filterName)); + c.orderings.removeWhere( + (e) => duplicatedOrderingNames.contains(e.orderingName)); + } + backRefFilters + .removeWhere((e) => duplicatedFilterNames.contains(e.filterName)); + } + } +} + +class ManagerWriter { + final Scope _scope; + final Scope _dbScope; + final String _dbClassName; + late final List _addedTables; + + /// Class used to write a manager for a database + ManagerWriter(this._scope, this._dbScope, this._dbClassName) { + _addedTables = []; + } + + /// Add a table to the manager + void addTable(DriftTable 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 { + if (_scope.generationOptions.isModular) { + return _scope.drift("GeneratedDatabase"); + } else { + return _dbClassName; + } + } + + /// The name of the manager class + String get databaseManagerName => '${_dbClassName}Manager'; + + /// The getter for the manager that will be added to the database + String get managerGetter { + return '$databaseManagerName get managers => $databaseManagerName(this);'; + } + + /// Write the manager to a provider [TextEmitter] + void write() { + final leaf = _scope.leaf(); + + // create the manager class for each table + final tableWriters = <_TableManagerWriter>[]; + for (var table in _addedTables) { + tableWriters.add( + _TableManagerWriter(table, _scope, _dbScope, databaseGenericName) + ..addFiltersAndOrderings(_addedTables)); + } + + // Remove ones that have custom row classes + tableWriters.removeWhere((t) => t.hasCustomRowClass); + + // Write each tables manager to the leaf and append the getter to the main manager + final tableManagerGetters = StringBuffer(); + for (var table in tableWriters) { + table.writeManager(leaf); + tableManagerGetters.writeln( + "${table.rootTableManager} get ${table.table.dbGetterName} => ${table.rootTableManager}(_db, _db.${table.table.dbGetterName});"); + } + + // Write the main manager class + leaf + ..writeln('class $databaseManagerName{') + ..writeln('final $_dbClassName _db;') + ..writeln('$databaseManagerName(this._db);') + ..writeln(tableManagerGetters) + ..writeln('}'); + } +} diff --git a/drift_dev/lib/src/writer/tables/table_writer.dart b/drift_dev/lib/src/writer/tables/table_writer.dart index a2fa749a..a6f8952c 100644 --- a/drift_dev/lib/src/writer/tables/table_writer.dart +++ b/drift_dev/lib/src/writer/tables/table_writer.dart @@ -46,7 +46,6 @@ abstract class TableOrViewWriter { final typeName = emitter.dartCode(emitter.writer.converterType(converter)); final code = emitter.dartCode(converter.expression); - buffer.write('static $typeName ${converter.fieldName} = $code;'); // Generate wrappers for non-nullable type converters that are applied to diff --git a/drift_dev/test/writer/data_class_writer_test.dart b/drift_dev/test/writer/data_class_writer_test.dart index ef89838b..93f71703 100644 --- a/drift_dev/test/writer/data_class_writer_test.dart +++ b/drift_dev/test/writer/data_class_writer_test.dart @@ -5,7 +5,6 @@ import 'package:analyzer/file_system/memory_file_system.dart'; import 'package:build/build.dart'; import 'package:build_test/build_test.dart'; import 'package:collection/collection.dart'; -import 'package:pub_semver/pub_semver.dart'; import 'package:test/test.dart'; import '../utils.dart'; @@ -409,10 +408,7 @@ class _GeneratesConstDataClasses extends Matcher { final parsed = parseFile( path: '/foo.dart', - featureSet: FeatureSet.fromEnableFlags2( - sdkLanguageVersion: Version(2, 12, 0), - flags: const [], - ), + featureSet: FeatureSet.latestLanguageVersion(), resourceProvider: resourceProvider, throwIfDiagnostics: true, ).unit; diff --git a/examples/migrations_example/test/generated/schema_v1.dart b/examples/migrations_example/test/generated/schema_v1.dart index 8a904d9e..9e433bc7 100644 --- a/examples/migrations_example/test/generated/schema_v1.dart +++ b/examples/migrations_example/test/generated/schema_v1.dart @@ -18,9 +18,10 @@ class Users extends Table with TableInfo { @override List get $columns => [id]; @override - String get aliasedName => _alias ?? 'users'; + String get aliasedName => _alias ?? actualTableName; @override - String get actualTableName => 'users'; + String get actualTableName => $name; + static const String $name = 'users'; @override Set get $primaryKey => {id}; @override diff --git a/examples/migrations_example/test/generated/schema_v10.dart b/examples/migrations_example/test/generated/schema_v10.dart index 2ad771ef..1dca648f 100644 --- a/examples/migrations_example/test/generated/schema_v10.dart +++ b/examples/migrations_example/test/generated/schema_v10.dart @@ -32,9 +32,10 @@ class Users extends Table with TableInfo { @override List get $columns => [id, name, birthday, nextUser]; @override - String get aliasedName => _alias ?? 'users'; + String get aliasedName => _alias ?? actualTableName; @override - String get actualTableName => 'users'; + String get actualTableName => $name; + static const String $name = 'users'; @override Set get $primaryKey => {id}; @override @@ -257,9 +258,10 @@ class Groups extends Table with TableInfo { @override List get $columns => [id, title, deleted, owner]; @override - String get aliasedName => _alias ?? 'groups'; + String get aliasedName => _alias ?? actualTableName; @override - String get actualTableName => 'groups'; + String get actualTableName => $name; + static const String $name = 'groups'; @override Set get $primaryKey => {id}; @override @@ -475,9 +477,10 @@ class Notes extends Table @override List get $columns => [title, content, searchTerms]; @override - String get aliasedName => _alias ?? 'notes'; + String get aliasedName => _alias ?? actualTableName; @override - String get actualTableName => 'notes'; + String get actualTableName => $name; + static const String $name = 'notes'; @override Set get $primaryKey => const {}; @override diff --git a/examples/migrations_example/test/generated/schema_v2.dart b/examples/migrations_example/test/generated/schema_v2.dart index 6999962e..95213483 100644 --- a/examples/migrations_example/test/generated/schema_v2.dart +++ b/examples/migrations_example/test/generated/schema_v2.dart @@ -21,9 +21,10 @@ class Users extends Table with TableInfo { @override List get $columns => [id, name]; @override - String get aliasedName => _alias ?? 'users'; + String get aliasedName => _alias ?? actualTableName; @override - String get actualTableName => 'users'; + String get actualTableName => $name; + static const String $name = 'users'; @override Set get $primaryKey => {id}; @override diff --git a/examples/migrations_example/test/generated/schema_v3.dart b/examples/migrations_example/test/generated/schema_v3.dart index 25b4e041..65fb64a9 100644 --- a/examples/migrations_example/test/generated/schema_v3.dart +++ b/examples/migrations_example/test/generated/schema_v3.dart @@ -21,9 +21,10 @@ class Users extends Table with TableInfo { @override List get $columns => [id, name]; @override - String get aliasedName => _alias ?? 'users'; + String get aliasedName => _alias ?? actualTableName; @override - String get actualTableName => 'users'; + String get actualTableName => $name; + static const String $name = 'users'; @override Set get $primaryKey => {id}; @override @@ -179,9 +180,10 @@ class Groups extends Table with TableInfo { @override List get $columns => [id, title, deleted, owner]; @override - String get aliasedName => _alias ?? 'groups'; + String get aliasedName => _alias ?? actualTableName; @override - String get actualTableName => 'groups'; + String get actualTableName => $name; + static const String $name = 'groups'; @override Set get $primaryKey => {id}; @override diff --git a/examples/migrations_example/test/generated/schema_v4.dart b/examples/migrations_example/test/generated/schema_v4.dart index a3a124da..f6366291 100644 --- a/examples/migrations_example/test/generated/schema_v4.dart +++ b/examples/migrations_example/test/generated/schema_v4.dart @@ -23,9 +23,10 @@ class Users extends Table with TableInfo { @override List get $columns => [id, name]; @override - String get aliasedName => _alias ?? 'users'; + String get aliasedName => _alias ?? actualTableName; @override - String get actualTableName => 'users'; + String get actualTableName => $name; + static const String $name = 'users'; @override Set get $primaryKey => {id}; @override @@ -181,9 +182,10 @@ class Groups extends Table with TableInfo { @override List get $columns => [id, title, deleted, owner]; @override - String get aliasedName => _alias ?? 'groups'; + String get aliasedName => _alias ?? actualTableName; @override - String get actualTableName => 'groups'; + String get actualTableName => $name; + static const String $name = 'groups'; @override Set get $primaryKey => {id}; @override diff --git a/examples/migrations_example/test/generated/schema_v5.dart b/examples/migrations_example/test/generated/schema_v5.dart index f1a2c19e..e8b57f49 100644 --- a/examples/migrations_example/test/generated/schema_v5.dart +++ b/examples/migrations_example/test/generated/schema_v5.dart @@ -29,9 +29,10 @@ class Users extends Table with TableInfo { @override List get $columns => [id, name, nextUser]; @override - String get aliasedName => _alias ?? 'users'; + String get aliasedName => _alias ?? actualTableName; @override - String get actualTableName => 'users'; + String get actualTableName => $name; + static const String $name = 'users'; @override Set get $primaryKey => {id}; @override @@ -218,9 +219,10 @@ class Groups extends Table with TableInfo { @override List get $columns => [id, title, deleted, owner]; @override - String get aliasedName => _alias ?? 'groups'; + String get aliasedName => _alias ?? actualTableName; @override - String get actualTableName => 'groups'; + String get actualTableName => $name; + static const String $name = 'groups'; @override Set get $primaryKey => {id}; @override diff --git a/examples/migrations_example/test/generated/schema_v6.dart b/examples/migrations_example/test/generated/schema_v6.dart index 5b1a8615..ea4bf018 100644 --- a/examples/migrations_example/test/generated/schema_v6.dart +++ b/examples/migrations_example/test/generated/schema_v6.dart @@ -32,9 +32,10 @@ class Users extends Table with TableInfo { @override List get $columns => [id, name, birthday, nextUser]; @override - String get aliasedName => _alias ?? 'users'; + String get aliasedName => _alias ?? actualTableName; @override - String get actualTableName => 'users'; + String get actualTableName => $name; + static const String $name = 'users'; @override Set get $primaryKey => {id}; @override @@ -250,9 +251,10 @@ class Groups extends Table with TableInfo { @override List get $columns => [id, title, deleted, owner]; @override - String get aliasedName => _alias ?? 'groups'; + String get aliasedName => _alias ?? actualTableName; @override - String get actualTableName => 'groups'; + String get actualTableName => $name; + static const String $name = 'groups'; @override Set get $primaryKey => {id}; @override diff --git a/examples/migrations_example/test/generated/schema_v7.dart b/examples/migrations_example/test/generated/schema_v7.dart index a78587c0..cbb66aa1 100644 --- a/examples/migrations_example/test/generated/schema_v7.dart +++ b/examples/migrations_example/test/generated/schema_v7.dart @@ -32,9 +32,10 @@ class Users extends Table with TableInfo { @override List get $columns => [id, name, birthday, nextUser]; @override - String get aliasedName => _alias ?? 'users'; + String get aliasedName => _alias ?? actualTableName; @override - String get actualTableName => 'users'; + String get actualTableName => $name; + static const String $name = 'users'; @override Set get $primaryKey => {id}; @override @@ -250,9 +251,10 @@ class Groups extends Table with TableInfo { @override List get $columns => [id, title, deleted, owner]; @override - String get aliasedName => _alias ?? 'groups'; + String get aliasedName => _alias ?? actualTableName; @override - String get actualTableName => 'groups'; + String get actualTableName => $name; + static const String $name = 'groups'; @override Set get $primaryKey => {id}; @override @@ -603,9 +605,10 @@ class Notes extends Table @override List get $columns => [title, content, searchTerms]; @override - String get aliasedName => _alias ?? 'notes'; + String get aliasedName => _alias ?? actualTableName; @override - String get actualTableName => 'notes'; + String get actualTableName => $name; + static const String $name = 'notes'; @override Set get $primaryKey => const {}; @override diff --git a/examples/migrations_example/test/generated/schema_v8.dart b/examples/migrations_example/test/generated/schema_v8.dart index 83e39b74..0a593021 100644 --- a/examples/migrations_example/test/generated/schema_v8.dart +++ b/examples/migrations_example/test/generated/schema_v8.dart @@ -32,9 +32,10 @@ class Users extends Table with TableInfo { @override List get $columns => [id, name, birthday, nextUser]; @override - String get aliasedName => _alias ?? 'users'; + String get aliasedName => _alias ?? actualTableName; @override - String get actualTableName => 'users'; + String get actualTableName => $name; + static const String $name = 'users'; @override Set get $primaryKey => {id}; @override @@ -254,9 +255,10 @@ class Groups extends Table with TableInfo { @override List get $columns => [id, title, deleted, owner]; @override - String get aliasedName => _alias ?? 'groups'; + String get aliasedName => _alias ?? actualTableName; @override - String get actualTableName => 'groups'; + String get actualTableName => $name; + static const String $name = 'groups'; @override Set get $primaryKey => {id}; @override @@ -607,9 +609,10 @@ class Notes extends Table @override List get $columns => [title, content, searchTerms]; @override - String get aliasedName => _alias ?? 'notes'; + String get aliasedName => _alias ?? actualTableName; @override - String get actualTableName => 'notes'; + String get actualTableName => $name; + static const String $name = 'notes'; @override Set get $primaryKey => const {}; @override diff --git a/examples/migrations_example/test/generated/schema_v9.dart b/examples/migrations_example/test/generated/schema_v9.dart index 91fbb247..e819e605 100644 --- a/examples/migrations_example/test/generated/schema_v9.dart +++ b/examples/migrations_example/test/generated/schema_v9.dart @@ -32,9 +32,10 @@ class Users extends Table with TableInfo { @override List get $columns => [id, name, birthday, nextUser]; @override - String get aliasedName => _alias ?? 'users'; + String get aliasedName => _alias ?? actualTableName; @override - String get actualTableName => 'users'; + String get actualTableName => $name; + static const String $name = 'users'; @override Set get $primaryKey => {id}; @override @@ -257,9 +258,10 @@ class Groups extends Table with TableInfo { @override List get $columns => [id, title, deleted, owner]; @override - String get aliasedName => _alias ?? 'groups'; + String get aliasedName => _alias ?? actualTableName; @override - String get actualTableName => 'groups'; + String get actualTableName => $name; + static const String $name = 'groups'; @override Set get $primaryKey => {id}; @override @@ -475,9 +477,10 @@ class Notes extends Table @override List get $columns => [title, content, searchTerms]; @override - String get aliasedName => _alias ?? 'notes'; + String get aliasedName => _alias ?? actualTableName; @override - String get actualTableName => 'notes'; + String get actualTableName => $name; + static const String $name = 'notes'; @override Set get $primaryKey => const {}; @override