From b3c4898c5d3ecfee2de0e1329418b7567d8d6531 Mon Sep 17 00:00:00 2001 From: guke Date: Thu, 25 Jun 2026 16:21:50 +0800 Subject: [PATCH] =?UTF-8?q?fix(alembic):=20cps=5Fv2=5Fplatforms=20?= =?UTF-8?q?=E5=85=BC=E5=AE=B9=20SQLite=20=E4=B8=94=E5=8F=AF=E9=87=8D?= =?UTF-8?q?=E8=B7=91?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit batch_alter_table 包 ALTER(SQLite 无原生 ALTER COLUMN),server_default 按方言分叉(PG ::jsonb / SQLite 纯文本),add_column 加 _has_column 存在性守卫,让历史半应用的 SQLite 库自愈重跑。 Co-Authored-By: Claude Opus 4.8 (1M context) --- alembic/versions/cps_v2_platforms.py | 79 ++++++++++++++++++---------- 1 file changed, 52 insertions(+), 27 deletions(-) diff --git a/alembic/versions/cps_v2_platforms.py b/alembic/versions/cps_v2_platforms.py index e80c1a6..531c097 100644 --- a/alembic/versions/cps_v2_platforms.py +++ b/alembic/versions/cps_v2_platforms.py @@ -23,39 +23,64 @@ branch_labels: Union[str, Sequence[str], None] = None depends_on: Union[str, Sequence[str], None] = None -def upgrade() -> None: - # 活动:淘宝淘口令 / 京东链接 - op.add_column("cps_activity", sa.Column("payload", sa.Text(), nullable=True)) +def _has_column(insp: sa.Inspector, table: str, column: str) -> bool: + return any(c["name"] == column for c in insp.get_columns(table)) - # 群:多平台(现有群都是美团,server_default 回填)+ sid 可空 - op.add_column( - "cps_group", - sa.Column( - "platforms", - sa.JSON().with_variant(postgresql.JSONB(), "postgresql"), - nullable=False, - server_default=sa.text("'[\"meituan\"]'::jsonb"), - ), + +def upgrade() -> None: + bind = op.get_bind() + insp = sa.inspect(bind) + # SQLite 不认 ::jsonb cast(会报 "unrecognized token: :"),只有 PG 用显式 cast。 + # 两边列类型都靠上面的 with_variant 区分,这里只管 server_default 文本。 + platforms_default = ( + sa.text("'[\"meituan\"]'::jsonb") + if bind.dialect.name == "postgresql" + else sa.text("'[\"meituan\"]'") ) - op.alter_column("cps_group", "sid", existing_type=sa.String(64), nullable=True) + + # 活动:淘宝淘口令 / 京东链接 + # 加 add_column 存在性守卫:本迁移历史上在 SQLite 第一次跑时加完 payload 即在 + # platforms 那步炸掉(::jsonb),DDL 非事务不回滚 → 半应用状态。守卫让其可自愈重跑。 + if not _has_column(insp, "cps_activity", "payload"): + op.add_column("cps_activity", sa.Column("payload", sa.Text(), nullable=True)) + + # 群:多平台(现有群都是美团,server_default 回填)+ sid 可空。 + # SQLite 不支持 ALTER COLUMN,必须用 batch(重建表)才能改 nullable;PG 走原生 ALTER。 + with op.batch_alter_table("cps_group") as batch_op: + if not _has_column(insp, "cps_group", "platforms"): + batch_op.add_column( + sa.Column( + "platforms", + sa.JSON().with_variant(postgresql.JSONB(), "postgresql"), + nullable=False, + server_default=platforms_default, + ) + ) + batch_op.alter_column("sid", existing_type=sa.String(64), nullable=True) # link:sid 可空 + target 加长 - op.alter_column("cps_link", "sid", existing_type=sa.String(64), nullable=True) - op.alter_column("cps_link", "target_url", existing_type=sa.String(1024), type_=sa.String(2048)) + with op.batch_alter_table("cps_link") as batch_op: + batch_op.alter_column("sid", existing_type=sa.String(64), nullable=True) + batch_op.alter_column("target_url", existing_type=sa.String(1024), type_=sa.String(2048)) # click:sid 可空 + 事件类型 - op.alter_column("cps_click", "sid", existing_type=sa.String(64), nullable=True) - op.add_column( - "cps_click", - sa.Column("event_type", sa.String(16), nullable=False, server_default="visit"), - ) + with op.batch_alter_table("cps_click") as batch_op: + batch_op.alter_column("sid", existing_type=sa.String(64), nullable=True) + if not _has_column(insp, "cps_click", "event_type"): + batch_op.add_column( + sa.Column("event_type", sa.String(16), nullable=False, server_default="visit") + ) def downgrade() -> None: - op.drop_column("cps_click", "event_type") - op.alter_column("cps_click", "sid", existing_type=sa.String(64), nullable=False) - op.alter_column("cps_link", "target_url", existing_type=sa.String(2048), type_=sa.String(1024)) - op.alter_column("cps_link", "sid", existing_type=sa.String(64), nullable=False) - op.drop_column("cps_group", "platforms") - op.alter_column("cps_group", "sid", existing_type=sa.String(64), nullable=False) - op.drop_column("cps_activity", "payload") + with op.batch_alter_table("cps_click") as batch_op: + batch_op.drop_column("event_type") + batch_op.alter_column("sid", existing_type=sa.String(64), nullable=False) + with op.batch_alter_table("cps_link") as batch_op: + batch_op.alter_column("target_url", existing_type=sa.String(2048), type_=sa.String(1024)) + batch_op.alter_column("sid", existing_type=sa.String(64), nullable=False) + with op.batch_alter_table("cps_group") as batch_op: + batch_op.drop_column("platforms") + batch_op.alter_column("sid", existing_type=sa.String(64), nullable=False) + with op.batch_alter_table("cps_activity") as batch_op: + batch_op.drop_column("payload")