--- name: houndarr-database description: Houndarr's SQLite schema and migration discipline. Loads when reading or editing src/houndarr/database.py. Covers the schema table reference, the SCHEMA_VERSION bump checklist, and the version-locking rule for migration constants that prevents later renames from retroactively breaking earlier rebuild migrations. paths: - "src/houndarr/database.py" --- # Houndarr database conventions ## Database basics SQLite via aiosqlite. `get_db()` is an async context manager that opens a fresh connection per call (FKs enabled per connection; WAL mode set once in `init_db()`). Schema version is currently 19. Bump `SCHEMA_VERSION` and add a `_migrate_to_vN` when changing schema. ## Schema reference | Table | Purpose | Key constraints | |-------|---------|-----------------| | `settings` | Key-value config store | `key TEXT PK` | | `widget_api_key` | External widget API key, single row (v19) | `id INTEGER PRIMARY KEY CHECK(id = 1)`; `hash` is SHA-256 hex digest with `CHECK(length(hash) = 64 AND hash NOT GLOB '*[^0-9a-f]*')`; no FK | | `instances` | *arr instance configs | `type CHECK IN ('radarr','sonarr','lidarr','readarr','whisparr_v2','whisparr_v3')`; many policy columns with CHECK constraints; `monitored_total` / `unreleased_count` / `snapshot_refreshed_at` populated by the supervisor's snapshot refresh task | | `cooldowns` | Per-item search cooldown tracking | `instance_id FK→instances ON DELETE CASCADE`; `UNIQUE(instance_id, item_id, item_type)`; `search_kind CHECK IN ('missing','cutoff','upgrade')` (v15) | | `search_log` | Audit trail | `instance_id FK→instances ON DELETE SET NULL`; `action CHECK IN ('searched','skipped','error','info')` | Full DDL and migrations live in `src/houndarr/database.py`. ## Migration constants are version-locked Rebuild migrations (`CREATE TABLE foo_new ... INSERT INTO foo_new SELECT ...`) must reference a snapshot constant frozen at the introducing schema version, never the current `_ITEM_TYPES` / `_INSTANCE_TYPES` alias. The snapshots (`_ITEM_TYPES_V5`, `_ITEM_TYPES_V10`, `_ITEM_TYPES_V15`, `_ITEM_TYPES_V16`, `_INSTANCE_TYPES_V5`, `_INSTANCE_TYPES_V10`) live at the top of `database.py` and are immutable after their migration ships. Fresh-install DDL in `_SCHEMA_SQL` uses the latest snapshot via the `_ITEM_TYPES` / `_INSTANCE_TYPES` aliases. When adding a migration that renames a value: introduce a new `_FOO_TYPES_VN` constant, point the `_FOO_TYPES` alias at it, write the new migration with the new constant plus a CASE WHEN translation in its COPY, and leave the prior snapshot (and prior migrations) untouched. This prevents the class of bug where a later rename retroactively breaks an earlier rebuild migration's CHECK clause. ## Schema bump checklist 1. Bump `SCHEMA_VERSION` at the top of `database.py`. 2. Add a `_migrate_to_vN(conn)` function that performs the migration. 3. If the migration renames a value used in CHECK constraints, introduce a fresh `_FOO_TYPES_VN` snapshot and reference it in the new migration. 4. Add an entry to `_MIGRATIONS` keyed by the new version. 5. Update `_SCHEMA_SQL` if the canonical fresh-install DDL changed. 6. Add tests under `tests/test_database/` covering the migration path.