gendesign/data/sql/130_cad_parcels_default_source.sql
Light1YT 51966b38c8
Some checks failed
Deploy / build-worker (push) Successful in 39s
Deploy / deploy (push) Has been cancelled
Deploy / changes (push) Successful in 6s
Deploy / build-frontend (push) Has been skipped
Deploy / build-backend (push) Successful in 36s
fix(db): default cad_parcels.source in trigger (on-demand ingest 2nd NOT NULL gap)
After #129 fixed quarter_cad_number, the on-demand НСПД ingest of a real
not-pre-loaded parcel hit the NEXT cad_parcels NOT NULL column: source
(NotNullViolation) → geometry transaction rolled back → POST /analyze still
stuck. Repro 66:41:0610029:83 (job 57: quarter derived by #129, source null).

Migration 130: extend the #129 BEFORE INSERT/UPDATE trigger function to also
default source→'nspd' when NULL (column DEFAULT can't help — INSERT binds
explicit NULL; trigger overrides it). Explicit sources ("search"/"wms_grid_walk")
respected — only NULL is filled. cad_parcels NOT NULL surface now fully covered
(cad_num/quarter[#129]/category_id[literal]/source[#130]/fetched_at+updated_at[NOW]).
Dry-run-verified on prod: null quarter+source → 66:41:0610029 + nspd, INSERT ok.
2026-06-04 12:49:46 +05:00

71 lines
5.4 KiB
PL/PgSQL
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

-- 130_cad_parcels_default_source.sql
-- FIX (prod, продолжение #129) — on-demand НСПД-ингест cad_parcels падает на ВТОРОЙ NOT NULL
-- колонке `source` (после того как #129 закрыл quarter_cad_number).
--
-- БАГ (ground-truth, измерено на prod после деплоя #129):
-- cad_parcels NOT NULL колонки: cad_num, quarter_cad_number, category_id, source,
-- fetched_at, updated_at. #129-триггер достроил quarter_cad_number, но on-demand-ингест
-- (bulk_harvest.upsert_parcel / parcels-fetch воркер для не-предзагруженных участков)
-- доезжает до INSERT с source=NULL →
-- psycopg.errors.NotNullViolation: null value in column "source" of relation "cad_parcels"
-- → строка падает → geometry-транзакция роллбэчится → cad_parcels_geom тоже не сохраняется
-- → POST /analyze для участка навсегда залипает «Геометрия загружается из НСПД…».
-- Воспроизведено на РЕАЛЬНОМ участке 66:41:0610029:83 (job 57: упавшая строка несёт
-- quarter_cad_number=66:41:0610029 (достроен #129) + валидный geom, null только в source).
--
-- Все три code-вызывающих upsert_features передают непустой source ("search",
-- "search_per_cat", "wms_grid_walk"), т.е. в локальном коде source не None — но в проде
-- до INSERT доезжает NULL (как и с quarter_cad_number, путь ускользает: вероятно отдельная
-- ветка/стейл-фронт grid-walk). Вместо охоты за каждым код-путём — гарантируем на уровне БД,
-- как для quarter_cad_number: source — метаданные происхождения, для on-demand НСПД-фетча
-- осмысленный дефолт 'nspd'.
--
-- ПОЧЕМУ ТРИГГЕР (а не DEFAULT): колоночный DEFAULT срабатывает только когда колонка ОПУЩЕНА
-- из INSERT; здесь INSERT биндит source ЯВНЫМ NULL → DEFAULT не применился бы. BEFORE-триггер
-- же переопределяет явный NULL. Расширяем СУЩЕСТВУЮЩУЮ функцию #129 (CREATE OR REPLACE) — сам
-- триггер cad_parcels_derive_quarter_cad (BEFORE INSERT OR UPDATE, #129) уже навешен, меняем
-- только тело функции. Один инвариант-страж для всех NOT NULL метаданных, выводимых/дефолтимых.
--
-- ПОВЕДЕНИЕ (дополнение к #129):
-- • quarter_cad_number IS NULL + cad_num ≥3 сегмента → достроить (как #129).
-- • source IS NULL → 'nspd' (осмысленный дефолт происхождения для on-demand НСПД-ингеста;
-- не маскирует данные — это просто метка источника, явный source от bulk-загрузчиков
-- ("search"/"wms_grid_walk"/...) уважается, перетираем ТОЛЬКО NULL).
--
-- Idempotent: CREATE OR REPLACE FUNCTION (переопределяет тело на месте). Триггер не трогаем
-- (#129 его создал). BEGIN/COMMIT. Никаких DROP TABLE/TRUNCATE/строк.
-- Deploy order: чистая СХЕМА, без зависимости от кода. Эффект мгновенный со следующего ингеста:
-- on-demand cad_parcels INSERT больше не падает на NOT NULL (quarter+source достраиваются) →
-- geometry персистится → реальный новый участок анализируется.
BEGIN;
CREATE OR REPLACE FUNCTION cad_parcels_derive_quarter_cad() RETURNS TRIGGER AS $$
BEGIN
-- #129: достроить quarter_cad_number из cad_num (первые 3 colon-сегмента), если NULL.
IF NEW.quarter_cad_number IS NULL
AND NEW.cad_num IS NOT NULL
AND array_length(string_to_array(NEW.cad_num, ':'), 1) >= 3
THEN
NEW.quarter_cad_number :=
array_to_string((string_to_array(NEW.cad_num, ':'))[1:3], ':');
END IF;
-- #130: source NOT NULL — on-demand-ингест доезжает с NULL. Дефолт 'nspd' (метка
-- источника). Перетираем ТОЛЬКО NULL → явные source ("search"/"wms_grid_walk"/...) целы.
IF NEW.source IS NULL THEN
NEW.source := 'nspd';
END IF;
RETURN NEW;
END;
$$ LANGUAGE plpgsql;
COMMENT ON FUNCTION cad_parcels_derive_quarter_cad() IS
'BEFORE INSERT OR UPDATE-триггер cad_parcels (#129+#130): достраивает NOT NULL '
'quarter_cad_number из cad_num когда NULL, и дефолтит NOT NULL source→''nspd'' когда NULL. '
'Чинит NotNullViolation во ВСЕХ путях записи on-demand НСПД-ингеста (репро 66:41:0610029:83). '
'Явные значения уважаются — перетираются только NULL. <3-сегм cad_num → quarter остаётся '
'NULL (NOT NULL отклонит реальный мусор).';
COMMIT;