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 <noreply@anthropic.com>
This commit is contained in:
@@ -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()
|
||||
|
||||
@@ -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
|
||||
|
||||
Reference in New Issue
Block a user