Merge remote-tracking branch 'origin/development'
This commit is contained in:
34
api/alembic/script_helpers.py
Normal file
34
api/alembic/script_helpers.py
Normal file
@@ -0,0 +1,34 @@
|
||||
"""Alembic 迁移共享工具(仅用于 versions/ 下的迁移脚本)。"""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import sqlalchemy as sa
|
||||
from alembic import op
|
||||
|
||||
|
||||
def table_exists(table_name: str) -> bool:
|
||||
bind = op.get_bind()
|
||||
return table_name in sa.inspect(bind).get_table_names()
|
||||
|
||||
|
||||
def has_column(table_name: str, column_name: str) -> bool:
|
||||
if not table_exists(table_name):
|
||||
return False
|
||||
bind = op.get_bind()
|
||||
columns = sa.inspect(bind).get_columns(table_name)
|
||||
return any(column["name"] == column_name for column in columns)
|
||||
|
||||
|
||||
def add_column_if_missing(table_name: str, column: sa.Column) -> bool:
|
||||
"""若列不存在则 add_column;返回是否执行了添加。"""
|
||||
if has_column(table_name, column.name):
|
||||
return False
|
||||
op.add_column(table_name, column)
|
||||
return True
|
||||
|
||||
|
||||
def drop_column_if_exists(table_name: str, column_name: str) -> bool:
|
||||
if not has_column(table_name, column_name):
|
||||
return False
|
||||
op.drop_column(table_name, column_name)
|
||||
return True
|
||||
@@ -6,9 +6,10 @@ chapters 含 story 物化字段:markdown_compose_dirty、markdown_composed_at
|
||||
(阅读片段快照,随 ORM 一并 create_all)。
|
||||
|
||||
已并入原 0002(stories-first:无 chapter_sections / memoir_images.section_id)与原 0003(segments.tts_audio_urls)
|
||||
的语义:新库仅由当前 ORM 建表即可,无需后续 ALTER。
|
||||
conversation_messages(会话轮次 durable log)由 app.features.conversation.models.ConversationMessage 一并 create_all。
|
||||
segments.audio_duration_seconds(语音条时长秒数,历史 API / Redis 回填)由 ORM 一并 create_all,无独立迁移。
|
||||
的语义:仅对「全新库」由 create_all 建出;**已有库不会 ALTER**。
|
||||
老库缺列见 0019_align_legacy_schema(segments.audio_duration_seconds、tts_audio_urls,
|
||||
conversations.deleted_at,conversation_messages.tts_audio_urls 等)。
|
||||
conversation_messages 表由 ORM 在 0001 create_all 中创建(新库);老库若缺表须单独处理。
|
||||
story_image_intents 无 source_span(主图回填在正文末尾,意图仅存 caption / prompt_brief 等)。
|
||||
|
||||
新库 / 删库重来:`alembic upgrade head`。
|
||||
|
||||
64
api/alembic/versions/0019_align_legacy_schema.py
Normal file
64
api/alembic/versions/0019_align_legacy_schema.py
Normal file
@@ -0,0 +1,64 @@
|
||||
"""补齐 0001 create_all 未覆盖的老库列(显式清单,禁止内省全库)
|
||||
|
||||
0001 对「已存在的表」不会 ALTER。下列列在 ORM / 0001 注释中视为新库默认字段,
|
||||
但 staging/production 等自 squash 前就存在的库需要本迁移显式 add_column。
|
||||
|
||||
新增 ORM 字段时:禁止改已部署 revision id;在本文件追加列定义,或新建 0020_* 迁移。
|
||||
|
||||
Revision ID: 0019_align_legacy_schema
|
||||
Revises: 0018_users_language_preference
|
||||
"""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import sys
|
||||
from pathlib import Path
|
||||
from typing import Sequence, Union
|
||||
|
||||
import sqlalchemy as sa
|
||||
|
||||
# versions/ -> alembic/
|
||||
_ALEMBIC_DIR = Path(__file__).resolve().parents[1]
|
||||
if str(_ALEMBIC_DIR) not in sys.path:
|
||||
sys.path.insert(0, str(_ALEMBIC_DIR))
|
||||
|
||||
from script_helpers import add_column_if_missing, drop_column_if_exists, table_exists
|
||||
|
||||
revision: str = "0019_align_legacy_schema"
|
||||
down_revision: Union[str, None] = "0018_users_language_preference"
|
||||
branch_labels: Union[str, Sequence[str], None] = None
|
||||
depends_on: Union[str, Sequence[str], None] = None
|
||||
|
||||
# (table, column) — 仅列「0002–0018 未 add_column 且老库常缺」的字段
|
||||
_LEGACY_COLUMNS: tuple[tuple[str, sa.Column], ...] = (
|
||||
(
|
||||
"segments",
|
||||
sa.Column("audio_duration_seconds", sa.Integer(), nullable=True),
|
||||
),
|
||||
(
|
||||
"conversations",
|
||||
sa.Column("deleted_at", sa.DateTime(timezone=True), nullable=True),
|
||||
),
|
||||
(
|
||||
"segments",
|
||||
sa.Column("tts_audio_urls", sa.JSON(), nullable=True),
|
||||
),
|
||||
(
|
||||
"conversation_messages",
|
||||
sa.Column("tts_audio_urls", sa.JSON(), nullable=True),
|
||||
),
|
||||
)
|
||||
|
||||
|
||||
def upgrade() -> None:
|
||||
for table_name, column in _LEGACY_COLUMNS:
|
||||
if not table_exists(table_name):
|
||||
continue
|
||||
add_column_if_missing(table_name, column)
|
||||
|
||||
|
||||
def downgrade() -> None:
|
||||
for table_name, column in reversed(_LEGACY_COLUMNS):
|
||||
if not table_exists(table_name):
|
||||
continue
|
||||
drop_column_if_exists(table_name, column.name)
|
||||
Reference in New Issue
Block a user