T08: Drift schema mirroring the Room baseline

Three tables with CASCADE foreign keys, indices, WAL, and a real MigrationStrategy
from schema version 1. No destructive fallback, ever.

Two things needed deliberate handling. SQLite defaults foreign_keys to OFF and
Drift, unlike Room, does not enable it -- without the pragma every CASCADE is
decorative, so there is now a test that reads the pragma back. And Drift both
snake_cases columns and names row classes after tables; build.yaml sets
case_from_dart_to_sql: preserve so the schema stays column-for-column identical
to Room's, and @DataClassName keeps row types from colliding with the domain
models.

SchemaTest was instrumented and needed a device; the Drift version is a plain
unit test that runs in under a second with no emulator. 16 tests.

95 tests passing, analyze clean.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
2026-08-15 01:13:45 -05:00
parent 398157b1f5
commit d7d9854dc9
5 changed files with 3749 additions and 0 deletions

View File

@@ -277,3 +277,43 @@ comment in `run.sh` says so explicitly.
its ~965 lines of tests, and proven equivalent rather than assumed equivalent.
Next: **T08 (Drift schema)**, the first task that touches persistence.
---
## T08 — Drift schema · **complete**
`lib/src/data/database.dart` (+ generated `database.g.dart`). **16 tests.**
Mirrors `app/schemas/com.rippr.data.AppDatabase/2.json`: three tables, CASCADE foreign
keys, indices on `tripId` / `segmentId` / `synced`, WAL with `synchronous = NORMAL`.
Starts at Dart schema version 1 with a real `MigrationStrategy` — the destructive
fallback never comes back.
### Foreign keys are OFF by default in SQLite
Room switched them on for us. **Drift does not.** Without `PRAGMA foreign_keys = ON` in
`beforeOpen`, every `CASCADE` in the schema is decorative and deleting a trip silently
orphans all of its points. There is now a test that reads the pragma back and asserts it
is `1`, because this is invisible until data is already wrong.
### Two collisions worth recording
**Drift generates row classes named after the table.** `Trips` → `Trip`, colliding with
the domain model of the same name and producing 21 confusing analyzer errors of the form
*"Trip can't be assigned to Trip"*. Fixed with `@DataClassName('TripRow')` etc. The
mapping functions `_toTrip` / `_toSegment` / `_toPoint` convert row → domain, so the
domain layer stays unaware Drift exists.
**Drift snake_cases column names.** `speedKmh` became `speed_kmh`, which broke the one
raw-SQL query (the live stats aggregate) with `no such column: speedKmh`. Rather than 30
`.named()` annotations, `build.yaml` sets `case_from_dart_to_sql: preserve`. That keeps
the schema column-for-column identical to Room's, lets the raw SQL stay byte-identical to
the Kotlin DAO query it was ported from, and leaves a Room-file importer possible later.
### The instrumented-to-unit win, realised
`SchemaTest` needed a device and an emulator. The Drift equivalent runs on the Dart VM in
well under a second with nothing booted. `TripRepositoryTest` and `MergeTest` (441 more
lines) should convert the same way in T09.
**95 tests passing, analyze clean.**