fix(messagestore): match TEXT-stored UUID form in FTS bulk insert and LoadByGuids
messages.Id is declared BLOB but stored as TEXT because Microsoft.Data.Sqlite binds Guid parameters as UUID strings (UpsertMessage uses AddWithValue with a Guid). RebuildFtsIndex cast reader.GetValue(0) to byte[] and threw InvalidCastException at the first row. LoadByGuids bound byte[] params against the TEXT-stored Id and would have returned no rows once the index had built. - RebuildFtsIndex reads via GetGuid and stores ToString() in messages_fts.message_guid. - LoadByGuids parses incoming UUID strings and binds them as Guid so Microsoft.Data.Sqlite re-serialises to TEXT, matching the messages.Id storage form. - DbViewer caller variable renamed hexIds -> guidHits for clarity.
This commit is contained in:
+22
-14
@@ -690,7 +690,12 @@ internal class MessageStore : IDisposable
|
||||
{
|
||||
ct.ThrowIfCancellationRequested();
|
||||
|
||||
var guidBytes = (byte[])reader.GetValue(0);
|
||||
// messages.Id is BLOB-typed in the schema but stored as TEXT
|
||||
// because Microsoft.Data.Sqlite binds Guid parameters as UUID
|
||||
// strings by default (UpsertMessage uses AddWithValue("$Id",
|
||||
// message.Id)). reader.GetValue(0) therefore returns string,
|
||||
// not byte[]; GetGuid parses the TEXT form regardless.
|
||||
var idGuid = reader.GetGuid(0);
|
||||
var senderChunks = MessagePackSerializer.Deserialize<List<Chunk>>(
|
||||
reader.GetFieldValue<byte[]>(1),
|
||||
MsgPackOptions
|
||||
@@ -700,7 +705,7 @@ internal class MessageStore : IDisposable
|
||||
MsgPackOptions
|
||||
);
|
||||
|
||||
pG.Value = Convert.ToHexString(guidBytes);
|
||||
pG.Value = idGuid.ToString();
|
||||
pS.Value = ChunkUtil.ToRawString(senderChunks);
|
||||
pC.Value = ChunkUtil.ToRawString(contentChunks);
|
||||
insert.ExecuteNonQuery();
|
||||
@@ -760,24 +765,27 @@ internal class MessageStore : IDisposable
|
||||
}
|
||||
}
|
||||
|
||||
// Joins hex-encoded GUIDs from FullTextSearch back to Message rows. The
|
||||
// primary key is BLOB, so we decode the hex back to bytes for the IN(...)
|
||||
// lookup. SQLite has a hard parameter limit of 999 in default builds, so
|
||||
// we chunk the input -- a 1000-hit FTS query never explodes the SELECT.
|
||||
// Result ordering is not guaranteed; callers re-sort (e.g. DbViewer sorts
|
||||
// by Date descending in Sub-Task 4.4).
|
||||
public IReadOnlyList<Message> LoadByGuids(IReadOnlyList<string> hexIds)
|
||||
// Joins UUID strings from FullTextSearch back to Message rows. messages.Id
|
||||
// is BLOB-declared in the schema but actually stored as TEXT (UUID form)
|
||||
// because Microsoft.Data.Sqlite serialises Guid parameters as strings by
|
||||
// default. Binding the lookup parameters as Guid keeps the same TEXT
|
||||
// storage form on both sides so the IN(...) compare matches. SQLite has a
|
||||
// hard parameter limit of 999 in default builds, so we chunk the input --
|
||||
// a 1000-hit FTS query never explodes the SELECT. Result ordering is not
|
||||
// guaranteed; callers re-sort (e.g. DbViewer sorts by Date descending in
|
||||
// Sub-Task 4.4).
|
||||
public IReadOnlyList<Message> LoadByGuids(IReadOnlyList<string> guidStrings)
|
||||
{
|
||||
if (hexIds.Count == 0)
|
||||
if (guidStrings.Count == 0)
|
||||
return Array.Empty<Message>();
|
||||
|
||||
lock (_readLock)
|
||||
{
|
||||
var result = new List<Message>(hexIds.Count);
|
||||
var result = new List<Message>(guidStrings.Count);
|
||||
const int chunkSize = 500;
|
||||
for (var offset = 0; offset < hexIds.Count; offset += chunkSize)
|
||||
for (var offset = 0; offset < guidStrings.Count; offset += chunkSize)
|
||||
{
|
||||
var batch = hexIds.Skip(offset).Take(chunkSize).ToList();
|
||||
var batch = guidStrings.Skip(offset).Take(chunkSize).ToList();
|
||||
using var cmd = Connection.CreateCommand();
|
||||
var placeholders = string.Join(",", batch.Select((_, i) => $"$id{i}"));
|
||||
cmd.CommandText = $"""
|
||||
@@ -787,7 +795,7 @@ internal class MessageStore : IDisposable
|
||||
WHERE Id IN ({placeholders}) AND Deleted = false;
|
||||
""";
|
||||
for (var i = 0; i < batch.Count; i++)
|
||||
cmd.Parameters.AddWithValue($"$id{i}", Convert.FromHexString(batch[i]));
|
||||
cmd.Parameters.AddWithValue($"$id{i}", Guid.Parse(batch[i]));
|
||||
|
||||
using var reader = cmd.ExecuteReader();
|
||||
while (reader.Read())
|
||||
|
||||
@@ -485,8 +485,8 @@ public class DbViewer : Window
|
||||
// still serves the page.
|
||||
if (UseFullTextSearch && Plugin.MessageManager.Store.IsFtsIndexBuilt)
|
||||
{
|
||||
var hexIds = Plugin.MessageManager.Store.FullTextSearch(SimpleSearchTerm);
|
||||
var resolved = Plugin.MessageManager.Store.LoadByGuids(hexIds);
|
||||
var guidHits = Plugin.MessageManager.Store.FullTextSearch(SimpleSearchTerm);
|
||||
var resolved = Plugin.MessageManager.Store.LoadByGuids(guidHits);
|
||||
return new ConcurrentStack<Message>(resolved.OrderByDescending(m => m.Date));
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user