(medium) Не-idempotent миграции (110 ADD CONSTRAINT, 74 DROP TABLE→VIEW) при ложном заявлении об идемпотентности ломают DR-restore #1367

Closed
opened 2026-06-14 18:09:16 +00:00 by bot-backend · 1 comment
Collaborator

Описание

Две миграции заявлены идемпотентными в заголовке, но не являются таковыми, что ломает повторное применение при DR-restore (deploy.yml использует psql -v ON_ERROR_STOP=on → одна ошибка обрывает весь цикл миграций).

110_22j_dedup_infrastructure.sql:36-38 — голый ALTER TABLE ... ADD CONSTRAINT uq_infra_dedupe БЕЗ guard. Заголовок (строка 14) утверждает «constraint uses IF NOT EXISTS equivalent via DO block», но DO-блока нет, а у PostgreSQL ADD CONSTRAINT не имеет формы IF NOT EXISTS — второй apply падает с «constraint already exists».

74_dedupe_unified_views.sql:33-36DROP TABLE IF EXISTS ekb_districts_geom на имени, которое после первого прогона становится VIEW. IF EXISTS гасит только not-exist, НЕ wrong-relkind: при ре-apply будет ERROR: "ekb_districts_geom" is not a table. Заголовок ложно заявляет «Идемпотентно через CREATE OR REPLACE VIEW». Файл также не обёрнут в BEGIN/COMMIT (нарушение sql.md).

Корень

DDL без guard на существование/relkind + устаревшие/ложные заголовки об идемпотентности. Нарушено .claude/rules/sql.md (не-idempotent файлы и миграции без BEGIN/COMMIT запрещены). Поздние миграции (125/128/132/140; m.92 для DROP TABLE по relkind) уже используют guard-DO-блоки — эти две предшествуют этому паттерну. Норм. деплой пропускает их через _schema_migrations, но DR-restore (потерянная tracking-строка) обрывает весь цикл.

Evidence

110:36-38:

ALTER TABLE domrf_kn_infrastructure
  ADD CONSTRAINT uq_infra_dedupe
  UNIQUE NULLS NOT DISTINCT (obj_id, poi_category, poi_name, poi_address);
-- заголовок: 'Idempotent: yes — ... via DO $$ block' (DO-блока нет)

74:33-36:

DROP TABLE IF EXISTS ekb_districts_geom;
CREATE OR REPLACE VIEW ekb_districts_geom AS
SELECT district_name, geom, osm_id, geom_fetched_at AS fetched_at
  FROM ekb_districts WHERE geom IS NOT NULL;

(в 74 grep по BEGIN/COMMIT пуст — обёртки нет).

Suggested fix

110: обернуть step 3 в guard-DO-блок (зеркало m.125/m.128): DO $$ BEGIN IF NOT EXISTS (SELECT 1 FROM pg_constraint WHERE conname='uq_infra_dedupe') THEN ALTER TABLE domrf_kn_infrastructure ADD CONSTRAINT uq_infra_dedupe UNIQUE NULLS NOT DISTINCT (...); END IF; END $$;.
74: guard на relkind (как m.92): DO $$ BEGIN IF EXISTS (SELECT 1 FROM information_schema.tables WHERE table_name='ekb_districts_geom' AND table_type='BASE TABLE') THEN DROP TABLE ekb_districts_geom; END IF; END $$; и обернуть весь файл в BEGIN; ... COMMIT;.

Обоснование severity

medium (макс из medium+low): не портит данные и не триггерится в нормальных деплоях, но жёсткий блокер для DR-recovery — именно того сценария, где надёжное ре-apply критично. confidence 0.95/0.82.


week ревью 1 · multi-agent аудит 2026-06-14 · severity=medium · category=data-integrity

Затронутые файлы:

  • /Users/anton/Птица/gendesign/data/sql/110_22j_dedup_infrastructure.sql
  • /Users/anton/Птица/gendesign/data/sql/74_dedupe_unified_views.sql
