-- 190_user_weight_profiles_one_default.sql -- #2464 — «дефолтный профиль весов» становится единственным на уровне БД. -- -- БАГ. create_profile/update_profile делают «снять is_default у всех → поставить -- новому» двумя отдельными операторами. Между ними инвариант нарушен, и при -- одновременных запросах у пользователя может оказаться ДВА профиля с -- is_default=TRUE. Читающий запрос _SELECT_DEFAULT брал LIMIT 1 без ORDER BY, -- то есть выбор молча перескакивал между ними от запроса к запросу. -- -- ЧТО ДЕЛАЕМ. Частичный уникальный индекс — «не более одного дефолта на -- пользователя». Порядок операторов в коде уже правильный (сначала снять, потом -- поставить), поэтому индекс не мешает штатной переустановке дефолта: после -- UPDATE ... SET is_default=FALSE дефолтов ноль, и следующая установка проходит. -- -- БЕЗОПАСНОСТЬ. Проверено на проде 2026-08-20: нарушений нет — у admin один -- дефолт, у __system__ ноль, ни одного пользователя с двумя. Таблица крошечная -- (4 строки), создание индекса мгновенное. -- -- Idempotent: CREATE UNIQUE INDEX IF NOT EXISTS. -- Apply after: 189_land_reservation_nulls_not_distinct.sql BEGIN; -- #2752: блокирующий DDL обязан иметь lock_timeout, иначе встанет в очередь за -- чужой сессией и уведёт за собой запросы приложения. Пять секунд — про ОЖИДАНИЕ -- блокировки, не про работу: таблица в четыре строки индексируется мгновенно. SET LOCAL lock_timeout = '5s'; CREATE UNIQUE INDEX IF NOT EXISTS user_weight_profiles_one_default ON user_weight_profiles (user_id) WHERE is_default; COMMIT;