From a4eaf9b7705081170b32ba1375a3a9d758991416 Mon Sep 17 00:00:00 2001 From: king Date: Fri, 29 May 2026 03:22:08 +0900 Subject: [PATCH] =?UTF-8?q?fix(expense-db):=20=EC=95=B1=20=EB=B6=80?= =?UTF-8?q?=ED=8C=85=20=EC=8B=9C=20DDL=20=EC=8B=A4=ED=96=89=20=EC=A0=9C?= =?UTF-8?q?=EA=B1=B0=20+=20lazy=20pool=20open?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit PostgreSQL 15+ 는 public 스키마 CREATE 권한을 PUBLIC 역할에서 회수했기 때문에 expense_app (앱 전용 비특권 계정) 으로 CREATE TABLE 시도하면 "permission denied for schema public" 으로 컨테이너 부팅 실패. 스키마 책임 분리: - DDL: scripts/sql/expense_db_init.sql + expense_db_002_workflow_attachments.sql (superuser 가 사전 적용) - 런타임: SELECT/INSERT/UPDATE/DELETE 만 ConnectionPool 도 open=False + lazy open 으로 변경. DB 가 잠시 끊겨도 컨테이너가 죽지 않게 함. --- app/modules/expense/db.py | 36 +++++++++++------------------------- 1 file changed, 11 insertions(+), 25 deletions(-) diff --git a/app/modules/expense/db.py b/app/modules/expense/db.py index d83433e..f1f4424 100644 --- a/app/modules/expense/db.py +++ b/app/modules/expense/db.py @@ -20,27 +20,17 @@ from psycopg_pool import ConnectionPool from .store import CATEGORIES, METHODS, STATUSES -_DDL = """ -CREATE TABLE IF NOT EXISTS expense_items ( - id TEXT PRIMARY KEY, - owner TEXT NOT NULL, - spent_at DATE NOT NULL, - category TEXT NOT NULL, - method TEXT NOT NULL, - merchant TEXT NOT NULL DEFAULT '', - amount BIGINT NOT NULL DEFAULT 0 CHECK (amount >= 0), - memo TEXT NOT NULL DEFAULT '', - status TEXT NOT NULL DEFAULT '작성중', - created_at TIMESTAMPTZ NOT NULL DEFAULT now(), - updated_at TIMESTAMPTZ NOT NULL DEFAULT now() -); -CREATE INDEX IF NOT EXISTS idx_expense_owner_spent ON expense_items (owner, spent_at DESC); -CREATE INDEX IF NOT EXISTS idx_expense_status ON expense_items (status); -""" - class ExpenseDBStore: - """`ExpenseStore` 와 동일한 메서드 시그니처.""" + """`ExpenseStore` 와 동일한 메서드 시그니처. + + 스키마(테이블/인덱스/트리거)는 앱이 직접 만들지 않는다. + `scripts/sql/expense_db_init.sql` 과 `expense_db_002_*.sql` 을 통해 + superuser 가 사전 적용한다. 앱 계정(expense_app)은 SELECT/INSERT/UPDATE/DELETE + 권한만 받기 때문에 PostgreSQL 15+ 의 strict public-schema 정책과 충돌하지 않음. + + 연결 풀은 lazy open — 부팅 시점에 DB 가 잠시 끊겨도 컨테이너가 죽지 않게. + """ def __init__(self, dsn: str, *, min_size: int = 1, max_size: int = 5): self._pool = ConnectionPool( @@ -48,17 +38,13 @@ class ExpenseDBStore: min_size=min_size, max_size=max_size, kwargs={"row_factory": dict_row, "autocommit": True}, - open=True, + open=False, ) - self._ensure_schema() + self._pool.open(wait=False) def close(self) -> None: self._pool.close() - def _ensure_schema(self) -> None: - with self._pool.connection() as conn: - conn.execute(_DDL) - # ── 조회 ── def list_for(self, email: str) -> list[dict[str, Any]]: email = email.lower().strip()