fix(migrations): R1 baseline 런타임 버그 3건 — init_db asyncpg 경로 (R1 fix)
★실제 init_db() 런타임 검증(psql migration_smoke 가 못 잡는 asyncpg 경로)에서 발견·수정:
1. baseline 덤프에 CREATE TABLE schema_migrations 포함 → init_db 가 IF NOT EXISTS 로 선-CREATE
후 baseline 이 재-CREATE 충돌. --exclude-table=schema_migrations 재덤프(init_db 가 소유).
2. baseline 은 multi-statement 인데 exec_driver_sql(asyncpg prepared)은 multi-statement 불허
('cannot insert multiple commands into a prepared statement'). raw asyncpg simple 프로토콜
execute() 로 적재(같은 connection = 트랜잭션 내).
3. 마이그 360(10 DROP)·361(DELETE+CREATE)이 multi-statement → init_db 적용 실패. 360=콤마구분
단일 DROP, 361=단일 CREATE UNIQUE INDEX(prod 중복0·fresh 빈테이블이라 dedup DELETE 불요).
★검증: scripts/ci/initdb_runtime_test.py 로 실제 init_db 2회 — 1st(fresh: baseline 262 스탬프 +
359/360/361 적용, documents·purge_col·cand_drop·attempt_unique 전부 확인), 2nd(멱등 skip) PASS.
psql migration_smoke 도 PASS 유지.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,51 @@
|
||||
"""init_db() baseline 부팅 런타임 검증 (R1) — psql migration_smoke 가 못 잡는 asyncpg 경로 확인.
|
||||
|
||||
migration_smoke.sh(psql)는 SQL 유효성만 검증한다. init_db 는 asyncpg exec_driver_sql(prepared)
|
||||
경로라 ① multi-statement 불허 ② baseline 의 raw asyncpg 적재 ③ skip/stamp/멱등 — 이걸 실측한다.
|
||||
|
||||
실행 (worktree 루트):
|
||||
python3.11 -m venv /tmp/v && /tmp/v/bin/pip install -q "sqlalchemy[asyncio]>=2" asyncpg pydantic pyyaml
|
||||
docker run -d --name idb -p 55432:5432 -e POSTGRES_HOST_AUTH_METHOD=trust pgvector/pgvector:pg16
|
||||
docker exec idb psql -U postgres -c "CREATE DATABASE pkm"
|
||||
ln -sfn ../migrations app/migrations # Docker 의 /app/migrations 레이아웃 모사 (테스트 후 rm)
|
||||
PYTHONPATH=app DATABASE_URL="postgresql+asyncpg://postgres@localhost:55432/pkm" /tmp/v/bin/python scripts/ci/initdb_runtime_test.py
|
||||
rm -f app/migrations; docker rm -f idb
|
||||
|
||||
기대: 1st OK(documents=True·purge_col=1·cand_qwen=0·attempt_unique=1), 2nd 멱등동일=True.
|
||||
"""
|
||||
import asyncio
|
||||
from sqlalchemy import text
|
||||
|
||||
|
||||
async def main():
|
||||
from core.config import settings
|
||||
url = settings.database_url
|
||||
print("effective DATABASE_URL:", url)
|
||||
assert "localhost" in url or "127.0.0.1" in url, f"SAFETY ABORT non-local: {url}"
|
||||
from core.database import init_db, async_session, engine
|
||||
|
||||
print("=== 1st init_db (fresh DB) ===")
|
||||
await init_db()
|
||||
async with async_session() as s:
|
||||
cnt = (await s.execute(text("SELECT count(*) FROM schema_migrations"))).scalar()
|
||||
mx = (await s.execute(text("SELECT max(version) FROM schema_migrations"))).scalar()
|
||||
bl = (await s.execute(text("SELECT count(*) FROM schema_migrations WHERE name LIKE 'baseline:%'"))).scalar()
|
||||
docs = (await s.execute(text("SELECT to_regclass('public.documents') IS NOT NULL"))).scalar()
|
||||
purge = (await s.execute(text("SELECT count(*) FROM information_schema.columns WHERE table_name='documents' AND column_name='purge_requested_at'"))).scalar()
|
||||
cand = (await s.execute(text("SELECT count(*) FROM information_schema.tables WHERE table_name LIKE 'documents_cand_qwen%'"))).scalar()
|
||||
uq = (await s.execute(text("SELECT count(*) FROM pg_indexes WHERE indexname='uq_attempt_session_question'"))).scalar()
|
||||
print(f" schema_migrations count={cnt} max={mx} baseline_stamped={bl}")
|
||||
print(f" documents={docs} purge_col={purge} cand_qwen_tables={cand} attempt_unique={uq}")
|
||||
assert docs and purge == 1 and cand == 0 and uq == 1, "FAIL: 기대 스키마 상태 불일치"
|
||||
|
||||
print("=== 2nd init_db (rerun = baseline skip + 멱등) ===")
|
||||
await init_db()
|
||||
async with async_session() as s:
|
||||
cnt2 = (await s.execute(text("SELECT count(*) FROM schema_migrations"))).scalar()
|
||||
assert cnt == cnt2, "FAIL: 멱등 아님 (재실행이 schema_migrations 변경)"
|
||||
print(f" count={cnt2} 멱등동일={cnt == cnt2}")
|
||||
print("RESULT: PASS — init_db baseline 부팅/멱등 검증")
|
||||
await engine.dispose()
|
||||
|
||||
|
||||
asyncio.run(main())
|
||||
Reference in New Issue
Block a user