-- ============================================================================= -- Vignette · migration 21 — learner-persona별 단일 활성 회기 -- ============================================================================= -- 기존 DB 적용 전 owner가 아래 읽기 전용 점검을 실행한다. -- 중복 또는 실패 후 남은 invalid index가 있으면 임의 종료·DROP 하지 말고 -- 소유자가 보존·복구 방식을 결정해야 한다. -- -- SELECT learner_id, persona_id, count(*) -- FROM app.sessions -- WHERE ended_at IS NULL AND persona_id IS NOT NULL -- GROUP BY learner_id, persona_id -- HAVING count(*) > 1; -- -- CREATE INDEX CONCURRENTLY는 transaction block 안에서 실행할 수 없다. -- release agent가 online migration으로 owner psql에 전달한다. -- 이 파일의 전·후 condition은 IF NOT EXISTS가 invalid/wrong index를 조용히 -- 건너뛰는 경우를 fail-closed로 막는다. DO $$ DECLARE target_index oid := to_regclass('app.uq_sessions_one_active_learner_persona'); BEGIN IF EXISTS ( SELECT 1 FROM app.sessions WHERE ended_at IS NULL AND persona_id IS NOT NULL GROUP BY learner_id, persona_id HAVING count(*) > 1 ) THEN RAISE EXCEPTION 'migration 21 blocked: duplicate active learner-persona sessions exist'; END IF; IF target_index IS NOT NULL AND NOT EXISTS ( SELECT 1 FROM pg_index AS index_meta WHERE index_meta.indexrelid = target_index AND index_meta.indrelid = 'app.sessions'::regclass AND index_meta.indisvalid AND index_meta.indisready AND index_meta.indisunique AND index_meta.indnkeyatts = 2 AND pg_get_indexdef(index_meta.indexrelid, 1, true) = 'learner_id' AND pg_get_indexdef(index_meta.indexrelid, 2, true) = 'persona_id' AND regexp_replace( pg_get_expr(index_meta.indpred, index_meta.indrelid), '[[:space:]()]', '', 'g' ) = 'ended_atISNULLANDpersona_idISNOTNULL' ) THEN RAISE EXCEPTION 'migration 21 blocked: target index exists but is invalid or has a different definition'; END IF; END $$; CREATE UNIQUE INDEX CONCURRENTLY IF NOT EXISTS uq_sessions_one_active_learner_persona ON app.sessions (learner_id, persona_id) WHERE ended_at IS NULL AND persona_id IS NOT NULL; DO $$ BEGIN IF NOT EXISTS ( SELECT 1 FROM pg_index AS index_meta WHERE index_meta.indexrelid = 'app.uq_sessions_one_active_learner_persona'::regclass AND index_meta.indrelid = 'app.sessions'::regclass AND index_meta.indisvalid AND index_meta.indisready AND index_meta.indisunique AND index_meta.indnkeyatts = 2 AND pg_get_indexdef(index_meta.indexrelid, 1, true) = 'learner_id' AND pg_get_indexdef(index_meta.indexrelid, 2, true) = 'persona_id' AND regexp_replace( pg_get_expr(index_meta.indpred, index_meta.indrelid), '[[:space:]()]', '', 'g' ) = 'ended_atISNULLANDpersona_idISNOTNULL' ) THEN RAISE EXCEPTION 'migration 21 failed: valid target unique index was not created'; END IF; END $$;