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 const int MessageQueryLimit = 10_000;
|
||||||
|
|
||||||
private string DbPath { get; }
|
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 =
|
internal static readonly MessagePackSerializerOptions MsgPackOptions =
|
||||||
MessagePackSerializerOptions.Standard.WithResolver(
|
MessagePackSerializerOptions.Standard.WithResolver(
|
||||||
@@ -227,13 +232,19 @@ internal class MessageStore : IDisposable
|
|||||||
migrationsToDo.Add(Migrate1);
|
migrationsToDo.Add(Migrate1);
|
||||||
migrationsToDo.Add(Migrate2);
|
migrationsToDo.Add(Migrate2);
|
||||||
migrationsToDo.Add(Migrate3);
|
migrationsToDo.Add(Migrate3);
|
||||||
|
migrationsToDo.Add(Migrate4);
|
||||||
break;
|
break;
|
||||||
case 1:
|
case 1:
|
||||||
migrationsToDo.Add(Migrate2);
|
migrationsToDo.Add(Migrate2);
|
||||||
migrationsToDo.Add(Migrate3);
|
migrationsToDo.Add(Migrate3);
|
||||||
|
migrationsToDo.Add(Migrate4);
|
||||||
break;
|
break;
|
||||||
case 2:
|
case 2:
|
||||||
migrationsToDo.Add(Migrate3);
|
migrationsToDo.Add(Migrate3);
|
||||||
|
migrationsToDo.Add(Migrate4);
|
||||||
|
break;
|
||||||
|
case 3:
|
||||||
|
migrationsToDo.Add(Migrate4);
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -344,6 +355,30 @@ internal class MessageStore : IDisposable
|
|||||||
SetMigrationVersion(3);
|
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)
|
private void SetMigrationVersion(int version)
|
||||||
{
|
{
|
||||||
_logger.Information($"Setting version {version}");
|
_logger.Information($"Setting version {version}");
|
||||||
@@ -493,6 +528,16 @@ internal class MessageStore : IDisposable
|
|||||||
return Convert.ToInt32(cmd.ExecuteScalar());
|
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)
|
internal void UpsertMessage(Message message)
|
||||||
{
|
{
|
||||||
// Privacy filter -- drop disallowed ChatTypes before they reach storage.
|
// Privacy filter -- drop disallowed ChatTypes before they reach storage.
|
||||||
|
|||||||
Reference in New Issue
Block a user