"""Oracle ADB price_history CRUD.""" from __future__ import annotations import os from contextlib import contextmanager from typing import Generator, Optional import oracledb _pool: Optional[oracledb.ConnectionPool] = None def _get_pool() -> oracledb.ConnectionPool: global _pool if _pool is None: kwargs: dict = dict( user=os.environ["ORACLE_USER"], password=os.environ["ORACLE_PASSWORD"], dsn=os.environ["ORACLE_DSN"], min=1, max=3, increment=1, ) wallet = os.environ.get("ORACLE_WALLET") if wallet: kwargs["config_dir"] = wallet _pool = oracledb.create_pool(**kwargs) return _pool @contextmanager def _conn() -> Generator[oracledb.Connection, None, None]: pool = _get_pool() conn = pool.acquire() try: yield conn conn.commit() except Exception: conn.rollback() raise finally: pool.release(conn) def insert_prices(ticker_prices: dict[str, float]) -> None: """여러 종목의 현재가를 한 번에 저장.""" if not ticker_prices: return rows = [(ticker, price) for ticker, price in ticker_prices.items()] sql = "INSERT INTO price_history (ticker, price) VALUES (:1, :2)" with _conn() as conn: conn.cursor().executemany(sql, rows) def get_price_n_hours_ago(ticker: str, hours: float) -> Optional[float]: """N시간 전 가장 가까운 가격 반환. 데이터 없으면 None.""" sql = """ SELECT price FROM price_history WHERE ticker = :ticker AND recorded_at BETWEEN SYSTIMESTAMP - INTERVAL ':h' HOUR - INTERVAL '10' MINUTE AND SYSTIMESTAMP - INTERVAL ':h' HOUR + INTERVAL '10' MINUTE ORDER BY ABS(CAST(recorded_at AS DATE) - CAST(SYSTIMESTAMP - INTERVAL ':h' HOUR AS DATE)) FETCH FIRST 1 ROWS ONLY """ # Oracle INTERVAL bind param 미지원으로 직접 포맷 h = int(hours) sql = f""" SELECT price FROM price_history WHERE ticker = :ticker AND recorded_at BETWEEN SYSTIMESTAMP - ({h}/24) - (10/1440) AND SYSTIMESTAMP - ({h}/24) + (10/1440) ORDER BY ABS(CAST(recorded_at AS DATE) - CAST(SYSTIMESTAMP - ({h}/24) AS DATE)) FETCH FIRST 1 ROWS ONLY """ with _conn() as conn: cursor = conn.cursor() cursor.execute(sql, {"ticker": ticker}) row = cursor.fetchone() return float(row[0]) if row else None def cleanup_old_prices(keep_hours: int = 48) -> None: """N시간 이상 오래된 데이터 삭제 (DB 용량 관리).""" sql = f"DELETE FROM price_history WHERE recorded_at < SYSTIMESTAMP - ({keep_hours}/24)" with _conn() as conn: conn.cursor().execute(sql)