""" Oracle Database Connection Pool - Shared între toate aplicațiile ROA2WEB Folosește oracledb cu connection pooling pentru performance optimă """ import oracledb import os from contextlib import asynccontextmanager from typing import Optional import logging logger = logging.getLogger(__name__) class OraclePool: """ Singleton class pentru Oracle connection pool Partajat între toate microservicele ROA2WEB """ _instance: Optional['OraclePool'] = None _pool: Optional[oracledb.ConnectionPool] = None def __new__(cls): if cls._instance is None: cls._instance = super(OraclePool, cls).__new__(cls) return cls._instance async def initialize(self, **config): """Inițializează pool-ul de conexiuni""" if self._pool is None: # Check if we have DSN or individual parameters dsn = config.get('dsn', os.getenv('ORACLE_DSN')) if dsn: # Use DSN connection self._pool = oracledb.create_pool( user=config.get('user', os.getenv('ORACLE_USER')), password=config.get('password', os.getenv('ORACLE_PASSWORD')), dsn=dsn, min=config.get('min_connections', 2), max=config.get('max_connections', 10), increment=config.get('increment', 1), getmode=oracledb.POOL_GETMODE_WAIT ) else: # Use individual parameters (host, port, service_name or sid) # Prefer SERVICE_NAME over SID (more modern Oracle approach) service_name = config.get('service_name', os.getenv('ORACLE_SERVICE_NAME')) sid = config.get('sid', os.getenv('ORACLE_SID')) pool_params = { 'user': config.get('user', os.getenv('ORACLE_USER')), 'password': config.get('password', os.getenv('ORACLE_PASSWORD')), 'host': config.get('host', os.getenv('ORACLE_HOST', 'localhost')), 'port': config.get('port', int(os.getenv('ORACLE_PORT', '1526'))), 'min': config.get('min_connections', 2), 'max': config.get('max_connections', 10), 'increment': config.get('increment', 1), 'getmode': oracledb.POOL_GETMODE_WAIT } # Use service_name if available, otherwise fall back to sid if service_name: pool_params['service_name'] = service_name logger.info(f"Using SERVICE_NAME: {service_name}") elif sid: pool_params['sid'] = sid logger.info(f"Using SID: {sid}") else: # Default fallback pool_params['service_name'] = 'ROA' logger.info("Using default SERVICE_NAME: ROA") self._pool = oracledb.create_pool(**pool_params) logger.info(f"Oracle pool created with {self._pool.opened} connections") @asynccontextmanager async def get_connection(self): """Context manager pentru obținerea unei conexiuni din pool""" if self._pool is None: raise RuntimeError("Pool not initialized. Call initialize() first.") connection = None try: connection = self._pool.acquire() logger.debug("Connection acquired from pool") yield connection finally: if connection is not None: connection.close() logger.debug("Connection returned to pool") async def execute_query(self, query: str, parameters=None): """ Execute a SQL query and return all results Based on official Oracle python-oracledb patterns """ if self._pool is None: raise RuntimeError("Pool not initialized. Call initialize() first.") connection = None try: connection = self._pool.acquire() logger.debug(f"Executing query: {query[:100]}...") with connection.cursor() as cursor: if parameters: cursor.execute(query, parameters) else: cursor.execute(query) # Check if this is a SELECT statement if query.strip().upper().startswith('SELECT') or query.strip().upper().startswith('WITH'): return cursor.fetchall() else: # For DML statements, return affected row count connection.commit() return cursor.rowcount except Exception as e: if connection: connection.rollback() logger.error(f"Query execution failed: {str(e)}") raise finally: if connection is not None: connection.close() logger.debug("Connection returned to pool") async def close_pool(self): """Închide pool-ul de conexiuni""" if self._pool is not None: self._pool.close() self._pool = None logger.info("Oracle pool closed") # Instance globală pentru folosire în toate aplicațiile oracle_pool = OraclePool()