From 5bde1c765cb5e79051685002dd80d29f89f89aad Mon Sep 17 00:00:00 2001 From: hyungi Date: Sun, 7 Jun 2026 20:42:32 +0900 Subject: [PATCH] =?UTF-8?q?fix(migrations):=20eid=20301~305=20multi-statem?= =?UTF-8?q?ent=20=E2=86=92=201-statement/=ED=8C=8C=EC=9D=BC=20=EB=B6=84?= =?UTF-8?q?=EB=A6=AC=20(301~316)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit asyncpg 러너가 exec_driver_sql 을 prepared statement(extended protocol)로 처리해 multi-statement 를 거부(cannot insert multiple commands) → fastapi init_db crash. (001 등 초기 multi-stmt 는 postgres initdb=psql simple protocol 로 적용됐던 것 — 작성자 가정 오류.) 301~305(각 2~4 stmt)를 내용 불변으로 16개 single-statement 파일(301~316)로 분리: eid_study_weakness(table/rule2/idx)·eid_review_set_draft(동)·eid_weekly_recap(동) ·approval_requests(table/idx)·eid_schedule_views(view2). 원순서·FK 의존성 보존. 프로덕션 pkm DB 대상 트랜잭션 dry-run(ROLLBACK) 16/16 무오류 통과. Co-Authored-By: Claude Opus 4.8 (1M context) --- migrations/301_eid_study_weakness.sql | 40 ------------------- migrations/301_eid_study_weakness_table.sql | 16 ++++++++ migrations/302_eid_review_set_draft.sql | 26 ------------ .../302_eid_study_weakness_no_update.sql | 3 ++ .../303_eid_study_weakness_no_delete.sql | 3 ++ migrations/303_eid_weekly_recap.sql | 27 ------------- migrations/304_approval_requests.sql | 24 ----------- migrations/304_eid_study_weakness_idx.sql | 4 ++ migrations/305_eid_review_set_draft_table.sql | 14 +++++++ migrations/305_eid_schedule_views.sql | 33 --------------- .../306_eid_review_set_draft_no_update.sql | 3 ++ .../307_eid_review_set_draft_no_delete.sql | 3 ++ migrations/308_eid_review_set_draft_idx.sql | 3 ++ migrations/309_eid_weekly_recap_table.sql | 15 +++++++ migrations/310_eid_weekly_recap_no_update.sql | 3 ++ migrations/311_eid_weekly_recap_no_delete.sql | 3 ++ migrations/312_eid_weekly_recap_idx.sql | 4 ++ migrations/313_approval_requests_table.sql | 14 +++++++ migrations/314_approval_requests_idx.sql | 3 ++ ...15_eid_schedule_views_v_schedule_today.sql | 16 ++++++++ ...chedule_views_v_schedule_defer_pattern.sql | 10 +++++ 21 files changed, 117 insertions(+), 150 deletions(-) delete mode 100644 migrations/301_eid_study_weakness.sql create mode 100644 migrations/301_eid_study_weakness_table.sql delete mode 100644 migrations/302_eid_review_set_draft.sql create mode 100644 migrations/302_eid_study_weakness_no_update.sql create mode 100644 migrations/303_eid_study_weakness_no_delete.sql delete mode 100644 migrations/303_eid_weekly_recap.sql delete mode 100644 migrations/304_approval_requests.sql create mode 100644 migrations/304_eid_study_weakness_idx.sql create mode 100644 migrations/305_eid_review_set_draft_table.sql delete mode 100644 migrations/305_eid_schedule_views.sql create mode 100644 migrations/306_eid_review_set_draft_no_update.sql create mode 100644 migrations/307_eid_review_set_draft_no_delete.sql create mode 100644 migrations/308_eid_review_set_draft_idx.sql create mode 100644 migrations/309_eid_weekly_recap_table.sql create mode 100644 migrations/310_eid_weekly_recap_no_update.sql create mode 100644 migrations/311_eid_weekly_recap_no_delete.sql create mode 100644 migrations/312_eid_weekly_recap_idx.sql create mode 100644 migrations/313_approval_requests_table.sql create mode 100644 migrations/314_approval_requests_idx.sql create mode 100644 migrations/315_eid_schedule_views_v_schedule_today.sql create mode 100644 migrations/316_eid_schedule_views_v_schedule_defer_pattern.sql diff --git a/migrations/301_eid_study_weakness.sql b/migrations/301_eid_study_weakness.sql deleted file mode 100644 index 38c74c4..0000000 --- a/migrations/301_eid_study_weakness.sql +++ /dev/null @@ -1,40 +0,0 @@ --- 301_eid_study_weakness.sql --- 이드 학습 약점 스냅샷 (append-only derived-fact). eid_study_weakness 워커가 study_question_progress --- + study_quiz_sessions 집계로 산출(LLM 0). study_diagnosis 표면이 최신 행을 읽어 코치 발화. --- --- ★ append-only 구조강제 (project_eid_persona_substrate 불변식 #8) — 2중: --- (1) INSERT 스탬프 누락 거부: actor·source_generated_at = NOT NULL·DEFAULT 없음 --- → 스탬프 없는 INSERT 를 DB 가 거부. NOT NULL 은 owner 포함 모든 role 에 적용(role 독립). --- (2) UPDATE/DELETE 차단: CREATE RULE ... DO INSTEAD NOTHING → 행 불변(owner·superuser 독립). --- --- ★ 설계 원안 'REVOKE UPDATE,DELETE' 정정(load-bearing): 단일 DB role `pkm` 이 테이블 OWNER 라 --- REVOKE 가 무효(owner 는 GRANT/REVOKE 우회). plpgsql trigger(RAISE)는 migration 검증기가 --- 본문의 BEGIN 키워드를 거부(_validate_sql_content)해 불가. → RULE 이 owner 독립 + 검증기 통과하는 --- 유일한 구조 enforcement(silent no-op, 행은 구조적으로 불변). 별도 read-only role 미존재. --- --- ★ '현재' 스냅샷 = 최신 created_at 행(WHERE status='active'). 상태전이 UPDATE 없음(append-only). --- dispute = status='disputed' + supersedes_id 로 특정 스냅샷 무효화(새 INSERT). 표면이 disputed 제외. --- --- runner = exec_driver_sql(simple protocol) → multi-statement 처리(001_initial_schema 선례, 18 stmt). --- BEGIN/COMMIT/ROLLBACK 없음(검증기 통과). CREATE RULE 은 IF NOT EXISTS 미지원 → OR REPLACE 로 idempotent. - -CREATE TABLE IF NOT EXISTS eid_study_weakness ( - id BIGSERIAL PRIMARY KEY, - user_id BIGINT NOT NULL REFERENCES users(id) ON DELETE CASCADE, - weaknesses JSONB NOT NULL, -- [{topic_id,topic,chronic,relapsed,leech,coverage_gap,unsure,overdue,trend,tier}] - habit_signals JSONB NOT NULL, -- {avoidance_topics,session_abandon_rate,stale_due_count,skew_topics} - trend_label VARCHAR(20) NOT NULL, -- overall 개선|정체|악화 (코드 산출) - sample_attempts INTEGER NOT NULL DEFAULT 0, - is_shallow_sample BOOLEAN NOT NULL DEFAULT false, - status VARCHAR(20) NOT NULL DEFAULT 'active', -- 'active' | 'disputed'(dispute marker) - supersedes_id BIGINT REFERENCES eid_study_weakness(id) ON DELETE SET NULL, - actor VARCHAR(20) NOT NULL, -- 스탬프(no default) = 'eid' - source_generated_at TIMESTAMPTZ NOT NULL, -- 스탬프(no default) = 집계 시점 - created_at TIMESTAMPTZ NOT NULL DEFAULT now() -); - -CREATE OR REPLACE RULE eid_study_weakness_no_update AS ON UPDATE TO eid_study_weakness DO INSTEAD NOTHING; -CREATE OR REPLACE RULE eid_study_weakness_no_delete AS ON DELETE TO eid_study_weakness DO INSTEAD NOTHING; - -CREATE INDEX IF NOT EXISTS idx_eid_weakness_user_current - ON eid_study_weakness (user_id, created_at DESC) WHERE status = 'active'; diff --git a/migrations/301_eid_study_weakness_table.sql b/migrations/301_eid_study_weakness_table.sql new file mode 100644 index 0000000..b2c7407 --- /dev/null +++ b/migrations/301_eid_study_weakness_table.sql @@ -0,0 +1,16 @@ +-- 301_eid_study_weakness_table.sql — 301_eid_study_weakness.sql 분리본 (single-statement). asyncpg 러너가 prepared statement 로 처리해 multi-statement 거부 → 1 stmt/파일. 내용 불변. + +CREATE TABLE IF NOT EXISTS eid_study_weakness ( + id BIGSERIAL PRIMARY KEY, + user_id BIGINT NOT NULL REFERENCES users(id) ON DELETE CASCADE, + weaknesses JSONB NOT NULL, -- [{topic_id,topic,chronic,relapsed,leech,coverage_gap,unsure,overdue,trend,tier}] + habit_signals JSONB NOT NULL, -- {avoidance_topics,session_abandon_rate,stale_due_count,skew_topics} + trend_label VARCHAR(20) NOT NULL, -- overall 개선|정체|악화 (코드 산출) + sample_attempts INTEGER NOT NULL DEFAULT 0, + is_shallow_sample BOOLEAN NOT NULL DEFAULT false, + status VARCHAR(20) NOT NULL DEFAULT 'active', -- 'active' | 'disputed'(dispute marker) + supersedes_id BIGINT REFERENCES eid_study_weakness(id) ON DELETE SET NULL, + actor VARCHAR(20) NOT NULL, -- 스탬프(no default) = 'eid' + source_generated_at TIMESTAMPTZ NOT NULL, -- 스탬프(no default) = 집계 시점 + created_at TIMESTAMPTZ NOT NULL DEFAULT now() +); diff --git a/migrations/302_eid_review_set_draft.sql b/migrations/302_eid_review_set_draft.sql deleted file mode 100644 index 84cddfd..0000000 --- a/migrations/302_eid_review_set_draft.sql +++ /dev/null @@ -1,26 +0,0 @@ --- 302_eid_review_set_draft.sql --- 이드 복습세트 초안 (append-only derived-fact). 워커가 약점 스냅샷에서 권장 복습세트를 '제안'만 한다. --- study overlay 항목6: "복습세트를 실제 복습 큐에 편성은 자율로 못 한다 — 초안만 제시, 사용자 1클릭". --- 실제 편성(study_question_progress.due_at 편집)은 별도 T2 액션 — 이 draft 는 불변 제안 기록. --- --- append-only 구조강제(=301 동일): actor·source_generated_at NOT NULL no-default(스탬프) + RULE(불변). --- 상태전이 없음 — '현재 제안' = 최신 created_at. 새 제안은 supersedes_id 로 이전 것 가리킴(새 INSERT). --- question_ids = ordered list[int] snapshot (study_quiz_sessions.question_ids 패턴, junction 안 씀). - -CREATE TABLE IF NOT EXISTS eid_review_set_draft ( - id BIGSERIAL PRIMARY KEY, - user_id BIGINT NOT NULL REFERENCES users(id) ON DELETE CASCADE, - study_topic_id BIGINT REFERENCES study_topics(id) ON DELETE CASCADE, -- nullable = cross-topic 세트 - question_ids JSONB NOT NULL, -- ordered list[int] - reason VARCHAR(40) NOT NULL, -- chronic | relapse | coverage | overdue - actor VARCHAR(20) NOT NULL, -- 스탬프 = 'eid' - source_weakness_id BIGINT REFERENCES eid_study_weakness(id) ON DELETE SET NULL, - source_generated_at TIMESTAMPTZ NOT NULL, -- 스탬프 - supersedes_id BIGINT REFERENCES eid_review_set_draft(id) ON DELETE SET NULL, - created_at TIMESTAMPTZ NOT NULL DEFAULT now() -); - -CREATE OR REPLACE RULE eid_review_set_draft_no_update AS ON UPDATE TO eid_review_set_draft DO INSTEAD NOTHING; -CREATE OR REPLACE RULE eid_review_set_draft_no_delete AS ON DELETE TO eid_review_set_draft DO INSTEAD NOTHING; - -CREATE INDEX IF NOT EXISTS idx_eid_review_draft_user ON eid_review_set_draft (user_id, created_at DESC); diff --git a/migrations/302_eid_study_weakness_no_update.sql b/migrations/302_eid_study_weakness_no_update.sql new file mode 100644 index 0000000..912df2a --- /dev/null +++ b/migrations/302_eid_study_weakness_no_update.sql @@ -0,0 +1,3 @@ +-- 302_eid_study_weakness_no_update.sql — 301_eid_study_weakness.sql 분리본 (single-statement). asyncpg 러너가 prepared statement 로 처리해 multi-statement 거부 → 1 stmt/파일. 내용 불변. + +CREATE OR REPLACE RULE eid_study_weakness_no_update AS ON UPDATE TO eid_study_weakness DO INSTEAD NOTHING; diff --git a/migrations/303_eid_study_weakness_no_delete.sql b/migrations/303_eid_study_weakness_no_delete.sql new file mode 100644 index 0000000..24f4c96 --- /dev/null +++ b/migrations/303_eid_study_weakness_no_delete.sql @@ -0,0 +1,3 @@ +-- 303_eid_study_weakness_no_delete.sql — 301_eid_study_weakness.sql 분리본 (single-statement). asyncpg 러너가 prepared statement 로 처리해 multi-statement 거부 → 1 stmt/파일. 내용 불변. + +CREATE OR REPLACE RULE eid_study_weakness_no_delete AS ON DELETE TO eid_study_weakness DO INSTEAD NOTHING; diff --git a/migrations/303_eid_weekly_recap.sql b/migrations/303_eid_weekly_recap.sql deleted file mode 100644 index fab5ca5..0000000 --- a/migrations/303_eid_weekly_recap.sql +++ /dev/null @@ -1,27 +0,0 @@ --- 303_eid_weekly_recap.sql --- 이드 주간 회고 카드 (append-only derived-fact). 회고 워커(scaffold, 미배선 — W4/Phase2)가 산출. --- recap overlay: 'T1 write 자율 eid_weekly_recap(append-only)'. 미결 액션아이템 open/done UPDATE 는 --- events 측(가변)이지 이 카드가 아님 — 카드 자체는 불변 스냅샷. --- 현재는 통합 migration 의 scaffold 테이블(dispatch enum WRITE_WEEKLY_RECAP 의 write target 예약). --- --- append-only 구조강제(=301 동일): 스탬프 NOT NULL no-default + RULE(불변). '현재' = 최신 created_at. - -CREATE TABLE IF NOT EXISTS eid_weekly_recap ( - id BIGSERIAL PRIMARY KEY, - user_id BIGINT NOT NULL REFERENCES users(id) ON DELETE CASCADE, - period_start DATE NOT NULL, - period_end DATE NOT NULL, - recap JSONB NOT NULL, -- {activity_summary, open_action_items:[event_id], recurring_topics} - trend_label VARCHAR(20), - status VARCHAR(20) NOT NULL DEFAULT 'active', -- 'active' | 'disputed' - supersedes_id BIGINT REFERENCES eid_weekly_recap(id) ON DELETE SET NULL, - actor VARCHAR(20) NOT NULL, -- 스탬프 = 'eid' - source_generated_at TIMESTAMPTZ NOT NULL, -- 스탬프 - created_at TIMESTAMPTZ NOT NULL DEFAULT now() -); - -CREATE OR REPLACE RULE eid_weekly_recap_no_update AS ON UPDATE TO eid_weekly_recap DO INSTEAD NOTHING; -CREATE OR REPLACE RULE eid_weekly_recap_no_delete AS ON DELETE TO eid_weekly_recap DO INSTEAD NOTHING; - -CREATE INDEX IF NOT EXISTS idx_eid_recap_user_current - ON eid_weekly_recap (user_id, created_at DESC) WHERE status = 'active'; diff --git a/migrations/304_approval_requests.sql b/migrations/304_approval_requests.sql deleted file mode 100644 index 899c7f8..0000000 --- a/migrations/304_approval_requests.sql +++ /dev/null @@ -1,24 +0,0 @@ --- 304_approval_requests.sql --- 외부 전송 승인 큐 (★ 가변 workflow queue — append-only 아님). 설계 3-4 명시 카브아웃: --- "approval_requests 는 status 를 pending→approved 로 바꾸는 가변 state 라 eid_* 불변 REVOKE/RULE 대상 아님". --- → 여기엔 RULE(append-only) 안 건다. status 전이(UPDATE) 허용. --- --- ★ Phase1 현재: app/eid/tools/dispatch.py 의 request_external_approval = 즉시 거부(INSERT 0). --- dispatcher 워커(유일 egress 집행)는 Phase3. 이 테이블은 그때까지 scaffold(빈 상태). --- ★ payload 는 고정 템플릿 슬롯만(free-form 금지) — app 층이 request_type 별 화이트리스트 검증. --- 승인 UI 는 전송 body 전문 diff 노출. 불변 결정 원장이 필요하면 별도 append-only approval_events(Phase3). - -CREATE TABLE IF NOT EXISTS approval_requests ( - id BIGSERIAL PRIMARY KEY, - user_id BIGINT NOT NULL REFERENCES users(id) ON DELETE CASCADE, - request_type VARCHAR(40) NOT NULL, -- 고정 템플릿 슬롯 타입(app 화이트리스트) - payload JSONB NOT NULL, -- 고정 템플릿 슬롯만 - status VARCHAR(20) NOT NULL DEFAULT 'pending', -- pending | approved | rejected (전이 허용) - requester VARCHAR(20) NOT NULL, -- 'eid' - decided_by VARCHAR(40), - decided_at TIMESTAMPTZ, - created_at TIMESTAMPTZ NOT NULL DEFAULT now(), - updated_at TIMESTAMPTZ NOT NULL DEFAULT now() -); - -CREATE INDEX IF NOT EXISTS idx_approval_requests_status ON approval_requests (status, created_at); diff --git a/migrations/304_eid_study_weakness_idx.sql b/migrations/304_eid_study_weakness_idx.sql new file mode 100644 index 0000000..0d0d904 --- /dev/null +++ b/migrations/304_eid_study_weakness_idx.sql @@ -0,0 +1,4 @@ +-- 304_eid_study_weakness_idx.sql — 301_eid_study_weakness.sql 분리본 (single-statement). asyncpg 러너가 prepared statement 로 처리해 multi-statement 거부 → 1 stmt/파일. 내용 불변. + +CREATE INDEX IF NOT EXISTS idx_eid_weakness_user_current + ON eid_study_weakness (user_id, created_at DESC) WHERE status = 'active'; diff --git a/migrations/305_eid_review_set_draft_table.sql b/migrations/305_eid_review_set_draft_table.sql new file mode 100644 index 0000000..d32043a --- /dev/null +++ b/migrations/305_eid_review_set_draft_table.sql @@ -0,0 +1,14 @@ +-- 305_eid_review_set_draft_table.sql — 302_eid_review_set_draft.sql 분리본 (single-statement). asyncpg 러너가 prepared statement 로 처리해 multi-statement 거부 → 1 stmt/파일. 내용 불변. + +CREATE TABLE IF NOT EXISTS eid_review_set_draft ( + id BIGSERIAL PRIMARY KEY, + user_id BIGINT NOT NULL REFERENCES users(id) ON DELETE CASCADE, + study_topic_id BIGINT REFERENCES study_topics(id) ON DELETE CASCADE, -- nullable = cross-topic 세트 + question_ids JSONB NOT NULL, -- ordered list[int] + reason VARCHAR(40) NOT NULL, -- chronic | relapse | coverage | overdue + actor VARCHAR(20) NOT NULL, -- 스탬프 = 'eid' + source_weakness_id BIGINT REFERENCES eid_study_weakness(id) ON DELETE SET NULL, + source_generated_at TIMESTAMPTZ NOT NULL, -- 스탬프 + supersedes_id BIGINT REFERENCES eid_review_set_draft(id) ON DELETE SET NULL, + created_at TIMESTAMPTZ NOT NULL DEFAULT now() +); diff --git a/migrations/305_eid_schedule_views.sql b/migrations/305_eid_schedule_views.sql deleted file mode 100644 index 8873aae..0000000 --- a/migrations/305_eid_schedule_views.sql +++ /dev/null @@ -1,33 +0,0 @@ --- 305_eid_schedule_views.sql --- 이드 일정(schedule_brief, 미래 surface) 파생뷰 2. 신규 schedule 테이블 0 — events/events_history 재활용. --- quadrant(중요×긴급)·D-N 정렬은 app 층(schedule overlay). 뷰는 raw 입력 필드 + today/defer 집계만. --- CREATE VIEW 선례 = 010_soft_delete / 283_corpus_chunks. BEGIN/COMMIT 없음. --- --- v_schedule_today: 오늘(Asia/Seoul local day) 활성 일정. active 필터 = events.py:list_today reference. --- today 경계 = Seoul 자정→UTC 변환(date_trunc ... AT TIME ZONE 왕복). LATERAL 로 1회 계산. --- v_schedule_defer_pattern: events_history change_kind IN(defer,reschedule) 를 event_id 별 COUNT. --- '반복 미룸' 임계 3회+ (schedule overlay 판단근거 #5). reactivate 는 제외. - -CREATE OR REPLACE VIEW v_schedule_today AS -SELECT e.id, e.user_id, e.title, e.kind, e.status, e.priority, - e.due_at, e.start_at, e.end_at, e.started_at, e.defer_until, e.project_tag -FROM events e -CROSS JOIN LATERAL ( - SELECT (date_trunc('day', now() AT TIME ZONE 'Asia/Seoul') AT TIME ZONE 'Asia/Seoul') AS lo -) b -WHERE (e.status IN ('inbox','next','scheduled','in_progress') - OR (e.status = 'deferred' AND e.defer_until IS NOT NULL AND e.defer_until <= now())) - AND ( - (e.kind = 'task' AND e.due_at >= b.lo AND e.due_at < b.lo + interval '1 day') - OR (e.kind = 'calendar_event' AND e.start_at >= b.lo AND e.start_at < b.lo + interval '1 day') - OR (e.kind = 'activity_log' AND e.started_at >= b.lo AND e.started_at < b.lo + interval '1 day') - ); - -CREATE OR REPLACE VIEW v_schedule_defer_pattern AS -SELECT eh.event_id, - COUNT(*)::int AS defer_reschedule_count, - MAX(eh.changed_at) AS last_changed_at, - (COUNT(*) >= 3) AS is_repeat_defer -FROM events_history eh -WHERE eh.change_kind IN ('defer','reschedule') -GROUP BY eh.event_id; diff --git a/migrations/306_eid_review_set_draft_no_update.sql b/migrations/306_eid_review_set_draft_no_update.sql new file mode 100644 index 0000000..1dfc41f --- /dev/null +++ b/migrations/306_eid_review_set_draft_no_update.sql @@ -0,0 +1,3 @@ +-- 306_eid_review_set_draft_no_update.sql — 302_eid_review_set_draft.sql 분리본 (single-statement). asyncpg 러너가 prepared statement 로 처리해 multi-statement 거부 → 1 stmt/파일. 내용 불변. + +CREATE OR REPLACE RULE eid_review_set_draft_no_update AS ON UPDATE TO eid_review_set_draft DO INSTEAD NOTHING; diff --git a/migrations/307_eid_review_set_draft_no_delete.sql b/migrations/307_eid_review_set_draft_no_delete.sql new file mode 100644 index 0000000..2a3cf1b --- /dev/null +++ b/migrations/307_eid_review_set_draft_no_delete.sql @@ -0,0 +1,3 @@ +-- 307_eid_review_set_draft_no_delete.sql — 302_eid_review_set_draft.sql 분리본 (single-statement). asyncpg 러너가 prepared statement 로 처리해 multi-statement 거부 → 1 stmt/파일. 내용 불변. + +CREATE OR REPLACE RULE eid_review_set_draft_no_delete AS ON DELETE TO eid_review_set_draft DO INSTEAD NOTHING; diff --git a/migrations/308_eid_review_set_draft_idx.sql b/migrations/308_eid_review_set_draft_idx.sql new file mode 100644 index 0000000..c73482f --- /dev/null +++ b/migrations/308_eid_review_set_draft_idx.sql @@ -0,0 +1,3 @@ +-- 308_eid_review_set_draft_idx.sql — 302_eid_review_set_draft.sql 분리본 (single-statement). asyncpg 러너가 prepared statement 로 처리해 multi-statement 거부 → 1 stmt/파일. 내용 불변. + +CREATE INDEX IF NOT EXISTS idx_eid_review_draft_user ON eid_review_set_draft (user_id, created_at DESC); diff --git a/migrations/309_eid_weekly_recap_table.sql b/migrations/309_eid_weekly_recap_table.sql new file mode 100644 index 0000000..6ef2cd3 --- /dev/null +++ b/migrations/309_eid_weekly_recap_table.sql @@ -0,0 +1,15 @@ +-- 309_eid_weekly_recap_table.sql — 303_eid_weekly_recap.sql 분리본 (single-statement). asyncpg 러너가 prepared statement 로 처리해 multi-statement 거부 → 1 stmt/파일. 내용 불변. + +CREATE TABLE IF NOT EXISTS eid_weekly_recap ( + id BIGSERIAL PRIMARY KEY, + user_id BIGINT NOT NULL REFERENCES users(id) ON DELETE CASCADE, + period_start DATE NOT NULL, + period_end DATE NOT NULL, + recap JSONB NOT NULL, -- {activity_summary, open_action_items:[event_id], recurring_topics} + trend_label VARCHAR(20), + status VARCHAR(20) NOT NULL DEFAULT 'active', -- 'active' | 'disputed' + supersedes_id BIGINT REFERENCES eid_weekly_recap(id) ON DELETE SET NULL, + actor VARCHAR(20) NOT NULL, -- 스탬프 = 'eid' + source_generated_at TIMESTAMPTZ NOT NULL, -- 스탬프 + created_at TIMESTAMPTZ NOT NULL DEFAULT now() +); diff --git a/migrations/310_eid_weekly_recap_no_update.sql b/migrations/310_eid_weekly_recap_no_update.sql new file mode 100644 index 0000000..788ae8c --- /dev/null +++ b/migrations/310_eid_weekly_recap_no_update.sql @@ -0,0 +1,3 @@ +-- 310_eid_weekly_recap_no_update.sql — 303_eid_weekly_recap.sql 분리본 (single-statement). asyncpg 러너가 prepared statement 로 처리해 multi-statement 거부 → 1 stmt/파일. 내용 불변. + +CREATE OR REPLACE RULE eid_weekly_recap_no_update AS ON UPDATE TO eid_weekly_recap DO INSTEAD NOTHING; diff --git a/migrations/311_eid_weekly_recap_no_delete.sql b/migrations/311_eid_weekly_recap_no_delete.sql new file mode 100644 index 0000000..0b83cbf --- /dev/null +++ b/migrations/311_eid_weekly_recap_no_delete.sql @@ -0,0 +1,3 @@ +-- 311_eid_weekly_recap_no_delete.sql — 303_eid_weekly_recap.sql 분리본 (single-statement). asyncpg 러너가 prepared statement 로 처리해 multi-statement 거부 → 1 stmt/파일. 내용 불변. + +CREATE OR REPLACE RULE eid_weekly_recap_no_delete AS ON DELETE TO eid_weekly_recap DO INSTEAD NOTHING; diff --git a/migrations/312_eid_weekly_recap_idx.sql b/migrations/312_eid_weekly_recap_idx.sql new file mode 100644 index 0000000..b797d33 --- /dev/null +++ b/migrations/312_eid_weekly_recap_idx.sql @@ -0,0 +1,4 @@ +-- 312_eid_weekly_recap_idx.sql — 303_eid_weekly_recap.sql 분리본 (single-statement). asyncpg 러너가 prepared statement 로 처리해 multi-statement 거부 → 1 stmt/파일. 내용 불변. + +CREATE INDEX IF NOT EXISTS idx_eid_recap_user_current + ON eid_weekly_recap (user_id, created_at DESC) WHERE status = 'active'; diff --git a/migrations/313_approval_requests_table.sql b/migrations/313_approval_requests_table.sql new file mode 100644 index 0000000..463c62b --- /dev/null +++ b/migrations/313_approval_requests_table.sql @@ -0,0 +1,14 @@ +-- 313_approval_requests_table.sql — 304_approval_requests.sql 분리본 (single-statement). asyncpg 러너가 prepared statement 로 처리해 multi-statement 거부 → 1 stmt/파일. 내용 불변. + +CREATE TABLE IF NOT EXISTS approval_requests ( + id BIGSERIAL PRIMARY KEY, + user_id BIGINT NOT NULL REFERENCES users(id) ON DELETE CASCADE, + request_type VARCHAR(40) NOT NULL, -- 고정 템플릿 슬롯 타입(app 화이트리스트) + payload JSONB NOT NULL, -- 고정 템플릿 슬롯만 + status VARCHAR(20) NOT NULL DEFAULT 'pending', -- pending | approved | rejected (전이 허용) + requester VARCHAR(20) NOT NULL, -- 'eid' + decided_by VARCHAR(40), + decided_at TIMESTAMPTZ, + created_at TIMESTAMPTZ NOT NULL DEFAULT now(), + updated_at TIMESTAMPTZ NOT NULL DEFAULT now() +); diff --git a/migrations/314_approval_requests_idx.sql b/migrations/314_approval_requests_idx.sql new file mode 100644 index 0000000..a50ca43 --- /dev/null +++ b/migrations/314_approval_requests_idx.sql @@ -0,0 +1,3 @@ +-- 314_approval_requests_idx.sql — 304_approval_requests.sql 분리본 (single-statement). asyncpg 러너가 prepared statement 로 처리해 multi-statement 거부 → 1 stmt/파일. 내용 불변. + +CREATE INDEX IF NOT EXISTS idx_approval_requests_status ON approval_requests (status, created_at); diff --git a/migrations/315_eid_schedule_views_v_schedule_today.sql b/migrations/315_eid_schedule_views_v_schedule_today.sql new file mode 100644 index 0000000..69e1817 --- /dev/null +++ b/migrations/315_eid_schedule_views_v_schedule_today.sql @@ -0,0 +1,16 @@ +-- 315_eid_schedule_views_v_schedule_today.sql — 305_eid_schedule_views.sql 분리본 (single-statement). asyncpg 러너가 prepared statement 로 처리해 multi-statement 거부 → 1 stmt/파일. 내용 불변. + +CREATE OR REPLACE VIEW v_schedule_today AS +SELECT e.id, e.user_id, e.title, e.kind, e.status, e.priority, + e.due_at, e.start_at, e.end_at, e.started_at, e.defer_until, e.project_tag +FROM events e +CROSS JOIN LATERAL ( + SELECT (date_trunc('day', now() AT TIME ZONE 'Asia/Seoul') AT TIME ZONE 'Asia/Seoul') AS lo +) b +WHERE (e.status IN ('inbox','next','scheduled','in_progress') + OR (e.status = 'deferred' AND e.defer_until IS NOT NULL AND e.defer_until <= now())) + AND ( + (e.kind = 'task' AND e.due_at >= b.lo AND e.due_at < b.lo + interval '1 day') + OR (e.kind = 'calendar_event' AND e.start_at >= b.lo AND e.start_at < b.lo + interval '1 day') + OR (e.kind = 'activity_log' AND e.started_at >= b.lo AND e.started_at < b.lo + interval '1 day') + ); diff --git a/migrations/316_eid_schedule_views_v_schedule_defer_pattern.sql b/migrations/316_eid_schedule_views_v_schedule_defer_pattern.sql new file mode 100644 index 0000000..239df11 --- /dev/null +++ b/migrations/316_eid_schedule_views_v_schedule_defer_pattern.sql @@ -0,0 +1,10 @@ +-- 316_eid_schedule_views_v_schedule_defer_pattern.sql — 305_eid_schedule_views.sql 분리본 (single-statement). asyncpg 러너가 prepared statement 로 처리해 multi-statement 거부 → 1 stmt/파일. 내용 불변. + +CREATE OR REPLACE VIEW v_schedule_defer_pattern AS +SELECT eh.event_id, + COUNT(*)::int AS defer_reschedule_count, + MAX(eh.changed_at) AS last_changed_at, + (COUNT(*) >= 3) AS is_repeat_defer +FROM events_history eh +WHERE eh.change_kind IN ('defer','reschedule') +GROUP BY eh.event_id;