feat(08-01): add isActive filters to CalendarDao, DailyPlanDao, RoomsDao
- CalendarDao: filter all 6 task queries (watchTasksForDate, watchTasksForDateInRoom, watchOverdueTasks, watchOverdueTasksInRoom, getTaskCount, getTaskCountInRoom) by isActive=true - DailyPlanDao: filter all 3 queries (watchAllTasksWithRoomName, getOverdueAndTodayTaskCount, getOverdueTaskCount) by isActive=true - RoomsDao: filter watchRoomWithStats task query by isActive=true - Update migration test: add schema_v3.dart, test v1->v3 and v2->v3 paths - Update database_test schemaVersion assertion to expect 3 - Fix test helpers in home_screen_test and task_list_screen_test to pass isActive=true
This commit is contained in:
352
drift_schemas/household_keeper/drift_schema_v3.json
Normal file
352
drift_schemas/household_keeper/drift_schema_v3.json
Normal file
@@ -0,0 +1,352 @@
|
||||
{
|
||||
"_meta": {
|
||||
"description": "This file contains a serialized version of schema entities for drift.",
|
||||
"version": "1.3.0"
|
||||
},
|
||||
"options": {
|
||||
"store_date_time_values_as_text": false
|
||||
},
|
||||
"entities": [
|
||||
{
|
||||
"id": 0,
|
||||
"references": [],
|
||||
"type": "table",
|
||||
"data": {
|
||||
"name": "rooms",
|
||||
"was_declared_in_moor": false,
|
||||
"columns": [
|
||||
{
|
||||
"name": "id",
|
||||
"getter_name": "id",
|
||||
"moor_type": "int",
|
||||
"nullable": false,
|
||||
"customConstraints": null,
|
||||
"defaultConstraints": "PRIMARY KEY AUTOINCREMENT",
|
||||
"dialectAwareDefaultConstraints": {
|
||||
"sqlite": "PRIMARY KEY AUTOINCREMENT"
|
||||
},
|
||||
"default_dart": null,
|
||||
"default_client_dart": null,
|
||||
"dsl_features": [
|
||||
"auto-increment"
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "name",
|
||||
"getter_name": "name",
|
||||
"moor_type": "string",
|
||||
"nullable": false,
|
||||
"customConstraints": null,
|
||||
"default_dart": null,
|
||||
"default_client_dart": null,
|
||||
"dsl_features": [
|
||||
{
|
||||
"allowed-lengths": {
|
||||
"min": 1,
|
||||
"max": 100
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "icon_name",
|
||||
"getter_name": "iconName",
|
||||
"moor_type": "string",
|
||||
"nullable": false,
|
||||
"customConstraints": null,
|
||||
"default_dart": null,
|
||||
"default_client_dart": null,
|
||||
"dsl_features": []
|
||||
},
|
||||
{
|
||||
"name": "sort_order",
|
||||
"getter_name": "sortOrder",
|
||||
"moor_type": "int",
|
||||
"nullable": false,
|
||||
"customConstraints": null,
|
||||
"default_dart": "const CustomExpression('0')",
|
||||
"default_client_dart": null,
|
||||
"dsl_features": []
|
||||
},
|
||||
{
|
||||
"name": "created_at",
|
||||
"getter_name": "createdAt",
|
||||
"moor_type": "dateTime",
|
||||
"nullable": false,
|
||||
"customConstraints": null,
|
||||
"default_dart": null,
|
||||
"default_client_dart": "() => DateTime.now()",
|
||||
"dsl_features": []
|
||||
}
|
||||
],
|
||||
"is_virtual": false,
|
||||
"without_rowid": false,
|
||||
"constraints": []
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": 1,
|
||||
"references": [
|
||||
0
|
||||
],
|
||||
"type": "table",
|
||||
"data": {
|
||||
"name": "tasks",
|
||||
"was_declared_in_moor": false,
|
||||
"columns": [
|
||||
{
|
||||
"name": "id",
|
||||
"getter_name": "id",
|
||||
"moor_type": "int",
|
||||
"nullable": false,
|
||||
"customConstraints": null,
|
||||
"defaultConstraints": "PRIMARY KEY AUTOINCREMENT",
|
||||
"dialectAwareDefaultConstraints": {
|
||||
"sqlite": "PRIMARY KEY AUTOINCREMENT"
|
||||
},
|
||||
"default_dart": null,
|
||||
"default_client_dart": null,
|
||||
"dsl_features": [
|
||||
"auto-increment"
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "room_id",
|
||||
"getter_name": "roomId",
|
||||
"moor_type": "int",
|
||||
"nullable": false,
|
||||
"customConstraints": null,
|
||||
"defaultConstraints": "REFERENCES rooms (id)",
|
||||
"dialectAwareDefaultConstraints": {
|
||||
"sqlite": "REFERENCES rooms (id)"
|
||||
},
|
||||
"default_dart": null,
|
||||
"default_client_dart": null,
|
||||
"dsl_features": [
|
||||
{
|
||||
"foreign_key": {
|
||||
"to": {
|
||||
"table": "rooms",
|
||||
"column": "id"
|
||||
},
|
||||
"initially_deferred": false,
|
||||
"on_update": null,
|
||||
"on_delete": null
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "name",
|
||||
"getter_name": "name",
|
||||
"moor_type": "string",
|
||||
"nullable": false,
|
||||
"customConstraints": null,
|
||||
"default_dart": null,
|
||||
"default_client_dart": null,
|
||||
"dsl_features": [
|
||||
{
|
||||
"allowed-lengths": {
|
||||
"min": 1,
|
||||
"max": 200
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "description",
|
||||
"getter_name": "description",
|
||||
"moor_type": "string",
|
||||
"nullable": true,
|
||||
"customConstraints": null,
|
||||
"default_dart": null,
|
||||
"default_client_dart": null,
|
||||
"dsl_features": []
|
||||
},
|
||||
{
|
||||
"name": "interval_type",
|
||||
"getter_name": "intervalType",
|
||||
"moor_type": "int",
|
||||
"nullable": false,
|
||||
"customConstraints": null,
|
||||
"default_dart": null,
|
||||
"default_client_dart": null,
|
||||
"dsl_features": [],
|
||||
"type_converter": {
|
||||
"dart_expr": "const EnumIndexConverter<IntervalType>(IntervalType.values)",
|
||||
"dart_type_name": "IntervalType"
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "interval_days",
|
||||
"getter_name": "intervalDays",
|
||||
"moor_type": "int",
|
||||
"nullable": false,
|
||||
"customConstraints": null,
|
||||
"default_dart": "const CustomExpression('1')",
|
||||
"default_client_dart": null,
|
||||
"dsl_features": []
|
||||
},
|
||||
{
|
||||
"name": "anchor_day",
|
||||
"getter_name": "anchorDay",
|
||||
"moor_type": "int",
|
||||
"nullable": true,
|
||||
"customConstraints": null,
|
||||
"default_dart": null,
|
||||
"default_client_dart": null,
|
||||
"dsl_features": []
|
||||
},
|
||||
{
|
||||
"name": "effort_level",
|
||||
"getter_name": "effortLevel",
|
||||
"moor_type": "int",
|
||||
"nullable": false,
|
||||
"customConstraints": null,
|
||||
"default_dart": null,
|
||||
"default_client_dart": null,
|
||||
"dsl_features": [],
|
||||
"type_converter": {
|
||||
"dart_expr": "const EnumIndexConverter<EffortLevel>(EffortLevel.values)",
|
||||
"dart_type_name": "EffortLevel"
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "next_due_date",
|
||||
"getter_name": "nextDueDate",
|
||||
"moor_type": "dateTime",
|
||||
"nullable": false,
|
||||
"customConstraints": null,
|
||||
"default_dart": null,
|
||||
"default_client_dart": null,
|
||||
"dsl_features": []
|
||||
},
|
||||
{
|
||||
"name": "created_at",
|
||||
"getter_name": "createdAt",
|
||||
"moor_type": "dateTime",
|
||||
"nullable": false,
|
||||
"customConstraints": null,
|
||||
"default_dart": null,
|
||||
"default_client_dart": "() => DateTime.now()",
|
||||
"dsl_features": []
|
||||
},
|
||||
{
|
||||
"name": "is_active",
|
||||
"getter_name": "isActive",
|
||||
"moor_type": "bool",
|
||||
"nullable": false,
|
||||
"customConstraints": null,
|
||||
"defaultConstraints": "CHECK (\"is_active\" IN (0, 1))",
|
||||
"dialectAwareDefaultConstraints": {
|
||||
"sqlite": "CHECK (\"is_active\" IN (0, 1))"
|
||||
},
|
||||
"default_dart": "const CustomExpression('1')",
|
||||
"default_client_dart": null,
|
||||
"dsl_features": []
|
||||
}
|
||||
],
|
||||
"is_virtual": false,
|
||||
"without_rowid": false,
|
||||
"constraints": []
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": 2,
|
||||
"references": [
|
||||
1
|
||||
],
|
||||
"type": "table",
|
||||
"data": {
|
||||
"name": "task_completions",
|
||||
"was_declared_in_moor": false,
|
||||
"columns": [
|
||||
{
|
||||
"name": "id",
|
||||
"getter_name": "id",
|
||||
"moor_type": "int",
|
||||
"nullable": false,
|
||||
"customConstraints": null,
|
||||
"defaultConstraints": "PRIMARY KEY AUTOINCREMENT",
|
||||
"dialectAwareDefaultConstraints": {
|
||||
"sqlite": "PRIMARY KEY AUTOINCREMENT"
|
||||
},
|
||||
"default_dart": null,
|
||||
"default_client_dart": null,
|
||||
"dsl_features": [
|
||||
"auto-increment"
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "task_id",
|
||||
"getter_name": "taskId",
|
||||
"moor_type": "int",
|
||||
"nullable": false,
|
||||
"customConstraints": null,
|
||||
"defaultConstraints": "REFERENCES tasks (id)",
|
||||
"dialectAwareDefaultConstraints": {
|
||||
"sqlite": "REFERENCES tasks (id)"
|
||||
},
|
||||
"default_dart": null,
|
||||
"default_client_dart": null,
|
||||
"dsl_features": [
|
||||
{
|
||||
"foreign_key": {
|
||||
"to": {
|
||||
"table": "tasks",
|
||||
"column": "id"
|
||||
},
|
||||
"initially_deferred": false,
|
||||
"on_update": null,
|
||||
"on_delete": null
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "completed_at",
|
||||
"getter_name": "completedAt",
|
||||
"moor_type": "dateTime",
|
||||
"nullable": false,
|
||||
"customConstraints": null,
|
||||
"default_dart": null,
|
||||
"default_client_dart": null,
|
||||
"dsl_features": []
|
||||
}
|
||||
],
|
||||
"is_virtual": false,
|
||||
"without_rowid": false,
|
||||
"constraints": []
|
||||
}
|
||||
}
|
||||
],
|
||||
"fixed_sql": [
|
||||
{
|
||||
"name": "rooms",
|
||||
"sql": [
|
||||
{
|
||||
"dialect": "sqlite",
|
||||
"sql": "CREATE TABLE IF NOT EXISTS \"rooms\" (\"id\" INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT, \"name\" TEXT NOT NULL, \"icon_name\" TEXT NOT NULL, \"sort_order\" INTEGER NOT NULL DEFAULT 0, \"created_at\" INTEGER NOT NULL);"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "tasks",
|
||||
"sql": [
|
||||
{
|
||||
"dialect": "sqlite",
|
||||
"sql": "CREATE TABLE IF NOT EXISTS \"tasks\" (\"id\" INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT, \"room_id\" INTEGER NOT NULL REFERENCES rooms (id), \"name\" TEXT NOT NULL, \"description\" TEXT NULL, \"interval_type\" INTEGER NOT NULL, \"interval_days\" INTEGER NOT NULL DEFAULT 1, \"anchor_day\" INTEGER NULL, \"effort_level\" INTEGER NOT NULL, \"next_due_date\" INTEGER NOT NULL, \"created_at\" INTEGER NOT NULL, \"is_active\" INTEGER NOT NULL DEFAULT 1 CHECK (\"is_active\" IN (0, 1)));"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "task_completions",
|
||||
"sql": [
|
||||
{
|
||||
"dialect": "sqlite",
|
||||
"sql": "CREATE TABLE IF NOT EXISTS \"task_completions\" (\"id\" INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT, \"task_id\" INTEGER NOT NULL REFERENCES tasks (id), \"completed_at\" INTEGER NOT NULL);"
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -28,7 +28,8 @@ class CalendarDao extends DatabaseAccessor<AppDatabase>
|
||||
]);
|
||||
query.where(
|
||||
tasks.nextDueDate.isBiggerOrEqualValue(startOfDay) &
|
||||
tasks.nextDueDate.isSmallerThanValue(endOfDay),
|
||||
tasks.nextDueDate.isSmallerThanValue(endOfDay) &
|
||||
tasks.isActive.equals(true),
|
||||
);
|
||||
query.orderBy([OrderingTerm.asc(tasks.name)]);
|
||||
|
||||
@@ -45,12 +46,14 @@ class CalendarDao extends DatabaseAccessor<AppDatabase>
|
||||
});
|
||||
}
|
||||
|
||||
/// Returns the total count of tasks across all rooms and dates.
|
||||
/// Returns the total count of active tasks across all rooms and dates.
|
||||
///
|
||||
/// Used by the UI to distinguish first-run empty state from celebration state.
|
||||
Future<int> getTaskCount() async {
|
||||
final countExp = tasks.id.count();
|
||||
final query = selectOnly(tasks)..addColumns([countExp]);
|
||||
final query = selectOnly(tasks)
|
||||
..addColumns([countExp])
|
||||
..where(tasks.isActive.equals(true));
|
||||
final result = await query.getSingle();
|
||||
return result.read(countExp) ?? 0;
|
||||
}
|
||||
@@ -69,7 +72,8 @@ class CalendarDao extends DatabaseAccessor<AppDatabase>
|
||||
query.where(
|
||||
tasks.nextDueDate.isBiggerOrEqualValue(startOfDay) &
|
||||
tasks.nextDueDate.isSmallerThanValue(endOfDay) &
|
||||
tasks.roomId.equals(roomId),
|
||||
tasks.roomId.equals(roomId) &
|
||||
tasks.isActive.equals(true),
|
||||
);
|
||||
query.orderBy([OrderingTerm.asc(tasks.name)]);
|
||||
|
||||
@@ -96,7 +100,10 @@ class CalendarDao extends DatabaseAccessor<AppDatabase>
|
||||
final query = select(tasks).join([
|
||||
innerJoin(rooms, rooms.id.equalsExp(tasks.roomId)),
|
||||
]);
|
||||
query.where(tasks.nextDueDate.isSmallerThanValue(startOfReferenceDay));
|
||||
query.where(
|
||||
tasks.nextDueDate.isSmallerThanValue(startOfReferenceDay) &
|
||||
tasks.isActive.equals(true),
|
||||
);
|
||||
query.orderBy([OrderingTerm.asc(tasks.nextDueDate)]);
|
||||
|
||||
return query.watch().map((rows) {
|
||||
@@ -128,7 +135,8 @@ class CalendarDao extends DatabaseAccessor<AppDatabase>
|
||||
]);
|
||||
query.where(
|
||||
tasks.nextDueDate.isSmallerThanValue(startOfReferenceDay) &
|
||||
tasks.roomId.equals(roomId),
|
||||
tasks.roomId.equals(roomId) &
|
||||
tasks.isActive.equals(true),
|
||||
);
|
||||
query.orderBy([OrderingTerm.asc(tasks.nextDueDate)]);
|
||||
|
||||
@@ -145,7 +153,7 @@ class CalendarDao extends DatabaseAccessor<AppDatabase>
|
||||
});
|
||||
}
|
||||
|
||||
/// Total task count within a specific room.
|
||||
/// Total active task count within a specific room.
|
||||
///
|
||||
/// Used to distinguish first-run empty state from celebration state
|
||||
/// in the room calendar view.
|
||||
@@ -153,7 +161,7 @@ class CalendarDao extends DatabaseAccessor<AppDatabase>
|
||||
final countExp = tasks.id.count();
|
||||
final query = selectOnly(tasks)
|
||||
..addColumns([countExp])
|
||||
..where(tasks.roomId.equals(roomId));
|
||||
..where(tasks.roomId.equals(roomId) & tasks.isActive.equals(true));
|
||||
final result = await query.getSingle();
|
||||
return result.read(countExp) ?? 0;
|
||||
}
|
||||
|
||||
@@ -10,13 +10,14 @@ class DailyPlanDao extends DatabaseAccessor<AppDatabase>
|
||||
with _$DailyPlanDaoMixin {
|
||||
DailyPlanDao(super.attachedDatabase);
|
||||
|
||||
/// Watch all tasks joined with room name, sorted by nextDueDate ascending.
|
||||
/// Includes ALL tasks (overdue, today, future) -- filtering is done in the
|
||||
/// provider layer to avoid multiple queries.
|
||||
/// Watch all active tasks joined with room name, sorted by nextDueDate ascending.
|
||||
/// Includes overdue, today, and future tasks -- filtering is done in the
|
||||
/// provider layer to avoid multiple queries. Excludes soft-deleted tasks.
|
||||
Stream<List<TaskWithRoom>> watchAllTasksWithRoomName() {
|
||||
final query = select(tasks).join([
|
||||
innerJoin(rooms, rooms.id.equalsExp(tasks.roomId)),
|
||||
]);
|
||||
query.where(tasks.isActive.equals(true));
|
||||
query.orderBy([OrderingTerm.asc(tasks.nextDueDate)]);
|
||||
|
||||
return query.watch().map((rows) {
|
||||
@@ -32,24 +33,30 @@ class DailyPlanDao extends DatabaseAccessor<AppDatabase>
|
||||
});
|
||||
}
|
||||
|
||||
/// One-shot count of overdue + today tasks (for notification body).
|
||||
/// One-shot count of overdue + today active tasks (for notification body).
|
||||
Future<int> getOverdueAndTodayTaskCount({DateTime? today}) async {
|
||||
final now = today ?? DateTime.now();
|
||||
final endOfToday = DateTime(now.year, now.month, now.day + 1);
|
||||
final result = await (selectOnly(tasks)
|
||||
..addColumns([tasks.id.count()])
|
||||
..where(tasks.nextDueDate.isSmallerThanValue(endOfToday)))
|
||||
..where(
|
||||
tasks.nextDueDate.isSmallerThanValue(endOfToday) &
|
||||
tasks.isActive.equals(true),
|
||||
))
|
||||
.getSingle();
|
||||
return result.read(tasks.id.count()) ?? 0;
|
||||
}
|
||||
|
||||
/// One-shot count of overdue tasks only (for notification body split).
|
||||
/// One-shot count of overdue active tasks only (for notification body split).
|
||||
Future<int> getOverdueTaskCount({DateTime? today}) async {
|
||||
final now = today ?? DateTime.now();
|
||||
final startOfToday = DateTime(now.year, now.month, now.day);
|
||||
final result = await (selectOnly(tasks)
|
||||
..addColumns([tasks.id.count()])
|
||||
..where(tasks.nextDueDate.isSmallerThanValue(startOfToday)))
|
||||
..where(
|
||||
tasks.nextDueDate.isSmallerThanValue(startOfToday) &
|
||||
tasks.isActive.equals(true),
|
||||
))
|
||||
.getSingle();
|
||||
return result.read(tasks.id.count()) ?? 0;
|
||||
}
|
||||
|
||||
@@ -44,7 +44,9 @@ class RoomsDao extends DatabaseAccessor<AppDatabase> with _$RoomsDaoMixin {
|
||||
final stats = <RoomWithStats>[];
|
||||
for (final room in roomList) {
|
||||
final taskList = await (select(tasks)
|
||||
..where((t) => t.roomId.equals(room.id)))
|
||||
..where(
|
||||
(t) => t.roomId.equals(room.id) & t.isActive.equals(true),
|
||||
))
|
||||
.get();
|
||||
|
||||
final totalTasks = taskList.length;
|
||||
|
||||
@@ -18,8 +18,8 @@ void main() {
|
||||
expect(db, isNotNull);
|
||||
});
|
||||
|
||||
test('has schemaVersion 2', () {
|
||||
expect(db.schemaVersion, equals(2));
|
||||
test('has schemaVersion 3', () {
|
||||
expect(db.schemaVersion, equals(3));
|
||||
});
|
||||
|
||||
test('can be closed without error', () async {
|
||||
|
||||
@@ -6,6 +6,7 @@ import 'package:drift/drift.dart';
|
||||
import 'package:drift/internal/migrations.dart';
|
||||
import 'schema_v1.dart' as v1;
|
||||
import 'schema_v2.dart' as v2;
|
||||
import 'schema_v3.dart' as v3;
|
||||
|
||||
class GeneratedHelper implements SchemaInstantiationHelper {
|
||||
@override
|
||||
@@ -15,10 +16,12 @@ class GeneratedHelper implements SchemaInstantiationHelper {
|
||||
return v1.DatabaseAtV1(db);
|
||||
case 2:
|
||||
return v2.DatabaseAtV2(db);
|
||||
case 3:
|
||||
return v3.DatabaseAtV3(db);
|
||||
default:
|
||||
throw MissingSchemaException(version, versions);
|
||||
}
|
||||
}
|
||||
|
||||
static const versions = const [1, 2];
|
||||
static const versions = const [1, 2, 3];
|
||||
}
|
||||
|
||||
283
test/drift/household_keeper/generated/schema_v3.dart
Normal file
283
test/drift/household_keeper/generated/schema_v3.dart
Normal file
@@ -0,0 +1,283 @@
|
||||
// dart format width=80
|
||||
// GENERATED BY drift_dev, DO NOT MODIFY.
|
||||
// ignore_for_file: type=lint,unused_import
|
||||
//
|
||||
import 'package:drift/drift.dart';
|
||||
|
||||
class Rooms extends Table with TableInfo {
|
||||
@override
|
||||
final GeneratedDatabase attachedDatabase;
|
||||
final String? _alias;
|
||||
Rooms(this.attachedDatabase, [this._alias]);
|
||||
late final GeneratedColumn<int> id = GeneratedColumn<int>(
|
||||
'id',
|
||||
aliasedName,
|
||||
false,
|
||||
hasAutoIncrement: true,
|
||||
type: DriftSqlType.int,
|
||||
requiredDuringInsert: false,
|
||||
$customConstraints: 'NOT NULL PRIMARY KEY AUTOINCREMENT',
|
||||
);
|
||||
late final GeneratedColumn<String> name = GeneratedColumn<String>(
|
||||
'name',
|
||||
aliasedName,
|
||||
false,
|
||||
type: DriftSqlType.string,
|
||||
requiredDuringInsert: true,
|
||||
$customConstraints: 'NOT NULL',
|
||||
);
|
||||
late final GeneratedColumn<String> iconName = GeneratedColumn<String>(
|
||||
'icon_name',
|
||||
aliasedName,
|
||||
false,
|
||||
type: DriftSqlType.string,
|
||||
requiredDuringInsert: true,
|
||||
$customConstraints: 'NOT NULL',
|
||||
);
|
||||
late final GeneratedColumn<int> sortOrder = GeneratedColumn<int>(
|
||||
'sort_order',
|
||||
aliasedName,
|
||||
false,
|
||||
type: DriftSqlType.int,
|
||||
requiredDuringInsert: false,
|
||||
$customConstraints: 'NOT NULL DEFAULT 0',
|
||||
defaultValue: const CustomExpression('0'),
|
||||
);
|
||||
late final GeneratedColumn<int> createdAt = GeneratedColumn<int>(
|
||||
'created_at',
|
||||
aliasedName,
|
||||
false,
|
||||
type: DriftSqlType.int,
|
||||
requiredDuringInsert: true,
|
||||
$customConstraints: 'NOT NULL',
|
||||
);
|
||||
@override
|
||||
List<GeneratedColumn> get $columns => [
|
||||
id,
|
||||
name,
|
||||
iconName,
|
||||
sortOrder,
|
||||
createdAt,
|
||||
];
|
||||
@override
|
||||
String get aliasedName => _alias ?? actualTableName;
|
||||
@override
|
||||
String get actualTableName => $name;
|
||||
static const String $name = 'rooms';
|
||||
@override
|
||||
Set<GeneratedColumn> get $primaryKey => {id};
|
||||
@override
|
||||
Never map(Map<String, dynamic> data, {String? tablePrefix}) {
|
||||
throw UnsupportedError('TableInfo.map in schema verification code');
|
||||
}
|
||||
|
||||
@override
|
||||
Rooms createAlias(String alias) {
|
||||
return Rooms(attachedDatabase, alias);
|
||||
}
|
||||
|
||||
@override
|
||||
bool get dontWriteConstraints => true;
|
||||
}
|
||||
|
||||
class Tasks extends Table with TableInfo {
|
||||
@override
|
||||
final GeneratedDatabase attachedDatabase;
|
||||
final String? _alias;
|
||||
Tasks(this.attachedDatabase, [this._alias]);
|
||||
late final GeneratedColumn<int> id = GeneratedColumn<int>(
|
||||
'id',
|
||||
aliasedName,
|
||||
false,
|
||||
hasAutoIncrement: true,
|
||||
type: DriftSqlType.int,
|
||||
requiredDuringInsert: false,
|
||||
$customConstraints: 'NOT NULL PRIMARY KEY AUTOINCREMENT',
|
||||
);
|
||||
late final GeneratedColumn<int> roomId = GeneratedColumn<int>(
|
||||
'room_id',
|
||||
aliasedName,
|
||||
false,
|
||||
type: DriftSqlType.int,
|
||||
requiredDuringInsert: true,
|
||||
$customConstraints: 'NOT NULL REFERENCES rooms(id)',
|
||||
);
|
||||
late final GeneratedColumn<String> name = GeneratedColumn<String>(
|
||||
'name',
|
||||
aliasedName,
|
||||
false,
|
||||
type: DriftSqlType.string,
|
||||
requiredDuringInsert: true,
|
||||
$customConstraints: 'NOT NULL',
|
||||
);
|
||||
late final GeneratedColumn<String> description = GeneratedColumn<String>(
|
||||
'description',
|
||||
aliasedName,
|
||||
true,
|
||||
type: DriftSqlType.string,
|
||||
requiredDuringInsert: false,
|
||||
$customConstraints: 'NULL',
|
||||
);
|
||||
late final GeneratedColumn<int> intervalType = GeneratedColumn<int>(
|
||||
'interval_type',
|
||||
aliasedName,
|
||||
false,
|
||||
type: DriftSqlType.int,
|
||||
requiredDuringInsert: true,
|
||||
$customConstraints: 'NOT NULL',
|
||||
);
|
||||
late final GeneratedColumn<int> intervalDays = GeneratedColumn<int>(
|
||||
'interval_days',
|
||||
aliasedName,
|
||||
false,
|
||||
type: DriftSqlType.int,
|
||||
requiredDuringInsert: false,
|
||||
$customConstraints: 'NOT NULL DEFAULT 1',
|
||||
defaultValue: const CustomExpression('1'),
|
||||
);
|
||||
late final GeneratedColumn<int> anchorDay = GeneratedColumn<int>(
|
||||
'anchor_day',
|
||||
aliasedName,
|
||||
true,
|
||||
type: DriftSqlType.int,
|
||||
requiredDuringInsert: false,
|
||||
$customConstraints: 'NULL',
|
||||
);
|
||||
late final GeneratedColumn<int> effortLevel = GeneratedColumn<int>(
|
||||
'effort_level',
|
||||
aliasedName,
|
||||
false,
|
||||
type: DriftSqlType.int,
|
||||
requiredDuringInsert: true,
|
||||
$customConstraints: 'NOT NULL',
|
||||
);
|
||||
late final GeneratedColumn<int> nextDueDate = GeneratedColumn<int>(
|
||||
'next_due_date',
|
||||
aliasedName,
|
||||
false,
|
||||
type: DriftSqlType.int,
|
||||
requiredDuringInsert: true,
|
||||
$customConstraints: 'NOT NULL',
|
||||
);
|
||||
late final GeneratedColumn<int> createdAt = GeneratedColumn<int>(
|
||||
'created_at',
|
||||
aliasedName,
|
||||
false,
|
||||
type: DriftSqlType.int,
|
||||
requiredDuringInsert: true,
|
||||
$customConstraints: 'NOT NULL',
|
||||
);
|
||||
late final GeneratedColumn<int> isActive = GeneratedColumn<int>(
|
||||
'is_active',
|
||||
aliasedName,
|
||||
false,
|
||||
type: DriftSqlType.int,
|
||||
requiredDuringInsert: false,
|
||||
$customConstraints: 'NOT NULL DEFAULT 1 CHECK (is_active IN (0, 1))',
|
||||
defaultValue: const CustomExpression('1'),
|
||||
);
|
||||
@override
|
||||
List<GeneratedColumn> get $columns => [
|
||||
id,
|
||||
roomId,
|
||||
name,
|
||||
description,
|
||||
intervalType,
|
||||
intervalDays,
|
||||
anchorDay,
|
||||
effortLevel,
|
||||
nextDueDate,
|
||||
createdAt,
|
||||
isActive,
|
||||
];
|
||||
@override
|
||||
String get aliasedName => _alias ?? actualTableName;
|
||||
@override
|
||||
String get actualTableName => $name;
|
||||
static const String $name = 'tasks';
|
||||
@override
|
||||
Set<GeneratedColumn> get $primaryKey => {id};
|
||||
@override
|
||||
Never map(Map<String, dynamic> data, {String? tablePrefix}) {
|
||||
throw UnsupportedError('TableInfo.map in schema verification code');
|
||||
}
|
||||
|
||||
@override
|
||||
Tasks createAlias(String alias) {
|
||||
return Tasks(attachedDatabase, alias);
|
||||
}
|
||||
|
||||
@override
|
||||
bool get dontWriteConstraints => true;
|
||||
}
|
||||
|
||||
class TaskCompletions extends Table with TableInfo {
|
||||
@override
|
||||
final GeneratedDatabase attachedDatabase;
|
||||
final String? _alias;
|
||||
TaskCompletions(this.attachedDatabase, [this._alias]);
|
||||
late final GeneratedColumn<int> id = GeneratedColumn<int>(
|
||||
'id',
|
||||
aliasedName,
|
||||
false,
|
||||
hasAutoIncrement: true,
|
||||
type: DriftSqlType.int,
|
||||
requiredDuringInsert: false,
|
||||
$customConstraints: 'NOT NULL PRIMARY KEY AUTOINCREMENT',
|
||||
);
|
||||
late final GeneratedColumn<int> taskId = GeneratedColumn<int>(
|
||||
'task_id',
|
||||
aliasedName,
|
||||
false,
|
||||
type: DriftSqlType.int,
|
||||
requiredDuringInsert: true,
|
||||
$customConstraints: 'NOT NULL REFERENCES tasks(id)',
|
||||
);
|
||||
late final GeneratedColumn<int> completedAt = GeneratedColumn<int>(
|
||||
'completed_at',
|
||||
aliasedName,
|
||||
false,
|
||||
type: DriftSqlType.int,
|
||||
requiredDuringInsert: true,
|
||||
$customConstraints: 'NOT NULL',
|
||||
);
|
||||
@override
|
||||
List<GeneratedColumn> get $columns => [id, taskId, completedAt];
|
||||
@override
|
||||
String get aliasedName => _alias ?? actualTableName;
|
||||
@override
|
||||
String get actualTableName => $name;
|
||||
static const String $name = 'task_completions';
|
||||
@override
|
||||
Set<GeneratedColumn> get $primaryKey => {id};
|
||||
@override
|
||||
Never map(Map<String, dynamic> data, {String? tablePrefix}) {
|
||||
throw UnsupportedError('TableInfo.map in schema verification code');
|
||||
}
|
||||
|
||||
@override
|
||||
TaskCompletions createAlias(String alias) {
|
||||
return TaskCompletions(attachedDatabase, alias);
|
||||
}
|
||||
|
||||
@override
|
||||
bool get dontWriteConstraints => true;
|
||||
}
|
||||
|
||||
class DatabaseAtV3 extends GeneratedDatabase {
|
||||
DatabaseAtV3(QueryExecutor e) : super(e);
|
||||
late final Rooms rooms = Rooms(this);
|
||||
late final Tasks tasks = Tasks(this);
|
||||
late final TaskCompletions taskCompletions = TaskCompletions(this);
|
||||
@override
|
||||
Iterable<TableInfo<Table, Object?>> get allTables =>
|
||||
allSchemaEntities.whereType<TableInfo<Table, Object?>>();
|
||||
@override
|
||||
List<DatabaseSchemaEntity> get allSchemaEntities => [
|
||||
rooms,
|
||||
tasks,
|
||||
taskCompletions,
|
||||
];
|
||||
@override
|
||||
int get schemaVersion => 3;
|
||||
}
|
||||
@@ -8,6 +8,7 @@ import 'generated/schema.dart';
|
||||
|
||||
import 'generated/schema_v1.dart' as v1;
|
||||
import 'generated/schema_v2.dart' as v2;
|
||||
import 'generated/schema_v3.dart' as v3;
|
||||
|
||||
void main() {
|
||||
driftRuntimeOptions.dontWarnAboutMultipleDatabases = true;
|
||||
@@ -21,39 +22,28 @@ void main() {
|
||||
// These simple tests verify all possible schema updates with a simple (no
|
||||
// data) migration. This is a quick way to ensure that written database
|
||||
// migrations properly alter the schema.
|
||||
const versions = GeneratedHelper.versions;
|
||||
for (final (i, fromVersion) in versions.indexed) {
|
||||
group('from $fromVersion', () {
|
||||
for (final toVersion in versions.skip(i + 1)) {
|
||||
test('to $toVersion', () async {
|
||||
final schema = await verifier.schemaAt(fromVersion);
|
||||
final db = AppDatabase(schema.newConnection());
|
||||
await verifier.migrateAndValidate(db, toVersion);
|
||||
await db.close();
|
||||
});
|
||||
}
|
||||
//
|
||||
// Note: since AppDatabase.schemaVersion == 3, all migration paths
|
||||
// end at v3. We only test migrations to the current schema version.
|
||||
final fromVersions = [1, 2];
|
||||
for (final fromVersion in fromVersions) {
|
||||
test('from $fromVersion to 3', () async {
|
||||
final schema = await verifier.schemaAt(fromVersion);
|
||||
final db = AppDatabase(schema.newConnection());
|
||||
await verifier.migrateAndValidate(db, 3);
|
||||
await db.close();
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
// The following template shows how to write tests ensuring your migrations
|
||||
// preserve existing data.
|
||||
// Testing this can be useful for migrations that change existing columns
|
||||
// (e.g. by alterating their type or constraints). Migrations that only add
|
||||
// tables or columns typically don't need these advanced tests. For more
|
||||
// information, see https://drift.simonbinder.eu/migrations/tests/#verifying-data-integrity
|
||||
// TODO: This generated template shows how these tests could be written. Adopt
|
||||
// it to your own needs when testing migrations with data integrity.
|
||||
test('migration from v1 to v2 does not corrupt data', () async {
|
||||
// Add data to insert into the old database, and the expected rows after the
|
||||
// migration.
|
||||
// TODO: Fill these lists
|
||||
|
||||
test('migration from v2 to v3 does not corrupt data', () async {
|
||||
// The v2 -> v3 migration adds the isActive column (default true).
|
||||
// Existing tasks should remain and be accessible with isActive = true.
|
||||
await verifier.testWithDataIntegrity(
|
||||
oldVersion: 1,
|
||||
newVersion: 2,
|
||||
createOld: v1.DatabaseAtV1.new,
|
||||
createNew: v2.DatabaseAtV2.new,
|
||||
oldVersion: 2,
|
||||
newVersion: 3,
|
||||
createOld: v2.DatabaseAtV2.new,
|
||||
createNew: v3.DatabaseAtV3.new,
|
||||
openTestedDatabase: AppDatabase.new,
|
||||
createItems: (batch, oldDb) {},
|
||||
validateItems: (newDb) async {},
|
||||
|
||||
@@ -31,6 +31,7 @@ Task _makeTask({
|
||||
effortLevel: EffortLevel.medium,
|
||||
nextDueDate: nextDueDate,
|
||||
createdAt: DateTime(2026, 1, 1),
|
||||
isActive: true,
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
@@ -30,6 +30,7 @@ Task _makeTask({
|
||||
effortLevel: EffortLevel.medium,
|
||||
nextDueDate: nextDueDate,
|
||||
createdAt: DateTime(2026, 1, 1),
|
||||
isActive: true,
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user