идентификатор новой сущности — всегда ULID

- R1 больше не ветвится по признаку внешней адресуемости: заранее отличить
  внутренние сущности, которые станут внешними, невозможно
- целочисленный ключ остаётся у существующих схем и идёт вместе с
  AUTOINCREMENT — переиспользованный rowid молча наводит протухшую ссылку
  на другую строку (db-schema R12)
- таблица R5 ограничена внешними источниками, регион «решение» убран
This commit is contained in:
av
2026-07-25 20:15:35 +03:00
parent 6456b81d91
commit 0fc1994db7
3 changed files with 53 additions and 51 deletions
+21 -20
View File
@@ -152,32 +152,33 @@ down останавливает сразу и заставляет пересо
типа. Такой дефект не падает, не виден в логе и переживает тесты, которые
проверяют, что список не пуст.
### R11. Вид первичного ключа задаёт `arch/db-identifiers.md`
### R11. Первичный ключ новой таблицы — TEXT ULID
**ДОЛЖЕН.** В репозитории, подписанном на `arch/db-identifiers.md`, вид
ключа выбирается по её R1, и `AUTOINCREMENT` в миграции не пишется.
**ДОЛЖЕН.** Колонка ключа объявляется как `TEXT`, значение приходит из
приложения (`arch/db-identifiers.md` R1, R2).
**Почему.** Вопрос о виде ключа решается один раз на репозиторий
(`arch/db-identifiers.md` R1). Повторив здесь его ветвление, мы завели бы
второй источник правды, и соседние таблицы разъехались бы по разным
ответам на один и тот же вопрос.
**Почему.** Здесь конвенция схемы ничего не решает — она реализует решение,
принятое в `arch/db-identifiers.md`. Повторить там ветвление или условие
значило бы завести второй источник правды, и соседние таблицы разъехались бы
по разным ответам на один вопрос.
`AUTOINCREMENT` не нужен ни в одной из веток R1. Строкового ключа он не
касается вовсе, а целочисленному даёт единственную гарантию — что значение
rowid не будет переиспользовано после удаления строки, — ценой служебной
таблицы `sqlite_sequence` и записи в неё на каждой вставке. Гарантия эта
имеет смысл, только если старые идентификаторы живут где-то вне базы.
`AUTOINCREMENT` в такой таблице невозможен: SQLite разрешает его только на
`INTEGER PRIMARY KEY`. То есть для новых таблиц запрещать нечего.
### R12. Вне `arch/db-identifiers.md` первичный ключ — автоинкремент
### R12. Целочисленный ключ идёт вместе с `AUTOINCREMENT`
**ДОПУСКАЕТСЯ.** Репозиторий, не подписанный на `arch/db-identifiers.md`,
берёт целочисленный автоинкрементный ключ.
**ДОЛЖЕН.** Там, где первичный ключ всё-таки целочисленный — существующая
схема, миграция легаси-таблицы, — он объявляется с `AUTOINCREMENT`.
**Почему.** Явное разрешение нужно, чтобы R11 не читался как требование
подписаться на `arch/db-identifiers.md`. Выбор вида ключа — решение уровня
репозитория, и конвенция про типы колонок его за репозиторий не принимает;
приложению, сущности которого не адресуют снаружи, целочисленный ключ
ничего не стоит.
**Почему.** Без него SQLite выдаёт rowid как `max(rowid)+1`, поэтому после
удаления последней строки номер переиспользуется. Протухшая ссылка на
удалённую запись — из закладки, из чужой таблицы, из старого лога — молча
наводится на другую сущность и возвращает правдоподобный, но чужой ответ.
Обнаружить это по данным нельзя: обе строки валидны.
Цена — служебная таблица `sqlite_sequence` и запись в неё на каждой
вставке — против этого пренебрежима. Для новых таблиц вопрос не возникает:
там ключ строковый (R11).
<!-- local:механизировано -->
<!-- /local -->