perf/fix(data): batch sync updates, paging tiebreaker, migration-registration test #320

Merged
JMR-dev merged 4 commits from fix-310-311-312-dao into main 2026-07-04 08:20:59 +00:00
JMR-dev commented 2026-07-04 07:31:20 +00:00 (Migrated from github.com)

Three small data/local review fixes from the whole-repo review.

#310 — batch sync header updates in one transaction

MailSyncer refreshed a whole recent window with entities.forEach { updateHeaderContent(...) } = N separate Room transactions (journal write/fsync each) per folder per foreground sync, amplified on the encrypted cache. Added a single @Transaction DAO method MessageDao.updateHeaderContents(List<MessageEntity>) that applies the per-row refreshes in one commit, and routed the sync loop through it. The single-row updateHeaderContent stays (still used by MailBackfiller / MailRepositoryImpl).

Closes #310

#311 — paging ORDER BY tiebreaker

The four PagingSource queries ordered by timestampMillis DESC only; timestampMillis isn't unique (bulk mail shares a second), so rows tied at a LIMIT/OFFSET page boundary could duplicate or skip. Appended the id primary key (ORDER BY timestampMillis DESC, id) for a total order. id is already in the projection, so no new index.

No schema re-export / version bump: the exported Room identityHash is derived from table/index/view structure, not @Query SQL. Confirmed — KSP ran clean and git status shows no change under app/schemas/.

Closes #311

#312 — migration-registration safety net

MigrationTest reflectively discovers and replay-tests every Migration, but nothing asserted DatabaseModule actually registers them. With no destructive fallback, a migration authored + schema-committed but forgotten in addMigrations passes every test yet crash-loops all upgrading users at DB open. Exposed the registered list as DatabaseModule.ALL_MIGRATIONS (spread into addMigrations) and added a MigrationTest assertion that it equals the reflectively-discovered set.

Closes #312

Tests / validation

  • New MessageDaoTest: batch-update refresh, and the id tiebreaker across all four pagers (browse + search).
  • New MigrationTest.databaseModuleRegistersEveryDeclaredMigration.
  • Wired updateHeaderContents into MailSyncConcurrencyTest's fake DAO.
  • Green locally: :app:testDebugUnitTest + :app:compileDebugAndroidTestKotlin + :app:ktlintCheck + :app:detekt, and instrumented MessageDaoTest + MigrationTest (31 tests) on a cold-booted emulator.

🤖 Generated with Claude Code

Three small data/local review fixes from the whole-repo review. ## #310 — batch sync header updates in one transaction `MailSyncer` refreshed a whole recent window with `entities.forEach { updateHeaderContent(...) }` = N separate Room transactions (journal write/fsync each) per folder per foreground sync, amplified on the encrypted cache. Added a single `@Transaction` DAO method `MessageDao.updateHeaderContents(List<MessageEntity>)` that applies the per-row refreshes in one commit, and routed the sync loop through it. The single-row `updateHeaderContent` stays (still used by `MailBackfiller` / `MailRepositoryImpl`). Closes #310 ## #311 — paging ORDER BY tiebreaker The four `PagingSource` queries ordered by `timestampMillis DESC` only; `timestampMillis` isn't unique (bulk mail shares a second), so rows tied at a LIMIT/OFFSET page boundary could duplicate or skip. Appended the `id` primary key (`ORDER BY timestampMillis DESC, id`) for a total order. `id` is already in the projection, so no new index. No schema re-export / version bump: the exported Room `identityHash` is derived from table/index/view structure, not `@Query` SQL. Confirmed — KSP ran clean and `git status` shows no change under `app/schemas/`. Closes #311 ## #312 — migration-registration safety net `MigrationTest` reflectively discovers and replay-tests every `Migration`, but nothing asserted `DatabaseModule` actually *registers* them. With no destructive fallback, a migration authored + schema-committed but forgotten in `addMigrations` passes every test yet crash-loops all upgrading users at DB open. Exposed the registered list as `DatabaseModule.ALL_MIGRATIONS` (spread into `addMigrations`) and added a `MigrationTest` assertion that it equals the reflectively-discovered set. Closes #312 ## Tests / validation - New `MessageDaoTest`: batch-update refresh, and the `id` tiebreaker across all four pagers (browse + search). - New `MigrationTest.databaseModuleRegistersEveryDeclaredMigration`. - Wired `updateHeaderContents` into `MailSyncConcurrencyTest`'s fake DAO. - Green locally: `:app:testDebugUnitTest` + `:app:compileDebugAndroidTestKotlin` + `:app:ktlintCheck` + `:app:detekt`, and instrumented `MessageDaoTest` + `MigrationTest` (31 tests) on a cold-booted emulator. 🤖 Generated with [Claude Code](https://claude.com/claude-code)
Sign in to join this conversation.