fix(migrations): add _table_exists guard to migrations touching create_all()-only tables

- 2df63b7ce038: was checking _column_exists but not _table_exists.
  If llm_providers/validation_policies/llm_validation_results don't
  exist (fresh DB), _column_exists returns False and add_column crashes.
  Now checks _table_exists first.
- a1b2c3d4e5f6 (20260603_add_token_limits_to_llm_providers): no guard
  at all. llm_providers is created by create_all() at runtime. Guard
  matches pattern used by ed28d34edde7, 9f8e7d6c5b4a and others.
This commit is contained in:
2026-06-11 17:11:56 +03:00
parent 6855bb7010
commit 06ced7608d
2 changed files with 31 additions and 5 deletions

View File

@@ -1,5 +1,11 @@
"""Add context_window and max_output_tokens to llm_providers
@RATIONALE llm_providers is created at runtime by init_db() →
Base.metadata.create_all(), not by any Alembic migration. On a fresh
database, the table doesn't exist when migrations run. Guard with
_table_exists() — create_all() will create the table with the model's
columns (which already include context_window and max_output_tokens).
Revision ID: a1b2c3d4e5f6
Revises: f1a2b3c4d5e6
Create Date: 2026-06-03
@@ -12,6 +18,7 @@ Both NULL = use PROVIDER_DEFAULTS fallback from model name.
from typing import Sequence, Union
from alembic import op
from sqlalchemy import inspect
import sqlalchemy as sa
@@ -22,7 +29,15 @@ branch_labels: Union[str, Sequence[str], None] = None
depends_on: Union[str, Sequence[str], None] = None
def _table_exists(table_name: str) -> bool:
conn = op.get_bind()
inspector = inspect(conn)
return inspector.has_table(table_name)
def upgrade() -> None:
if not _table_exists("llm_providers"):
return
op.add_column(
"llm_providers",
sa.Column(
@@ -44,5 +59,7 @@ def upgrade() -> None:
def downgrade() -> None:
if not _table_exists("llm_providers"):
return
op.drop_column("llm_providers", "max_output_tokens")
op.drop_column("llm_providers", "context_window")

View File

@@ -18,6 +18,7 @@ from collections.abc import Sequence
import sqlalchemy as sa
from alembic import op
from sqlalchemy import inspect
# revision identifiers, used by Alembic.
revision: str = '2df63b7ce038'
@@ -26,27 +27,35 @@ branch_labels: str | Sequence[str] | None = None
depends_on: str | Sequence[str] | None = None
def _table_exists(table: str) -> bool:
"""Check if a table exists in the current database connection."""
conn = op.get_bind()
inspector = inspect(conn)
return inspector.has_table(table)
def upgrade() -> None:
"""Upgrade schema."""
# Use IF NOT EXISTS — this is a catch-up migration for columns that
# may already exist (inserted by out-of-band schema changes).
conn = op.get_bind()
# Add is_multimodal to llm_providers (from 9f8e7d6c5b4a)
if not _column_exists(conn, "llm_providers", "is_multimodal"):
# llm_providers is created by create_all() at runtime — skip if not exist
if _table_exists("llm_providers") and not _column_exists(conn, "llm_providers", "is_multimodal"):
op.add_column("llm_providers",
sa.Column("is_multimodal", sa.Boolean(), nullable=False, server_default="false")
)
op.alter_column("llm_providers", "is_multimodal", server_default=None)
# Add provider_id to validation_policies (from a7b1c2d3e4f5)
if not _column_exists(conn, "validation_policies", "provider_id"):
# validation_policies is created by create_all() at runtime — skip if not exist
if _table_exists("validation_policies") and not _column_exists(conn, "validation_policies", "provider_id"):
op.add_column("validation_policies",
sa.Column("provider_id", sa.String(), nullable=True)
)
# Add policy_id to llm_validation_results (from b1c2d3e4f5a6)
if not _column_exists(conn, "llm_validation_results", "policy_id"):
# llm_validation_results is created by create_all() at runtime — skip if not exist
if _table_exists("llm_validation_results") and not _column_exists(conn, "llm_validation_results", "policy_id"):
op.add_column("llm_validation_results",
sa.Column("policy_id", sa.String(), nullable=True, index=True)
)