This commit is contained in:
Simon Binder
2025-10-18 00:14:46 +02:00
parent 6f1a0629a8
commit f7f6865564
44 changed files with 988 additions and 696 deletions
@@ -34,4 +34,5 @@ class TodoItems extends Table {
TextColumn get content => text()();
IntColumn get author => integer().references(Users, #id)();
}
// #enddocregion record-explicit
@@ -18,4 +18,5 @@ class User {
User.fromDb({required this.id, required this.name, required this.birthday});
}
// #enddocregion named
@@ -61,13 +61,16 @@ void _queryManager(Database db) async {
// #enddocregion generated-dataclass
// #docregion generated-value
await db.users.insertOne(UsersCompanion(
id: Value.absent(), // (1)!
username: Value('user'), // (2)!
));
await db.users.insertOne(
UsersCompanion(
id: Value.absent(), // (1)!
username: Value('user'), // (2)!
),
);
await (db.update(db.users)..where((tbl) => tbl.id.equals(1)))
.write(UsersCompanion(username: Value("Updated name")));
await (db.update(db.users)..where((tbl) => tbl.id.equals(1))).write(
UsersCompanion(username: Value("Updated name")),
);
// #enddocregion generated-value
// #docregion companion-custom
@@ -8,24 +8,30 @@ extension MigrateToTextDateTimes on GeneratedDatabase {
// `allTables` getter returns all tables of the current schema, which may
// be incorrect.
for (final table in allTables) {
final dateTimeColumns =
table.$columns.where((c) => c.type == DriftSqlType.dateTime);
final dateTimeColumns = table.$columns.where(
(c) => c.type == DriftSqlType.dateTime,
);
if (dateTimeColumns.isNotEmpty) {
// This table has dateTime columns which need to be migrated.
await m.alterTable(TableMigration(
table,
columnTransformer: {
for (final column in dateTimeColumns)
// We assume that the column in the database is an int (unix
// timestamp), use `fromUnixEpoch` to convert it to a date time.
// Note that the resulting value in the database is in UTC.
column: DateTimeExpressions.fromUnixEpoch(column.dartCast<int>()),
},
));
await m.alterTable(
TableMigration(
table,
columnTransformer: {
for (final column in dateTimeColumns)
// We assume that the column in the database is an int (unix
// timestamp), use `fromUnixEpoch` to convert it to a date time.
// Note that the resulting value in the database is in UTC.
column: DateTimeExpressions.fromUnixEpoch(
column.dartCast<int>(),
),
},
),
);
}
}
}
// #enddocregion unix-to-text
}
@@ -37,45 +43,54 @@ extension MigrateToTimestamps on GeneratedDatabase {
// `allTables` getter returns all tables of the current schema, which may
// be incorrect.
for (final table in allTables) {
final dateTimeColumns =
table.$columns.where((c) => c.type == DriftSqlType.dateTime);
final dateTimeColumns = table.$columns.where(
(c) => c.type == DriftSqlType.dateTime,
);
if (dateTimeColumns.isNotEmpty) {
// This table has dateTime columns which need to be migrated.
await m.alterTable(TableMigration(
table,
columnTransformer: {
for (final column in dateTimeColumns)
// We assume that the column in the database is a string. We want
// to parse it to a date in SQL and then get the unix timestamp of
// it.
// Note that this requires sqlite version 3.38 or above.
column: FunctionCallExpression('unixepoch', [column]),
},
));
await m.alterTable(
TableMigration(
table,
columnTransformer: {
for (final column in dateTimeColumns)
// We assume that the column in the database is a string. We want
// to parse it to a date in SQL and then get the unix timestamp of
// it.
// Note that this requires sqlite version 3.38 or above.
column: FunctionCallExpression('unixepoch', [column]),
},
),
);
}
}
}
// #enddocregion text-to-unix
Future<void> migrateFromTextDateTimesToUnixTimestampsPre338(
Migrator m) async {
Migrator m,
) async {
for (final table in allTables) {
final dateTimeColumns =
table.$columns.where((c) => c.type == DriftSqlType.dateTime);
final dateTimeColumns = table.$columns.where(
(c) => c.type == DriftSqlType.dateTime,
);
if (dateTimeColumns.isNotEmpty) {
await m.alterTable(TableMigration(
table,
// #docregion text-to-unix-old
columnTransformer: {
for (final column in dateTimeColumns)
// Use this as an alternative to `unixepoch`:
column: FunctionCallExpression(
'strftime', [const Constant('%s'), column]).cast<int>(),
},
// #enddocregion text-to-unix-old
));
await m.alterTable(
TableMigration(
table,
// #docregion text-to-unix-old
columnTransformer: {
for (final column in dateTimeColumns)
// Use this as an alternative to `unixepoch`:
column: FunctionCallExpression('strftime', [
const Constant('%s'),
column,
]).cast<int>(),
},
// #enddocregion text-to-unix-old
),
);
}
}
}
+28 -22
View File
@@ -34,8 +34,9 @@ extension Snippets on CanUseCommonTables {
..where((a) => a.averageLivespan.isSmallerThan(a.amountOfLegs)));
Future<List<Animal>> findAnimalsByLegs(int legCount) {
return (select(animals)..where((a) => a.amountOfLegs.equals(legCount)))
.get();
return (select(
animals,
)..where((a) => a.amountOfLegs.equals(legCount))).get();
}
// #enddocregion comparison
@@ -54,28 +55,25 @@ extension Snippets on CanUseCommonTables {
Animals a = animals.asDslTable;
// #docregion listand
Expression.and([
a.isMammal,
a.amountOfLegs.equals(4),
]);
Expression.and([a.isMammal, a.amountOfLegs.equals(4)]);
// #enddocregion listand
}
// #docregion arithmetic
Future<List<Product>> canBeBought(int amount, int budget) {
return (select(products)
..where((p) {
final totalPrice = p.price * Variable(amount);
return totalPrice.isSmallerOrEqualValue(budget);
}))
return (select(products)..where((p) {
final totalPrice = p.price * Variable(amount);
return totalPrice.isSmallerOrEqualValue(budget);
}))
.get();
}
// #enddocregion arithmetic
// #docregion emptyCategories
Future<List<Category>> emptyCategories() {
final hasNoTodo = notExistsQuery(select(todoItems)
..where((row) => row.category.equalsExp(categories.id)));
final hasNoTodo = notExistsQuery(
select(todoItems)..where((row) => row.category.equalsExp(categories.id)),
);
return (select(categories)..where((row) => hasNoTodo)).get();
}
// #enddocregion emptyCategories
@@ -118,7 +116,7 @@ extension Snippets on CanUseCommonTables {
todoItems,
todoItems.category.equalsExp(categories.id),
useColumns: false,
)
),
]);
query
..addColumns([amountOfTodos])
@@ -149,16 +147,18 @@ extension Snippets on CanUseCommonTables {
void customExpressions() {
// #docregion custom
const inactive =
CustomExpression<bool>("julianday('now') - julianday(last_login) > 60");
const inactive = CustomExpression<bool>(
"julianday('now') - julianday(last_login) > 60",
);
select(users)..where((u) => inactive);
// #enddocregion custom
// #enddocregion custom
}
// #docregion date2
Future<void> increaseDueDates() async {
final change = TodoItemsCompanion.custom(
dueDate: todoItems.dueDate + Duration(days: 1));
dueDate: todoItems.dueDate + Duration(days: 1),
);
await update(todoItems).write(change);
}
// #enddocregion date2
@@ -166,8 +166,10 @@ extension Snippets on CanUseCommonTables {
// #docregion date3
Future<void> moveDueDateToNextMonday() async {
final change = TodoItemsCompanion.custom(
dueDate: todoItems.dueDate
.modify(DateTimeModifier.weekday(DateTime.monday)));
dueDate: todoItems.dueDate.modify(
DateTimeModifier.weekday(DateTime.monday),
),
);
await update(todoItems).write(change);
}
// #enddocregion date3
@@ -210,10 +212,13 @@ extension Snippets on CanUseCommonTables {
void rowValuesUsage() {
select(animals).where((row) {
// Generates (amount_of_legs, average_livespan) < (?, ?)
return RowValues([row.amountOfLegs, row.averageLivespan])
.isSmallerThan(RowValues([Variable(2), Variable(10)]));
return RowValues([
row.amountOfLegs,
row.averageLivespan,
]).isSmallerThan(RowValues([Variable(2), Variable(10)]));
});
}
// #enddocregion rowvalue-use
}
@@ -248,4 +253,5 @@ final class RowValues extends Expression<Never> {
context.buffer.write(')');
}
}
// #enddocregion row-values
+8 -4
View File
@@ -64,17 +64,21 @@ class MyDatabase extends _$MyDatabase {
// #docregion calls-with-contacts
Future<List<(Call, Contact)>> callsWithContact() async {
final phoneNumbersForContact =
contacts.data.jsonEach(this, r'$.phoneNumbers');
final phoneNumbersForContact = contacts.data.jsonEach(
this,
r'$.phoneNumbers',
);
final phoneNumberQuery = selectOnly(phoneNumbersForContact)
..addColumns([phoneNumbersForContact.value]);
final query = select(calls).join(
[innerJoin(contacts, calls.phoneNumber.isInQuery(phoneNumberQuery))]);
final query = select(calls).join([
innerJoin(contacts, calls.phoneNumber.isInQuery(phoneNumberQuery)),
]);
return query
.map((row) => (row.readTable(calls), row.readTable(contacts)))
.get();
}
// #enddocregion calls-with-contacts
}
+53 -51
View File
@@ -49,14 +49,16 @@ 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'));
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);
(o) => o(title: 'Title', content: 'New Content'),
mode: InsertMode.replace,
);
// We can also create multiple items at once
await managers.todoItems.bulkCreate(
@@ -88,8 +90,9 @@ extension ManagerExamples on AppDatabase {
await managers.todoItems.replace(obj);
// Replace multiple items
var objs =
await managers.todoItems.filter((o) => o.id.isIn([1, 2, 3])).get();
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);
}
@@ -135,14 +138,15 @@ extension ManagerExamples on AppDatabase {
Future<void> filterWithType() async {
// Filter all items created since 7 days ago
managers.todoItems.filter(
(f) => f.createdAt.isAfter(DateTime.now().subtract(Duration(days: 7))));
(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
// #enddocregion manager_type_specific_filter
// #docregion manager_ordering
// #docregion manager_ordering
Future<void> orderWithType() async {
// Order all items by their creation date in ascending order
managers.todoItems.orderBy((o) => o.createdAt.asc());
@@ -150,9 +154,9 @@ extension ManagerExamples on AppDatabase {
// 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
// #enddocregion manager_ordering
// #docregion manager_count
// #docregion manager_count
Future<void> count() async {
// Count all items
await managers.todoItems.count();
@@ -160,9 +164,9 @@ extension ManagerExamples on AppDatabase {
// Count all items with a title of "Title"
await managers.todoItems.filter((f) => f.title("Title")).count();
}
// #enddocregion manager_count
// #enddocregion manager_count
// #docregion manager_exists
// #docregion manager_exists
Future<void> exists() async {
// Check if any items exist
await managers.todoItems.exists();
@@ -170,9 +174,9 @@ extension ManagerExamples on AppDatabase {
// Check if any items with a title of "Title" exist
await managers.todoItems.filter((f) => f.title("Title")).exists();
}
// #enddocregion manager_exists
// #enddocregion manager_exists
// #docregion manager_filter_forward_references
// #docregion manager_filter_forward_references
Future<void> relationalFilter() async {
// Get all items with a category description of "School"
managers.todoItems.filter((f) => f.category.description("School"));
@@ -180,14 +184,12 @@ extension ManagerExamples on AppDatabase {
// 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.description("School"),
)
.filter((f) => f.title("Title") | f.category.description("School"))
.exists();
}
// #enddocregion manager_filter_forward_references
// #enddocregion manager_filter_forward_references
// #docregion manager_filter_back_references
// #docregion manager_filter_back_references
Future<void> 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)));
@@ -198,9 +200,9 @@ extension ManagerExamples on AppDatabase {
(f) => f.description("School") | f.todoItemsRefs((f) => f.id(1)),
);
}
// #enddocregion manager_filter_back_references
// #enddocregion manager_filter_back_references
// #docregion manager_filter_custom_back_references
// #docregion manager_filter_custom_back_references
Future<void> 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
@@ -210,9 +212,9 @@ extension ManagerExamples on AppDatabase {
f.ownedGroups((f) => f.id.isIn([1, 2, 4, 5])),
);
}
// #enddocregion manager_filter_custom_back_references
// #enddocregion manager_filter_custom_back_references
// #docregion manager_references
// #docregion manager_references
Future<void> references() async {
/// Get each todo, along with a its categories
final todosWithRefs = await managers.todoItems.withReferences().get();
@@ -221,21 +223,20 @@ extension ManagerExamples on AppDatabase {
}
/// This also works in the reverse
final categoriesWithRefs =
await managers.todoCategory.withReferences().get();
final categoriesWithRefs = await managers.todoCategory
.withReferences()
.get();
for (final (category, refs) in categoriesWithRefs) {
final todos = await refs.todoItemsRefs.get();
}
}
// #enddocregion manager_references
// #docregion manager_prefetch_references
// #enddocregion manager_references
// #docregion manager_prefetch_references
Future<void> referencesPrefetch() async {
/// Get each todo, along with a its categories
final todosWithRefs = await managers.todoItems
.withReferences(
(prefetch) => prefetch(category: true),
)
.withReferences((prefetch) => prefetch(category: true))
.get();
for (final (todo, refs) in todosWithRefs) {
final category = refs.category?.prefetchedData?.firstOrNull;
@@ -253,26 +254,24 @@ extension ManagerExamples on AppDatabase {
//final todos = await refs.todoItemsRefs.get();
}
}
// #enddocregion manager_prefetch_references
// #enddocregion manager_prefetch_references
Future<void> referencesPrefetchStream() async {
// #docregion manager_prefetch_references_stream
// #docregion manager_prefetch_references_stream
/// Get each todo, along with a its categories
managers.todoCategory
.withReferences((prefetch) => prefetch(todoItemsRefs: true, user: true))
.watch()
.listen(
(catWithRefs) {
for (final (cat, refs) in catWithRefs) {
// Updates to the user table will trigger a query
final users = refs.user?.prefetchedData;
.listen((catWithRefs) {
for (final (cat, refs) in catWithRefs) {
// Updates to the user table will trigger a query
final users = refs.user?.prefetchedData;
// However, updates to the TodoItems table will not trigger a query
final todos = refs.todoItemsRefs.prefetchedData;
}
},
);
// #enddocregion manager_prefetch_references_stream
// However, updates to the TodoItems table will not trigger a query
final todos = refs.todoItemsRefs.prefetchedData;
}
});
// #enddocregion manager_prefetch_references_stream
}
}
@@ -338,8 +337,9 @@ Future<void> customOrdering(AppDatabase db) async {
void _managerAnnotations(AppDatabase db) async {
// #docregion manager_annotations
// First create an computed field with an expression you want to use
final titleLengthField =
db.managers.todoItems.computedField((o) => o.title.length);
final titleLengthField = db.managers.todoItems.computedField(
(o) => o.title.length,
);
/// Create a copy of the manager with the computed fields you want to use
final manager = db.managers.todoItems.withFields([titleLengthField]);
@@ -357,14 +357,15 @@ void _managerAnnotations(AppDatabase db) async {
final titleLength = titleLengthField.read(refs);
print('Item ${item.id} has a title length of $titleLength');
}
// #enddocregion manager_annotations
// #enddocregion manager_annotations
}
void _managerReferencedAnnotations(AppDatabase db) async {
// #docregion referenced_annotations
// This computed field will get the name of the user of this todo
final todoUserName =
db.managers.todoItems.computedField((o) => o.category.user.name);
final todoUserName = db.managers.todoItems.computedField(
(o) => o.category.user.name,
);
/// Create a copy of the manager with the computed fields you want to use
final manager = db.managers.todoItems.withFields([todoUserName]);
@@ -381,8 +382,9 @@ void _managerAggregatedAnnotations(AppDatabase db) async {
// #docregion aggregated_annotations
// You can aggregate over multiple rows in a related table
// to perform calculations on them
final todoCountcomputedField = db.managers.todoCategory
.computedField((o) => o.todoItemsRefs((o) => o.id).count());
final todoCountcomputedField = db.managers.todoCategory.computedField(
(o) => o.todoItemsRefs((o) => o.id).count(),
);
/// Create a copy of the manager with the computed fields you want to use
final manager = db.managers.todoCategory.withFields([todoCountcomputedField]);
+52 -37
View File
@@ -26,9 +26,9 @@ extension SelectExamples on CanUseCommonTables {
// #docregion order-by
Future<List<TodoItem>> sortEntriesAlphabetically() {
return (select(todoItems)
..orderBy([(t) => OrderingTerm(expression: t.title)]))
.get();
return (select(
todoItems,
)..orderBy([(t) => OrderingTerm(expression: t.title)])).get();
}
// #enddocregion order-by
@@ -82,7 +82,7 @@ extension SelectExamples on CanUseCommonTables {
});
// #enddocregion results
}
// #enddocregion joinIntro
// #enddocregion joinIntro
// #docregion otherTodosInSameCategory
/// Searches for todo entries in the same category as the ones having
@@ -97,12 +97,17 @@ extension SelectExamples on CanUseCommonTables {
// In joins, `useColumns: false` tells drift to not add columns of the
// joined table to the result set. This is useful here, since we only join
// the tables so that we can refer to them in the where clause.
innerJoin(categories, categories.id.equalsExp(otherTodos.category),
useColumns: false),
innerJoin(todoItems, todoItems.category.equalsExp(categories.id),
useColumns: false),
])
..where(todoItems.title.contains(titleQuery));
innerJoin(
categories,
categories.id.equalsExp(otherTodos.category),
useColumns: false,
),
innerJoin(
todoItems,
todoItems.category.equalsExp(categories.id),
useColumns: false,
),
])..where(todoItems.title.contains(titleQuery));
return query.map((row) => row.readTable(otherTodos)).get();
}
@@ -117,7 +122,7 @@ extension SelectExamples on CanUseCommonTables {
todoItems,
todoItems.category.equalsExp(categories.id),
useColumns: false,
)
),
]);
query
..addColumns([amountOfTodos])
@@ -126,8 +131,10 @@ extension SelectExamples on CanUseCommonTables {
final result = await query.get();
for (final row in result) {
print('there are ${row.read(amountOfTodos)} entries in'
'${row.readTable(categories)}');
print(
'there are ${row.read(amountOfTodos)} entries in'
'${row.readTable(categories)}',
);
}
}
// #enddocregion countTodosInCategories
@@ -148,9 +155,9 @@ extension SelectExamples on CanUseCommonTables {
..where(todoItems.category.isNull())
..addColumns([newDescription]);
await into(categories).insertFromSelect(query, columns: {
categories.name: newDescription,
});
await into(
categories,
).insertFromSelect(query, columns: {categories.name: newDescription});
}
// #enddocregion createCategoryForUnassignedTodoEntries
@@ -168,19 +175,18 @@ extension SelectExamples on CanUseCommonTables {
// we're not selecting from `todos`. Instead, we'll use Subquery.ref to read
// from a column in a subquery.
final itemCount = longestTodos.ref(todoItems.title).count();
final query = select(categories).join(
[
innerJoin(
longestTodos,
// Again using .ref() here to access the category in the outer select
// statement.
longestTodos.ref(todoItems.category).equalsExp(categories.id),
useColumns: false,
)
],
)
..addColumns([itemCount])
..groupBy([categories.id]);
final query =
select(categories).join([
innerJoin(
longestTodos,
// Again using .ref() here to access the category in the outer select
// statement.
longestTodos.ref(todoItems.category).equalsExp(categories.id),
useColumns: false,
),
])
..addColumns([itemCount])
..groupBy([categories.id]);
final rows = await query.get();
@@ -214,23 +220,32 @@ extension SelectExamples on CanUseCommonTables {
// #docregion compound
Future<List<(String?, int)>> todoItemsInCategory() async {
final countWithCategory = subqueryExpression<int>(selectOnly(todoItems)
..addColumns([countAll()])
..where(todoItems.category.equalsExp(categories.id)));
final countWithCategory = subqueryExpression<int>(
selectOnly(todoItems)
..addColumns([countAll()])
..where(todoItems.category.equalsExp(categories.id)),
);
final countWithoutCategory = subqueryExpression<int>(selectOnly(todoItems)
..addColumns([countAll()])
..where(todoItems.category.isNull()));
final countWithoutCategory = subqueryExpression<int>(
selectOnly(todoItems)
..addColumns([countAll()])
..where(todoItems.category.isNull()),
);
final query = db.selectOnly(categories)
..addColumns([categories.name, countWithoutCategory])
..groupBy([categories.id]);
query.unionAll(db.selectExpressions(
[const Constant<String>(null), countWithoutCategory]));
query.unionAll(
db.selectExpressions([
const Constant<String>(null),
countWithoutCategory,
]),
);
return query
.map((row) => (row.read(categories.name), row.read(countWithCategory)!))
.get();
}
// #enddocregion compound
}
+6 -4
View File
@@ -12,10 +12,9 @@ extension CoreApi on AppDatabase {
// #docregion tableUpdates
Future<void> listenForUpdates() async {
final stream = tableUpdates(TableUpdateQuery.onTable(
todoItems,
limitUpdateKind: UpdateKind.update,
));
final stream = tableUpdates(
TableUpdateQuery.onTable(todoItems, limitUpdateKind: UpdateKind.update),
);
await for (final event in stream) {
print('Update on todos table: $event');
@@ -27,6 +26,7 @@ extension CoreApi on AppDatabase {
void markUpdated() {
notifyUpdates({TableUpdate.onTable(todoItems, kind: UpdateKind.insert)});
}
// #enddocregion markUpdated
}
@@ -35,6 +35,7 @@ extension Manager on AppDatabase {
Selectable<TodoItem> allItemsAfter(DateTime min) {
return managers.todoItems.filter((c) => c.createdAt.isAfter(min));
}
// #enddocregion manager
}
@@ -47,5 +48,6 @@ extension CustomQueries on AppDatabase {
readsFrom: {todoItems}, // (1)!
).map((row) => todoItems.map(row.data));
}
// #enddocregion custom
}
@@ -26,8 +26,9 @@ extension Snippets on CanUseCommonTables {
// this is a nested transaction:
await transaction(() async {
// At this point, the first category is visible
await into(categories)
.insert(CategoriesCompanion.insert(name: 'second'));
await into(
categories,
).insert(CategoriesCompanion.insert(name: 'second'));
// Here, the second category is only visible inside this nested
// transaction.
});
@@ -37,8 +38,9 @@ extension Snippets on CanUseCommonTables {
try {
await transaction(() async {
// At this point, both categories are visible
await into(categories)
.insert(CategoriesCompanion.insert(name: 'third'));
await into(
categories,
).insert(CategoriesCompanion.insert(name: 'third'));
// The third category is only visible here.
throw Exception('Abort in the second nested transaction');
});
@@ -54,5 +56,6 @@ extension Snippets on CanUseCommonTables {
// After the transaction, two categories are visible.
}
// #enddocregion nested
}
+4 -3
View File
@@ -26,8 +26,9 @@ abstract class CategoryTodoCount extends View {
Query as() =>
// Views can select columns defined as expression getters on the class, or
// they can reference columns from other tables.
select([categories.description, itemCount])
.from(categories)
.join([innerJoin(todos, todos.category.equalsExp(categories.id))]);
select([categories.description, itemCount]).from(categories).join([
innerJoin(todos, todos.category.equalsExp(categories.id)),
]);
}
// #enddocregion view
@@ -18,9 +18,10 @@ class CategoryWithCount {
@DriftDatabase(
tables: [TodoItems, Categories],
queries: {
'categoriesWithCount': 'SELECT *, '
'categoriesWithCount':
'SELECT *, '
'(SELECT COUNT(*) FROM todo_items WHERE category = c.id) AS "amount" '
'FROM categories c;'
'FROM categories c;',
},
)
class MyDatabase extends $MyDatabase {
@@ -69,16 +70,19 @@ class MyDatabase extends $MyDatabase {
// earlier, drift knows how to parse a category. The only thing left to do
// manually is extracting the amount.
return rows
.map((row) => CategoryWithCount(
category: categories.map(row.data),
count: row.read<int>('amount'),
))
.map(
(row) => CategoryWithCount(
category: categories.map(row.data),
count: row.read<int>('amount'),
),
)
.toList();
});
}
// #enddocregion manual
// #enddocregion manual
// #docregion setup
}
// #enddocregion setup
@@ -4,9 +4,7 @@ import 'package:drift/native.dart';
part 'database.g.dart';
@DriftDatabase(
include: {'tables.drift'},
)
@DriftDatabase(include: {'tables.drift'})
class MyDb extends _$MyDb {
// This example creates a simple in-memory database (without actual
// persistence).
@@ -23,5 +21,6 @@ extension MoreSnippets on MyDb {
Future<void> insert(TodosCompanion companion) async {
await into(todos).insert(companion);
}
// #enddocregion dart_interop_insert
}
@@ -25,7 +25,8 @@ QueryExecutor openConnection() {
final blob = await rootBundle.load('assets/my_database.db');
final buffer = blob.buffer;
await file.writeAsBytes(
buffer.asUint8List(blob.offsetInBytes, blob.lengthInBytes));
buffer.asUint8List(blob.offsetInBytes, blob.lengthInBytes),
);
}
return file.path;
@@ -20,7 +20,8 @@ QueryExecutor openConnection() {
final blob = await rootBundle.load('assets/my_database.db');
final buffer = blob.buffer;
await file.writeAsBytes(
buffer.asUint8List(blob.offsetInBytes, blob.lengthInBytes));
buffer.asUint8List(blob.offsetInBytes, blob.lengthInBytes),
);
}
// Also work around limitations on old Android versions
+25 -23
View File
@@ -113,10 +113,12 @@ void main() async {
void connectSynchronously() {
// #docregion delayed
MyDatabase(
DatabaseConnection.delayed(Future.sync(() async {
final isolate = await createIsolate();
return isolate.connect(singleClientMode: true);
})),
DatabaseConnection.delayed(
Future.sync(() async {
final isolate = await createIsolate();
return isolate.connect(singleClientMode: true);
}),
),
);
// #enddocregion delayed
}
@@ -167,10 +169,12 @@ class _IsolateStartRequest {
// #docregion init_connect
DatabaseConnection createDriftIsolateAndConnect() {
return DatabaseConnection.delayed(Future.sync(() async {
final isolate = await _createDriftIsolate();
return await isolate.connect(singleClientMode: true);
}));
return DatabaseConnection.delayed(
Future.sync(() async {
final isolate = await _createDriftIsolate();
return await isolate.connect(singleClientMode: true);
}),
);
}
// #enddocregion init_connect
@@ -233,21 +237,19 @@ Future<void> insertBulkData(MyDatabase database) async {
Future<void> customIsolateUsage(MyDatabase database) async {
final connection = await database.serializableConnection();
await Isolate.run(
() async {
// We can't share the [database] object across isolates, but the connection
// is fine!
final databaseForIsolate = MyDatabase(await connection.connect());
await Isolate.run(() async {
// We can't share the [database] object across isolates, but the connection
// is fine!
final databaseForIsolate = MyDatabase(await connection.connect());
try {
await databaseForIsolate.batch((batch) {
// (...)
});
} finally {
databaseForIsolate.close();
}
},
debugName: 'My custom database task',
);
try {
await databaseForIsolate.batch((batch) {
// (...)
});
} finally {
databaseForIsolate.close();
}
}, debugName: 'My custom database task');
}
// #enddocregion custom-compute
+44 -13
View File
@@ -9,7 +9,9 @@ import 'dart_api/manager.dart';
// #docregion class
class LogInterceptor extends QueryInterceptor {
Future<T> _run<T>(
String description, FutureOr<T> Function() operation) async {
String description,
FutureOr<T> Function() operation,
) async {
final stopwatch = Stopwatch()..start();
print('Running $description');
@@ -41,44 +43,73 @@ class LogInterceptor extends QueryInterceptor {
@override
Future<void> runBatched(
QueryExecutor executor, BatchedStatements statements) {
QueryExecutor executor,
BatchedStatements statements,
) {
return _run(
'batch with $statements', () => executor.runBatched(statements));
'batch with $statements',
() => executor.runBatched(statements),
);
}
@override
Future<int> runInsert(
QueryExecutor executor, String statement, List<Object?> args) {
QueryExecutor executor,
String statement,
List<Object?> args,
) {
return _run(
'$statement with $args', () => executor.runInsert(statement, args));
'$statement with $args',
() => executor.runInsert(statement, args),
);
}
@override
Future<int> runUpdate(
QueryExecutor executor, String statement, List<Object?> args) {
QueryExecutor executor,
String statement,
List<Object?> args,
) {
return _run(
'$statement with $args', () => executor.runUpdate(statement, args));
'$statement with $args',
() => executor.runUpdate(statement, args),
);
}
@override
Future<int> runDelete(
QueryExecutor executor, String statement, List<Object?> args) {
QueryExecutor executor,
String statement,
List<Object?> args,
) {
return _run(
'$statement with $args', () => executor.runDelete(statement, args));
'$statement with $args',
() => executor.runDelete(statement, args),
);
}
@override
Future<void> runCustom(
QueryExecutor executor, String statement, List<Object?> args) {
QueryExecutor executor,
String statement,
List<Object?> args,
) {
return _run(
'$statement with $args', () => executor.runCustom(statement, args));
'$statement with $args',
() => executor.runCustom(statement, args),
);
}
@override
Future<List<Map<String, Object?>>> runSelect(
QueryExecutor executor, String statement, List<Object?> args) {
QueryExecutor executor,
String statement,
List<Object?> args,
) {
return _run(
'$statement with $args', () => executor.runSelect(statement, args));
'$statement with $args',
() => executor.runSelect(statement, args),
);
}
}
// #enddocregion class
@@ -62,10 +62,13 @@ class MyDatabase extends _$MyDatabase {
// Assert that the schema is valid after migrations
if (kDebugMode) {
final wrongForeignKeys =
await customSelect('PRAGMA foreign_key_check').get();
assert(wrongForeignKeys.isEmpty,
'${wrongForeignKeys.map((e) => e.data)}');
final wrongForeignKeys = await customSelect(
'PRAGMA foreign_key_check',
).get();
assert(
wrongForeignKeys.isEmpty,
'${wrongForeignKeys.map((e) => e.data)}',
);
}
},
beforeOpen: (details) async {
@@ -81,9 +84,12 @@ class MyDatabase extends _$MyDatabase {
// #docregion change_type
from1To2: (m, schema) async {
await m.alterTable(
TableMigration(schema.todos, columnTransformer: {
todos.category: schema.todos.category.cast<int>(),
}),
TableMigration(
schema.todos,
columnTransformer: {
todos.category: schema.todos.category.cast<int>(),
},
),
);
},
// #enddocregion change_type
@@ -14,7 +14,7 @@ abstract class _$MyDatabase extends GeneratedDatabase {
// #docregion native
class MyDatabase extends _$MyDatabase {
// #enddocregion native
// #enddocregion native
MyDatabase(super.executor);
@override
@@ -27,17 +27,22 @@ class MyDatabase extends _$MyDatabase {
// #docregion native
@override
MigrationStrategy get migration => MigrationStrategy(
onCreate: (m) async {/* ... */},
onUpgrade: (m, from, to) async {/* your existing migration logic */},
beforeOpen: (details) async {
// your existing beforeOpen callback, enable foreign keys, etc.
onCreate: (m) async {
/* ... */
},
onUpgrade: (m, from, to) async {
/* your existing migration logic */
},
beforeOpen: (details) async {
// your existing beforeOpen callback, enable foreign keys, etc.
if (kDebugMode) {
// This check pulls in a fair amount of code that's not needed
// anywhere else, so we recommend only doing it in debug builds.
await validateDatabaseSchema();
}
},
);
if (kDebugMode) {
// This check pulls in a fair amount of code that's not needed
// anywhere else, so we recommend only doing it in debug builds.
await validateDatabaseSchema();
}
},
);
}
// #enddocregion native
@@ -15,7 +15,7 @@ abstract class _$MyDatabase extends GeneratedDatabase {
// #docregion web
class MyDatabase extends _$MyDatabase {
// #enddocregion web
// #enddocregion web
MyDatabase(super.executor);
@override
@@ -28,22 +28,29 @@ class MyDatabase extends _$MyDatabase {
// #docregion web
@override
MigrationStrategy get migration => MigrationStrategy(
onCreate: (m) async {/* ... */},
onUpgrade: (m, from, to) async {/* your existing migration logic */},
beforeOpen: (details) async {
// your existing beforeOpen callback, enable foreign keys, etc.
onCreate: (m) async {
/* ... */
},
onUpgrade: (m, from, to) async {
/* your existing migration logic */
},
beforeOpen: (details) async {
// your existing beforeOpen callback, enable foreign keys, etc.
// This check pulls in a fair amount of code that's not needed
// anywhere else, so we recommend only doing it in debug builds.
if (kDebugMode) {
// The web schema verifier needs a sqlite3 instance to open another
// version of your database so that the two can be compared.
final sqlite3 = await WasmSqlite3.loadFromUrl(Uri.parse('/'));
sqlite3.registerVirtualFileSystem(InMemoryFileSystem(),
makeDefault: true);
await validateDatabaseSchema(sqlite3: sqlite3);
}
},
);
// This check pulls in a fair amount of code that's not needed
// anywhere else, so we recommend only doing it in debug builds.
if (kDebugMode) {
// The web schema verifier needs a sqlite3 instance to open another
// version of your database so that the two can be compared.
final sqlite3 = await WasmSqlite3.loadFromUrl(Uri.parse('/'));
sqlite3.registerVirtualFileSystem(
InMemoryFileSystem(),
makeDefault: true,
);
await validateDatabaseSchema(sqlite3: sqlite3);
}
},
);
}
// #enddocregion web
@@ -7,25 +7,18 @@ import 'package:drift/drift.dart'; // ignore_for_file: type=lint,unused_import
final class Schema2 extends i0.VersionedSchema {
Schema2({required super.database}) : super(version: 2);
@override
late final List<i1.DatabaseSchemaEntity> entities = [
todos,
];
late final List<i1.DatabaseSchemaEntity> entities = [todos];
late final Shape0 todos = Shape0(
source: i0.VersionedTable(
entityName: 'todos',
withoutRowId: false,
isStrict: false,
tableConstraints: [],
columns: [
_column_0,
_column_1,
_column_2,
_column_3,
_column_4,
],
attachedDatabase: database,
),
alias: null);
source: i0.VersionedTable(
entityName: 'todos',
withoutRowId: false,
isStrict: false,
tableConstraints: [],
columns: [_column_0, _column_1, _column_2, _column_3, _column_4],
attachedDatabase: database,
),
alias: null,
);
}
class Shape0 extends i0.VersionedTable {
@@ -43,49 +36,71 @@ class Shape0 extends i0.VersionedTable {
}
i1.GeneratedColumn<int> _column_0(String aliasedName) =>
i1.GeneratedColumn<int>('id', aliasedName, false,
hasAutoIncrement: true,
type: i1.DriftSqlType.int,
defaultConstraints:
i1.GeneratedColumn.constraintIsAlways('PRIMARY KEY AUTOINCREMENT'));
i1.GeneratedColumn<int>(
'id',
aliasedName,
false,
hasAutoIncrement: true,
type: i1.DriftSqlType.int,
defaultConstraints: i1.GeneratedColumn.constraintIsAlways(
'PRIMARY KEY AUTOINCREMENT',
),
);
i1.GeneratedColumn<String> _column_1(String aliasedName) =>
i1.GeneratedColumn<String>('title', aliasedName, false,
additionalChecks: i1.GeneratedColumn.checkTextLength(
minTextLength: 6, maxTextLength: 10),
type: i1.DriftSqlType.string);
i1.GeneratedColumn<String>(
'title',
aliasedName,
false,
additionalChecks: i1.GeneratedColumn.checkTextLength(
minTextLength: 6,
maxTextLength: 10,
),
type: i1.DriftSqlType.string,
);
i1.GeneratedColumn<String> _column_2(String aliasedName) =>
i1.GeneratedColumn<String>('body', aliasedName, false,
type: i1.DriftSqlType.string);
i1.GeneratedColumn<String>(
'body',
aliasedName,
false,
type: i1.DriftSqlType.string,
);
i1.GeneratedColumn<int> _column_3(String aliasedName) =>
i1.GeneratedColumn<int>('category', aliasedName, true,
type: i1.DriftSqlType.int);
i1.GeneratedColumn<int>(
'category',
aliasedName,
true,
type: i1.DriftSqlType.int,
);
i1.GeneratedColumn<DateTime> _column_4(String aliasedName) =>
i1.GeneratedColumn<DateTime>('due_date', aliasedName, true,
type: i1.DriftSqlType.dateTime);
i1.GeneratedColumn<DateTime>(
'due_date',
aliasedName,
true,
type: i1.DriftSqlType.dateTime,
);
final class Schema3 extends i0.VersionedSchema {
Schema3({required super.database}) : super(version: 3);
@override
late final List<i1.DatabaseSchemaEntity> entities = [
todos,
];
late final List<i1.DatabaseSchemaEntity> entities = [todos];
late final Shape1 todos = Shape1(
source: i0.VersionedTable(
entityName: 'todos',
withoutRowId: false,
isStrict: false,
tableConstraints: [],
columns: [
_column_0,
_column_1,
_column_2,
_column_3,
_column_4,
_column_5,
],
attachedDatabase: database,
),
alias: null);
source: i0.VersionedTable(
entityName: 'todos',
withoutRowId: false,
isStrict: false,
tableConstraints: [],
columns: [
_column_0,
_column_1,
_column_2,
_column_3,
_column_4,
_column_5,
],
attachedDatabase: database,
),
alias: null,
);
}
class Shape1 extends i0.VersionedTable {
@@ -105,8 +120,12 @@ class Shape1 extends i0.VersionedTable {
}
i1.GeneratedColumn<int> _column_5(String aliasedName) =>
i1.GeneratedColumn<int>('priority', aliasedName, true,
type: i1.DriftSqlType.int);
i1.GeneratedColumn<int>(
'priority',
aliasedName,
true,
type: i1.DriftSqlType.int,
);
i0.MigrationStepWithVersion migrationSteps({
required Future<void> Function(i1.Migrator m, Schema2 schema) from1To2,
required Future<void> Function(i1.Migrator m, Schema3 schema) from2To3,
@@ -132,9 +151,6 @@ i0.MigrationStepWithVersion migrationSteps({
i1.OnUpgrade stepByStep({
required Future<void> Function(i1.Migrator m, Schema2 schema) from1To2,
required Future<void> Function(i1.Migrator m, Schema3 schema) from2To3,
}) =>
i0.VersionedSchema.stepByStepHelper(
step: migrationSteps(
from1To2: from1To2,
from2To3: from2To3,
));
}) => i0.VersionedSchema.stepByStepHelper(
step: migrationSteps(from1To2: from1To2, from2To3: from2To3),
);
@@ -31,6 +31,7 @@ class StepByStep {
),
);
}
// #enddocregion stepbystep
}
@@ -65,10 +66,13 @@ extension StepByStep2 on GeneratedDatabase {
if (kDebugMode) {
// Fail if the migration broke foreign keys
final wrongForeignKeys =
await customSelect('PRAGMA foreign_key_check').get();
assert(wrongForeignKeys.isEmpty,
'${wrongForeignKeys.map((e) => e.data)}');
final wrongForeignKeys = await customSelect(
'PRAGMA foreign_key_check',
).get();
assert(
wrongForeignKeys.isEmpty,
'${wrongForeignKeys.map((e) => e.data)}',
);
}
await customStatement('PRAGMA foreign_keys = ON;');
@@ -119,10 +123,13 @@ extension StepByStep3 on MyDatabase {
if (kDebugMode) {
// Fail if the migration broke foreign keys
final wrongForeignKeys =
await customSelect('PRAGMA foreign_key_check').get();
assert(wrongForeignKeys.isEmpty,
'${wrongForeignKeys.map((e) => e.data)}');
final wrongForeignKeys = await customSelect(
'PRAGMA foreign_key_check',
).get();
assert(
wrongForeignKeys.isEmpty,
'${wrongForeignKeys.map((e) => e.data)}',
);
}
await customStatement('PRAGMA foreign_keys = ON;');
@@ -9,24 +9,41 @@ class Todos extends Table with TableInfo<Todos, TodosData> {
final String? _alias;
Todos(this.attachedDatabase, [this._alias]);
late final GeneratedColumn<int> id = GeneratedColumn<int>(
'id', aliasedName, false,
hasAutoIncrement: true,
type: DriftSqlType.int,
requiredDuringInsert: false,
defaultConstraints:
GeneratedColumn.constraintIsAlways('PRIMARY KEY AUTOINCREMENT'));
'id',
aliasedName,
false,
hasAutoIncrement: true,
type: DriftSqlType.int,
requiredDuringInsert: false,
defaultConstraints: GeneratedColumn.constraintIsAlways(
'PRIMARY KEY AUTOINCREMENT',
),
);
late final GeneratedColumn<String> title = GeneratedColumn<String>(
'title', aliasedName, false,
additionalChecks:
GeneratedColumn.checkTextLength(minTextLength: 6, maxTextLength: 10),
type: DriftSqlType.string,
requiredDuringInsert: true);
'title',
aliasedName,
false,
additionalChecks: GeneratedColumn.checkTextLength(
minTextLength: 6,
maxTextLength: 10,
),
type: DriftSqlType.string,
requiredDuringInsert: true,
);
late final GeneratedColumn<String> content = GeneratedColumn<String>(
'body', aliasedName, false,
type: DriftSqlType.string, requiredDuringInsert: true);
'body',
aliasedName,
false,
type: DriftSqlType.string,
requiredDuringInsert: true,
);
late final GeneratedColumn<int> category = GeneratedColumn<int>(
'category', aliasedName, true,
type: DriftSqlType.int, requiredDuringInsert: false);
'category',
aliasedName,
true,
type: DriftSqlType.int,
requiredDuringInsert: false,
);
@override
List<GeneratedColumn> get $columns => [id, title, content, category];
@override
@@ -40,14 +57,22 @@ class Todos extends Table with TableInfo<Todos, TodosData> {
TodosData map(Map<String, dynamic> data, {String? tablePrefix}) {
final effectivePrefix = tablePrefix != null ? '$tablePrefix.' : '';
return TodosData(
id: attachedDatabase.typeMapping
.read(DriftSqlType.int, data['${effectivePrefix}id'])!,
title: attachedDatabase.typeMapping
.read(DriftSqlType.string, data['${effectivePrefix}title'])!,
content: attachedDatabase.typeMapping
.read(DriftSqlType.string, data['${effectivePrefix}body'])!,
category: attachedDatabase.typeMapping
.read(DriftSqlType.int, data['${effectivePrefix}category']),
id: attachedDatabase.typeMapping.read(
DriftSqlType.int,
data['${effectivePrefix}id'],
)!,
title: attachedDatabase.typeMapping.read(
DriftSqlType.string,
data['${effectivePrefix}title'],
)!,
content: attachedDatabase.typeMapping.read(
DriftSqlType.string,
data['${effectivePrefix}body'],
)!,
category: attachedDatabase.typeMapping.read(
DriftSqlType.int,
data['${effectivePrefix}category'],
),
);
}
@@ -62,11 +87,12 @@ class TodosData extends DataClass implements Insertable<TodosData> {
final String title;
final String content;
final int? category;
const TodosData(
{required this.id,
required this.title,
required this.content,
this.category});
const TodosData({
required this.id,
required this.title,
required this.content,
this.category,
});
@override
Map<String, Expression> toColumns(bool nullToAbsent) {
final map = <String, Expression>{};
@@ -90,8 +116,10 @@ class TodosData extends DataClass implements Insertable<TodosData> {
);
}
factory TodosData.fromJson(Map<String, dynamic> json,
{ValueSerializer? serializer}) {
factory TodosData.fromJson(
Map<String, dynamic> json, {
ValueSerializer? serializer,
}) {
serializer ??= driftRuntimeOptions.defaultSerializer;
return TodosData(
id: serializer.fromJson<int>(json['id']),
@@ -111,17 +139,17 @@ class TodosData extends DataClass implements Insertable<TodosData> {
};
}
TodosData copyWith(
{int? id,
String? title,
String? content,
Value<int?> category = const Value.absent()}) =>
TodosData(
id: id ?? this.id,
title: title ?? this.title,
content: content ?? this.content,
category: category.present ? category.value : this.category,
);
TodosData copyWith({
int? id,
String? title,
String? content,
Value<int?> category = const Value.absent(),
}) => TodosData(
id: id ?? this.id,
title: title ?? this.title,
content: content ?? this.content,
category: category.present ? category.value : this.category,
);
TodosData copyWithCompanion(TodosCompanion data) {
return TodosData(
id: data.id.present ? data.id.value : this.id,
@@ -170,8 +198,8 @@ class TodosCompanion extends UpdateCompanion<TodosData> {
required String title,
required String content,
this.category = const Value.absent(),
}) : title = Value(title),
content = Value(content);
}) : title = Value(title),
content = Value(content);
static Insertable<TodosData> custom({
Expression<int>? id,
Expression<String>? title,
@@ -186,11 +214,12 @@ class TodosCompanion extends UpdateCompanion<TodosData> {
});
}
TodosCompanion copyWith(
{Value<int>? id,
Value<String>? title,
Value<String>? content,
Value<int?>? category}) {
TodosCompanion copyWith({
Value<int>? id,
Value<String>? title,
Value<String>? content,
Value<int?>? category,
}) {
return TodosCompanion(
id: id ?? this.id,
title: title ?? this.title,
@@ -9,27 +9,48 @@ class Todos extends Table with TableInfo<Todos, TodosData> {
final String? _alias;
Todos(this.attachedDatabase, [this._alias]);
late final GeneratedColumn<int> id = GeneratedColumn<int>(
'id', aliasedName, false,
hasAutoIncrement: true,
type: DriftSqlType.int,
requiredDuringInsert: false,
defaultConstraints:
GeneratedColumn.constraintIsAlways('PRIMARY KEY AUTOINCREMENT'));
'id',
aliasedName,
false,
hasAutoIncrement: true,
type: DriftSqlType.int,
requiredDuringInsert: false,
defaultConstraints: GeneratedColumn.constraintIsAlways(
'PRIMARY KEY AUTOINCREMENT',
),
);
late final GeneratedColumn<String> title = GeneratedColumn<String>(
'title', aliasedName, false,
additionalChecks:
GeneratedColumn.checkTextLength(minTextLength: 6, maxTextLength: 10),
type: DriftSqlType.string,
requiredDuringInsert: true);
'title',
aliasedName,
false,
additionalChecks: GeneratedColumn.checkTextLength(
minTextLength: 6,
maxTextLength: 10,
),
type: DriftSqlType.string,
requiredDuringInsert: true,
);
late final GeneratedColumn<String> content = GeneratedColumn<String>(
'body', aliasedName, false,
type: DriftSqlType.string, requiredDuringInsert: true);
'body',
aliasedName,
false,
type: DriftSqlType.string,
requiredDuringInsert: true,
);
late final GeneratedColumn<int> category = GeneratedColumn<int>(
'category', aliasedName, true,
type: DriftSqlType.int, requiredDuringInsert: false);
'category',
aliasedName,
true,
type: DriftSqlType.int,
requiredDuringInsert: false,
);
late final GeneratedColumn<DateTime> dueDate = GeneratedColumn<DateTime>(
'due_date', aliasedName, true,
type: DriftSqlType.dateTime, requiredDuringInsert: false);
'due_date',
aliasedName,
true,
type: DriftSqlType.dateTime,
requiredDuringInsert: false,
);
@override
List<GeneratedColumn> get $columns => [id, title, content, category, dueDate];
@override
@@ -43,16 +64,26 @@ class Todos extends Table with TableInfo<Todos, TodosData> {
TodosData map(Map<String, dynamic> data, {String? tablePrefix}) {
final effectivePrefix = tablePrefix != null ? '$tablePrefix.' : '';
return TodosData(
id: attachedDatabase.typeMapping
.read(DriftSqlType.int, data['${effectivePrefix}id'])!,
title: attachedDatabase.typeMapping
.read(DriftSqlType.string, data['${effectivePrefix}title'])!,
content: attachedDatabase.typeMapping
.read(DriftSqlType.string, data['${effectivePrefix}body'])!,
category: attachedDatabase.typeMapping
.read(DriftSqlType.int, data['${effectivePrefix}category']),
dueDate: attachedDatabase.typeMapping
.read(DriftSqlType.dateTime, data['${effectivePrefix}due_date']),
id: attachedDatabase.typeMapping.read(
DriftSqlType.int,
data['${effectivePrefix}id'],
)!,
title: attachedDatabase.typeMapping.read(
DriftSqlType.string,
data['${effectivePrefix}title'],
)!,
content: attachedDatabase.typeMapping.read(
DriftSqlType.string,
data['${effectivePrefix}body'],
)!,
category: attachedDatabase.typeMapping.read(
DriftSqlType.int,
data['${effectivePrefix}category'],
),
dueDate: attachedDatabase.typeMapping.read(
DriftSqlType.dateTime,
data['${effectivePrefix}due_date'],
),
);
}
@@ -68,12 +99,13 @@ class TodosData extends DataClass implements Insertable<TodosData> {
final String content;
final int? category;
final DateTime? dueDate;
const TodosData(
{required this.id,
required this.title,
required this.content,
this.category,
this.dueDate});
const TodosData({
required this.id,
required this.title,
required this.content,
this.category,
this.dueDate,
});
@override
Map<String, Expression> toColumns(bool nullToAbsent) {
final map = <String, Expression>{};
@@ -103,8 +135,10 @@ class TodosData extends DataClass implements Insertable<TodosData> {
);
}
factory TodosData.fromJson(Map<String, dynamic> json,
{ValueSerializer? serializer}) {
factory TodosData.fromJson(
Map<String, dynamic> json, {
ValueSerializer? serializer,
}) {
serializer ??= driftRuntimeOptions.defaultSerializer;
return TodosData(
id: serializer.fromJson<int>(json['id']),
@@ -126,19 +160,19 @@ class TodosData extends DataClass implements Insertable<TodosData> {
};
}
TodosData copyWith(
{int? id,
String? title,
String? content,
Value<int?> category = const Value.absent(),
Value<DateTime?> dueDate = const Value.absent()}) =>
TodosData(
id: id ?? this.id,
title: title ?? this.title,
content: content ?? this.content,
category: category.present ? category.value : this.category,
dueDate: dueDate.present ? dueDate.value : this.dueDate,
);
TodosData copyWith({
int? id,
String? title,
String? content,
Value<int?> category = const Value.absent(),
Value<DateTime?> dueDate = const Value.absent(),
}) => TodosData(
id: id ?? this.id,
title: title ?? this.title,
content: content ?? this.content,
category: category.present ? category.value : this.category,
dueDate: dueDate.present ? dueDate.value : this.dueDate,
);
TodosData copyWithCompanion(TodosCompanion data) {
return TodosData(
id: data.id.present ? data.id.value : this.id,
@@ -193,8 +227,8 @@ class TodosCompanion extends UpdateCompanion<TodosData> {
required String content,
this.category = const Value.absent(),
this.dueDate = const Value.absent(),
}) : title = Value(title),
content = Value(content);
}) : title = Value(title),
content = Value(content);
static Insertable<TodosData> custom({
Expression<int>? id,
Expression<String>? title,
@@ -211,12 +245,13 @@ class TodosCompanion extends UpdateCompanion<TodosData> {
});
}
TodosCompanion copyWith(
{Value<int>? id,
Value<String>? title,
Value<String>? content,
Value<int?>? category,
Value<DateTime?>? dueDate}) {
TodosCompanion copyWith({
Value<int>? id,
Value<String>? title,
Value<String>? content,
Value<int?>? category,
Value<DateTime?>? dueDate,
}) {
return TodosCompanion(
id: id ?? this.id,
title: title ?? this.title,
@@ -9,33 +9,64 @@ class Todos extends Table with TableInfo<Todos, TodosData> {
final String? _alias;
Todos(this.attachedDatabase, [this._alias]);
late final GeneratedColumn<int> id = GeneratedColumn<int>(
'id', aliasedName, false,
hasAutoIncrement: true,
type: DriftSqlType.int,
requiredDuringInsert: false,
defaultConstraints:
GeneratedColumn.constraintIsAlways('PRIMARY KEY AUTOINCREMENT'));
'id',
aliasedName,
false,
hasAutoIncrement: true,
type: DriftSqlType.int,
requiredDuringInsert: false,
defaultConstraints: GeneratedColumn.constraintIsAlways(
'PRIMARY KEY AUTOINCREMENT',
),
);
late final GeneratedColumn<String> title = GeneratedColumn<String>(
'title', aliasedName, false,
additionalChecks:
GeneratedColumn.checkTextLength(minTextLength: 6, maxTextLength: 10),
type: DriftSqlType.string,
requiredDuringInsert: true);
'title',
aliasedName,
false,
additionalChecks: GeneratedColumn.checkTextLength(
minTextLength: 6,
maxTextLength: 10,
),
type: DriftSqlType.string,
requiredDuringInsert: true,
);
late final GeneratedColumn<String> content = GeneratedColumn<String>(
'body', aliasedName, false,
type: DriftSqlType.string, requiredDuringInsert: true);
'body',
aliasedName,
false,
type: DriftSqlType.string,
requiredDuringInsert: true,
);
late final GeneratedColumn<int> category = GeneratedColumn<int>(
'category', aliasedName, true,
type: DriftSqlType.int, requiredDuringInsert: false);
'category',
aliasedName,
true,
type: DriftSqlType.int,
requiredDuringInsert: false,
);
late final GeneratedColumn<DateTime> dueDate = GeneratedColumn<DateTime>(
'due_date', aliasedName, true,
type: DriftSqlType.dateTime, requiredDuringInsert: false);
'due_date',
aliasedName,
true,
type: DriftSqlType.dateTime,
requiredDuringInsert: false,
);
late final GeneratedColumn<int> priority = GeneratedColumn<int>(
'priority', aliasedName, true,
type: DriftSqlType.int, requiredDuringInsert: false);
'priority',
aliasedName,
true,
type: DriftSqlType.int,
requiredDuringInsert: false,
);
@override
List<GeneratedColumn> get $columns =>
[id, title, content, category, dueDate, priority];
List<GeneratedColumn> get $columns => [
id,
title,
content,
category,
dueDate,
priority,
];
@override
String get aliasedName => _alias ?? actualTableName;
@override
@@ -47,18 +78,30 @@ class Todos extends Table with TableInfo<Todos, TodosData> {
TodosData map(Map<String, dynamic> data, {String? tablePrefix}) {
final effectivePrefix = tablePrefix != null ? '$tablePrefix.' : '';
return TodosData(
id: attachedDatabase.typeMapping
.read(DriftSqlType.int, data['${effectivePrefix}id'])!,
title: attachedDatabase.typeMapping
.read(DriftSqlType.string, data['${effectivePrefix}title'])!,
content: attachedDatabase.typeMapping
.read(DriftSqlType.string, data['${effectivePrefix}body'])!,
category: attachedDatabase.typeMapping
.read(DriftSqlType.int, data['${effectivePrefix}category']),
dueDate: attachedDatabase.typeMapping
.read(DriftSqlType.dateTime, data['${effectivePrefix}due_date']),
priority: attachedDatabase.typeMapping
.read(DriftSqlType.int, data['${effectivePrefix}priority']),
id: attachedDatabase.typeMapping.read(
DriftSqlType.int,
data['${effectivePrefix}id'],
)!,
title: attachedDatabase.typeMapping.read(
DriftSqlType.string,
data['${effectivePrefix}title'],
)!,
content: attachedDatabase.typeMapping.read(
DriftSqlType.string,
data['${effectivePrefix}body'],
)!,
category: attachedDatabase.typeMapping.read(
DriftSqlType.int,
data['${effectivePrefix}category'],
),
dueDate: attachedDatabase.typeMapping.read(
DriftSqlType.dateTime,
data['${effectivePrefix}due_date'],
),
priority: attachedDatabase.typeMapping.read(
DriftSqlType.int,
data['${effectivePrefix}priority'],
),
);
}
@@ -75,13 +118,14 @@ class TodosData extends DataClass implements Insertable<TodosData> {
final int? category;
final DateTime? dueDate;
final int? priority;
const TodosData(
{required this.id,
required this.title,
required this.content,
this.category,
this.dueDate,
this.priority});
const TodosData({
required this.id,
required this.title,
required this.content,
this.category,
this.dueDate,
this.priority,
});
@override
Map<String, Expression> toColumns(bool nullToAbsent) {
final map = <String, Expression>{};
@@ -117,8 +161,10 @@ class TodosData extends DataClass implements Insertable<TodosData> {
);
}
factory TodosData.fromJson(Map<String, dynamic> json,
{ValueSerializer? serializer}) {
factory TodosData.fromJson(
Map<String, dynamic> json, {
ValueSerializer? serializer,
}) {
serializer ??= driftRuntimeOptions.defaultSerializer;
return TodosData(
id: serializer.fromJson<int>(json['id']),
@@ -142,21 +188,21 @@ class TodosData extends DataClass implements Insertable<TodosData> {
};
}
TodosData copyWith(
{int? id,
String? title,
String? content,
Value<int?> category = const Value.absent(),
Value<DateTime?> dueDate = const Value.absent(),
Value<int?> priority = const Value.absent()}) =>
TodosData(
id: id ?? this.id,
title: title ?? this.title,
content: content ?? this.content,
category: category.present ? category.value : this.category,
dueDate: dueDate.present ? dueDate.value : this.dueDate,
priority: priority.present ? priority.value : this.priority,
);
TodosData copyWith({
int? id,
String? title,
String? content,
Value<int?> category = const Value.absent(),
Value<DateTime?> dueDate = const Value.absent(),
Value<int?> priority = const Value.absent(),
}) => TodosData(
id: id ?? this.id,
title: title ?? this.title,
content: content ?? this.content,
category: category.present ? category.value : this.category,
dueDate: dueDate.present ? dueDate.value : this.dueDate,
priority: priority.present ? priority.value : this.priority,
);
TodosData copyWithCompanion(TodosCompanion data) {
return TodosData(
id: data.id.present ? data.id.value : this.id,
@@ -218,8 +264,8 @@ class TodosCompanion extends UpdateCompanion<TodosData> {
this.category = const Value.absent(),
this.dueDate = const Value.absent(),
this.priority = const Value.absent(),
}) : title = Value(title),
content = Value(content);
}) : title = Value(title),
content = Value(content);
static Insertable<TodosData> custom({
Expression<int>? id,
Expression<String>? title,
@@ -238,13 +284,14 @@ class TodosCompanion extends UpdateCompanion<TodosData> {
});
}
TodosCompanion copyWith(
{Value<int>? id,
Value<String>? title,
Value<String>? content,
Value<int?>? category,
Value<DateTime?>? dueDate,
Value<int?>? priority}) {
TodosCompanion copyWith({
Value<int>? id,
Value<String>? title,
Value<String>? content,
Value<int?>? category,
Value<DateTime?>? dueDate,
Value<int?>? priority,
}) {
return TodosCompanion(
id: id ?? this.id,
title: title ?? this.title,
@@ -29,4 +29,5 @@ void main() {
await verifier.migrateAndValidate(db, 2);
});
}
// #enddocregion setup
@@ -11,7 +11,7 @@ import 'generated_migrations/schema_v2.dart' as v2;
// #docregion main
void main() {
// #enddocregion main
// #enddocregion main
late SchemaVerifier verifier;
setUpAll(() {
@@ -20,17 +20,21 @@ void main() {
verifier = SchemaVerifier(GeneratedHelper());
});
// #docregion main
// #docregion main
// ...
test('upgrade from v1 to v2', () async {
final schema = await verifier.schemaAt(1);
// Add some data to the table being migrated
final oldDb = v1.DatabaseAtV1(schema.newConnection());
await oldDb.into(oldDb.todos).insert(v1.TodosCompanion.insert(
title: 'my first todo entry',
content: 'should still be there after the migration',
));
await oldDb
.into(oldDb.todos)
.insert(
v1.TodosCompanion.insert(
title: 'my first todo entry',
content: 'should still be there after the migration',
),
);
await oldDb.close();
// Run the migration and verify that it adds the name column.
@@ -46,4 +50,5 @@ void main() {
await migratedDb.close();
});
}
// #enddocregion main
@@ -3,7 +3,8 @@ import 'type.dart';
class PeriodicReminders extends Table {
IntColumn get id => integer().autoIncrement()();
Column<Duration> get frequency => customType(const DurationType())
.clientDefault(() => Duration(minutes: 15))();
Column<Duration> get frequency => customType(
const DurationType(),
).clientDefault(() => Duration(minutes: 15))();
TextColumn get reminder => text()();
}
@@ -47,8 +47,6 @@ class _FallbackDurationType implements CustomSqlType<Duration> {
const durationType = DialectAwareSqlType<Duration>.via(
fallback: _FallbackDurationType(),
overrides: {
SqlDialect.postgres: DurationType(),
},
overrides: {SqlDialect.postgres: DurationType()},
);
// #enddocregion fallback
@@ -7,5 +7,6 @@ class DartExample extends ExampleDrift {
Stream<List<Todo>> watchInCategory(int category) {
return filterTodos((todos) => todos.category.equals(category)).watch();
}
// #enddocregion watchInCategory
}
@@ -14,4 +14,5 @@ class UserWithFriends {
UserWithFriends(this.user, {this.friends = const []});
}
// #enddocregion userwithfriends
@@ -8,10 +8,7 @@ import 'shared.drift.dart';
part 'json.g.dart';
typedef ShoppingCartWithItems = ({
ShoppingCart cart,
List<BuyableItem> items,
});
typedef ShoppingCartWithItems = ({ShoppingCart cart, List<BuyableItem> items});
// #docregion tables
@DataClassName('ShoppingCart')
@@ -54,8 +51,9 @@ class JsonBasedDatabase extends $JsonBasedDatabase {
// #docregion createEmptyCart
Future<ShoppingCartWithItems> createEmptyCart() async {
final cart = await into(shoppingCarts)
.insertReturning(const ShoppingCartsCompanion());
final cart = await into(
shoppingCarts,
).insertReturning(const ShoppingCartsCompanion());
// we set the items property to [] because we've just created the cart - it
// will be empty
@@ -65,10 +63,13 @@ class JsonBasedDatabase extends $JsonBasedDatabase {
// #docregion updateCart
Future<void> updateCart(ShoppingCartWithItems entry) async {
await update(shoppingCarts).replace(entry.cart.copyWith(
entries: ShoppingCartEntries(items: [
for (final item in entry.items) item.id,
])));
await update(shoppingCarts).replace(
entry.cart.copyWith(
entries: ShoppingCartEntries(
items: [for (final item in entry.items) item.id],
),
),
);
}
// #enddocregion updateCart
@@ -76,17 +77,15 @@ class JsonBasedDatabase extends $JsonBasedDatabase {
Stream<ShoppingCartWithItems> watchCart(int id) {
final referencedItems = shoppingCarts.entries.jsonEach(this, r'#$.items');
final cartWithEntries = select(shoppingCarts).join(
[
// Join every referenced item from the json array
innerJoin(referencedItems, const Constant(true), useColumns: false),
// And use that to join the items
innerJoin(
buyableItems,
buyableItems.id.equalsExp(referencedItems.value.cast()),
),
],
)..where(shoppingCarts.id.equals(id));
final cartWithEntries = select(shoppingCarts).join([
// Join every referenced item from the json array
innerJoin(referencedItems, const Constant(true), useColumns: false),
// And use that to join the items
innerJoin(
buyableItems,
buyableItems.id.equalsExp(referencedItems.value.cast()),
),
])..where(shoppingCarts.id.equals(id));
return cartWithEntries.watch().map((rows) {
late ShoppingCart cart;
@@ -106,17 +105,15 @@ class JsonBasedDatabase extends $JsonBasedDatabase {
Stream<List<ShoppingCartWithItems>> watchAllCarts() {
final referencedItems = shoppingCarts.entries.jsonEach(this, r'#$.items');
final cartWithEntries = select(shoppingCarts).join(
[
// Join every referenced item from the json array
innerJoin(referencedItems, const Constant(true), useColumns: false),
// And use that to join the items
innerJoin(
buyableItems,
buyableItems.id.equalsExp(referencedItems.value.cast()),
),
],
);
final cartWithEntries = select(shoppingCarts).join([
// Join every referenced item from the json array
innerJoin(referencedItems, const Constant(true), useColumns: false),
// And use that to join the items
innerJoin(
buyableItems,
buyableItems.id.equalsExp(referencedItems.value.cast()),
),
]);
return cartWithEntries.watch().map((rows) {
final entriesByCart = <ShoppingCart, List<BuyableItem>>{};
@@ -130,9 +127,10 @@ class JsonBasedDatabase extends $JsonBasedDatabase {
return [
for (final entry in entriesByCart.entries)
(cart: entry.key, items: entry.value)
(cart: entry.key, items: entry.value),
];
});
}
// #enddocregion watchAllCarts
}
@@ -6,10 +6,7 @@ import 'shared.drift.dart';
import 'relational.drift.dart';
typedef ShoppingCartWithItems = ({
ShoppingCart cart,
List<BuyableItem> items,
});
typedef ShoppingCartWithItems = ({ShoppingCart cart, List<BuyableItem> items});
// #docregion cart_tables
class ShoppingCarts extends Table {
@@ -45,14 +42,15 @@ class RelationalDatabase extends $RelationalDatabase {
await update(shoppingCarts).replace(cart);
// we replace the entries of the cart, so first delete the old ones
await (delete(shoppingCartEntries)
..where((entry) => entry.shoppingCart.equals(cart.id)))
.go();
await (delete(
shoppingCartEntries,
)..where((entry) => entry.shoppingCart.equals(cart.id))).go();
// And write the new ones
for (final item in entry.items) {
await into(shoppingCartEntries)
.insert(ShoppingCartEntry(shoppingCart: cart.id, item: item.id));
await into(
shoppingCartEntries,
).insert(ShoppingCartEntry(shoppingCart: cart.id, item: item.id));
}
});
}
@@ -60,8 +58,9 @@ class RelationalDatabase extends $RelationalDatabase {
// #docregion createEmptyCart
Future<ShoppingCartWithItems> createEmptyCart() async {
final cart = await into(shoppingCarts)
.insertReturning(const ShoppingCartsCompanion());
final cart = await into(
shoppingCarts,
).insertReturning(const ShoppingCartsCompanion());
// we set the items property to [] because we've just created the cart - it
// will be empty
return (cart: cart, items: <BuyableItem>[]);
@@ -75,14 +74,12 @@ class RelationalDatabase extends $RelationalDatabase {
..where((cart) => cart.id.equals(id));
// and also load information about the entries in this cart
final contentQuery = select(shoppingCartEntries).join(
[
innerJoin(
buyableItems,
buyableItems.id.equalsExp(shoppingCartEntries.item),
),
],
)..where(shoppingCartEntries.shoppingCart.equals(id));
final contentQuery = select(shoppingCartEntries).join([
innerJoin(
buyableItems,
buyableItems.id.equalsExp(shoppingCartEntries.item),
),
])..where(shoppingCartEntries.shoppingCart.equals(id));
final cartStream = cartQuery.watchSingle();
@@ -93,8 +90,10 @@ class RelationalDatabase extends $RelationalDatabase {
});
// now, we can merge the two queries together in one stream
return Rx.combineLatest2(cartStream, contentStream,
(ShoppingCart cart, List<BuyableItem> items) {
return Rx.combineLatest2(cartStream, contentStream, (
ShoppingCart cart,
List<BuyableItem> items,
) {
return (cart: cart, items: items);
});
}
@@ -113,14 +112,12 @@ class RelationalDatabase extends $RelationalDatabase {
final ids = idToCart.keys;
// select all entries that are included in any cart that we found
final entryQuery = select(shoppingCartEntries).join(
[
innerJoin(
buyableItems,
buyableItems.id.equalsExp(shoppingCartEntries.item),
)
],
)..where(shoppingCartEntries.shoppingCart.isIn(ids));
final entryQuery = select(shoppingCartEntries).join([
innerJoin(
buyableItems,
buyableItems.id.equalsExp(shoppingCartEntries.item),
),
])..where(shoppingCartEntries.shoppingCart.isIn(ids));
return entryQuery.watch().map((rows) {
// Store the list of entries for each cart, again using maps for faster
@@ -144,5 +141,6 @@ class RelationalDatabase extends $RelationalDatabase {
});
});
}
// #enddocregion watchAllCarts
}
@@ -9,10 +9,7 @@ abstract class ShoppingCart {
}
// #docregion interface
typedef ShoppingCartWithItems = ({
ShoppingCart cart,
List<BuyableItem> items,
});
typedef ShoppingCartWithItems = ({ShoppingCart cart, List<BuyableItem> items});
abstract class CartRepository {
Future<ShoppingCartWithItems> createEmptyCart();
@@ -30,4 +27,5 @@ class BuyableItems extends Table {
IntColumn get price => integer()();
// we could add more columns as we wish.
}
// #enddocregion buyable_items
@@ -6,21 +6,23 @@ import 'drift/example.drift.dart';
extension FindById<Table extends HasResultSet, Row>
on ResultSetImplementation<Table, Row> {
Selectable<Row> findById(int id) {
return select()
..where((row) {
final idColumn = columnsByName['id'];
return select()..where((row) {
final idColumn = columnsByName['id'];
if (idColumn == null) {
throw ArgumentError.value(
this, 'this', 'Must be a table with an id column');
}
if (idColumn == null) {
throw ArgumentError.value(
this,
'this',
'Must be a table with an id column',
);
}
if (idColumn.type != DriftSqlType.int) {
throw ArgumentError('Column `id` is not an integer');
}
if (idColumn.type != DriftSqlType.int) {
throw ArgumentError('Column `id` is not an integer');
}
return idColumn.equals(id);
});
return idColumn.equals(id);
});
}
}
// #enddocregion findById
@@ -28,7 +30,10 @@ extension FindById<Table extends HasResultSet, Row>
// #docregion updateTitle
extension UpdateTitle on DatabaseConnectionUser {
Future<Row?> updateTitle<T extends TableInfo<Table, Row>, Row>(
T table, int id, String newTitle) async {
T table,
int id,
String newTitle,
) async {
final columnsByName = table.columnsByName;
final stmt = update(table)
..where((tbl) {
@@ -36,7 +41,10 @@ extension UpdateTitle on DatabaseConnectionUser {
if (idColumn == null) {
throw ArgumentError.value(
this, 'this', 'Must be a table with an id column');
this,
'this',
'Must be a table with an id column',
);
}
if (idColumn.type != DriftSqlType.int) {
@@ -46,9 +54,9 @@ extension UpdateTitle on DatabaseConnectionUser {
return idColumn.equals(id);
});
final rows = await stmt.writeReturning(RawValuesInsertable({
'title': Variable<String>(newTitle),
}));
final rows = await stmt.writeReturning(
RawValuesInsertable({'title': Variable<String>(newTitle)}),
);
return rows.singleOrNull;
}
@@ -68,5 +76,6 @@ extension FindTodoEntryById on GeneratedDatabase {
Future<Todo?> updateTodoTitle(int id, String newTitle) {
return updateTitle(todos, id, newTitle);
}
// #enddocregion updateTodo
}
+16 -7
View File
@@ -22,8 +22,8 @@ class MatchResults extends Table {
@override
List<Set<Column<Object>>>? get uniqueKeys => [
{teamA, teamB}
];
{teamA, teamB},
];
}
// #enddocregion upsert-target
@@ -36,7 +36,8 @@ extension DocumentationSnippets on ModularAccessor {
return into(words).insert(
WordsCompanion.insert(word: word),
onConflict: DoUpdate(
(old) => WordsCompanion.custom(usages: old.usages + Constant(1))),
(old) => WordsCompanion.custom(usages: old.usages + Constant(1)),
),
);
}
// #enddocregion track-word
@@ -44,11 +45,19 @@ extension DocumentationSnippets on ModularAccessor {
// #docregion upsert-target
Future<void> insertMatch(String teamA, String teamB, bool teamAWon) {
final data = MatchResultsCompanion.insert(
teamA: teamA, teamB: teamB, teamAWon: teamAWon);
teamA: teamA,
teamB: teamB,
teamAWon: teamAWon,
);
return into(matches).insert(data,
onConflict:
DoUpdate((old) => data, target: [matches.teamA, matches.teamB]));
return into(matches).insert(
data,
onConflict: DoUpdate(
(old) => data,
target: [matches.teamA, matches.teamB],
),
);
}
// #enddocregion upsert-target
}
@@ -5,8 +5,11 @@ import 'package:drift/web/worker.dart';
class Approach1 {
// #docregion approach1
Future<DatabaseConnection> connectToWorker() async {
return await connectToDriftWorker('/database_worker.dart.js',
mode: DriftWorkerMode.dedicatedInShared);
return await connectToDriftWorker(
'/database_worker.dart.js',
mode: DriftWorkerMode.dedicatedInShared,
);
}
// #enddocregion approach1
}
@@ -13,4 +13,5 @@ DynamicLibrary _openOnLinux() {
final libraryNextToScript = File('${scriptDir.path}/sqlite3.so');
return DynamicLibrary.open(libraryNextToScript.path);
}
// _openOnWindows could be implemented similarly by opening `sqlite3.dll`
@@ -125,4 +125,5 @@ class DialectAwareTime extends Table {
// default date type (integer or text) on sqlite databases.
Column<PgDateTime> get timeValue => customType(dateTime)();
}
// #enddocregion time-dialectaware
+1
View File
@@ -50,4 +50,5 @@ QueryExecutor openMultiThreadedDatabase() {
readPool: 4,
);
}
// #enddocregion background-pool
+90 -83
View File
@@ -10,23 +10,27 @@ typedef _$MyWebDatabase = GeneratedDatabase;
// #docregion connect
DatabaseConnection connectOnWeb() {
return DatabaseConnection.delayed(Future(() async {
final result = await WasmDatabase.open(
databaseName: 'my_app_db', // prefer to only use valid identifiers here
sqlite3Uri: Uri.parse('sqlite3.wasm'),
driftWorkerUri: Uri.parse('drift_worker.dart.js'),
);
return DatabaseConnection.delayed(
Future(() async {
final result = await WasmDatabase.open(
databaseName: 'my_app_db', // prefer to only use valid identifiers here
sqlite3Uri: Uri.parse('sqlite3.wasm'),
driftWorkerUri: Uri.parse('drift_worker.dart.js'),
);
if (result.missingFeatures.isNotEmpty) {
// Depending how central local persistence is to your app, you may want
// to show a warning to the user if only unrealiable implemetentations
// are available.
print('Using ${result.chosenImplementation} due to missing browser '
'features: ${result.missingFeatures}');
}
if (result.missingFeatures.isNotEmpty) {
// Depending how central local persistence is to your app, you may want
// to show a warning to the user if only unrealiable implemetentations
// are available.
print(
'Using ${result.chosenImplementation} due to missing browser '
'features: ${result.missingFeatures}',
);
}
return result.resolvedExecutor;
}));
return result.resolvedExecutor;
}),
);
}
// You can then use this method to open your database:
@@ -43,7 +47,7 @@ class MyWebDatabase extends _$MyWebDatabase {
@override
int get schemaVersion => throw UnimplementedError();
// #docregion connect
// #docregion connect
}
// #enddocregion connect
@@ -61,20 +65,22 @@ QueryExecutor connectWithDriftFlutter() {
DatabaseConnection connectWithExisting() {
// #docregion existing
return DatabaseConnection.delayed(Future(() async {
final result = await WasmDatabase.open(
databaseName: 'my_app_db', // prefer to only use valid identifiers here
sqlite3Uri: Uri.parse('sqlite3.wasm'),
driftWorkerUri: Uri.parse('drift_worker.dart.js'),
initializeDatabase: () async {
final data = await rootBundle.load('my_database');
return data.buffer.asUint8List();
},
);
return DatabaseConnection.delayed(
Future(() async {
final result = await WasmDatabase.open(
databaseName: 'my_app_db', // prefer to only use valid identifiers here
sqlite3Uri: Uri.parse('sqlite3.wasm'),
driftWorkerUri: Uri.parse('drift_worker.dart.js'),
initializeDatabase: () async {
final data = await rootBundle.load('my_database');
return data.buffer.asUint8List();
},
);
// ...
return result.resolvedExecutor;
}));
// ...
return result.resolvedExecutor;
}),
);
// #enddocregion existing
}
@@ -85,72 +91,75 @@ Future<WasmDatabase> customDatabase() async {
final fs = await IndexedDbFileSystem.open(dbName: 'my_app');
sqlite3.registerVirtualFileSystem(fs, makeDefault: true);
return WasmDatabase(
sqlite3: sqlite3,
path: '/app.db',
);
return WasmDatabase(sqlite3: sqlite3, path: '/app.db');
}
// #enddocregion migrate-wasm
DatabaseConnection migrateAndConnect() {
return DatabaseConnection.delayed(Future(() async {
// #docregion migrate-wasm
// Then you can migrate like this
final result = await WasmDatabase.open(
databaseName: 'my_app',
sqlite3Uri: Uri.parse('sqlite3.wasm'),
driftWorkerUri: Uri.parse('drift_worker.dart.js'),
initializeDatabase: () async {
// Manually open the file system previously used
final fs = await IndexedDbFileSystem.open(dbName: 'my_app');
const oldPath = '/app.db'; // The path passed to WasmDatabase before
return DatabaseConnection.delayed(
Future(() async {
// #docregion migrate-wasm
// Then you can migrate like this
final result = await WasmDatabase.open(
databaseName: 'my_app',
sqlite3Uri: Uri.parse('sqlite3.wasm'),
driftWorkerUri: Uri.parse('drift_worker.dart.js'),
initializeDatabase: () async {
// Manually open the file system previously used
final fs = await IndexedDbFileSystem.open(dbName: 'my_app');
const oldPath = '/app.db'; // The path passed to WasmDatabase before
Uint8List? oldDatabase;
Uint8List? oldDatabase;
// Check if the old database exists
if (fs.xAccess(oldPath, 0) != 0) {
// It does, then copy the old file
final (file: file, outFlags: _) =
fs.xOpen(Sqlite3Filename(oldPath), 0);
final blob = Uint8List(file.xFileSize());
file.xRead(blob, 0);
file.xClose();
fs.xDelete(oldPath, 0);
// Check if the old database exists
if (fs.xAccess(oldPath, 0) != 0) {
// It does, then copy the old file
final (file: file, outFlags: _) = fs.xOpen(
Sqlite3Filename(oldPath),
0,
);
final blob = Uint8List(file.xFileSize());
file.xRead(blob, 0);
file.xClose();
fs.xDelete(oldPath, 0);
oldDatabase = blob;
}
oldDatabase = blob;
}
await fs.close();
return oldDatabase;
},
);
// #enddocregion migrate-wasm
await fs.close();
return oldDatabase;
},
);
// #enddocregion migrate-wasm
return result.resolvedExecutor;
}));
return result.resolvedExecutor;
}),
);
}
DatabaseConnection migrateFromLegacy() {
return DatabaseConnection.delayed(Future(() async {
// #docregion migrate-legacy
final result = await WasmDatabase.open(
databaseName: 'my_app',
sqlite3Uri: Uri.parse('sqlite3.wasm'),
driftWorkerUri: Uri.parse('drift_worker.dart.js'),
initializeDatabase: () async {
final storage = await DriftWebStorage.indexedDbIfSupported('old_db');
await storage.open();
return DatabaseConnection.delayed(
Future(() async {
// #docregion migrate-legacy
final result = await WasmDatabase.open(
databaseName: 'my_app',
sqlite3Uri: Uri.parse('sqlite3.wasm'),
driftWorkerUri: Uri.parse('drift_worker.dart.js'),
initializeDatabase: () async {
final storage = await DriftWebStorage.indexedDbIfSupported('old_db');
await storage.open();
final blob = await storage.restore();
await storage.close();
final blob = await storage.restore();
await storage.close();
return blob;
},
);
// #enddocregion migrate-legacy
return blob;
},
);
// #enddocregion migrate-legacy
return result.resolvedExecutor;
}));
return result.resolvedExecutor;
}),
);
}
// #docregion setupAll
@@ -162,9 +171,7 @@ void setupDatabase(CommonDatabase database) {
}
void main() {
WasmDatabase.workerMainForOpen(
setupAllDatabases: setupDatabase,
);
WasmDatabase.workerMainForOpen(setupAllDatabases: setupDatabase);
}
// #enddocregion setupAll
+15 -7
View File
@@ -13,18 +13,26 @@ void main() {
// Call drift function that will set up this worker
driftWorkerMain(() {
return WebDatabase.withStorage(DriftWebStorage.indexedDb('worker',
migrateFromLocalStorage: false, inWebWorker: true));
return WebDatabase.withStorage(
DriftWebStorage.indexedDb(
'worker',
migrateFromLocalStorage: false,
inWebWorker: true,
),
);
});
}
// #enddocregion worker
// #docregion client
DatabaseConnection connectToWorker() {
return DatabaseConnection.delayed(connectToDriftWorker(
'worker.dart.js',
// Note that SharedWorkers may not be available on all browsers and platforms.
mode: DriftWorkerMode.shared,
));
return DatabaseConnection.delayed(
connectToDriftWorker(
'worker.dart.js',
// Note that SharedWorkers may not be available on all browsers and platforms.
mode: DriftWorkerMode.shared,
),
);
}
// #enddocregion client
+14 -9
View File
@@ -31,7 +31,7 @@ class TodoItems extends Table {
// #enddocregion table
@DriftDatabase(tables: [TodoItems])
class AppDatabase extends _$AppDatabase {
// #enddocregion before_generation
// #enddocregion before_generation
// After generating code, this class needs to define a `schemaVersion` getter
// and a constructor telling drift where the database should be stored.
// These are described in the getting started guide: https://drift.simonbinder.eu/setup/
@@ -45,12 +45,12 @@ class AppDatabase extends _$AppDatabase {
throw 'should not show as snippet';
}
// #docregion before_generation
// #docregion before_generation
}
// #enddocregion before_generation
class OpenFlutter {
// #docregion flutter
// #docregion flutter
static QueryExecutor _openConnection() {
return driftDatabase(
name: 'my_database',
@@ -66,7 +66,7 @@ class OpenFlutter {
// #enddocregion flutter
class OpenPostgres {
// #docregion postgres
// #docregion postgres
static QueryExecutor _openConnection() {
return PgDatabase(
endpoint: pg.Endpoint(
@@ -81,7 +81,7 @@ class OpenPostgres {
// #enddocregion postgres
class OpenSqlite3 {
// #docregion sqlite3
// #docregion sqlite3
static QueryExecutor _openConnection() {
return NativeDatabase.createInBackground(File('path/to/your/database'));
}
@@ -98,12 +98,17 @@ void main() async {
final database = AppDatabase();
await database.into(database.todoItems).insert(TodoItemsCompanion.insert(
title: 'todo: finish drift setup',
content: 'We can now write queries and define our own tables.',
));
await database
.into(database.todoItems)
.insert(
TodoItemsCompanion.insert(
title: 'todo: finish drift setup',
content: 'We can now write queries and define our own tables.',
),
);
List<TodoItem> allItems = await database.select(database.todoItems).get();
print('items in database: $allItems');
}
// #enddocregion use
+6 -2
View File
@@ -31,12 +31,16 @@ final class PackageVersionDataLoader implements DataLoader {
return;
}
final pubspec = await File(resolvedPackage.root.resolve('pubspec.yaml').toFilePath()).readAsString();
final pubspec = await File(
resolvedPackage.root.resolve('pubspec.yaml').toFilePath(),
).readAsString();
final parsedPubspec = loadYaml(pubspec);
resolvedVersions[package] = (parsedPubspec as Map)['version'].toString();
}
await Future.wait([for (final package in packages) resolvePackage(package)]);
await Future.wait([
for (final package in packages) resolvePackage(package),
]);
return resolvedVersions;
}