Merge pull request #2939 from dickermoshe/develop

Add Manager
This commit is contained in:
Simon Binder 2024-04-23 14:54:19 +02:00 committed by GitHub
commit 231dd63948
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
47 changed files with 6054 additions and 93 deletions

View File

@ -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<void> 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<void> 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<void> 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<void> 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<void> 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<void> 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<DateTime> {
// 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<DateTime> {
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

View File

@ -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<CategoriesWithCountResult> categoriesWithCount() {
@ -28,6 +30,227 @@ abstract class $MyDatabase extends i0.GeneratedDatabase {
[categories, todoItems];
}
class $$CategoriesTableFilterComposer
extends i0.FilterComposer<i0.GeneratedDatabase, i1.$CategoriesTable> {
$$CategoriesTableFilterComposer(super.db, super.table);
i0.ColumnFilters<int> get id => i0.ColumnFilters($table.id);
i0.ColumnFilters<String> 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<i1.$TodoItemsTable>('todo_items'),
getCurrentColumn: (f) => f.id,
getReferencedColumn: (f) => f.category,
getReferencedComposer: (db, table) =>
$$TodoItemsTableFilterComposer(db, table),
builder: f);
}
}
class $$CategoriesTableOrderingComposer
extends i0.OrderingComposer<i0.GeneratedDatabase, i1.$CategoriesTable> {
$$CategoriesTableOrderingComposer(super.db, super.table);
i0.ColumnOrderings<int> get id => i0.ColumnOrderings($table.id);
i0.ColumnOrderings<String> 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<int> id,
required String name,
});
typedef $$CategoriesTableUpdateCompanionBuilder = i1.CategoriesCompanion
Function({
i0.Value<int> id,
i0.Value<String> 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<int> id = const i0.Value.absent(),
i0.Value<String> name = const i0.Value.absent(),
}) =>
i1.CategoriesCompanion(
id: id,
name: name,
),
getInsertCompanionBuilder: ({
i0.Value<int> id = const i0.Value.absent(),
required String name,
}) =>
i1.CategoriesCompanion.insert(
id: id,
name: name,
)));
}
class $$TodoItemsTableFilterComposer
extends i0.FilterComposer<i0.GeneratedDatabase, i1.$TodoItemsTable> {
$$TodoItemsTableFilterComposer(super.db, super.table);
i0.ColumnFilters<int> get id => i0.ColumnFilters($table.id);
i0.ColumnFilters<String> get title => i0.ColumnFilters($table.title);
i0.ColumnFilters<String> get content => i0.ColumnFilters($table.content);
i0.ColumnFilters<int> 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<i1.$CategoriesTable>('categories'),
getCurrentColumn: (f) => f.category,
getReferencedColumn: (f) => f.id,
getReferencedComposer: (db, table) =>
$$CategoriesTableFilterComposer(db, table),
builder: f);
}
i0.ColumnFilters<DateTime> get dueDate => i0.ColumnFilters($table.dueDate);
}
class $$TodoItemsTableOrderingComposer
extends i0.OrderingComposer<i0.GeneratedDatabase, i1.$TodoItemsTable> {
$$TodoItemsTableOrderingComposer(super.db, super.table);
i0.ColumnOrderings<int> get id => i0.ColumnOrderings($table.id);
i0.ColumnOrderings<String> get title => i0.ColumnOrderings($table.title);
i0.ColumnOrderings<String> get content => i0.ColumnOrderings($table.content);
i0.ColumnOrderings<int> 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<i1.$CategoriesTable>('categories'),
getCurrentColumn: (f) => f.category,
getReferencedColumn: (f) => f.id,
getReferencedComposer: (db, table) =>
$$CategoriesTableOrderingComposer(db, table),
builder: o);
}
i0.ColumnOrderings<DateTime> 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<int> id,
required String title,
required String content,
i0.Value<int?> category,
i0.Value<DateTime?> dueDate,
});
typedef $$TodoItemsTableUpdateCompanionBuilder = i1.TodoItemsCompanion
Function({
i0.Value<int> id,
i0.Value<String> title,
i0.Value<String> content,
i0.Value<int?> category,
i0.Value<DateTime?> 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<int> id = const i0.Value.absent(),
i0.Value<String> title = const i0.Value.absent(),
i0.Value<String> content = const i0.Value.absent(),
i0.Value<int?> category = const i0.Value.absent(),
i0.Value<DateTime?> dueDate = const i0.Value.absent(),
}) =>
i1.TodoItemsCompanion(
id: id,
title: title,
content: content,
category: category,
dueDate: dueDate,
),
getInsertCompanionBuilder: ({
i0.Value<int> id = const i0.Value.absent(),
required String title,
required String content,
i0.Value<int?> category = const i0.Value.absent(),
i0.Value<DateTime?> 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;

View File

@ -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<i0.GeneratedDatabase, i1.$BuyableItemsTable> {
$$BuyableItemsTableFilterComposer(super.db, super.table);
i0.ColumnFilters<int> get id => i0.ColumnFilters($table.id);
i0.ColumnFilters<String> get description =>
i0.ColumnFilters($table.description);
i0.ColumnFilters<int> get price => i0.ColumnFilters($table.price);
}
class $$BuyableItemsTableOrderingComposer
extends i0.OrderingComposer<i0.GeneratedDatabase, i1.$BuyableItemsTable> {
$$BuyableItemsTableOrderingComposer(super.db, super.table);
i0.ColumnOrderings<int> get id => i0.ColumnOrderings($table.id);
i0.ColumnOrderings<String> get description =>
i0.ColumnOrderings($table.description);
i0.ColumnOrderings<int> 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<int> id,
required String description,
required int price,
});
typedef $$BuyableItemsTableUpdateCompanionBuilder = i1.BuyableItemsCompanion
Function({
i0.Value<int> id,
i0.Value<String> description,
i0.Value<int> 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<int> id = const i0.Value.absent(),
i0.Value<String> description = const i0.Value.absent(),
i0.Value<int> price = const i0.Value.absent(),
}) =>
i1.BuyableItemsCompanion(
id: id,
description: description,
price: price,
),
getInsertCompanionBuilder: ({
i0.Value<int> 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<i0.GeneratedDatabase, i2.$ShoppingCartsTable> {
$$ShoppingCartsTableFilterComposer(super.db, super.table);
i0.ColumnFilters<int> get id => i0.ColumnFilters($table.id);
i0.ColumnFilters<String> get entriesValue => i0.ColumnFilters($table.entries);
i0.ColumnWithTypeConverterFilters<
i3.ShoppingCartEntries,
i3.ShoppingCartEntries,
String> get entries => i0.ColumnWithTypeConverterFilters($table.entries);
}
class $$ShoppingCartsTableOrderingComposer
extends i0.OrderingComposer<i0.GeneratedDatabase, i2.$ShoppingCartsTable> {
$$ShoppingCartsTableOrderingComposer(super.db, super.table);
i0.ColumnOrderings<int> get id => i0.ColumnOrderings($table.id);
i0.ColumnOrderings<String> 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<int> id,
required i3.ShoppingCartEntries entries,
});
typedef $$ShoppingCartsTableUpdateCompanionBuilder = i2.ShoppingCartsCompanion
Function({
i0.Value<int> id,
i0.Value<i3.ShoppingCartEntries> 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<int> id = const i0.Value.absent(),
i0.Value<i3.ShoppingCartEntries> entries =
const i0.Value.absent(),
}) =>
i2.ShoppingCartsCompanion(
id: id,
entries: entries,
),
getInsertCompanionBuilder: ({
i0.Value<int> 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

View File

@ -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<i0.GeneratedDatabase, i1.$BuyableItemsTable> {
$$BuyableItemsTableFilterComposer(super.db, super.table);
i0.ColumnFilters<int> get id => i0.ColumnFilters($table.id);
i0.ColumnFilters<String> get description =>
i0.ColumnFilters($table.description);
i0.ColumnFilters<int> 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<i2.$ShoppingCartEntriesTable>('shopping_cart_entries'),
getCurrentColumn: (f) => f.id,
getReferencedColumn: (f) => f.item,
getReferencedComposer: (db, table) =>
$$ShoppingCartEntriesTableFilterComposer(db, table),
builder: f);
}
}
class $$BuyableItemsTableOrderingComposer
extends i0.OrderingComposer<i0.GeneratedDatabase, i1.$BuyableItemsTable> {
$$BuyableItemsTableOrderingComposer(super.db, super.table);
i0.ColumnOrderings<int> get id => i0.ColumnOrderings($table.id);
i0.ColumnOrderings<String> get description =>
i0.ColumnOrderings($table.description);
i0.ColumnOrderings<int> 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<int> id,
required String description,
required int price,
});
typedef $$BuyableItemsTableUpdateCompanionBuilder = i1.BuyableItemsCompanion
Function({
i0.Value<int> id,
i0.Value<String> description,
i0.Value<int> 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<int> id = const i0.Value.absent(),
i0.Value<String> description = const i0.Value.absent(),
i0.Value<int> price = const i0.Value.absent(),
}) =>
i1.BuyableItemsCompanion(
id: id,
description: description,
price: price,
),
getInsertCompanionBuilder: ({
i0.Value<int> 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<i0.GeneratedDatabase, i2.$ShoppingCartsTable> {
$$ShoppingCartsTableFilterComposer(super.db, super.table);
i0.ColumnFilters<int> 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<i2.$ShoppingCartEntriesTable>('shopping_cart_entries'),
getCurrentColumn: (f) => f.id,
getReferencedColumn: (f) => f.shoppingCart,
getReferencedComposer: (db, table) =>
$$ShoppingCartEntriesTableFilterComposer(db, table),
builder: f);
}
}
class $$ShoppingCartsTableOrderingComposer
extends i0.OrderingComposer<i0.GeneratedDatabase, i2.$ShoppingCartsTable> {
$$ShoppingCartsTableOrderingComposer(super.db, super.table);
i0.ColumnOrderings<int> 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<int> id,
});
typedef $$ShoppingCartsTableUpdateCompanionBuilder = i2.ShoppingCartsCompanion
Function({
i0.Value<int> 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<int> id = const i0.Value.absent(),
}) =>
i2.ShoppingCartsCompanion(
id: id,
),
getInsertCompanionBuilder: ({
i0.Value<int> id = const i0.Value.absent(),
}) =>
i2.ShoppingCartsCompanion.insert(
id: id,
)));
}
class $$ShoppingCartEntriesTableFilterComposer extends i0
.FilterComposer<i0.GeneratedDatabase, i2.$ShoppingCartEntriesTable> {
$$ShoppingCartEntriesTableFilterComposer(super.db, super.table);
i0.ColumnFilters<int> 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<i2.$ShoppingCartsTable>('shopping_carts'),
getCurrentColumn: (f) => f.shoppingCart,
getReferencedColumn: (f) => f.id,
getReferencedComposer: (db, table) =>
$$ShoppingCartsTableFilterComposer(db, table),
builder: f);
}
i0.ColumnFilters<int> 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<i1.$BuyableItemsTable>('buyable_items'),
getCurrentColumn: (f) => f.item,
getReferencedColumn: (f) => f.id,
getReferencedComposer: (db, table) =>
$$BuyableItemsTableFilterComposer(db, table),
builder: f);
}
}
class $$ShoppingCartEntriesTableOrderingComposer extends i0
.OrderingComposer<i0.GeneratedDatabase, i2.$ShoppingCartEntriesTable> {
$$ShoppingCartEntriesTableOrderingComposer(super.db, super.table);
i0.ColumnOrderings<int> 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<i2.$ShoppingCartsTable>('shopping_carts'),
getCurrentColumn: (f) => f.shoppingCart,
getReferencedColumn: (f) => f.id,
getReferencedComposer: (db, table) =>
$$ShoppingCartsTableOrderingComposer(db, table),
builder: o);
}
i0.ColumnOrderings<int> 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<i1.$BuyableItemsTable>('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<int> rowid,
});
typedef $$ShoppingCartEntriesTableUpdateCompanionBuilder
= i2.ShoppingCartEntriesCompanion Function({
i0.Value<int> shoppingCart,
i0.Value<int> item,
i0.Value<int> 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<int> shoppingCart = const i0.Value.absent(),
i0.Value<int> item = const i0.Value.absent(),
i0.Value<int> rowid = const i0.Value.absent(),
}) =>
i2.ShoppingCartEntriesCompanion(
shoppingCart: shoppingCart,
item: item,
rowid: rowid,
),
getInsertCompanionBuilder: ({
required int shoppingCart,
required int item,
i0.Value<int> 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<i2.ShoppingCart> {
}
}
class $ShoppingCartEntriesTable extends i3.ShoppingCartEntries
class $ShoppingCartEntriesTable extends i4.ShoppingCartEntries
with i0.TableInfo<$ShoppingCartEntriesTable, i2.ShoppingCartEntry> {
@override
final i0.GeneratedDatabase attachedDatabase;

View File

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

View File

@ -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<T>` 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<T>` 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' %}

View File

@ -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<String, dynamic>`.
* `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<String, dynamic>`.
- `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<T?>` 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<T?>` 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,9 +178,9 @@ 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.
which compiles sqlite3 with the R\*Tree extension enabled.
- `moor_ffi`: Enables support for functions that are only available when using a `NativeDatabase`. This contains `pow`, `sqrt` and a variety
of trigonometric functions. Details on those functions are available [here]({{ "../Platforms/vm.md#moor-only-functions" | pageUrl }}).
- `math`: Assumes that sqlite3 was compiled with [math functions](https://www.sqlite.org/lang_mathfunc.html).

View File

@ -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<int> get id => ColumnFilters($table.id);
ColumnFilters<String> 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<int> get id => ColumnOrderings($table.id);
ColumnOrderings<String> 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<int> id,
required String name,
});
typedef $$TodoCategoriesTableUpdateCompanionBuilder = TodoCategoriesCompanion
Function({
Value<int> id,
Value<String> 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<int> id = const Value.absent(),
Value<String> name = const Value.absent(),
}) =>
TodoCategoriesCompanion(
id: id,
name: name,
),
getInsertCompanionBuilder: ({
Value<int> 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<int> get id => ColumnFilters($table.id);
ColumnFilters<String> get title => ColumnFilters($table.title);
ColumnFilters<String> get content => ColumnFilters($table.content);
ColumnFilters<int> 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<String> get generatedText =>
ColumnFilters($table.generatedText);
}
class $$TodoItemsTableOrderingComposer
extends OrderingComposer<_$Database, $TodoItemsTable> {
$$TodoItemsTableOrderingComposer(super.db, super.table);
ColumnOrderings<int> get id => ColumnOrderings($table.id);
ColumnOrderings<String> get title => ColumnOrderings($table.title);
ColumnOrderings<String> get content => ColumnOrderings($table.content);
ColumnOrderings<int> 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<String> 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<int> id,
required String title,
Value<String?> content,
required int categoryId,
});
typedef $$TodoItemsTableUpdateCompanionBuilder = TodoItemsCompanion Function({
Value<int> id,
Value<String> title,
Value<String?> content,
Value<int> 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<int> id = const Value.absent(),
Value<String> title = const Value.absent(),
Value<String?> content = const Value.absent(),
Value<int> categoryId = const Value.absent(),
}) =>
TodoItemsCompanion(
id: id,
title: title,
content: content,
categoryId: categoryId,
),
getInsertCompanionBuilder: ({
Value<int> id = const Value.absent(),
required String title,
Value<String?> 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);
}

View File

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

View File

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

View File

@ -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<DB extends GeneratedDatabase, CT extends Table> {
/// 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<RT extends Table, QC extends Composer<DB, RT>,
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;
}
}

