refactor(migration): backfill all missing columns in ORM models

This migration updates the database schema by automatically adding all missing columns from the ORM models to the existing tables. It replaces the previous specific addition of 'tts_audio_urls' with a more comprehensive approach that inspects the ORM metadata and synchronizes it with the database schema. The downgrade function has been simplified to a no-op.
This commit is contained in:
penghanyuan
2026-05-17 22:04:54 +02:00
parent 8df6e42a30
commit 6b1d63524e
2 changed files with 94 additions and 27 deletions

View File

@@ -1,51 +1,66 @@
"""补建缺失列segments.tts_audio_urls, conversation_messages.tts_audio_urls
"""补建所有 ORM 模型中存在但数据库缺失的列
0001 用 create_all 建表,对已有表不会 ALTER 追加列。
本迁移补齐 ORM 模型中存在但生产库缺失的 tts_audio_urls 列。
本迁移自动内省 ORM 元数据与实际数据库 schema 的差异,
为每张已有表补齐缺失列(不含 FK 约束,仅补列定义)。
Revision ID: 0020_add_tts_audio_urls_column
Revision ID: 0020_backfill_all_missing_columns
Revises: 0019_backfill_missing_columns
"""
from __future__ import annotations
from typing import Sequence, Union
import sqlalchemy as sa
from alembic import op
revision: str = "0020_add_tts_audio_urls_column"
revision: str = "0020_backfill_all_missing_columns"
down_revision: Union[str, None] = "0019_backfill_missing_columns"
branch_labels: Union[str, Sequence[str], None] = None
depends_on: Union[str, Sequence[str], None] = None
def _column_names(table_name: str) -> set[str]:
bind = op.get_bind()
inspector = sa.inspect(bind)
return {column["name"] for column in inspector.get_columns(table_name)}
def _import_all_models() -> None:
from app.features.asset import models as _asset # noqa: F401
from app.features.auth import models as _auth # noqa: F401
from app.features.conversation import models as _conv # noqa: F401
from app.features.evaluation import models as _eval # noqa: F401
from app.features.memory import models as _memory # noqa: F401
from app.features.memoir import models as _memoir # noqa: F401
from app.features.payment import models as _payment # noqa: F401
from app.features.story import models as _story # noqa: F401
from app.features.user import models as _user # noqa: F401
def upgrade() -> None:
seg_cols = _column_names("segments")
if "tts_audio_urls" not in seg_cols:
op.add_column(
"segments",
sa.Column("tts_audio_urls", sa.JSON(), nullable=True),
)
from app.core.db import Base
msg_cols = _column_names("conversation_messages")
if "tts_audio_urls" not in msg_cols:
op.add_column(
"conversation_messages",
sa.Column("tts_audio_urls", sa.JSON(), nullable=True),
)
_import_all_models()
bind = op.get_bind()
inspector = sa.inspect(bind)
existing_tables = set(inspector.get_table_names())
for table in Base.metadata.sorted_tables:
if table.name not in existing_tables:
continue
db_cols = {c["name"] for c in inspector.get_columns(table.name)}
for col in table.columns:
if col.name in db_cols:
continue
kwargs: dict = {"nullable": True}
if col.server_default is not None:
kwargs["server_default"] = col.server_default.arg
new_col = sa.Column(col.name, col.type, **kwargs)
op.add_column(table.name, new_col)
def downgrade() -> None:
msg_cols = _column_names("conversation_messages")
if "tts_audio_urls" in msg_cols:
op.drop_column("conversation_messages", "tts_audio_urls")
seg_cols = _column_names("segments")
if "tts_audio_urls" in seg_cols:
op.drop_column("segments", "tts_audio_urls")
pass