#!/usr/bin/env python3 # -*- coding: utf-8 -*- """PBL-RTX-0005 生成器补丁:修 pbl_assert_append_only 过程体 + _with_delimiter 行尾注释加固。 每处替换都断言命中,未命中即 rc=1(禁止静默漏改)。 """ import io import os import re import sys HERE = os.path.dirname(os.path.abspath(__file__)) MODULE_DIR = os.path.dirname(HERE) TARGET = os.path.join(MODULE_DIR, "scripts", "pbl_runtime_event_ddl.py") src = io.open(TARGET, encoding="utf-8").read() orig = src applied = [] def rep(old, new, tag): global src n = src.count(old) if n != 1: print("FAIL 定位不唯一(%d):%s" % (n, tag)) sys.exit(1) src = src.replace(old, new) applied.append(tag) # ---------------------------------------------------------------- 1) 过程体 RETURN; → 正向条件 SIGNAL rep( """CREATE PROCEDURE `pbl_assert_append_only`( IN p_table VARCHAR(64), IN p_priv VARCHAR(16), IN p_detail VARCHAR(255)) READS SQL DATA BEGIN IF p_table <> '%(table)s' THEN RETURN; -- 只守护事件表,其他表不受影响 END IF; IF IFNULL(@pbl_guard_ctx, '') = '%(ctx)s' THEN RETURN; -- break-glass:一次性历史数据回填 END IF; SIGNAL SQLSTATE '45000' SET MESSAGE_TEXT = 'PBL_APPEND_ONLY_DENIED', MYSQL_ERRNO = 45001; END;""", """CREATE PROCEDURE `pbl_assert_append_only`( IN p_table VARCHAR(64), IN p_priv VARCHAR(16), IN p_detail VARCHAR(255)) READS SQL DATA BEGIN -- PBL-RTX-0005:MariaDB 存储过程体禁用 RETURN(RETURN 只在 FUNCTION 中合法), -- 旧写法 `IF ... THEN RETURN; END IF;` 在建过程阶段即 ERROR 1064,导致本过程 -- 连同其后的触发器/维护过程全部未创建。改为等价正向条件单语句 SIGNAL: -- 仅「是本表」且「非 break-glass 上下文」时才拒绝,其余情形不执行任何语句, -- 与旧的两段提前返回语义完全一致。 IF p_table = '%(table)s' AND IFNULL(@pbl_guard_ctx, '') <> '%(ctx)s' THEN SIGNAL SQLSTATE '45000' SET MESSAGE_TEXT = 'PBL_APPEND_ONLY_DENIED', MYSQL_ERRNO = 45001; END IF; END;""", "append_only_sql: RETURN; -> 正向条件 SIGNAL", ) # ---------------------------------------------------------------- 2) _with_delimiter 行尾注释加固 rep( '''_BARE_TOKEN = re.compile(r"^(CREATE|DROP|ALTER|GRANT|REVOKE)\\s*\\$\\$\\s*$", re.I)''', '''_BARE_TOKEN = re.compile(r"^(CREATE|DROP|ALTER|GRANT|REVOKE)\\s*\\$\\$\\s*$", re.I) # 语句末行的「代码 + 行尾注释」形态:`$$` 若与 `-- 注释` 同行,会被行注释吞掉, # 客户端永远等不到语句结束符(PBL-RTX-0005 加固项:注释上移,`$$` 独占行尾)。 _TRAILING_COMMENT = re.compile(r"^(?P\\S.*?\\s)(?P(?:--|#)\\s.*|/\\*.*\\*/)$") # 过程/函数/触发器体内非法的裸 RETURN(MariaDB 仅 FUNCTION 允许 RETURN;PROCEDURE 报 1064) _ILLEGAL_RETURN = re.compile(r"^RETURN\\s*;?\\s*$", re.I)''', "_with_delimiter: 新增 _TRAILING_COMMENT / _ILLEGAL_RETURN 常量", ) rep( """ def flush(): if not buf: if cmts: out.append("\\n".join(cmts)) del cmts[:] return stmt = re.sub(r";\\s*$", "", "\\n".join(buf).strip()) head = ("\\n".join(cmts) + "\\n") if cmts else "" out.append(head + stmt + delim) del buf[:] del cmts[:]""", """ def flush(): if not buf: if cmts: out.append("\\n".join(cmts)) del cmts[:] return lines = list(buf) # 末行行尾注释上移到语句之前:否则产出 `... -- note$$`,`$$` 落在注释内 # → 客户端认为语句未结束,与下一条语句黏连(同类 1064 根因)。 tail_cmts = [] m = _TRAILING_COMMENT.match(lines[-1].rstrip()) if m and not m.group("code").lstrip().startswith(("--", "#")): tail_cmts.append(m.group("cmt")) lines[-1] = m.group("code") stmt = re.sub(r";\\s*$", "", "\\n".join(lines).strip()) head_lines = cmts + tail_cmts head = ("\\n".join(head_lines) + "\\n") if head_lines else "" out.append(head + stmt + delim) del buf[:] del cmts[:]""", "_with_delimiter.flush(): 末行行尾注释上移", ) # ---------------------------------------------------------------- 3) 静态断言:禁止过程体裸 RETURN rep( '''def _delimiter_integrity_errors(text, label=""):''', '''def _procedure_body_return_errors(text, label=""): """PBL-RTX-0005 回归门禁:`CREATE PROCEDURE` 体内不得出现裸 `RETURN`。 MariaDB/MySQL 的 RETURN 语句只允许出现在 FUNCTION(及触发器外的函数语义)中; 存储过程里写 `RETURN;` 会在 CREATE PROCEDURE 阶段直接 ERROR 1064,且因为整段 在 `DELIMITER $$` 块内,失败会连带后续触发器/维护过程一起未创建(现场表现为 `SHOW PROCEDURE STATUS` 0 个 + CALL 报 1305 does not exist)。 返回缺陷描述列表;空列表 = 通过。 """ errs = [] pfx = ("[%s] " % label) if label else "" in_proc = False for i, ln in enumerate(text.split("\\n"), 1): s = ln.strip() if re.match(r"^CREATE\\s+(OR\\s+REPLACE\\s+)?PROCEDURE\\b", s, re.I): in_proc = True continue if in_proc and re.match(r"^CREATE\\s+\\w", s, re.I): in_proc = False if in_proc and _ILLEGAL_RETURN.match(s): errs.append(pfx + "L%d 存储过程体内出现裸 RETURN(MariaDB 过程体非法 → ERROR 1064): %r" % (i, s)) return errs def _delimiter_integrity_errors(text, label=""):''', "新增 _procedure_body_return_errors() 回归门禁函数", ) rep( """ struct_errs = [] for label, text in artifacts: struct_errs.extend(_delimiter_integrity_errors(text, label))""", """ struct_errs = [] for label, text in artifacts: struct_errs.extend(_delimiter_integrity_errors(text, label)) # PBL-RTX-0005:过程体裸 RETURN 静态门禁(实库 1064 的根因形态) struct_errs.extend(_procedure_body_return_errors(text, label))""", "verify(): 接入过程体 RETURN 静态门禁", ) if "PBL_APPEND_ONLY_DENIED" not in src or src.count("RETURN;") > 0: print("FAIL 校验:src 中仍残留 `RETURN;`") sys.exit(1) io.open(TARGET, "w", encoding="utf-8").write(src) print("OK 已应用 %d 处替换:" % len(applied)) for a in applied: print(" -", a) print("bytes %d -> %d" % (len(orig), len(src)))