View File

@ -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<T extends Object> {
/// 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<DateTime>{
/// ComposableFilter after2000() => isAfter(DateTime(2000));
///}
/// ```
const ColumnFilters(this.column, [this.inverted = false]);
/// Column that this [ColumnFilters] wraps
final Expression<T> 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<T> 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<T> 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<T extends String> on ColumnFilters<String> {
/// 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<bool> _buildExpression(
_StringFilterTypes type, String value, bool caseInsensitive) {
final Expression<String> 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<bool> {
/// 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<T extends num> on ColumnFilters<T> {
/// 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<T extends BigInt> on ColumnFilters<T> {
/// 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<T extends DateTime> on ColumnFilters<T> {
/// 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<CustomType, CustomTypeNonNullable,
T extends Object> {
/// 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<CustomType, T> 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<CustomType, CustomTypeNonNullable, T>
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<CustomTypeNonNullable> 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<JoinBuilder> joinBuilders;
/// The expression that will be applied to the query
late final Expression<bool> expression;
/// Create a new [ComposableFilter] for a column without any joins
ComposableFilter(Expression<bool> 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<DB extends GeneratedDatabase, T extends Table>
extends Composer<DB, T> {
/// Create a filter composer with an empty state
FilterComposer(super.$db, super.$table);
}

View File

@ -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<JoinBuilder> get joinBuilders;
/// Add a join builder to this class
void addJoinBuilders(Set<JoinBuilder> builders) {
joinBuilders.addAll(builders);
}
}

View File

@ -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<T extends Table, DT extends DataClass> {
const _StatementType();
}
class _SimpleResult<T extends Table, DT extends DataClass>
extends _StatementType<T, DT> {
final SimpleSelectStatement<T, DT> statement;
const _SimpleResult(this.statement);
}
class _JoinedResult<T extends Table, DT extends DataClass>
extends _StatementType<T, DT> {
final JoinedSelectStatement<T, DT> 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<DB, T>,
OS extends OrderingComposer<DB, T>,
C extends ProcessedTableManager<DB, T, DT, FS, OS, C, CI, CU>,
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<bool>? filter;
/// A set of [OrderingBuilder] which will be used to apply
/// [OrderingTerm]s to the statement when it's eventually built
final Set<OrderingBuilder> orderingBuilders;
/// A set of [JoinBuilder] which will be used to create [Join]s
/// that will be applied to the build statement
final Set<JoinBuilder> 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<DB, T, DT, FS, OS, C, CI, CU>)
_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<DB, T, DT, FS, OS, C, CI, CU>)
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<DB, T, DT, FS, OS, C, CI, CU> copyWith({
bool? distinct,
int? limit,
int? offset,
Expression<bool>? filter,
Set<OrderingBuilder>? orderingBuilders,
Set<JoinBuilder>? 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<T, DT> get _tableAsTableInfo => table as TableInfo<T, DT>;
/// Builds a select statement with the given target columns, or all columns if none are provided
_StatementType<T, DT> _buildSelectStatement(
{Iterable<Expression>? 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<T, DT> 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<T, DT>;
} else {
joinedStatement = db
.select(_tableAsTableInfo, distinct: distinct ?? false)
.join(joins) as JoinedSelectStatement<T, DT>;
}
// 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<DT> 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<T, DT> buildUpdateStatement() {
final UpdateStatement<T, DT> 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<T, DT>;
updateStatement.where((tbl) => col.isInQuery(subquery.statement));
}
}
return updateStatement;
}
/// Count the number of rows that would be returned by the built statement
Future<int> 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<bool> 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<T, DT>;
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<DB, T>,
OS extends OrderingComposer<DB, T>,
C extends ProcessedTableManager<DB, T, DT, FS, OS, C, CI, CU>,
CI extends Function,
CU extends Function>
implements
MultiSelectable<DT>,
SingleSelectable<DT>,
SingleOrNullSelectable<DT> {
/// The state for this manager
final TableManagerState<DB, T, DT, FS, OS, C, CI, CU> $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<int> update(Insertable<DT> 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<int> count([bool distinct = true]) {
return $state.copyWith(distinct: true).count();
}
/// Checks whether any rows exist
Future<bool> 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<int> 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<DT> 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<List<D>>`, this returns a
/// `Stream<D>`. 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<DT> 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<List<DT>> 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<List<DT>> 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<DT?> 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<List<D>>`, this
/// returns a `Stream<D?>`. 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<DT?> 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<DB, T>,
OS extends OrderingComposer<DB, T>,
C extends ProcessedTableManager<DB, T, D, FS, OS, C, CI, CU>,
CI extends Function,
CU extends Function>
extends BaseTableManager<DB, T, D, FS, OS, C, CI, CU>
implements
MultiSelectable<D>,
SingleSelectable<D>,
SingleOrNullSelectable<D> {
/// 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<DB, T>,
OS extends OrderingComposer<DB, T>,
C extends ProcessedTableManager<DB, T, D, FS, OS, C, CI, CU>,
CI extends Function,
CU extends Function> extends BaseTableManager<DB, T, D, FS, OS, C, CI, CU> {
/// 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<int> create(Insertable<D> Function(CI o) f,
{InsertMode? mode, UpsertClause<T, D>? 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<D> createReturning(Insertable<D> Function(CI o) f,
{InsertMode? mode, UpsertClause<T, D>? 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<D?> createReturningOrNull(Insertable<D> Function(CI o) f,
{InsertMode? mode, UpsertClause<T, D>? 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<void> bulkCreate(Iterable<Insertable<D>> Function(CI o) f,
{InsertMode? mode, UpsertClause<T, D>? 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<bool> replace(Insertable<D> 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<void> bulkReplace(Iterable<Insertable<D>> entities) {
return $state.db
.batch((b) => b.replaceAll($state._tableAsTableInfo, entities));
}
}

View File

@ -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<T extends Object> {
/// 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<T> 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<Object> 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<OrderingBuilder> orderingBuilders;
@override
final Set<JoinBuilder> 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<OrderingTerm> buildTerms() => orderingBuilders
.map((e) => OrderingTerm(mode: e.mode, expression: e.column))
.toList();
}
/// The class that orchestrates the composition of orderings
class OrderingComposer<DB extends GeneratedDatabase, T extends Table>
extends Composer<DB, T> {
/// Create an ordering composer with an empty state
OrderingComposer(super.$db, super.$table);
}

View File

@ -40,3 +40,4 @@ dev_dependencies:
shelf: ^1.3.0
test_descriptor: ^2.0.1
vm_service: ^14.0.0

View File

@ -1657,6 +1657,7 @@ class MyView extends ViewInfo<MyView, MyViewData> 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<String> get a => ColumnFilters($table.a);
ColumnFilters<int> get b => ColumnFilters($table.b);
}
class $WithDefaultsOrderingComposer
extends OrderingComposer<_$CustomTablesDb, WithDefaults> {
$WithDefaultsOrderingComposer(super.db, super.table);
ColumnOrderings<String> get a => ColumnOrderings($table.a);
ColumnOrderings<int> 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<String?> a,
Value<int?> b,
Value<int> rowid,
});
typedef $WithDefaultsUpdateCompanionBuilder = WithDefaultsCompanion Function({
Value<String?> a,
Value<int?> b,
Value<int> 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<String?> a = const Value.absent(),
Value<int?> b = const Value.absent(),
Value<int> rowid = const Value.absent(),
}) =>
WithDefaultsCompanion(
a: a,
b: b,
rowid: rowid,
),
getInsertCompanionBuilder: ({
Value<String?> a = const Value.absent(),
Value<int?> b = const Value.absent(),
Value<int> rowid = const Value.absent(),
}) =>
WithDefaultsCompanion.insert(
a: a,
b: b,
rowid: rowid,
)));
}
class $WithConstraintsFilterComposer
extends FilterComposer<_$CustomTablesDb, WithConstraints> {
$WithConstraintsFilterComposer(super.db, super.table);
ColumnFilters<String> get a => ColumnFilters($table.a);
ColumnFilters<int> get b => ColumnFilters($table.b);
ColumnFilters<double> get c => ColumnFilters($table.c);
}
class $WithConstraintsOrderingComposer
extends OrderingComposer<_$CustomTablesDb, WithConstraints> {
$WithConstraintsOrderingComposer(super.db, super.table);
ColumnOrderings<String> get a => ColumnOrderings($table.a);
ColumnOrderings<int> get b => ColumnOrderings($table.b);
ColumnOrderings<double> 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<String?> a,
required int b,
Value<double?> c,
Value<int> rowid,
});
typedef $WithConstraintsUpdateCompanionBuilder = WithConstraintsCompanion
Function({
Value<String?> a,
Value<int> b,
Value<double?> c,
Value<int> 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<String?> a = const Value.absent(),
Value<int> b = const Value.absent(),
Value<double?> c = const Value.absent(),
Value<int> rowid = const Value.absent(),
}) =>
WithConstraintsCompanion(
a: a,
b: b,
c: c,
rowid: rowid,
),
getInsertCompanionBuilder: ({
Value<String?> a = const Value.absent(),
required int b,
Value<double?> c = const Value.absent(),
Value<int> 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<String> get configKey => ColumnFilters($table.configKey);
ColumnFilters<DriftAny> get configValue => ColumnFilters($table.configValue);
ColumnFilters<int> get syncStateValue => ColumnFilters($table.syncState);
ColumnWithTypeConverterFilters<SyncType?, SyncType, int> get syncState =>
ColumnWithTypeConverterFilters($table.syncState);
ColumnFilters<int> get syncStateImplicitValue =>
ColumnFilters($table.syncStateImplicit);
ColumnWithTypeConverterFilters<SyncType?, SyncType, int>
get syncStateImplicit =>
ColumnWithTypeConverterFilters($table.syncStateImplicit);
}
class $ConfigTableOrderingComposer
extends OrderingComposer<_$CustomTablesDb, ConfigTable> {
$ConfigTableOrderingComposer(super.db, super.table);
ColumnOrderings<String> get configKey => ColumnOrderings($table.configKey);
ColumnOrderings<DriftAny> get configValue =>
ColumnOrderings($table.configValue);
ColumnOrderings<int> get syncState => ColumnOrderings($table.syncState);
ColumnOrderings<int> 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<DriftAny?> configValue,
Value<SyncType?> syncState,
Value<SyncType?> syncStateImplicit,
Value<int> rowid,
});
typedef $ConfigTableUpdateCompanionBuilder = ConfigCompanion Function({
Value<String> configKey,
Value<DriftAny?> configValue,
Value<SyncType?> syncState,
Value<SyncType?> syncStateImplicit,
Value<int> 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<String> configKey = const Value.absent(),
Value<DriftAny?> configValue = const Value.absent(),
Value<SyncType?> syncState = const Value.absent(),
Value<SyncType?> syncStateImplicit = const Value.absent(),
Value<int> rowid = const Value.absent(),
}) =>
ConfigCompanion(
configKey: configKey,
configValue: configValue,
syncState: syncState,
syncStateImplicit: syncStateImplicit,
rowid: rowid,
),
getInsertCompanionBuilder: ({
required String configKey,
Value<DriftAny?> configValue = const Value.absent(),
Value<SyncType?> syncState = const Value.absent(),
Value<SyncType?> syncStateImplicit = const Value.absent(),
Value<int> 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<int> get someid => ColumnFilters($table.someid);
ColumnFilters<String> get sometext => ColumnFilters($table.sometext);
ColumnFilters<bool> get isInserting => ColumnFilters($table.isInserting);
ColumnFilters<DateTime> get somedate => ColumnFilters($table.somedate);
}
class $MytableOrderingComposer
extends OrderingComposer<_$CustomTablesDb, Mytable> {
$MytableOrderingComposer(super.db, super.table);
ColumnOrderings<int> get someid => ColumnOrderings($table.someid);
ColumnOrderings<String> get sometext => ColumnOrderings($table.sometext);
ColumnOrderings<bool> get isInserting => ColumnOrderings($table.isInserting);
ColumnOrderings<DateTime> 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<int> someid,
Value<String?> sometext,
Value<bool?> isInserting,
Value<DateTime?> somedate,
});
typedef $MytableUpdateCompanionBuilder = MytableCompanion Function({
Value<int> someid,
Value<String?> sometext,
Value<bool?> isInserting,
Value<DateTime?> 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<int> someid = const Value.absent(),
Value<String?> sometext = const Value.absent(),
Value<bool?> isInserting = const Value.absent(),
Value<DateTime?> somedate = const Value.absent(),
}) =>
MytableCompanion(
someid: someid,
sometext: sometext,
isInserting: isInserting,
somedate: somedate,
),
getInsertCompanionBuilder: ({
Value<int> someid = const Value.absent(),
Value<String?> sometext = const Value.absent(),
Value<bool?> isInserting = const Value.absent(),
Value<DateTime?> 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<String> get sender => ColumnFilters($table.sender);
ColumnFilters<String> get title => ColumnFilters($table.title);
ColumnFilters<String> get body => ColumnFilters($table.body);
}
class $EmailOrderingComposer extends OrderingComposer<_$CustomTablesDb, Email> {
$EmailOrderingComposer(super.db, super.table);
ColumnOrderings<String> get sender => ColumnOrderings($table.sender);
ColumnOrderings<String> get title => ColumnOrderings($table.title);
ColumnOrderings<String> 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<int> rowid,
});
typedef $EmailUpdateCompanionBuilder = EmailCompanion Function({
Value<String> sender,
Value<String> title,
Value<String> body,
Value<int> 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<String> sender = const Value.absent(),
Value<String> title = const Value.absent(),
Value<String> body = const Value.absent(),
Value<int> rowid = const Value.absent(),
}) =>
EmailCompanion(
sender: sender,
title: title,
body: body,
rowid: rowid,
),
getInsertCompanionBuilder: ({
required String sender,
required String title,
required String body,
Value<int> 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<int> get sqlClass => ColumnFilters($table.sqlClass);
ColumnFilters<String> get textColumn => ColumnFilters($table.textColumn);
}
class $WeirdTableOrderingComposer
extends OrderingComposer<_$CustomTablesDb, WeirdTable> {
$WeirdTableOrderingComposer(super.db, super.table);
ColumnOrderings<int> get sqlClass => ColumnOrderings($table.sqlClass);
ColumnOrderings<String> 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<int> rowid,
});
typedef $WeirdTableUpdateCompanionBuilder = WeirdTableCompanion Function({
Value<int> sqlClass,
Value<String> textColumn,
Value<int> 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<int> sqlClass = const Value.absent(),
Value<String> textColumn = const Value.absent(),
Value<int> rowid = const Value.absent(),
}) =>
WeirdTableCompanion(
sqlClass: sqlClass,
textColumn: textColumn,
rowid: rowid,
),
getInsertCompanionBuilder: ({
required int sqlClass,
required String textColumn,
Value<int> 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<bool> Function(ConfigTable config);
typedef TypeConverterVar$pred = Expression<bool> Function(ConfigTable config);

View File

@ -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<TodoStatus>().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<TodoStatus>().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<UuidValue>.via(
TableWithoutPK,
PureDefaults,
WithCustomType,
TableWithEveryColumnType
],
views: [
CategoryTodoCountView,

File diff suppressed because it is too large Load Diff

View File

@ -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<TableInfo<Table, Object?>> get allTables =>
@ -191,3 +192,80 @@ abstract class _$_SomeDb extends GeneratedDatabase {
@override
List<DatabaseSchemaEntity> get allSchemaEntities => [someTable];
}
class $$_SomeTableTableFilterComposer
extends FilterComposer<_$_SomeDb, $_SomeTableTable> {
$$_SomeTableTableFilterComposer(super.db, super.table);
ColumnFilters<int> get id => ColumnFilters($table.id);
ColumnFilters<String> get name => ColumnFilters($table.name);
}
class $$_SomeTableTableOrderingComposer
extends OrderingComposer<_$_SomeDb, $_SomeTableTable> {
$$_SomeTableTableOrderingComposer(super.db, super.table);
ColumnOrderings<int> get id => ColumnOrderings($table.id);
ColumnOrderings<String> 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<int> id,
Value<String?> name,
});
typedef $$_SomeTableTableUpdateCompanionBuilder = _SomeTableCompanion Function({
Value<int> id,
Value<String?> 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<int> id = const Value.absent(),
Value<String?> name = const Value.absent(),
}) =>
_SomeTableCompanion(
id: id,
name: name,
),
getInsertCompanionBuilder: ({
Value<int> id = const Value.absent(),
Value<String?> 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);
}

View File

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

View File

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

View File

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

View File

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

View File

@ -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<SqlModule> 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,

View File

@ -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<List<DriftColumnConstraint>> _driftConstraintsFromCustomConstraints({
required bool isNullable,
String? customConstraints,

View File

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

View File

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

View File

@ -93,6 +93,7 @@ class GenerateUtilsCommand extends Command {
final options = DriftOptions.fromJson({
...cli.project.moorOptions.toJson(),
...schema.options,
'generate_manager': false,
});
final writer = Writer(

View File

@ -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<String, dynamic> _$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(),

View File

@ -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<DriftTable>()) {
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);

View File

@ -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<DriftTable> tables) {
// Utility function to get the referenced table and column
(DriftTable, DriftColumn)? getReferencedTableAndColumn(
DriftColumn column, List<DriftTable> tables) {
final referencedCol = column.constraints
.whereType<ForeignKeyReference>()
.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<String> duplicates(List<String> items) {
final seen = <String>{};
final duplicates = <String>[];
for (var item in items) {
if (!seen.add(item)) {
duplicates.add(item);
}
}
return duplicates;
}
/// First add the filters and orderings for the columns
/// 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<DriftTable> _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('}');
}
}

View File

@ -45,7 +45,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

View File

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

View File

@ -282,7 +282,7 @@ class Groups extends Table with TableInfo<Groups, Group> {
'deleted', aliasedName, true,
type: DriftSqlType.bool,
requiredDuringInsert: false,
$customConstraints: 'DEFAULT FALSE',
$customConstraints: 'NULL DEFAULT FALSE',
defaultValue: const CustomExpression('FALSE'));
static const VerificationMeta _ownerMeta = const VerificationMeta('owner');
late final GeneratedColumn<int> owner = GeneratedColumn<int>(

View File

@ -18,9 +18,10 @@ class Users extends Table with TableInfo<Users, UsersData> {
@override
List<GeneratedColumn> 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<GeneratedColumn> get $primaryKey => {id};
@override

View File

@ -32,9 +32,10 @@ class Users extends Table with TableInfo<Users, UsersData> {
@override
List<GeneratedColumn> 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<GeneratedColumn> get $primaryKey => {id};
@override
@ -257,9 +258,10 @@ class Groups extends Table with TableInfo<Groups, GroupsData> {
@override
List<GeneratedColumn> 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<GeneratedColumn> get $primaryKey => {id};
@override
@ -475,9 +477,10 @@ class Notes extends Table
@override
List<GeneratedColumn> 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<GeneratedColumn> get $primaryKey => const {};
@override

View File

@ -21,9 +21,10 @@ class Users extends Table with TableInfo<Users, UsersData> {
@override
List<GeneratedColumn> 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<GeneratedColumn> get $primaryKey => {id};
@override

View File

@ -21,9 +21,10 @@ class Users extends Table with TableInfo<Users, UsersData> {
@override
List<GeneratedColumn> 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<GeneratedColumn> get $primaryKey => {id};
@override
@ -179,9 +180,10 @@ class Groups extends Table with TableInfo<Groups, GroupsData> {
@override
List<GeneratedColumn> 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<GeneratedColumn> get $primaryKey => {id};
@override

View File

@ -23,9 +23,10 @@ class Users extends Table with TableInfo<Users, UsersData> {
@override
List<GeneratedColumn> 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<GeneratedColumn> get $primaryKey => {id};
@override
@ -181,9 +182,10 @@ class Groups extends Table with TableInfo<Groups, GroupsData> {
@override
List<GeneratedColumn> 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<GeneratedColumn> get $primaryKey => {id};
@override

View File

@ -29,9 +29,10 @@ class Users extends Table with TableInfo<Users, UsersData> {
@override
List<GeneratedColumn> 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<GeneratedColumn> get $primaryKey => {id};
@override
@ -218,9 +219,10 @@ class Groups extends Table with TableInfo<Groups, GroupsData> {
@override
List<GeneratedColumn> 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<GeneratedColumn> get $primaryKey => {id};
@override

View File

@ -32,9 +32,10 @@ class Users extends Table with TableInfo<Users, UsersData> {
@override
List<GeneratedColumn> 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<GeneratedColumn> get $primaryKey => {id};
@override
@ -250,9 +251,10 @@ class Groups extends Table with TableInfo<Groups, GroupsData> {
@override
List<GeneratedColumn> 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<GeneratedColumn> get $primaryKey => {id};
@override

View File

@ -32,9 +32,10 @@ class Users extends Table with TableInfo<Users, UsersData> {
@override
List<GeneratedColumn> 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<GeneratedColumn> get $primaryKey => {id};
@override
@ -250,9 +251,10 @@ class Groups extends Table with TableInfo<Groups, GroupsData> {
@override
List<GeneratedColumn> 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<GeneratedColumn> get $primaryKey => {id};
@override
@ -603,9 +605,10 @@ class Notes extends Table
@override
List<GeneratedColumn> 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<GeneratedColumn> get $primaryKey => const {};
@override

View File

@ -32,9 +32,10 @@ class Users extends Table with TableInfo<Users, UsersData> {
@override
List<GeneratedColumn> 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<GeneratedColumn> get $primaryKey => {id};
@override
@ -254,9 +255,10 @@ class Groups extends Table with TableInfo<Groups, GroupsData> {
@override
List<GeneratedColumn> 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<GeneratedColumn> get $primaryKey => {id};
@override
@ -607,9 +609,10 @@ class Notes extends Table
@override
List<GeneratedColumn> 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<GeneratedColumn> get $primaryKey => const {};
@override

View File

@ -32,9 +32,10 @@ class Users extends Table with TableInfo<Users, UsersData> {
@override
List<GeneratedColumn> 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<GeneratedColumn> get $primaryKey => {id};
@override
@ -257,9 +258,10 @@ class Groups extends Table with TableInfo<Groups, GroupsData> {
@override
List<GeneratedColumn> 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<GeneratedColumn> get $primaryKey => {id};
@override
@ -475,9 +477,10 @@ class Notes extends Table
@override
List<GeneratedColumn> 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<GeneratedColumn> get $primaryKey => const {};
@override

View File

@ -25,7 +25,7 @@ Future<void> main() async {
output.writeAsStringSync(json.encode(tracker.timings));
// Make sure the process exits. Otherwise, unclosed resources from the
// Make sure the process exits. Otherwise, unclosed resources from the
// benchmarks will keep the process alive.
exit(0);
}

View File

@ -18,7 +18,7 @@ SELECT * FROM key_values WHERE value IN (SELECT value FROM key_values WHERE valu
''';
final fs = <Future>[];
for (var i = 0; i < _numQueries; i++) {
fs.add(
_db.customSelect(queryToBench, variables: [Variable(uuid.v4())]).get(),