## Описание Две миграции заявлены идемпотентными в заголовке, но не являются таковыми, что ломает повторное применение при DR-restore (deploy.yml использует `psql -v ON_ERROR_STOP=on` → одна ошибка обрывает весь цикл миграций). **110_22j_dedup_infrastructure.sql:36-38** — голый `ALTER TABLE ... ADD CONSTRAINT uq_infra_dedupe` БЕЗ guard. Заголовок (строка 14) утверждает «constraint uses IF NOT EXISTS equivalent via DO $$ block», но DO-блока нет, а у PostgreSQL `ADD CONSTRAINT` не имеет формы IF NOT EXISTS — второй apply падает с «constraint already exists». **74_dedupe_unified_views.sql:33-36** — `DROP TABLE IF EXISTS ekb_districts_geom` на имени, которое после первого прогона становится VIEW. `IF EXISTS` гасит только not-exist, НЕ wrong-relkind: при ре-apply будет `ERROR: "ekb_districts_geom" is not a table`. Заголовок ложно заявляет «Идемпотентно через CREATE OR REPLACE VIEW». Файл также не обёрнут в BEGIN/COMMIT (нарушение sql.md). ## Корень DDL без guard на существование/relkind + устаревшие/ложные заголовки об идемпотентности. Нарушено `.claude/rules/sql.md` (не-idempotent файлы и миграции без BEGIN/COMMIT запрещены). Поздние миграции (125/128/132/140; m.92 для DROP TABLE по relkind) уже используют guard-DO-блоки — эти две предшествуют этому паттерну. Норм. деплой пропускает их через `_schema_migrations`, но DR-restore (потерянная tracking-строка) обрывает весь цикл. ## Evidence `110:36-38`: ```sql ALTER TABLE domrf_kn_infrastructure ADD CONSTRAINT uq_infra_dedupe UNIQUE NULLS NOT DISTINCT (obj_id, poi_category, poi_name, poi_address); -- заголовок: 'Idempotent: yes — ... via DO $$ block' (DO-блока нет) ``` `74:33-36`: ```sql DROP TABLE IF EXISTS ekb_districts_geom; CREATE OR REPLACE VIEW ekb_districts_geom AS SELECT district_name, geom, osm_id, geom_fetched_at AS fetched_at FROM ekb_districts WHERE geom IS NOT NULL; ``` (в 74 grep по BEGIN/COMMIT пуст — обёртки нет). ## Suggested fix 110: обернуть step 3 в guard-DO-блок (зеркало m.125/m.128): `DO $$ BEGIN IF NOT EXISTS (SELECT 1 FROM pg_constraint WHERE conname='uq_infra_dedupe') THEN ALTER TABLE domrf_kn_infrastructure ADD CONSTRAINT uq_infra_dedupe UNIQUE NULLS NOT DISTINCT (...); END IF; END $$;`. 74: guard на relkind (как m.92): `DO $$ BEGIN IF EXISTS (SELECT 1 FROM information_schema.tables WHERE table_name='ekb_districts_geom' AND table_type='BASE TABLE') THEN DROP TABLE ekb_districts_geom; END IF; END $$;` и обернуть весь файл в `BEGIN; ... COMMIT;`. ## Обоснование severity medium (макс из medium+low): не портит данные и не триггерится в нормальных деплоях, но жёсткий блокер для DR-recovery — именно того сценария, где надёжное ре-apply критично. confidence 0.95/0.82. --- _week ревью 1 · multi-agent аудит 2026-06-14 · severity=**medium** · category=`data-integrity`_ Затронутые файлы: - `/Users/anton/Птица/gendesign/data/sql/110_22j_dedup_infrastructure.sql` - `/Users/anton/Птица/gendesign/data/sql/74_dedupe_unified_views.sql`
bot-backend added the
week ревью 1
label 2026-06-14 18:09:16 +00:00
bot-backend added the
scope/backend
label 2026-06-15 11:01:53 +00:00
Owner

🔍 week ревью 1 — верификация (ПОДТВЕРЖДЕНО)

Текущее состояние кода:

  • data/sql/110_22j_dedup_infrastructure.sql:36-38 — голый ALTER TABLE domrf_kn_infrastructure ADD CONSTRAINT uq_infra_dedupe UNIQUE NULLS NOT DISTINCT (...) без DO-guard. Заголовок-комментарий :14 утверждает «Idempotent: yes — constraint uses IF NOT EXISTS equivalent via DO block» — DO-блока в файле нет. Файл обёрнут в BEGIN;/COMMIT; (стр. 19, 40).
  • data/sql/74_dedupe_unified_views.sql:33-36DROP TABLE IF EXISTS ekb_districts_geom; затем CREATE OR REPLACE VIEW ekb_districts_geom .... После первого прогона ekb_districts_geom становится VIEW; DROP TABLE IF EXISTS глушит только not-exist, не wrong-relkind. Заголовок :13 «Идемпотентно через CREATE OR REPLACE VIEW» — ложно. Обёртки BEGIN/COMMIT в файле нет (нарушение .claude/rules/sql.md).

Корень: DDL без guard на существование/relkind + устаревшие/ложные idempotency-заголовки. Норм. деплой пропускает файлы через _schema_migrations (применяются один раз), поэтому в проде не триггерится; но DR-restore с потерянной tracking-строкой обрывает весь цикл (psql -v ON_ERROR_STOP=on).

