diff --git a/HellionChat/MessageStore.cs b/HellionChat/MessageStore.cs index 0cfe180..1ec4372 100644 --- a/HellionChat/MessageStore.cs +++ b/HellionChat/MessageStore.cs @@ -279,18 +279,25 @@ internal class MessageStore : IDisposable migrationsToDo.Add(Migrate2); migrationsToDo.Add(Migrate3); migrationsToDo.Add(Migrate4); + migrationsToDo.Add(Migrate5); break; case 1: migrationsToDo.Add(Migrate2); migrationsToDo.Add(Migrate3); migrationsToDo.Add(Migrate4); + migrationsToDo.Add(Migrate5); break; case 2: migrationsToDo.Add(Migrate3); migrationsToDo.Add(Migrate4); + migrationsToDo.Add(Migrate5); break; case 3: migrationsToDo.Add(Migrate4); + migrationsToDo.Add(Migrate5); + break; + case 4: + migrationsToDo.Add(Migrate5); break; } @@ -430,6 +437,23 @@ internal class MessageStore : IDisposable SetMigrationVersion(4); } + private void Migrate5() + { + _logger.LogInformation("Running migration 5: Add (Receiver, Date) index for tell history"); + + // GetTellHistoryWithSender filters on Receiver and orders by Date DESC. + // Without a matching index SQLite sorts the whole receiver history into a + // temp b-tree before returning row one, which defeats the early break in + // the caller. (Receiver, ChatType, Date) does NOT help: the ChatType IN + // filter sits between the equality prefix and the sort column. + using var cmd = Connection.CreateCommand(); + cmd.CommandText = + "CREATE INDEX IF NOT EXISTS idx_messages_receiver_date ON messages (Receiver, Date);"; + cmd.ExecuteNonQuery(); + + SetMigrationVersion(5); + } + private void SetMigrationVersion(int version) { _logger.LogInformation($"Setting version {version}"); @@ -1014,7 +1038,8 @@ internal class MessageStore : IDisposable } // Returns up to `limit` tells exchanged with the named player, oldest-first. - // SQL narrows by Receiver + ChatType via the (Receiver, Date) index, then + // SQL narrows by Receiver + ChatType via the (Receiver, Date) index (migration + // 5; before that the ordering fell back to a temp b-tree over all rows), then // the client-side loop runs PlayerPayload comparison and breaks once // `limit` partner matches accumulate. Earlier versions had a hardcoded // 500-row scan cap that cut less-frequent pinned partners off the back of