feat(messagestore): add Migrate4 with standalone FTS5 virtual table
Lays down a messages_fts virtual table with message_guid (UNINDEXED, hex TEXT of the BLOB primary key), sender_text and content_text columns using the unicode61 tokenizer with diacritic folding. Standalone FTS5 without content='messages' linking, because messages.Id is BLOB and FTS5's content_rowid contract requires an INTEGER rowid alias. LoadByGuids (Task 4.3) will resolve the hex GUIDs back to messages rows via WHERE Id IN (...) joins. Schema step only -- the bulk-insert worker that fills the index lives in Task 4.2. Internal Connection property exposure plus a HasMessagesFtsTable helper let the Build-Suite verify Migrate4 without raw PRAGMA glue in each test. v1.4.8 H2 Sub-Task 4.1.
This commit is contained in:
@@ -126,7 +126,12 @@ internal class MessageStore : IDisposable
|
||||
private const int MessageQueryLimit = 10_000;
|
||||
|
||||
private string DbPath { get; }
|
||||
private SqliteConnection Connection { get; set; }
|
||||
|
||||
// Internal so the Build-Suite tests can verify Migrate4's CREATE VIRTUAL
|
||||
// TABLE result via a one-off PRAGMA without exposing a dedicated helper
|
||||
// for each schema invariant. Setter stays private; the ctor is the only
|
||||
// place that assigns.
|
||||
internal SqliteConnection Connection { get; private set; }
|
||||
|
||||
internal static readonly MessagePackSerializerOptions MsgPackOptions =
|
||||
MessagePackSerializerOptions.Standard.WithResolver(
|
||||
@@ -227,13 +232,19 @@ internal class MessageStore : IDisposable
|
||||
migrationsToDo.Add(Migrate1);
|
||||
migrationsToDo.Add(Migrate2);
|
||||
migrationsToDo.Add(Migrate3);
|
||||
migrationsToDo.Add(Migrate4);
|
||||
break;
|
||||
case 1:
|
||||
migrationsToDo.Add(Migrate2);
|
||||
migrationsToDo.Add(Migrate3);
|
||||
migrationsToDo.Add(Migrate4);
|
||||
break;
|
||||
case 2:
|
||||
migrationsToDo.Add(Migrate3);
|
||||
migrationsToDo.Add(Migrate4);
|
||||
break;
|
||||
case 3:
|
||||
migrationsToDo.Add(Migrate4);
|
||||
break;
|
||||
}
|
||||
|
||||
@@ -344,6 +355,30 @@ internal class MessageStore : IDisposable
|
||||
SetMigrationVersion(3);
|
||||
}
|
||||
|
||||
private void Migrate4()
|
||||
{
|
||||
_logger.Information("Running migration 4: Add FTS5 virtual table for full-text search");
|
||||
|
||||
// Standalone FTS5 table (no content='messages' linking, no content_rowid).
|
||||
// messages.Id is BLOB-PK (Guid), which is incompatible with FTS5's
|
||||
// content_rowid requirement of an INTEGER rowid alias. We store the
|
||||
// GUID as a hex TEXT column (UNINDEXED so the tokenizer skips it) and
|
||||
// FTS5 manages its own internal INTEGER rowid. LoadByGuids joins back
|
||||
// via WHERE Id IN (... unhex(message_guid)) when the search returns.
|
||||
using var cmd = Connection.CreateCommand();
|
||||
cmd.CommandText = """
|
||||
CREATE VIRTUAL TABLE IF NOT EXISTS messages_fts USING fts5(
|
||||
message_guid UNINDEXED,
|
||||
sender_text,
|
||||
content_text,
|
||||
tokenize='unicode61 remove_diacritics 2'
|
||||
);
|
||||
""";
|
||||
cmd.ExecuteNonQuery();
|
||||
|
||||
SetMigrationVersion(4);
|
||||
}
|
||||
|
||||
private void SetMigrationVersion(int version)
|
||||
{
|
||||
_logger.Information($"Setting version {version}");
|
||||
@@ -493,6 +528,16 @@ internal class MessageStore : IDisposable
|
||||
return Convert.ToInt32(cmd.ExecuteScalar());
|
||||
}
|
||||
|
||||
// Schema probe for the v1.4.8 FTS5 virtual table. Used by the Build-Suite
|
||||
// tests to verify Migrate4's CREATE VIRTUAL TABLE actually landed without
|
||||
// duplicating PRAGMA glue in each test body.
|
||||
internal bool HasMessagesFtsTable()
|
||||
{
|
||||
using var cmd = Connection.CreateCommand();
|
||||
cmd.CommandText = "SELECT count(*) FROM sqlite_master WHERE name='messages_fts';";
|
||||
return (long)(cmd.ExecuteScalar() ?? 0L) > 0;
|
||||
}
|
||||
|
||||
internal void UpsertMessage(Message message)
|
||||
{
|
||||
// Privacy filter -- drop disallowed ChatTypes before they reach storage.
|
||||
|
||||
Reference in New Issue
Block a user