"""中期记忆有效版本唯一性:memory_unit 增加生成列 `active_memory_key` 与唯一键。 依据 `docs/00-新数据库基线设计.md` 第 696 行:同一 `customer_id + memory_key` 可以 存在历史版本和待验证候选,但只能有一条当前"有效"记录;实现时增加生成列 `active_memory_key`(仅当状态为有效时返回 `memory_key`,否则返回 `NULL`),并建立 唯一键 `(customer_id, active_memory_key)`。 本迁移只新增列与唯一键,未重命名、删除或修改任何已有字段。 MySQL 语法说明:VIRTUAL 生成列允许索引,但不允许显式 DEFAULT 值;NULL 语义由 CASE 表达式给出(非 active 状态返回 NULL),因此唯一键不会约束历史版本行。 """ from alembic import op revision = "20260909_memory_active_key" down_revision = "20260909_constraint_fix" branch_labels = None depends_on = None TABLE = "memory_unit" GENERATED_COLUMN = "active_memory_key" UNIQUE_KEY = "uk_memory_unit_customer_active_key" # 生成列表达式中的状态值为 docs/00 规定的"有效"状态。 ACTIVE_STATUS = "active" EXPRESSION = f"(CASE WHEN `status` = '{ACTIVE_STATUS}' THEN `memory_key` ELSE NULL END)" def upgrade() -> None: op.execute( f"ALTER TABLE `{TABLE}` " f"ADD COLUMN `{GENERATED_COLUMN}` VARCHAR(128) " f"GENERATED ALWAYS AS {EXPRESSION} VIRTUAL, " f"ADD UNIQUE KEY `{UNIQUE_KEY}` (`customer_id`, `{GENERATED_COLUMN}`)" ) def downgrade() -> None: # 生成列为本次新增,回退时删除;不触碰任何基线已有字段。 op.execute( f"ALTER TABLE `{TABLE}` " f"DROP INDEX `{UNIQUE_KEY}`, " f"DROP COLUMN `{GENERATED_COLUMN}`" )