166 lines
6.6 KiB
Python
166 lines
6.6 KiB
Python
#!/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<code>\\S.*?\\s)(?P<cmt>(?:--|#)\\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)))
|