заведён реестр префиксов, правила канона перенумерованы

- идентификатор правила теперь `<ПРЕФИКС>-<номер>` вместо `R<номер>`:
  префикс уникален по всему канону, поэтому ссылка больше не требует пути
  к файлу и не зависит от того, на какой оси файл лежит
- префикс выбирается под файл, а не выводится по формуле, и хранится в
  conventions/prefixes.toml вместе с выбывшими; номера сохранены один в
  один вместе с дырами
This commit is contained in:
av
2026-07-25 20:55:37 +03:00
parent 972627f641
commit dcdd92230b
13 changed files with 552 additions and 470 deletions
+25 -21
View File
@@ -1,3 +1,7 @@
---
prefix: MIGR
---
# Схема и миграции (SQLite, Go)
Как пишутся миграции и какие типы колонок выбираются для схемы SQLite в
@@ -12,7 +16,7 @@ Go-приложении. Форма записи — `LANGUAGE.md`.
## Миграции
### R1. Миграции ведёт goose
### MIGR-1. Миграции ведёт goose
**ДОЛЖЕН.** Набор миграций репозитория применяется одним инструментом —
goose.
@@ -24,7 +28,7 @@ goose.
существующую таблицу. На сервере это означает ручной разбор состояния
схемы вместо автоматического деплоя.
### R2. Файлы миграций лежат рядом со store-слоем
### MIGR-2. Файлы миграций лежат рядом со store-слоем
**СЛЕДУЕТ.** Миграции хранятся рядом с кодом, который работает с этой
схемой.
@@ -35,14 +39,14 @@ goose.
код без миграции, либо миграция без кода; расходятся они на сервере, где
схема ещё старая.
### R3. Форма миграции выбирается по тому, нужен ли код
### MIGR-3. Форма миграции выбирается по тому, нужен ли код
**ДОЛЖЕН.** Миграция пишется в той форме, которой требует её содержимое:
| № | Что делает миграция | Форма |
|---|---|---|
| R3.1 | DDL: создание таблиц, индексы, изменение структуры | SQL-файл |
| R3.2 | требует кода: генерация идентификаторов, backfill, перенос данных между формами | Go-миграция (`goose.AddMigrationContext`) |
| MIGR-3.1 | DDL: создание таблиц, индексы, изменение структуры | SQL-файл |
| MIGR-3.2 | требует кода: генерация идентификаторов, backfill, перенос данных между формами | Go-миграция (`goose.AddMigrationContext`) |
**Почему.** DDL ничего не вычисляет, и SQL-файл показывает ровно тот текст,
который уедет в базу; обёртка на Go вокруг него добавляет место, где можно
@@ -50,12 +54,12 @@ goose.
Обратное направление дороже. Перенос данных и генерация идентификаторов
выражаются на SQL либо громоздко, либо неточно: идентификатор по
`arch/db-identifiers.md` R2 порождает приложение, и SQL-миграция вынуждена
`KEYS-2` порождает приложение, и SQL-миграция вынуждена
завести для него второй генератор — ровно то, что запрещает
`arch/db-identifiers.md` R3. Единообразие формы здесь покупается
`KEYS-3`. Единообразие формы здесь покупается
дублированием логики, которая уже есть в коде.
### R4. В деплое схема движется только вперёд
### MIGR-4. В деплое схема движется только вперёд
**НЕ ДОЛЖЕН.** Откат схемы на сервере не выполняется down-миграцией;
ошибка исправляется новой миграцией вперёд.
@@ -67,14 +71,14 @@ goose.
следующей миграцией, оставляет целыми и данные, и журнал применённых
версий.
### R5. Down пишется, когда он честно обращает up
### MIGR-5. Down пишется, когда он честно обращает up
**ДОЛЖЕН.** Наличие down-миграции определяется тем, обратим ли up:
| № | Что делает up | Down |
|---|---|---|
| R5.1 | добавляет структуру: таблицу, колонку, индекс | пишется, убирает добавленное |
| R5.2 | необратимо преобразует данные | не пишется |
| MIGR-5.1 | добавляет структуру: таблицу, колонку, индекс | пишется, убирает добавленное |
| MIGR-5.2 | необратимо преобразует данные | не пишется |
**Почему.** Down — инструмент разработки, где ветку переключают туда-сюда,
и именно там он обязан действительно обращать up. Имитация опаснее
@@ -83,7 +87,7 @@ goose.
down останавливает сразу и заставляет пересоздать базу — это дешевле, чем
отладка по данным, которых уже нет.
### R6. ER-схема обновляется в том же изменении
### MIGR-6. ER-схема обновляется в том же изменении
**ДОЛЖЕН.** Изменение структуры и правка ER-схемы в спеках едут одним
изменением.
@@ -99,7 +103,7 @@ down останавливает сразу и заставляет пересо
Правила ниже описывают хранение в SQLite: выбор типа диктует движок базы,
а не язык приложения.
### R7. Enum-поля — `TEXT`, допустимые значения держит код
### MIGR-7. Enum-поля — `TEXT`, допустимые значения держит код
**ДОЛЖЕН.** Поле-перечисление (`state`, `kind`, …) объявляется как `TEXT`
без `CHECK`-ограничения на список значений.
@@ -115,7 +119,7 @@ down останавливает сразу и заставляет пересо
таблицы соответствия, которую пришлось бы держать в голове для числового
кода.
### R8. Метки времени — `TEXT` в формате из `arch/time.md`
### MIGR-8. Метки времени — `TEXT` в формате из `arch/time.md`
**ДОЛЖЕН.** Колонка с меткой времени объявляется как `TEXT`, значения
пишутся в формате из `arch/time.md`.
@@ -127,7 +131,7 @@ down останавливает сразу и заставляет пересо
преобразования, а значит и без потери индекса. Соседство двух форматов в
одной колонке ломает и сравнение, и разбор на стороне Go.
### R9. Умолчание `DEFAULT (datetime('now'))` не ставится
### MIGR-9. Умолчание `DEFAULT (datetime('now'))` не ставится
**НЕ ДОЛЖЕН.** Колонка с меткой времени не получает значение по умолчанию
на уровне схемы.
@@ -138,10 +142,10 @@ down останавливает сразу и заставляет пересо
по ошибке.
Вдобавок `datetime('now')` даёт `YYYY-MM-DD HH:MM:SS` — без `T` и без `Z`,
то есть не тот формат, которого требует R8. В колонке оказываются строки
то есть не тот формат, которого требует MIGR-8. В колонке оказываются строки
двух видов, и ломается ровно то, ради чего формат выбран.
### R10. Булевы поля — `INTEGER` со значениями 0 и 1
### MIGR-10. Булевы поля — `INTEGER` со значениями 0 и 1
**ДОЛЖЕН.** Булево значение хранится как `INTEGER` 0/1.
@@ -152,10 +156,10 @@ down останавливает сразу и заставляет пересо
типа. Такой дефект не падает, не виден в логе и переживает тесты, которые
проверяют, что список не пуст.
### R11. Первичный ключ новой таблицы — TEXT ULID
### MIGR-11. Первичный ключ новой таблицы — TEXT ULID
**ДОЛЖЕН.** Колонка ключа объявляется как `TEXT`, значение приходит из
приложения (`arch/db-identifiers.md` R1, R2).
приложения (`KEYS-1`, `KEYS-2`).
**Почему.** Здесь конвенция схемы ничего не решает — она реализует решение,
принятое в `arch/db-identifiers.md`. Повторить там ветвление или условие
@@ -165,7 +169,7 @@ down останавливает сразу и заставляет пересо
`AUTOINCREMENT` в такой таблице невозможен: SQLite разрешает его только на
`INTEGER PRIMARY KEY`. То есть для новых таблиц запрещать нечего.
### R12. Целочисленный ключ идёт вместе с `AUTOINCREMENT`
### MIGR-12. Целочисленный ключ идёт вместе с `AUTOINCREMENT`
**ДОЛЖЕН.** Там, где первичный ключ всё-таки целочисленный — существующая
схема, миграция легаси-таблицы, — он объявляется с `AUTOINCREMENT`.
@@ -178,7 +182,7 @@ down останавливает сразу и заставляет пересо
Цена — служебная таблица `sqlite_sequence` и запись в неё на каждой
вставке — против этого пренебрежима. Для новых таблиц вопрос не возникает:
там ключ строковый (R11).
там ключ строковый (MIGR-11).
<!-- local:механизировано -->
<!-- /local -->