From 2e1ead69e1e2f401da528d877ce8c1949b0ce362 Mon Sep 17 00:00:00 2001 From: Claude Agent Date: Tue, 3 Feb 2026 15:15:57 +0000 Subject: [PATCH] refactor: simplify Oracle pool to use only ORACLE_SERVERS format - Remove legacy pool support (DSN, env vars fallback) - Use first registered server when server_id not specified - Show server dropdown even with single server in ORACLE_SERVERS - Email login only available for 2+ servers Co-Authored-By: Claude Opus 4.5 --- shared/database/oracle_pool.py | 116 ++++++--------------------------- shared/routes/system.py | 9 +-- 2 files changed, 26 insertions(+), 99 deletions(-) diff --git a/shared/database/oracle_pool.py b/shared/database/oracle_pool.py index ae65e87..d44b994 100644 --- a/shared/database/oracle_pool.py +++ b/shared/database/oracle_pool.py @@ -1,12 +1,11 @@ """ Oracle Database Connection Pool - Multi-Server Support for ROA2WEB -Supports both single-server (backward compatible) and multi-server configurations. +Uses ORACLE_SERVERS from .env for server configuration. Pool-uri sunt create lazy (la prima conexiune pe fiecare server) pentru optimizare. """ import asyncio import oracledb -import os from contextlib import asynccontextmanager from typing import Optional, Dict, Any import logging @@ -21,14 +20,13 @@ class OracleMultiPool: Supports: - Multiple Oracle servers with separate pools: {server_id: pool} - Lazy pool creation (created on first connection) - - Backward compatibility (default server when no server_id specified) + - First registered server used when no server_id specified - Graceful shutdown of all pools """ _instance: Optional['OracleMultiPool'] = None _pools: Dict[str, oracledb.ConnectionPool] _pool_configs: Dict[str, Dict[str, Any]] _pool_lock: asyncio.Lock - _legacy_pool: Optional[oracledb.ConnectionPool] # For backward compatibility _initialized: bool def __new__(cls): @@ -37,77 +35,23 @@ class OracleMultiPool: cls._instance._pools = {} cls._instance._pool_configs = {} cls._instance._pool_lock = asyncio.Lock() - cls._instance._legacy_pool = None cls._instance._initialized = False return cls._instance - async def initialize(self, **config): + async def initialize(self): """ Initialize pool manager. - For backward compatibility, this can: - 1. Create a legacy single pool (if called with individual params) - 2. Just mark as initialized (if using lazy multi-pool loading) + Call this after registering servers with register_server(). + Pools are created lazily on first connection. """ if self._initialized: logger.debug("Pool manager already initialized") return - # Check if we have DSN or individual parameters (legacy mode) - dsn = config.get('dsn', os.getenv('ORACLE_DSN')) - user = config.get('user', os.getenv('ORACLE_USER')) - - if dsn or user: - # Legacy single-pool mode - create pool immediately - await self._create_legacy_pool(config) - self._initialized = True logger.info("Oracle pool manager initialized") - async def _create_legacy_pool(self, config: Dict[str, Any]) -> None: - """Create legacy single pool for backward compatibility.""" - dsn = config.get('dsn', os.getenv('ORACLE_DSN')) - if dsn: - # Use DSN connection - self._legacy_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) - 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 - } - - 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: - pool_params['service_name'] = 'ROA' - logger.info("Using default SERVICE_NAME: ROA") - - self._legacy_pool = oracledb.create_pool(**pool_params) - - logger.info(f"Legacy Oracle pool created with {self._legacy_pool.opened} connections") - def register_server( self, server_id: str, @@ -185,20 +129,26 @@ class OracleMultiPool: logger.info(f"Pool created for server '{server_id}' with {pool.opened} connections") return pool + def _get_first_server_id(self) -> str: + """Get the first registered server ID.""" + if not self._pool_configs: + raise RuntimeError("No servers registered. Call register_server() first.") + return next(iter(self._pool_configs)) + @asynccontextmanager async def get_connection(self, server_id: Optional[str] = None): """ Context manager pentru obținerea unei conexiuni din pool. Args: - server_id: ID-ul serverului. Dacă None, folosește legacy pool sau default. + server_id: ID-ul serverului. Dacă None, folosește primul server înregistrat. Usage: - # Multi-server mode + # Explicit server async with oracle_pool.get_connection('romfast') as conn: ... - # Backward compatible (legacy single pool) + # First registered server (when only one server configured) async with oracle_pool.get_connection() as conn: ... """ @@ -207,21 +157,11 @@ class OracleMultiPool: try: if server_id is None: - # Backward compatibility: use legacy pool - if self._legacy_pool is None: - # If no legacy pool, try to use 'default' server - if 'default' in self._pool_configs: - pool = await self._get_or_create_pool('default') - else: - raise RuntimeError( - "No pool available. Either initialize() with config " - "or register_server() with server_id='default'." - ) - else: - pool = self._legacy_pool - else: - pool = await self._get_or_create_pool(server_id) + # Use first registered server + server_id = self._get_first_server_id() + logger.debug(f"No server_id specified, using first registered: '{server_id}'") + pool = await self._get_or_create_pool(server_id) connection = pool.acquire() logger.debug(f"Connection acquired from pool (server_id={server_id})") yield connection @@ -238,7 +178,7 @@ class OracleMultiPool: Args: query: SQL query string parameters: Query parameters (dict or tuple) - server_id: Server ID for multi-pool mode (optional) + server_id: Server ID (optional, uses first server if not specified) """ async with self.get_connection(server_id) as connection: logger.debug(f"Executing query on server '{server_id}': {query[:100]}...") @@ -272,11 +212,6 @@ class OracleMultiPool: logger.info(f"Closed pool for server '{server_id}'") else: # Close all pools (graceful shutdown) - if self._legacy_pool is not None: - self._legacy_pool.close() - self._legacy_pool = None - logger.info("Closed legacy pool") - for srv_id, pool in list(self._pools.items()): pool.close() logger.info(f"Closed pool for server '{srv_id}'") @@ -307,15 +242,6 @@ class OracleMultiPool: 'max': pool.max, } else: - # All pools including legacy - if self._legacy_pool: - stats['legacy'] = { - 'opened': self._legacy_pool.opened, - 'busy': self._legacy_pool.busy, - 'min': self._legacy_pool.min, - 'max': self._legacy_pool.max, - } - for srv_id, pool in self._pools.items(): stats[srv_id] = { 'opened': pool.opened, @@ -343,8 +269,8 @@ class OracleMultiPool: return list(self._pools.keys()) -# Backward compatibility: keep old class name as alias +# Backward compatibility alias OraclePool = OracleMultiPool -# Instance globală pentru folosire în toate aplicațiile +# Global instance oracle_pool = OracleMultiPool() diff --git a/shared/routes/system.py b/shared/routes/system.py index 2175b3f..ea798e3 100644 --- a/shared/routes/system.py +++ b/shared/routes/system.py @@ -59,14 +59,15 @@ def create_system_router() -> APIRouter: servers = settings.get_oracle_servers() - # Multi-server mode: 2+ servers configured via ORACLE_SERVERS - if servers and len(servers) > 1: + # Multi-server mode: ANY servers configured via ORACLE_SERVERS + # Shows server dropdown even with 1 server (explicit server selection) + if servers and len(servers) >= 1: return AuthModeResponse( mode="multi-server", - supports_email_login=True + supports_email_login=len(servers) > 1 # Email lookup only for 2+ servers ) - # Single-server mode: legacy config or single ORACLE_SERVERS entry + # Single-server mode: legacy config (no ORACLE_SERVERS, uses env vars) return AuthModeResponse( mode="single-server", supports_email_login=False