perf(chat): ingest a message batch in one transaction

Ingesting a scan's worth of chat messages persisted each new message
with its own append() call, so a batch of fresh messages opened and
committed one SQLite transaction per row.

- A new appendBatch persists many messages in a single transaction,
  using the same INSERT OR IGNORE dedup key as append() and preserving
  input order so load()'s rowid ordering is unchanged.
- Every message is serialized and encrypted before the transaction
  opens, so a message with a non-UTF-8 body is skipped on its own
  without leaving a transaction open; the valid siblings still persist.
- Ingest collects the newly stored messages and writes them once after
  the loop instead of per message. It is inert (returns 0) while locked
  or on empty input.

Tests cover a batch insert, dedup on re-insert, empty input, bad-UTF-8
isolation with a valid sibling and a following append(), insert order,
and the locked no-op.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
2026-10-13 22:03:00 -05:00
co-authored by Claude Opus 4.8
parent 1416654385
commit 3fd52fbdaf
4 changed files with 86 additions and 1 deletions
+6
View File
@@ -44,6 +44,12 @@ public:
// Returns true if newly inserted; false on duplicate or while locked.
bool append(const ChatMessage& message);
// Persist many messages in ONE transaction (INSERT OR IGNORE, same dedup key as append()).
// Serializes+encrypts every message BEFORE opening the transaction, so a non-UTF-8 body is
// skipped without leaving a transaction open. Preserves input order (load() relies on rowid).
// Returns the number of rows actually inserted. No-op (returns 0) while locked or on empty input.
int appendBatch(const std::vector<ChatMessage>& messages);
// Persist-or-overwrite one message by its (txid+position) dedup key. Unlike append(), this
// updates an existing row's payload — used for outgoing echoes whose delivery status changes
// (Sending → Sent/Failed). Returns true on success; false while locked / on error.