From 3dfd4f98a799a1a3a99d779165053d1fea751a55 Mon Sep 17 00:00:00 2001 From: Peter Steinberger Date: Tue, 21 Apr 2026 01:31:54 +0100 Subject: [PATCH] fix: detect existing FTS tables after reopen (#185) (thanks @iamhitarth) --- CHANGELOG.md | 1 + internal/store/db.go | 25 ++++++++++++++++--------- internal/store/search_fts_test.go | 25 +++++++++++++++++++++++++ 3 files changed, 42 insertions(+), 9 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 4439208..358fbd0 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -10,6 +10,7 @@ - Media: recover panics per download job so one bad payload no longer drains the worker pool. (#179 — thanks @shaun0927) - Messages: show display text for replies, reactions, and media in `messages context`. (#183 — thanks @fuleinist) +- Search: keep FTS5 enabled after reopening existing databases with already-applied migrations. (#185 — thanks @iamhitarth) - Send: persist retry-message plaintext so linked devices can decrypt retried messages. (#186 — thanks @SimDamDev) - Sync: include event type, stack trace, and recovery count when logging recovered event-handler panics. (#181 — thanks @shaun0927) diff --git a/internal/store/db.go b/internal/store/db.go index 38d94b6..dbe57ad 100644 --- a/internal/store/db.go +++ b/internal/store/db.go @@ -59,17 +59,24 @@ func (d *DB) init() error { return err } - // Detect FTS5 availability independently of migration state. - // The migration sets ftsEnabled only on first run; subsequent - // opens skip the migration and leave ftsEnabled as false. + // Detect FTS5 availability independently of migration state. The migration + // sets ftsEnabled only on first run; subsequent opens skip the migration. if !d.ftsEnabled { - if ok, _ := d.tableExists("messages_fts"); ok { - var n int - if err := d.sql.QueryRow("SELECT 1 FROM messages_fts LIMIT 1").Scan(&n); err == nil { - d.ftsEnabled = true - } - } + d.ftsEnabled = d.detectMessagesFTS() } return nil } + +func (d *DB) detectMessagesFTS() bool { + ok, err := d.tableExists("messages_fts") + if err != nil || !ok { + return false + } + hasDisplayText, err := d.tableHasColumn("messages_fts", "display_text") + if err != nil || !hasDisplayText { + return false + } + var n int + return d.sql.QueryRow("SELECT count(*) FROM messages_fts").Scan(&n) == nil +} diff --git a/internal/store/search_fts_test.go b/internal/store/search_fts_test.go index 8fd5d9e..85421ff 100644 --- a/internal/store/search_fts_test.go +++ b/internal/store/search_fts_test.go @@ -3,6 +3,7 @@ package store import ( + "path/filepath" "testing" "time" ) @@ -42,6 +43,30 @@ func TestSearchMessagesUsesFTSWhenEnabled(t *testing.T) { } } +func TestExistingEmptyFTSTableDetectedOnReopen(t *testing.T) { + path := filepath.Join(t.TempDir(), "store.db") + + db, err := Open(path) + if err != nil { + t.Fatalf("Open: %v", err) + } + if !db.HasFTS() { + t.Fatalf("expected initial FTS migration to enable FTS") + } + if err := db.Close(); err != nil { + t.Fatalf("Close: %v", err) + } + + db, err = Open(path) + if err != nil { + t.Fatalf("reopen: %v", err) + } + defer db.Close() + if !db.HasFTS() { + t.Fatalf("expected existing empty FTS table to enable FTS after reopen") + } +} + // TestSanitizeFTSQuery verifies that user input is sanitized before being // passed to the FTS5 MATCH clause, preventing query-syntax injection (#57). func TestSanitizeFTSQuery(t *testing.T) {