"""add BEFORE UPDATE trigger to audit_logs for DB-level insert-only enforcement Revision ID: 014 Revises: 013 Create Date: 2026-04-15 Note: TiDB does not support CREATE TRIGGER. On TiDB this migration is a no-op and insert-only enforcement is left to the application layer (no UPDATE statement is ever issued against audit_logs from Python). MySQL keeps the trigger for defense in depth. """ from alembic import op from sqlalchemy import text revision = '014' down_revision = '013' branch_labels = None depends_on = None def _is_tidb() -> bool: conn = op.get_bind() try: row = conn.execute(text("SELECT VERSION()")).scalar() except Exception: return False return bool(row) and "TiDB" in str(row) def _audit_logs_exists() -> bool: conn = op.get_bind() row = conn.execute( text( "SELECT COUNT(*) FROM information_schema.tables " "WHERE table_schema = DATABASE() AND table_name = 'audit_logs'" ) ).scalar() return (row or 0) > 0 def upgrade(): if _is_tidb() or not _audit_logs_exists(): return op.execute("DROP TRIGGER IF EXISTS audit_logs_no_update") op.execute( "CREATE TRIGGER audit_logs_no_update " "BEFORE UPDATE ON audit_logs " "FOR EACH ROW " "SIGNAL SQLSTATE '45000' " "SET MESSAGE_TEXT = 'audit_logs is insert-only: updates are not permitted'" ) def downgrade(): if _is_tidb() or not _audit_logs_exists(): return op.execute("DROP TRIGGER IF EXISTS audit_logs_no_update")