194 lines
7.1 KiB
Python
194 lines
7.1 KiB
Python
# app/utils/database_utils.py (execute_insert_with_return 메소드 추가)
|
|
"""
|
|
PostgreSQL 데이터베이스 유틸리티 (databases + asyncpg) - RETURNING 지원 추가
|
|
"""
|
|
import databases
|
|
import logging
|
|
from typing import Dict, Any, List, Optional
|
|
from app.config.settings import settings
|
|
from app.repositories.queries import BaseQueries
|
|
|
|
logger = logging.getLogger(__name__)
|
|
|
|
|
|
class SimpleDatabase:
|
|
"""PostgreSQL 데이터베이스 연결 클래스"""
|
|
|
|
def __init__(self):
|
|
self.database = databases.Database(settings.database_url)
|
|
self._connected = False
|
|
|
|
async def connect(self):
|
|
"""데이터베이스 연결"""
|
|
if not self._connected:
|
|
try:
|
|
await self.database.connect()
|
|
self._connected = True
|
|
logger.info("데이터베이스 연결 성공")
|
|
except Exception as e:
|
|
logger.error(f"데이터베이스 연결 실패: {str(e)}")
|
|
raise
|
|
|
|
async def disconnect(self):
|
|
"""데이터베이스 연결 해제"""
|
|
if self._connected:
|
|
try:
|
|
await self.database.disconnect()
|
|
self._connected = False
|
|
logger.info("데이터베이스 연결 해제")
|
|
except Exception as e:
|
|
logger.error(f"데이터베이스 연결 해제 실패: {str(e)}")
|
|
|
|
async def test_connection(self) -> Dict[str, Any]:
|
|
"""데이터베이스 연결 테스트"""
|
|
try:
|
|
if not self._connected:
|
|
await self.connect()
|
|
|
|
# 기본 쿼리 실행
|
|
test_result = await self.database.fetch_val(BaseQueries.CONNECTION_TEST)
|
|
db_version = await self.database.fetch_val(BaseQueries.DATABASE_VERSION)
|
|
current_db = await self.database.fetch_val(BaseQueries.CURRENT_DATABASE)
|
|
current_user = await self.database.fetch_val(BaseQueries.CURRENT_USER)
|
|
|
|
return {
|
|
"status": "connected",
|
|
"test_query": test_result,
|
|
"database_name": current_db,
|
|
"username": current_user,
|
|
"host": settings.db_host,
|
|
"port": settings.db_port,
|
|
"db_version": db_version[:50] + "..." if len(db_version) > 50 else db_version
|
|
}
|
|
|
|
except Exception as e:
|
|
logger.error(f"데이터베이스 연결 실패: {str(e)}")
|
|
return {
|
|
"status": "failed",
|
|
"error": str(e),
|
|
"error_type": type(e).__name__
|
|
}
|
|
|
|
async def list_tables(self) -> List[Dict[str, Any]]:
|
|
"""테이블 목록 조회"""
|
|
try:
|
|
if not self._connected:
|
|
await self.connect()
|
|
|
|
rows = await self.database.fetch_all(BaseQueries.LIST_TABLES)
|
|
|
|
tables = []
|
|
for row in rows:
|
|
tables.append({
|
|
"table_name": row["table_name"],
|
|
"table_schema": row["table_schema"],
|
|
"table_type": row["table_type"]
|
|
})
|
|
|
|
return tables
|
|
|
|
except Exception as e:
|
|
logger.error(f"테이블 목록 조회 실패: {str(e)}")
|
|
raise Exception(f"테이블 목록 조회 실패: {str(e)}")
|
|
|
|
async def query_table(self, table_name: str, limit: int = 5) -> Dict[str, Any]:
|
|
"""테이블 데이터 조회"""
|
|
try:
|
|
if not self._connected:
|
|
await self.connect()
|
|
|
|
# 안전한 쿼리 실행
|
|
query = BaseQueries.get_table_data_query(table_name, limit)
|
|
rows = await self.database.fetch_all(query)
|
|
|
|
# 컬럼 정보 조회
|
|
columns_result = await self.database.fetch_all(
|
|
BaseQueries.GET_TABLE_COLUMNS,
|
|
{"table_name": table_name}
|
|
)
|
|
columns = [col["column_name"] for col in columns_result]
|
|
|
|
# 결과 변환
|
|
data = []
|
|
for row in rows:
|
|
data.append(dict(row))
|
|
|
|
return {
|
|
"table_name": table_name,
|
|
"column_count": len(columns),
|
|
"row_count": len(data),
|
|
"columns": columns,
|
|
"data": data
|
|
}
|
|
|
|
except Exception as e:
|
|
logger.error(f"테이블 조회 실패: {str(e)}")
|
|
raise Exception(f"테이블 '{table_name}' 조회 실패: {str(e)}")
|
|
|
|
async def execute_query(self, query: str, values: Optional[Dict[str, Any]] = None) -> List[Dict[str, Any]]:
|
|
"""쿼리 실행 (SELECT 전용)"""
|
|
try:
|
|
if not self._connected:
|
|
await self.connect()
|
|
|
|
logger.info(f"쿼리 실행: {query[:100]}{'...' if len(query) > 100 else ''}")
|
|
|
|
# SELECT 쿼리 실행
|
|
rows = await self.database.fetch_all(query, values or {})
|
|
result = [dict(row) for row in rows]
|
|
|
|
logger.info(f"쿼리 결과: {len(result)}건 조회")
|
|
return result
|
|
|
|
except Exception as e:
|
|
logger.error(f"쿼리 실행 실패: {str(e)}")
|
|
logger.error(f"실행 쿼리: {query}")
|
|
logger.error(f"파라미터: {values}")
|
|
raise Exception(f"쿼리 실행 실패: {str(e)}")
|
|
|
|
async def execute_insert_update(self, query: str, values: Optional[Dict[str, Any]] = None) -> int:
|
|
"""INSERT, UPDATE, DELETE 쿼리 실행"""
|
|
try:
|
|
if not self._connected:
|
|
await self.connect()
|
|
|
|
logger.info(f"INSERT/UPDATE 실행: {query[:100]}{'...' if len(query) > 100 else ''}")
|
|
|
|
result = await self.database.execute(query, values or {})
|
|
|
|
logger.info(f"INSERT/UPDATE 결과: {result}건 영향")
|
|
return result
|
|
|
|
except Exception as e:
|
|
logger.error(f"INSERT/UPDATE 실행 실패: {str(e)}")
|
|
logger.error(f"실행 쿼리: {query}")
|
|
logger.error(f"파라미터: {values}")
|
|
raise Exception(f"INSERT/UPDATE 실행 실패: {str(e)}")
|
|
|
|
async def execute_insert_with_return(self, query: str, values: Optional[Dict[str, Any]] = None) -> Dict[str, Any]:
|
|
"""INSERT ... RETURNING 쿼리 실행"""
|
|
try:
|
|
if not self._connected:
|
|
await self.connect()
|
|
|
|
logger.info(f"INSERT RETURNING 실행: {query[:100]}{'...' if len(query) > 100 else ''}")
|
|
|
|
# RETURNING이 있는 INSERT는 fetch_one으로 실행
|
|
result = await self.database.fetch_one(query, values or {})
|
|
|
|
if result:
|
|
result_dict = dict(result)
|
|
logger.info(f"INSERT RETURNING 결과: {result_dict}")
|
|
return result_dict
|
|
else:
|
|
raise Exception("INSERT RETURNING 실행 결과가 없음")
|
|
|
|
except Exception as e:
|
|
logger.error(f"INSERT RETURNING 실행 실패: {str(e)}")
|
|
logger.error(f"실행 쿼리: {query}")
|
|
logger.error(f"파라미터: {values}")
|
|
raise Exception(f"INSERT RETURNING 실행 실패: {str(e)}")
|
|
|
|
|
|
# 전역 데이터베이스 인스턴스
|
|
simple_db = SimpleDatabase() |