"""Add durable reliability-monitoring persistence without destructive DDL.""" from datetime import datetime from sqlalchemy import Column, DateTime, MetaData, String, Table, inspect, select, text from sqlalchemy.engine import Connection from app.models import Base REVISION = "20260803_reliability_monitoring" _migration_metadata = MetaData() _schema_migrations = Table( "schema_migrations", _migration_metadata, Column("revision", String(64), primary_key=True), Column("applied_at", DateTime, nullable=False, default=datetime.now), ) _DEFAULTS_FOR_EXISTING_ROWS = { "is_valid": "TRUE", "notification_attempts": "0", } def _quote(connection: Connection, identifier: str) -> str: """Quote one database identifier through the active dialect.""" return connection.dialect.identifier_preparer.quote(identifier) def _add_missing_columns(connection: Connection) -> None: """Add only newly required nullable/defaulted columns to existing tables.""" inspector = inspect(connection) existing_tables = set(inspector.get_table_names()) for table in Base.metadata.sorted_tables: if table.name not in existing_tables: continue existing_columns = { column["name"] for column in inspector.get_columns(table.name) } for column in table.columns: if column.name in existing_columns: continue column_type = connection.dialect.type_compiler.process(column.type) definition = f"{_quote(connection, column.name)} {column_type}" default = _DEFAULTS_FOR_EXISTING_ROWS.get(column.name) if default is not None: if connection.dialect.name == "sqlite" and default == "TRUE": default = "1" definition = f"{definition} DEFAULT {default}" connection.execute( text( f"ALTER TABLE {_quote(connection, table.name)} " f"ADD COLUMN {definition}" ) ) def _create_missing_indexes(connection: Connection) -> None: """Create new performance indexes for tables that predate this revision.""" inspector = inspect(connection) for table in Base.metadata.sorted_tables: existing_indexes = { index["name"] for index in inspector.get_indexes(table.name) } for index in table.indexes: if index.name not in existing_indexes: index.create(bind=connection) def _upgrade_postgresql_alert_type(connection: Connection) -> None: """Permit the degraded alert value for databases using PostgreSQL enums.""" if connection.dialect.name == "postgresql": connection.execute( text("ALTER TYPE alerttypeenum ADD VALUE IF NOT EXISTS 'degraded'") ) def run_reliability_migration(connection: Connection) -> None: """Apply this revision once while retaining all existing monitoring data. This runner is deliberately idempotent: it creates only absent tables, columns, and indexes, then writes one immutable revision marker. It never issues DROP, DELETE, UPDATE, or data-copy statements. """ _migration_metadata.create_all(bind=connection) Base.metadata.create_all(bind=connection) already_applied = connection.execute( select(_schema_migrations.c.revision).where( _schema_migrations.c.revision == REVISION ) ).scalar_one_or_none() if already_applied is not None: return _upgrade_postgresql_alert_type(connection) _add_missing_columns(connection) _create_missing_indexes(connection) connection.execute(_schema_migrations.insert().values(revision=REVISION))