diff --git a/CLAUDE.md b/CLAUDE.md index f163bf1..33e6ed8 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -125,7 +125,7 @@ Non-trivial logic leaves one runnable check behind. Pure functions (`merge_polic subscriber. Two feeds publishing the same URL means only the first one scanned shows it. * **Read state lives in `entry_state`, per user, and nowhere else.** `entries` had `read`, `flagged` and `position` columns from before accounts; two bugs came from queries still reading them - (retention, and the entry pruner), and `migrate()` now drops them. + (retention, and the entry pruner), and they were dropped in 0.5. * **The catalogue is config.toml; the subscriptions are in the database.** A feed exists once; `subscriptions(user_id, feed_id)` says who wants it and with what settings. OPML children are derived and never written to config. @@ -140,10 +140,16 @@ Non-trivial logic leaves one runnable check behind. Pure functions (`merge_polic * `/api/settings` answering `200` does **not** mean the daemon is well — the web server is a different task. `ipx status` checks the control socket and the database; to see the worker getting through its jobs, watch for `scan complete` in the log. -* **Every `ipx` command runs `migrate()` when it opens the database**, the healthcheck's - `ipx status` included. A migration that rewrites a big table (`DROP COLUMN`) takes seconds on - production, and a command run meanwhile fails with `migrating schema`. It changes nothing; wait - for `daemon started` in the log. Copy `state.db` aside before deploying one. +* **The database goes through SeaORM, and the entities in `src/entity.rs` are the schema.** + `Db::open` creates any missing table or index from them (`create_missing`), on every `ipx` + command, the healthcheck's `ipx status` included, so it must never write when nothing is + missing: SeaORM's experimental schema sync dropped and remade an index on every open, the + write lock that took made `ipx status` time out behind a busy daemon, and it was removed for + it. A new column on an existing table needs its own `ALTER`; nothing adds one for you. +* **SQL written by hand in `db.rs` has to run on SQLite and Postgres both** (issue #18): `$1` + parameters, bound only if used; `ON CONFLICT`, not `INSERT OR IGNORE`; yes/no columns tested + as themselves (`NOT coalesce(s.read, false)`) and written as `true`/`false`, never compared to + 1; no `rowid`, `GLOB` or `UPDATE OR IGNORE`. `Args` in `db.rs` builds the parameters. ## House style diff --git a/docs/architecture.md b/docs/architecture.md index a5357dd..c675006 100644 --- a/docs/architecture.md +++ b/docs/architecture.md @@ -10,7 +10,8 @@ it to a running daemon. |---|---|---| | `src/main.rs` | CLI, dispatch, scan loop, download policy | `iPXAgent.py` | | `src/config.rs` | TOML load/save, `General`/`Feed`/`Web`, intervals, slugs | `iPXSettings.py`, `feeds.plist` | -| `src/db.rs` | SQLite schema, migrations, every query | `.ipxd` plists, `history.dat`, `qmcache.dat` | +| `src/db.rs` | Every query, through SeaORM; creates missing tables | `.ipxd` plists, `history.dat`, `qmcache.dat` | +| `src/entity.rs` | The tables, as SeaORM entities: the schema | — | | `src/feed.rs` | Conditional GET, RSS/Atom/OPML parsing | `FeedData.__getFeed/__getEntries` | | `src/download.rs` | Streaming download, naming, type sniffing, placement | `iPXDownloader.getFile` | | `src/torrent.rs` | librqbit session, seeding limits, stall abort | vendored BitTorrent 4.2.1 | @@ -65,14 +66,14 @@ entry_state user_id, feed_id, guid, read, flagged, position ``` Read state is `entry_state` alone. `entries` had `read`, `flagged` and `position` columns from -before accounts; two bugs came from queries still reading them, and `migrate()` drops them from an -older database. +before accounts; two bugs came from queries still reading them, and they were dropped in 0.5. -Schema changes: add the table or column to `SCHEMA`. `CREATE TABLE IF NOT EXISTS` leaves a table -that already exists alone, so a new column on one also goes in `migrate()`'s `wanted` list, and a -retired one in its `retired` list; both are checked with `PRAGMA table_info`. Columns from before -0.3.0, the oldest version an upgrade may start from, need no entry. `Db::memory()` runs the same -path as `Db::open`, so a migration cannot pass the tests while missing in production. +Schema changes: the tables are the entities in `src/entity.rs`, and `Db::open` creates whatever +table or index a database is missing from them (`db::create_missing`), with `IF NOT EXISTS`. It +never alters a table that exists, so a new column on one needs its own `ALTER` in +`create_missing`, or `sea-orm-migration` once there are several. `Db::memory()` builds its +database the same way, so the tests run on the schema production gets. A database from before +0.7 takes its last columns from the old `migrate()`, so it upgrades through a 0.7 release first. ## Control socket