- audiorecords вместо transcribe_jobs: приложения (texts, structures, recognitions, record_events, topics) живут своими коллекциями, ссылки на исходник и на приведённую копию перестали переставляться - рубеж называет достигнутое, отказ стал признаком остановки с причиной, а сторожей стало двое: число отказов и время в рубеже - воркеры потеряли специализацию, их число задаётся [pipeline] workers, шаг выбирается по рубежу, а захват отдаёт идентификатор и признак захвата
78 lines
7.0 KiB
Markdown
78 lines
7.0 KiB
Markdown
# Конвенция: база данных и идентификаторы
|
|
|
|
Как мы устраиваем таблицы и ключи. Актуальная схема — [../database.md](../database.md).
|
|
|
|
**Взято из проекта jellybit целиком.** Сегодняшний код transcriber следует
|
|
этому частью: ключи — UUID v4, а не ULID, и единой точки их генерации нет. Время
|
|
единой точкой читается с 2026-08-13 — `internal/clock`, метка в UTC, — и правило
|
|
держит линтер. Правила действуют на новый код; переписывание существующего —
|
|
отдельная работа, и до неё расхождение читается как долг, а не как нарушение.
|
|
|
|
**Механизировано:** сверка изменённого шага схемы с
|
|
[../database.md](../database.md) (`docs.py check`), чтение времени единой точкой
|
|
(`forbidigo` плюс `internal/clock`) и согласованность колонок очереди
|
|
(тест-сканер `internal/archrules`). Прочие пункты — прозой; адреса —
|
|
[go-linters.md](go-linters.md), «Механизировано».
|
|
|
|
## Первичные ключи — ULID, не автоинкремент
|
|
|
|
- **PK сущности — TEXT ULID** (26 символов Crockford base32), генерируется
|
|
**приложением** в момент создания записи.
|
|
*Расхождение:* идентификаторы записей выдаёт хранилище — 15 знаков
|
|
собственного алфавита. Своей точки генерации у приложения нет, и `ORDER BY id`
|
|
хронологией не является: порядок берут по колонке времени с ключом.
|
|
- Почему ULID: сортируем по времени создания (`ORDER BY id` = хронология),
|
|
компактен и удобен в URL и логах (без дефисов — grep и двойной клик берут id
|
|
целиком), глобально уникален между таблицами — поиск по голому id находит все
|
|
записи сущности в логах.
|
|
- **Точка генерации и разбора одна**: создание — при вставке записи в
|
|
репозитории, разбор — на входных границах. Самодельных генераторов по месту
|
|
вызова не заводим.
|
|
|
|
## Канонический вид — lowercase
|
|
|
|
- Генерим и храним id в **нижнем регистре**. Сравнение строк в SQLite
|
|
побайтовое, поэтому любой внешний id (URL, форма, поле запроса) обязательно
|
|
проходит разбор до запроса к БД — разбор проверяет формат и нормализует
|
|
регистр (base32 ULID нечувствителен к регистру при декодировании).
|
|
- Синтаксически неверный id считаем несуществующей сущностью (404), без похода
|
|
в БД.
|
|
|
|
## Естественные и составные ключи — для деталей
|
|
|
|
- У таблиц-деталей и связей допустим естественный или составной ключ вместо
|
|
ULID, когда он есть по природе данных. Отдельный ULID там — мёртвый вес.
|
|
- Прочие генерируемые идентификаторы — тем же способом, что и ключи сущностей:
|
|
единый формат, сортируемость, корреляция в логах.
|
|
|
|
## Прочее
|
|
|
|
- Enum-поля (`state`, `source`, …) — обычный `TEXT` без `CHECK`; допустимые
|
|
значения держит код.
|
|
*Расхождение:* перечни, по которым панель владельца правит запись руками,
|
|
закрыты схемой (`SelectField`), а не кодом: правка руками не должна заводить
|
|
значение, которого сервис не знает. Закрыты рубеж записи, причина её
|
|
остановки, вид текста, источник и исход события журнала. Цена названа: новое
|
|
значение любого из них потребует нового шага схемы, а применённый шаг не
|
|
переписывается. Прочие перечни остаются обычным `TEXT`.
|
|
- Временные метки — `TEXT` в **RFC 3339, UTC (суффикс `Z`)**, например
|
|
`2006-01-02T15:04:05Z` (секундная точность). Фиксированная ширина сохраняет
|
|
лексикографическую сортировку TEXT, то есть хронологию (`ORDER BY created_at`).
|
|
Единая точка генерации — приложение, а не умолчание в схеме: так забытая
|
|
вставка падает громко. Измерение длительности — не метка времени.
|
|
*Расхождение:* вид времени задаёт хранилище — `2006-01-02 15:04:05.000Z`,
|
|
пробел вместо `T` и доли секунды ([../database.md](../database.md), «Время»).
|
|
Правило RFC 3339 действует на то, что пишем мы сами мимо хранилища; вид
|
|
хранилища не меняем — сравнение строк в сыром запросе побайтово, и
|
|
разошедшийся вид молча обращает условие срока захвата в константу.
|
|
- Миграции — шаги PocketBase на Go
|
|
(`internal/adapter/repo/pocketbase/migrations`, файл на шаг): коллекции и их
|
|
поля заводятся кодом. При изменении структуры обновляем схему
|
|
[../database.md](../database.md) тем же изменением.
|
|
- Время в **сыром запросе** кладётся и сравнивается тем же видом, каким
|
|
хранилище пишет свои `created`/`updated`. Сравнение строк побайтово, и
|
|
разошедшийся вид обращает условие в постоянную истину или ложь — молча.
|
|
- Выборка «следующей» записи с `LIMIT 1` дополняется ключом в `ORDER BY`:
|
|
сравнение по неуникальному значению делает порядок обработки
|
|
невоспроизводимым.
|