# -*- coding: utf-8 -*- """ 迁移: pms_runtime_param.param_value 由 VARCHAR(200) 放宽为 TEXT ================================================================ 运行: docker compose run --rm --no-deps pms-web python scripts/migrate_param_value_text.py # 演练, 只打印 docker compose run --rm --no-deps pms-web python scripts/migrate_param_value_text.py --yes # 实际执行 为什么要迁: 这一列存的不全是短标量 —— 清场名单 (PMS_EXIT_CLEANUP_DONE, 闭仓票代码的 JSON 数组) 与 回放游标 (PMS_REPLAY_CURSOR, v2 起带 seen 去重字典) 都随运行越长越大。真机 2026-08-31 实测: 名单 30 只票就超 200 字符, 两处写入每分钟各报一次 1406 截断错, 游标推不动、 清场防呆标记存不上 (后者失守时, 闭仓后新下的建仓单会被清场误撤)。 为什么单独一个脚本, 不放进 init_db: init_db.py 只认 `CREATE TABLE IF NOT EXISTS` 与幂等 INSERT, ALTER 语句会被它判成残句 整批中止; 而 CREATE IF NOT EXISTS 对已存在的表一个字都不改。建表语句 (ddl_pms_v1.sql) 已同步改成 TEXT, **新库直接就有**, 不需要跑本脚本。 幂等: 列已是 TEXT 时自动跳过, 重复跑安全。绕过 db.session 的单表守卫 (直接用引擎), 与 migrate_archived_at 同一套做法。若 ALTER 被 ShardingSphere-Proxy 拒绝, 脚本会把完整 语句打出来, 直接拿去物理库 (my_quant_db) 执行即可。 """ import argparse import os import sys sys.path.insert(0, os.path.dirname(os.path.dirname(os.path.abspath(__file__)))) TABLE = "pms_runtime_param" COL = "param_value" TARGET_TYPE = "text" def _col_type(conn, table: str, col: str): from sqlalchemy import text rows = conn.execute(text(f"SHOW COLUMNS FROM {table}")).fetchall() for r in rows: if r[0] == col: return str(r[1] or "").lower() return None def main(): ap = argparse.ArgumentParser() ap.add_argument("--yes", action="store_true", help="确认执行 (缺省只演练)") args = ap.parse_args() from sqlalchemy import text from app.db.session import TABLE_PREFIX, get_engine, map_tables eng = get_engine("proxy") if TABLE_PREFIX: print(f"[双实例] 表名前缀 {TABLE_PREFIX!r} 生效: 实际迁移的是 {TABLE_PREFIX}{TABLE}") real_table = map_tables(TABLE) try: with eng.connect() as c: cur = _col_type(c, real_table, COL) except Exception as e: print(f"FAIL: 查列失败 {type(e).__name__}: {e} (表不存在就先跑 init_db)") sys.exit(1) if cur is None: print(f"FAIL: {real_table} 没有 {COL} 列 —— 表结构与预期不符, 先人工核对") sys.exit(1) print(f"当前类型: {real_table}.{COL} = {cur}") if cur.startswith(TARGET_TYPE): print("无需迁移: 已是 TEXT。") return stmt = map_tables( f"ALTER TABLE {TABLE} MODIFY COLUMN {COL} TEXT NOT NULL " f"COMMENT '参数值: 标量或 JSON; 2026-08-31 由 VARCHAR(200) 放宽 (清场名单/回放游标会长大)'") if not args.yes: print("\n[演练模式] 未执行任何语句。确认无误后加 --yes 重跑。将执行:") print(" " + stmt) return try: with eng.begin() as c: c.execute(text(stmt)) print("OK ALTER 执行成功") except Exception as e: print(f"FAIL {type(e).__name__}: {e}") print(f"\n完整语句如下 —— 可直接拿到物理库 (my_quant_db) 执行:\n{stmt};") sys.exit(1) with eng.connect() as c: now = _col_type(c, real_table, COL) if now and now.startswith(TARGET_TYPE): print(f"ALL OK: {real_table}.{COL} 已是 {now}。清场名单与回放游标的写入即刻恢复, 无需重启。") else: print(f"FAIL: 迁移后类型仍为 {now}") sys.exit(1) if __name__ == "__main__": main()