From f9030dc1e577810bd7459914f478230e6356d99d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bot=20of=20Thomas=20G=C3=BCttler?= Date: Thu, 14 May 2026 08:37:00 +0200 Subject: [PATCH] perf(P4): add indexes on mailboxes and threads for observeMailboxes/observeThreads (#36) --- lib/data/db/database.dart | 18 ++++++++++- test/unit/migration_test.dart | 57 +++++++++++++++++++++++++++++++++-- 2 files changed, 72 insertions(+), 3 deletions(-) diff --git a/lib/data/db/database.dart b/lib/data/db/database.dart index a83dc08..5d992ca 100644 --- a/lib/data/db/database.dart +++ b/lib/data/db/database.dart @@ -269,7 +269,7 @@ class AppDatabase extends _$AppDatabase { AppDatabase([QueryExecutor? executor]) : super(executor ?? _openConnection()); @override - int get schemaVersion => 24; + int get schemaVersion => 25; @override MigrationStrategy get migration => MigrationStrategy( @@ -431,6 +431,22 @@ class AppDatabase extends _$AppDatabase { if (from >= 4 && from < 24) { await m.addColumn(drafts, drafts.imapServerId); } + if (from < 25) { + // For observeMailboxes: filter by account_id, sort by path. + await m.createIndex( + Index( + 'mailboxes_account_id', + 'CREATE INDEX IF NOT EXISTS mailboxes_account_id ON mailboxes (account_id, path);', + ), + ); + // For observeThreads: filter by account_id+mailbox_path, sort by latest_date. + await m.createIndex( + Index( + 'threads_latest_date', + 'CREATE INDEX IF NOT EXISTS threads_latest_date ON threads (account_id, mailbox_path, latest_date DESC);', + ), + ); + } }, ); } diff --git a/test/unit/migration_test.dart b/test/unit/migration_test.dart index f7caf70..242fa7d 100644 --- a/test/unit/migration_test.dart +++ b/test/unit/migration_test.dart @@ -14,7 +14,7 @@ void main() { group('Migration', () { test('schemaVersion matches expected value', () async { final db = AppDatabase(NativeDatabase.memory()); - expect(db.schemaVersion, 24); + expect(db.schemaVersion, 25); await db.close(); }); @@ -141,6 +141,23 @@ void main() { ]), ); + // v18, v22, v25: indexes. + final allIndexes = await db + .customSelect("SELECT name FROM sqlite_master WHERE type='index'") + .get(); + final indexNames = allIndexes.map((r) => r.read('name')).toSet(); + expect( + indexNames, + containsAll([ + 'emails_received_at', // v18 + 'emails_thread_id', // v18 + 'pending_changes_account_id', // v18 + 'emails_snoozed_until', // v22 + 'mailboxes_account_id', // v25 + 'threads_latest_date', // v25 + ]), + ); + await db.close(); if (dbFile.existsSync()) dbFile.deleteSync(); }); @@ -186,6 +203,17 @@ void main() { updated_at INTEGER NOT NULL ); '''); + rawDb.execute(''' + CREATE TABLE mailboxes ( + id TEXT NOT NULL PRIMARY KEY, + account_id TEXT NOT NULL, + path TEXT NOT NULL, + name TEXT NOT NULL, + unread_count INTEGER NOT NULL DEFAULT 0, + total_count INTEGER NOT NULL DEFAULT 0, + role TEXT NULL + ); + '''); rawDb.execute(''' CREATE TABLE emails ( id TEXT NOT NULL PRIMARY KEY, @@ -210,6 +238,23 @@ void main() { snoozed_from_mailbox_path TEXT NULL ); '''); + rawDb.execute(''' + CREATE TABLE threads ( + account_id TEXT NOT NULL, + mailbox_path TEXT NOT NULL, + id TEXT NOT NULL, + subject TEXT NULL, + latest_date INTEGER NOT NULL, + message_count INTEGER NOT NULL DEFAULT 1, + has_unread INTEGER NOT NULL DEFAULT 0 CHECK ("has_unread" IN (0, 1)), + is_flagged INTEGER NOT NULL DEFAULT 0 CHECK ("is_flagged" IN (0, 1)), + participants_json TEXT NOT NULL DEFAULT '[]', + preview TEXT NULL, + latest_email_id TEXT NOT NULL, + email_ids_json TEXT NOT NULL DEFAULT '[]', + PRIMARY KEY (account_id, mailbox_path, id) + ); + '''); rawDb.execute('PRAGMA user_version = 22;'); rawDb.close(); @@ -223,11 +268,19 @@ void main() { final draftColumns = await _tableColumns(db, 'drafts'); expect(draftColumns, contains('imap_server_id')); + // v25: new indexes on mailboxes and threads. + final allIndexes = await db + .customSelect("SELECT name FROM sqlite_master WHERE type='index'") + .get(); + final indexNames = allIndexes.map((r) => r.read('name')).toSet(); + expect(indexNames, contains('mailboxes_account_id')); + expect(indexNames, contains('threads_latest_date')); + await db.close(); if (dbFile.existsSync()) dbFile.deleteSync(); }); - test('fresh install creates all tables at schemaVersion 24', () async { + test('fresh install creates all tables at schemaVersion 25', () async { final db = AppDatabase(NativeDatabase.memory()); await db.select(db.accounts).get();