# Database Module - Async PostgreSQL Persistence ## Purpose Async PostgreSQL persistence layer using SQLAlchemy 2.0. Handles all database operations with connection pooling and proper transaction management. ## Structure ``` app/database/ ├── __init__.py # Package exports ├── session.py # Async session management, Base declarative └── operations.py # DatabaseOperations class ``` ## Session Management ### Async Session Pattern ```python from app.database.session import get_session async def some_function(): async with get_session() as session: result = await session.execute(query) # Auto-commits on success, rolls back on exception ``` ### Connection Pool - **Driver**: asyncpg - **Pool**: SQLAlchemy async pool - Configurable pool size via env vars ## DatabaseOperations Singleton class with all database operations. ### Game Operations ```python from app.database.operations import DatabaseOperations db_ops = DatabaseOperations() await db_ops.create_game(game_id, league_id, home_team_id, away_team_id, ...) await db_ops.update_game_state(game_id, inning, half, home_score, away_score) game_data = await db_ops.load_game_state(game_id) ``` ### Play Operations ```python play_id = await db_ops.save_play(game_id, play_data, stats_data) plays = await db_ops.get_plays(game_id, limit=100) ``` ### Lineup Operations ```python lineup_id = await db_ops.add_pd_lineup_card(game_id, team_id, card_id, position, batting_order) lineup_id = await db_ops.add_sba_lineup_player(game_id, team_id, player_id, position, batting_order) lineup = await db_ops.get_active_lineup(game_id, team_id) await db_ops.deactivate_lineup_player(lineup_id) ``` ### Roster Operations ```python await db_ops.add_pd_roster_card(game_id, card_id, team_id) await db_ops.add_sba_roster_player(game_id, player_id, team_id) roster = await db_ops.get_pd_roster(game_id, team_id) ``` ### Session Operations ```python await db_ops.create_game_session(game_id) await db_ops.update_session_snapshot(game_id, state_snapshot) ``` ## Common Patterns ### Transaction Handling ```python async with AsyncSessionLocal() as session: try: # Multiple operations await session.commit() except Exception: await session.rollback() raise ``` ### Efficient Queries ```python # Use joinedload for relationships needed immediately from sqlalchemy.orm import joinedload query = select(Play).options( joinedload(Play.batter), joinedload(Play.pitcher) ) ``` ### Direct UPDATE ```python # More efficient than SELECT + modify + commit stmt = update(Game).where(Game.id == game_id).values(inning=5) await session.execute(stmt) ``` ## Database Schema See `../models/CLAUDE.md` for model details. | Table | Purpose | |-------|---------| | games | Game container, scores, status | | plays | At-bat records with 25+ stats | | lineups | Player assignments, substitutions | | game_sessions | WebSocket state | | roster_links | Eligible cards/players | ## Environment ```bash DATABASE_URL=postgresql+asyncpg://user:pass@10.10.0.42:5432/paperdynasty_dev ``` ## References - **Database Schema**: See `../../.claude/DATABASE_SCHEMA.md` for complete table details - **Models**: See `../models/CLAUDE.md` - **State Recovery**: See `../core/state_manager.py` --- **Tests**: `tests/integration/database/` | **Updated**: 2025-01-19