Что править:

  • 110: step 3 в guard-DO — DO $$ BEGIN IF NOT EXISTS (SELECT 1 FROM pg_constraint WHERE conname='uq_infra_dedupe') THEN ALTER TABLE domrf_kn_infrastructure ADD CONSTRAINT uq_infra_dedupe UNIQUE NULLS NOT DISTINCT (obj_id, poi_category, poi_name, poi_address); END IF; END $$;. Либо привести комментарий в соответствие.
  • 74: guard на relkind перед DROP — DO $$ BEGIN IF EXISTS (SELECT 1 FROM information_schema.tables WHERE table_name='ekb_districts_geom' AND table_type='BASE TABLE') THEN DROP TABLE ekb_districts_geom; END IF; END $$; и обернуть весь файл в BEGIN; ... COMMIT;.
  • Изменения миграций задним числом безопасны только если файлы уже в _schema_migrations (не пере-применятся в норм. деплое); правка нацелена именно на DR-сценарий ре-apply.

Что посмотреть:

  • Код: 110_22j_dedup_infrastructure.sql:36-38, 74_dedupe_unified_views.sql:33-36; референс guard-паттернов — поздние миграции (m.125/m.128 для CONSTRAINT, m.92 для DROP TABLE по relkind); .claude/rules/sql.md (Idempotency + BEGIN/COMMIT + Запреты).
  • Vault: релевантных заметок не найдено.
**🔍 week ревью 1 — верификация (ПОДТВЕРЖДЕНО)** **Текущее состояние кода:** - `data/sql/110_22j_dedup_infrastructure.sql:36-38` — голый `ALTER TABLE domrf_kn_infrastructure ADD CONSTRAINT uq_infra_dedupe UNIQUE NULLS NOT DISTINCT (...)` без DO-guard. Заголовок-комментарий `:14` утверждает «Idempotent: yes — constraint uses IF NOT EXISTS equivalent via DO $$ block» — DO-блока в файле нет. Файл обёрнут в `BEGIN;`/`COMMIT;` (стр. 19, 40). - `data/sql/74_dedupe_unified_views.sql:33-36` — `DROP TABLE IF EXISTS ekb_districts_geom;` затем `CREATE OR REPLACE VIEW ekb_districts_geom ...`. После первого прогона `ekb_districts_geom` становится VIEW; `DROP TABLE IF EXISTS` глушит только not-exist, не wrong-relkind. Заголовок `:13` «Идемпотентно через CREATE OR REPLACE VIEW» — ложно. Обёртки `BEGIN/COMMIT` в файле нет (нарушение `.claude/rules/sql.md`). **Корень:** DDL без guard на существование/relkind + устаревшие/ложные idempotency-заголовки. Норм. деплой пропускает файлы через `_schema_migrations` (применяются один раз), поэтому в проде не триггерится; но DR-restore с потерянной tracking-строкой обрывает весь цикл (`psql -v ON_ERROR_STOP=on`). **Что править:** - `110`: step 3 в guard-DO — `DO $$ BEGIN IF NOT EXISTS (SELECT 1 FROM pg_constraint WHERE conname='uq_infra_dedupe') THEN ALTER TABLE domrf_kn_infrastructure ADD CONSTRAINT uq_infra_dedupe UNIQUE NULLS NOT DISTINCT (obj_id, poi_category, poi_name, poi_address); END IF; END $$;`. Либо привести комментарий в соответствие. - `74`: guard на relkind перед DROP — `DO $$ BEGIN IF EXISTS (SELECT 1 FROM information_schema.tables WHERE table_name='ekb_districts_geom' AND table_type='BASE TABLE') THEN DROP TABLE ekb_districts_geom; END IF; END $$;` и обернуть весь файл в `BEGIN; ... COMMIT;`. - Изменения миграций задним числом безопасны только если файлы уже в `_schema_migrations` (не пере-применятся в норм. деплое); правка нацелена именно на DR-сценарий ре-apply. **Что посмотреть:** - Код: `110_22j_dedup_infrastructure.sql:36-38`, `74_dedupe_unified_views.sql:33-36`; референс guard-паттернов — поздние миграции (m.125/m.128 для CONSTRAINT, m.92 для DROP TABLE по relkind); `.claude/rules/sql.md` (Idempotency + BEGIN/COMMIT + Запреты). - Vault: релевантных заметок не найдено.
Sign in to join this conversation.
No milestone
No project
No assignees
2 participants
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set.

Reference: lekss361/gendesign#1367
No description provided.