drift/examples/app/lib/database/database.dart

149 lines
5.0 KiB
Dart

import 'package:drift/drift.dart';
import 'package:flutter/material.dart' show Colors;
import 'package:riverpod/riverpod.dart';
import 'connection/connection.dart' as impl;
import 'tables.dart';
// Manually generated by `drift_dev schema steps` - this file makes writing
// migrations easier. See this for details:
// https://drift.simonbinder.eu/docs/advanced-features/migrations/#step-by-step
import 'schema_versions.dart';
// Generated by drift_dev when running `build_runner build`
part 'database.g.dart';
@DriftDatabase(tables: [TodoEntries, Categories], include: {'sql.drift'})
class AppDatabase extends _$AppDatabase {
AppDatabase() : super(impl.connect());
AppDatabase.forTesting(DatabaseConnection connection) : super(connection);
@override
int get schemaVersion => 3;
@override
MigrationStrategy get migration {
return MigrationStrategy(
onUpgrade: stepByStep(
from1To2: (m, schema) async {
// The todoEntries.dueDate column was added in version 2.
await m.addColumn(schema.todoEntries, schema.todoEntries.dueDate);
},
from2To3: (m, schema) async {
// New triggers were added in version 3:
await m.create(schema.todosDelete);
await m.create(schema.todosUpdate);
// Also, the `REFERENCES` constraint was added to
// [TodoEntries.category]. Run a table migration to rebuild all
// column constraints without loosing data.
await m.alterTable(TableMigration(schema.todoEntries));
},
),
beforeOpen: (details) async {
// Make sure that foreign keys are enabled
await customStatement('PRAGMA foreign_keys = ON');
if (details.wasCreated) {
// Create a bunch of default values so the app doesn't look too empty
// on the first start.
await batch((b) {
b.insert(
categories,
CategoriesCompanion.insert(name: 'Important', color: Colors.red),
);
b.insertAll(todoEntries, [
TodoEntriesCompanion.insert(description: 'Check out drift'),
TodoEntriesCompanion.insert(
description: 'Fix session invalidation bug',
category: const Value(1)),
TodoEntriesCompanion.insert(
description: 'Add favorite movies to home page'),
]);
});
}
// This follows the recommendation to validate that the database schema
// matches what drift expects (https://drift.simonbinder.eu/docs/advanced-features/migrations/#verifying-a-database-schema-at-runtime).
// It allows catching bugs in the migration logic early.
await impl.validateDatabaseSchema(this);
},
);
}
Future<List<TodoEntryWithCategory>> search(String query) {
return _search(query).map((row) {
return TodoEntryWithCategory(entry: row.todos, category: row.cat);
}).get();
}
Stream<List<CategoryWithCount>> categoriesWithCount() {
// the _categoriesWithCount method has been generated automatically based
// on the query declared in the @DriftDatabase annotation
return _categoriesWithCount().map((row) {
final hasId = row.id != null;
final category = hasId
? Category(id: row.id!, name: row.name!, color: row.color!)
: null;
return CategoryWithCount(category, row.amount);
}).watch();
}
/// Returns an auto-updating stream of all todo entries in a given category
/// id.
Stream<List<TodoEntryWithCategory>> entriesInCategory(int? categoryId) {
final query = select(todoEntries).join([
leftOuterJoin(categories, categories.id.equalsExp(todoEntries.category))
]);
if (categoryId != null) {
query.where(categories.id.equals(categoryId));
} else {
query.where(categories.id.isNull());
}
return query.map((row) {
return TodoEntryWithCategory(
entry: row.readTable(todoEntries),
category: row.readTableOrNull(categories),
);
}).watch();
}
Future<void> deleteCategory(Category category) {
return transaction(() async {
// First, move todo entries that might remain into the default category
await (todoEntries.update()
..where((todo) => todo.category.equals(category.id)))
.write(const TodoEntriesCompanion(category: Value(null)));
// Then, delete the category
await categories.deleteOne(category);
});
}
static final StateProvider<AppDatabase> provider = StateProvider((ref) {
final database = AppDatabase();
ref.onDispose(database.close);
return database;
});
}
class TodoEntryWithCategory {
final TodoEntry entry;
final Category? category;
TodoEntryWithCategory({required this.entry, this.category});
}
class CategoryWithCount {
// can be null, in which case we count how many entries don't have a category
final Category? category;
final int count; // amount of entries in this category
CategoryWithCount(this.category, this.count);
}