fix(mailbox): project message list to avoid CursorWindow overflow #52

Merged
JMR-dev merged 1 commits from fix/message-list-cursorwindow-overflow into main 2026-07-01 18:54:36 +00:00
JMR-dev commented 2026-07-01 18:44:56 +00:00 (Migrated from github.com)

What & why

Fixes the fatal IllegalStateException: Couldn't read row N from CursorWindow crash (#51), pulled from a connected device.

MessageDao.observeAll() ran SELECT * FROM messages and returned full MessageEntity rows — including the potentially large body/isHtml columns — for every cached message at once. Observing them all drags big HTML bodies through SQLite's shared (~2 MB) CursorWindow, which overflows once enough large bodies are cached, taking down the process.

The mailbox list never renders or searches the body (matchesSearch only looks at sender/subject/snippet), and the reader already loads the body lazily per-message via getById/openMessage when a message is opened — so nothing else needs the body in the list stream.

Changes

  • MessageSummary (new) — a lightweight projection: every column the list uses, minus body/isHtml.
  • MessageDao — observeAll(): Flow<List<MessageEntity>> → observeSummaries(): Flow<List<MessageSummary>> with an explicit column projection.
  • Mappers — added MessageSummary.toDomain() (leaves body = "").
  • MailRepositoryImpl.observeMessages() — now consumes observeSummaries().
  • Tests — migrated the unit + DB tests to the new query, and added a regression test in LibreMailDatabaseTest that inserts messages whose bodies exceed the CursorWindow and asserts the list query reads them without crashing.

Verification

Local fast gate green: assembleDebug + testDebugUnitTest + lintDebug, plus compileDebugAndroidTestKotlin (instrumented tests were touched). The regression test is instrumented, so it runs on CI's emulator matrix.

Closes #51

## What & why Fixes the fatal `IllegalStateException: Couldn't read row N from CursorWindow` crash (#51), pulled from a connected device. `MessageDao.observeAll()` ran `SELECT * FROM messages` and returned full `MessageEntity` rows — including the potentially large `body`/`isHtml` columns — for **every** cached message at once. Observing them all drags big HTML bodies through SQLite's shared (~2 MB) `CursorWindow`, which overflows once enough large bodies are cached, taking down the process. The mailbox list never renders or searches the body (`matchesSearch` only looks at sender/subject/snippet), and the reader already loads the body lazily per-message via `getById`/`openMessage` when a message is opened — so nothing else needs the body in the list stream. ## Changes - **`MessageSummary`** (new) — a lightweight projection: every column the list uses, minus `body`/`isHtml`. - **`MessageDao`** — `observeAll(): Flow<List<MessageEntity>>` → `observeSummaries(): Flow<List<MessageSummary>>` with an explicit column projection. - **`Mappers`** — added `MessageSummary.toDomain()` (leaves `body = ""`). - **`MailRepositoryImpl.observeMessages()`** — now consumes `observeSummaries()`. - **Tests** — migrated the unit + DB tests to the new query, and added a regression test in `LibreMailDatabaseTest` that inserts messages whose bodies exceed the CursorWindow and asserts the list query reads them without crashing. ## Verification Local fast gate green: `assembleDebug` + `testDebugUnitTest` + `lintDebug`, plus `compileDebugAndroidTestKotlin` (instrumented tests were touched). The regression test is instrumented, so it runs on CI's emulator matrix. Closes #51
Sign in to join this conversation.