- шаги PocketBase переехали из файла в пакет internal/adapter/repo/pocketbase/migrations, файл на шаг с именем зарегистрированного шага; туда же имена коллекций, срок сессии — в provider.go - ключ migrations в docs/.docs.json наведён на этот каталог: прежнее значение указывало на несуществующий migrations/, и шаг гейта проходил зелёным при всякой правке схемы - app.go подключает пакет шагов явным пустым импортом: пропавшая ссылка на константы унесла бы регистрацию, и хранилище поднялось бы без коллекций
70 lines
5.9 KiB
Markdown
70 lines
5.9 KiB
Markdown
# Конвенция: база данных и идентификаторы
|
||
|
||
Как мы устраиваем таблицы и ключи. Актуальная схема — [../database.md](../database.md).
|
||
|
||
**Взято из проекта jellybit целиком.** Сегодняшний код transcriber этому не
|
||
следует ни в одном пункте: ключи — UUID v4, а не ULID; время — `time.Now()` по
|
||
месту вызова в локальной зоне, а не единой точкой в UTC; единой точки генерации
|
||
и разбора нет. Правила действуют на новый код; переписывание существующего —
|
||
отдельная работа, и до неё расхождение читается как долг, а не как нарушение.
|
||
|
||
**Механизировано:** одно — сверка изменённого шага схемы с
|
||
[../database.md](../database.md), шаг гейта `docs.py check`
|
||
([README.md](README.md), «Механизировано»). Под прочие пункты ни правила
|
||
линтера, ни теста-сканера в transcriber нет.
|
||
|
||
## Первичные ключи — 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` в **RFC 3339, UTC (суффикс `Z`)**, например
|
||
`2006-01-02T15:04:05Z` (секундная точность). Фиксированная ширина сохраняет
|
||
лексикографическую сортировку TEXT, то есть хронологию (`ORDER BY created_at`).
|
||
Единая точка генерации — приложение, а не умолчание в схеме: так забытая
|
||
вставка падает громко. Измерение длительности — не метка времени.
|
||
- Миграции — шаги PocketBase на Go
|
||
(`internal/adapter/repo/pocketbase/migrations`, файл на шаг): коллекции и их
|
||
поля заводятся кодом. При изменении структуры обновляем схему
|
||
[../database.md](../database.md) тем же изменением.
|
||
- Время в **сыром запросе** кладётся и сравнивается тем же видом, каким
|
||
хранилище пишет свои `created`/`updated`. Сравнение строк побайтово, и
|
||
разошедшийся вид обращает условие в постоянную истину или ложь — молча.
|
||
- Выборка «следующей» записи с `LIMIT 1` дополняется ключом в `ORDER BY`:
|
||
сравнение по неуникальному значению делает порядок обработки
|
||
невоспроизводимым.
|