5.6 KiB
5.6 KiB
Схема базы данных
Актуальная схема SQLite-хранилища: таблицы, поля и связи. Это живой документ — его поддерживаем в соответствии с миграциями.
Поддержка вместе с миграциями. Источник истины по схеме —
internal/store/migrations/*.sql(goose). При каждой новой миграции, меняющей структуру (таблица/столбец/индекс/связь), обновляем эту диаграмму в том же change. Расхождение схемы с миграциями считаем багом документации.Состояние на: миграции
0001_init,0002_recognition_plan.
Назначение таблиц и почему так — architecture.md →
«Хранилище». Значения state и переходы — workflow.md.
ER-диаграмма
erDiagram
download ||--o{ recognition : "распознавания"
download ||--o{ hint : "подсказки"
download ||--o{ override : "ручные правки"
download ||--o{ file_link : "хардлинки"
recognition ||--o{ metadata_candidate : "кандидаты базы"
download {
INTEGER id PK "AUTOINCREMENT"
TEXT source_type "NOT NULL; magnet|torrent|url"
TEXT source_ref "NOT NULL; magnet/url/путь"
TEXT context "NOT NULL DEFAULT ''"
TEXT infohash "nullable; может появиться позже приёма"
TEXT idempotency_key "nullable; UNIQUE если NOT NULL"
TEXT state "NOT NULL; см. workflow.md"
TEXT error_code "nullable"
TEXT error_msg "nullable"
TEXT created_at "NOT NULL DEFAULT datetime('now')"
TEXT updated_at "NOT NULL DEFAULT datetime('now')"
}
recognition {
INTEGER id PK "AUTOINCREMENT"
INTEGER download_id FK "NOT NULL; ON DELETE CASCADE"
INTEGER attempt_no "NOT NULL DEFAULT 1"
INTEGER is_current "NOT NULL DEFAULT 1; 0/1"
TEXT media_type "nullable; movie|series"
TEXT title "nullable"
TEXT original_title "nullable"
INTEGER year "nullable"
TEXT provider "nullable; tmdb|tvdb|tvmaze|none"
TEXT provider_id "nullable"
REAL confidence "nullable"
TEXT reasons "NOT NULL DEFAULT '[]'; JSON: причины не-авто"
TEXT raw_llm "nullable; сырой ответ LLM"
TEXT plan "nullable; JSON recognize.Plan (миграция 0002)"
TEXT created_at "NOT NULL DEFAULT datetime('now')"
}
hint {
INTEGER id PK "AUTOINCREMENT"
INTEGER download_id FK "NOT NULL; ON DELETE CASCADE"
TEXT text "NOT NULL"
TEXT created_at "NOT NULL DEFAULT datetime('now')"
}
override {
INTEGER id PK "AUTOINCREMENT"
INTEGER download_id FK "NOT NULL; ON DELETE CASCADE"
TEXT field "NOT NULL; UNIQUE(download_id, field)"
TEXT value "NOT NULL"
TEXT created_at "NOT NULL DEFAULT datetime('now')"
}
metadata_candidate {
INTEGER id PK "AUTOINCREMENT"
INTEGER recognition_id FK "NOT NULL; ON DELETE CASCADE"
TEXT provider "NOT NULL"
TEXT provider_id "NOT NULL"
TEXT title "nullable"
INTEGER year "nullable"
INTEGER chosen "NOT NULL DEFAULT 0; 0/1"
TEXT created_at "NOT NULL DEFAULT datetime('now')"
}
file_link {
INTEGER id PK "AUTOINCREMENT"
INTEGER download_id FK "NOT NULL; ON DELETE CASCADE"
TEXT apply_batch_id "NOT NULL; батч для точечного undo"
TEXT src_path "NOT NULL; исходный файл раздачи"
TEXT dst_path "NOT NULL; целевой хардлинк"
TEXT kind "NOT NULL; video|subtitle|..."
TEXT status "NOT NULL; linked|..."
TEXT created_at "NOT NULL DEFAULT datetime('now')"
}
Связи и кардинальность
download1 — Nrecognition/hint/override/file_link;recognition1 — Nmetadata_candidate. Все дочерние — сON DELETE CASCADE: удаление загрузки уносит её распознавания, подсказки, правки и ссылки.download↔file_link— один источник (раздача) ко многим разложенным файлам; внутри строкиfile_linkсвязьsrc_path → dst_path— 1:1. Не каждый файл раздачи попадает вfile_link(только распознанные медиа и субтитры); ссылки могут накапливаться несколькимиapply_batch_id.
Индексы и ограничения
download:UNIQUE(idempotency_key) WHERE idempotency_key IS NOT NULL; индекс поstate.recognition: индекс поdownload_id.override:UNIQUE(download_id, field).metadata_candidate: индекс поrecognition_id.file_link: индексы поdownload_idи поapply_batch_id.
Enum-поля (
source_type,state,provider,kind,status, флаги0/1) на уровне SQLite — обычныйTEXT/INTEGERбезCHECK; допустимые значения держит код (internal/store).