From 66996c693d724cdf209520d19b3a9ef7d68dce27 Mon Sep 17 00:00:00 2001 From: yumoqing Date: Thu, 17 Sep 2026 16:02:33 +0800 Subject: [PATCH] =?UTF-8?q?fix(dmig):=20DROP=20INDEX=20=E8=A2=AB=20DROP=20?= =?UTF-8?q?COLUMN=20=E5=AE=88=E5=8D=AB=E8=AF=AF=E8=A7=A3=E6=9E=90=E8=87=B4?= =?UTF-8?q?=E9=9D=99=E9=BB=98=E8=B7=B3=E8=BF=87=20+=20m0038=20=E5=94=AF?= =?UTF-8?q?=E4=B8=80=E7=B4=A2=E5=BC=95=E4=BF=AE=E6=AD=A3?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit dmig.py 两处(2026-09-17 m0038 部署预演时本地测试抓到): ① `ALTER TABLE t DROP INDEX idx` 会命中 DROP COLUMN 守卫的可选 `(COLUMN\s+)?` 分支,group(3) 捕获到字面词 'INDEX' 而非索引名 → column_exists(t,'INDEX') 恒 False → 整步被静默跳过(索引删不掉、无任何报错)。修: DROP INDEX 守卫 (ALTER 形态 + 独立 `DROP INDEX idx ON t` 形态,历史迁移 m0025/m0031 等 13 条 down 段用的就是独立形态、此前同样无守卫)放在 DROP COLUMN 之前, 幂等判据用 index_exists。 ② DROP COLUMN 正则收紧为强制 COLUMN 关键字后列名组号 3→2,调用点同步 (历史 22 条 DROP COLUMN 全部显式带 COLUMN、bare 形态 0 条,无回归)。 本地守卫测试 18/18 全绿(含 m0038 up/down/重复down 完整生命周期)。 m0038: m0037 把 models 声明的 uk_org_user_name(unique) 误写成普通 CREATE INDEX → 实测建出 KEY 而非 UNIQUE KEY,失去并发去重保护(同名可插重复行、 save_secret 幂等判定失效)。本迁移删误建索引并按 models JSON 权威定义重建 UNIQUE。backup_tables 照旧。不改已应用的 m0037(台账完整性)。 --- deploy/dmig.py | 34 ++++++++++++++++--- ...38_pipeline_user_secrets_unique_index.json | 25 ++++++++++++++ 2 files changed, 54 insertions(+), 5 deletions(-) create mode 100644 deploy/migrations/m0038_pipeline_user_secrets_unique_index.json diff --git a/deploy/dmig.py b/deploy/dmig.py index 438854a..2b4cc1d 100755 --- a/deploy/dmig.py +++ b/deploy/dmig.py @@ -168,14 +168,38 @@ def render_step(conf, step, direction): if m: if column_exists(conf, m.group(1), m.group(3)): return [], "列已存在: %s.%s" % (m.group(1), m.group(3)) - # DROP COLUMN 幂等守卫:列已不存在则跳过(生产表由 create_tables.py 按最新 - # models 建出、天然没有旧列,m0009/m0010 类删列迁移直跑会 1091 中止) - m = re.match(r"ALTER\s+TABLE\s+`?(\w+)`?\s+DROP\s+(COLUMN\s+)?`?(\w+)`?", sql, re.I) + # DROP INDEX 幂等守卫(2026-09-17 m0038 部署实抓的解析缺陷): + # 必须放在 DROP COLUMN 守卫**之前**——后者的可选 `(COLUMN\s+)?` 分支对 + # `ALTER TABLE t DROP INDEX idx` 同样匹配,且 group(3) 捕获到的是字面词 + # 'INDEX' 而非索引名 → column_exists(t,'INDEX') 恒 False → 整步被静默跳过 + # (索引永远删不掉、也不报错)。删索引的正确幂等判据是 index_exists。 + m = re.match(r"ALTER\s+TABLE\s+`?(\w+)`?\s+DROP\s+INDEX\s+`?(\w+)`?", sql, re.I) if m: if not table_exists(conf, m.group(1)): return [], "表不存在: %s" % m.group(1) - if not column_exists(conf, m.group(1), m.group(3)): - return [], "列已不存在: %s.%s" % (m.group(1), m.group(3)) + if not index_exists(conf, m.group(1), m.group(2)): + return [], "索引已不存在: %s.%s" % (m.group(1), m.group(2)) + # 独立形态 DROP INDEX idx ON table(MySQL/MariaDB 同样合法,历史迁移 + # m0025/m0031 等 13 条 down 段用的就是它)——同款幂等守卫,否则重复 + # rollback 会 1091 中止。 + m = re.match(r"DROP\s+INDEX\s+`?(\w+)`?\s+ON\s+`?(\w+)`?", sql, re.I) + if m: + if not table_exists(conf, m.group(2)): + return [], "表不存在: %s" % m.group(2) + if not index_exists(conf, m.group(2), m.group(1)): + return [], "索引已不存在: %s.%s" % (m.group(2), m.group(1)) + # DROP COLUMN 幂等守卫:列已不存在则跳过(生产表由 create_tables.py 按最新 + # models 建出、天然没有旧列,m0009/m0010 类删列迁移直跑会 1091 中止)。 + # ⚠️ 正则组数是 2(table, col)——原写法带可选 `(COLUMN\s+)?` 组时列名是 + # group(3),收紧为强制 COLUMN 关键字后列名变成 group(2),索引必须同步改, + # 否则 IndexError(2026-09-17 本地测试抓到)。历史迁移 22 条 DROP COLUMN + # 全部显式带 COLUMN 关键字、bare 形态 0 条,收紧无回归。 + m = re.match(r"ALTER\s+TABLE\s+`?(\w+)`?\s+DROP\s+COLUMN\s+`?(\w+)`?", sql, re.I) + if m: + if not table_exists(conf, m.group(1)): + return [], "表不存在: %s" % m.group(1) + if not column_exists(conf, m.group(1), m.group(2)): + return [], "列已不存在: %s.%s" % (m.group(1), m.group(2)) m = re.match(r"CREATE\s+(UNIQUE\s+)?INDEX\s+`?(\w+)`?\s+ON\s+`?(\w+)`?", sql, re.I) if m: if index_exists(conf, m.group(3), m.group(2)): diff --git a/deploy/migrations/m0038_pipeline_user_secrets_unique_index.json b/deploy/migrations/m0038_pipeline_user_secrets_unique_index.json new file mode 100644 index 0000000..9d6a860 --- /dev/null +++ b/deploy/migrations/m0038_pipeline_user_secrets_unique_index.json @@ -0,0 +1,25 @@ +{ + "id": "m0038", + "title": "修正 pipeline_user_secrets 唯一索引(2026-09-17): m0037 把 models 声明的 uk_org_user_name(unique) 误写成普通 CREATE INDEX → 实测建出 KEY 而非 UNIQUE KEY,失去并发去重保护(同名可插重复行、幂等判定失效)。本迁移删除误建的普通索引并按 models JSON 权威定义重建为 UNIQUE。DROP INDEX 用不带 IF EXISTS 的形态(MariaDB 10.6 对 DROP INDEX IF EXISTS 支持不确定,不赌语法),dmig 的 index_exists 守卫已覆盖幂等", + "backup_tables": ["pipeline_user_secrets"], + "up": [ + { + "op": "sql", + "sql": "ALTER TABLE pipeline_user_secrets DROP INDEX pipeline_user_secrets_idx_org_user_name" + }, + { + "op": "sql", + "sql": "CREATE UNIQUE INDEX uk_org_user_name ON pipeline_user_secrets(org_id, user_id, name)" + } + ], + "down": [ + { + "op": "sql", + "sql": "ALTER TABLE pipeline_user_secrets DROP INDEX uk_org_user_name" + }, + { + "op": "sql", + "sql": "CREATE INDEX pipeline_user_secrets_idx_org_user_name ON pipeline_user_secrets(org_id, user_id, name)" + } + ] +}