deliver: 交付收口(引擎代为提交)
This commit is contained in:
parent
7f0f5609d3
commit
ad744aaf06
@ -5,9 +5,7 @@
|
|||||||
DELIMITER $$
|
DELIMITER $$
|
||||||
|
|
||||||
-- append-only 断言过程:非白名单操作一律 SIGNAL 拒绝
|
-- append-only 断言过程:非白名单操作一律 SIGNAL 拒绝
|
||||||
DROP PROCEDURE IF EXISTS `pbl_assert_append_only`;
|
DROP PROCEDURE IF EXISTS `pbl_assert_append_only`$$
|
||||||
|
|
||||||
CREATE$$
|
|
||||||
|
|
||||||
CREATE PROCEDURE `pbl_assert_append_only`(
|
CREATE PROCEDURE `pbl_assert_append_only`(
|
||||||
IN p_table VARCHAR(64), IN p_priv VARCHAR(16), IN p_detail VARCHAR(255))
|
IN p_table VARCHAR(64), IN p_priv VARCHAR(16), IN p_detail VARCHAR(255))
|
||||||
@ -22,13 +20,11 @@ BEGIN
|
|||||||
SIGNAL SQLSTATE '45000'
|
SIGNAL SQLSTATE '45000'
|
||||||
SET MESSAGE_TEXT = 'PBL_APPEND_ONLY_DENIED',
|
SET MESSAGE_TEXT = 'PBL_APPEND_ONLY_DENIED',
|
||||||
MYSQL_ERRNO = 45001;
|
MYSQL_ERRNO = 45001;
|
||||||
END;
|
END$$
|
||||||
|
|
||||||
-- BEFORE UPDATE 护栏:把违规语句原文带进错误消息,便于定位调用方
|
-- BEFORE UPDATE 护栏:把违规语句原文带进错误消息,便于定位调用方
|
||||||
DROP TRIGGER IF EXISTS `trg_pbl_runtime_event_append_only_upd`$$
|
DROP TRIGGER IF EXISTS `trg_pbl_runtime_event_append_only_upd`$$
|
||||||
|
|
||||||
CREATE$$
|
|
||||||
|
|
||||||
CREATE TRIGGER `trg_pbl_runtime_event_append_only_upd`
|
CREATE TRIGGER `trg_pbl_runtime_event_append_only_upd`
|
||||||
BEFORE UPDATE ON `pbl_runtime_event` FOR EACH ROW
|
BEFORE UPDATE ON `pbl_runtime_event` FOR EACH ROW
|
||||||
BEGIN
|
BEGIN
|
||||||
@ -50,13 +46,11 @@ BEGIN
|
|||||||
SIGNAL SQLSTATE '45000' SET MESSAGE_TEXT = 'PBL_APPEND_ONLY_DENIED_UPDATE',
|
SIGNAL SQLSTATE '45000' SET MESSAGE_TEXT = 'PBL_APPEND_ONLY_DENIED_UPDATE',
|
||||||
MYSQL_ERRNO = 45002;
|
MYSQL_ERRNO = 45002;
|
||||||
END IF;
|
END IF;
|
||||||
END;
|
END$$
|
||||||
|
|
||||||
-- BEFORE DELETE 护栏
|
-- BEFORE DELETE 护栏
|
||||||
DROP TRIGGER IF EXISTS `trg_pbl_runtime_event_append_only_del`$$
|
DROP TRIGGER IF EXISTS `trg_pbl_runtime_event_append_only_del`$$
|
||||||
|
|
||||||
CREATE$$
|
|
||||||
|
|
||||||
CREATE TRIGGER `trg_pbl_runtime_event_append_only_del`
|
CREATE TRIGGER `trg_pbl_runtime_event_append_only_del`
|
||||||
BEFORE DELETE ON `pbl_runtime_event` FOR EACH ROW
|
BEFORE DELETE ON `pbl_runtime_event` FOR EACH ROW
|
||||||
BEGIN
|
BEGIN
|
||||||
@ -78,10 +72,10 @@ BEGIN
|
|||||||
SIGNAL SQLSTATE '45000' SET MESSAGE_TEXT = 'PBL_APPEND_ONLY_DENIED_DELETE',
|
SIGNAL SQLSTATE '45000' SET MESSAGE_TEXT = 'PBL_APPEND_ONLY_DENIED_DELETE',
|
||||||
MYSQL_ERRNO = 45003;
|
MYSQL_ERRNO = 45003;
|
||||||
END IF;
|
END IF;
|
||||||
END;
|
END$$
|
||||||
|
|
||||||
-- 权限层加固(可选,需 DBA 账号执行;app 账号只保留增/查)
|
-- 权限层加固(可选,需 DBA 账号执行;app 账号只保留增/查)
|
||||||
-- GRANT INSERT, SELECT, UPDATE, DELETE ON `pbls`.`pbl_runtime_event` TO NULL; -- 占位,勿执行
|
-- GRANT INSERT, SELECT, UPDATE, DELETE ON `pbls`.`pbl_runtime_event` TO NULL; -- 占位,勿执行
|
||||||
-- REVOKE UPDATE, DELETE ON `pbls`.`pbl_runtime_event` FROM 'pbls'@'%'$$
|
-- REVOKE UPDATE, DELETE ON `pbls`.`pbl_runtime_event` FROM 'pbls'@'%';
|
||||||
|
|
||||||
DELIMITER ;
|
DELIMITER ;
|
||||||
|
|||||||
@ -5,9 +5,7 @@
|
|||||||
DELIMITER $$
|
DELIMITER $$
|
||||||
|
|
||||||
-- 1) 幂等补建未来月分区:pmax 存在时用 REORGANIZE PARTITION 拆出新月份(不丢数据)
|
-- 1) 幂等补建未来月分区:pmax 存在时用 REORGANIZE PARTITION 拆出新月份(不丢数据)
|
||||||
DROP PROCEDURE IF EXISTS `pbl_runtime_event_ensure_partitions`;
|
DROP PROCEDURE IF EXISTS `pbl_runtime_event_ensure_partitions`$$
|
||||||
|
|
||||||
CREATE$$
|
|
||||||
|
|
||||||
CREATE PROCEDURE `pbl_runtime_event_ensure_partitions`(IN p_months_ahead INT)
|
CREATE PROCEDURE `pbl_runtime_event_ensure_partitions`(IN p_months_ahead INT)
|
||||||
MODIFIES SQL DATA
|
MODIFIES SQL DATA
|
||||||
@ -49,13 +47,11 @@ BEGIN
|
|||||||
END IF;
|
END IF;
|
||||||
END LOOP;
|
END LOOP;
|
||||||
CLOSE cur;
|
CLOSE cur;
|
||||||
END;
|
END$$
|
||||||
|
|
||||||
-- 2) 到期归档:整月 DROP PARTITION(append-only 表唯一合规的清理手段)
|
-- 2) 到期归档:整月 DROP PARTITION(append-only 表唯一合规的清理手段)
|
||||||
DROP PROCEDURE IF EXISTS `pbl_runtime_event_prune_partitions`$$
|
DROP PROCEDURE IF EXISTS `pbl_runtime_event_prune_partitions`$$
|
||||||
|
|
||||||
CREATE$$
|
|
||||||
|
|
||||||
CREATE PROCEDURE `pbl_runtime_event_prune_partitions`(IN p_keep_months INT)
|
CREATE PROCEDURE `pbl_runtime_event_prune_partitions`(IN p_keep_months INT)
|
||||||
MODIFIES SQL DATA
|
MODIFIES SQL DATA
|
||||||
BEGIN
|
BEGIN
|
||||||
@ -82,16 +78,11 @@ BEGIN
|
|||||||
DEALLOCATE PREPARE pbl_drop_st;
|
DEALLOCATE PREPARE pbl_drop_st;
|
||||||
END LOOP;
|
END LOOP;
|
||||||
CLOSE cur;
|
CLOSE cur;
|
||||||
END;
|
END$$
|
||||||
|
|
||||||
-- 3) 每月自动补分区(需 event_scheduler=ON;否则用 cron,见开发说明)
|
-- 3) 每月自动补分区(需 event_scheduler=ON;否则用 cron,见开发说明)
|
||||||
-- SET GLOBAL event_scheduler = ON$$
|
-- SET GLOBAL event_scheduler = ON;
|
||||||
|
DROP EVENT IF EXISTS `ev_pbl_runtime_event_partition_maintain`$$
|
||||||
DROP$$
|
|
||||||
|
|
||||||
DROP EVENT IF EXISTS `ev_pbl_runtime_event_partition_maintain`;
|
|
||||||
|
|
||||||
CREATE$$
|
|
||||||
|
|
||||||
CREATE EVENT IF NOT EXISTS `ev_pbl_runtime_event_partition_maintain`
|
CREATE EVENT IF NOT EXISTS `ev_pbl_runtime_event_partition_maintain`
|
||||||
ON SCHEDULE EVERY 1 MONTH
|
ON SCHEDULE EVERY 1 MONTH
|
||||||
|
|||||||
@ -384,23 +384,140 @@ def full_script(dbname="pbls", app_user="pbls", today=None, months=None):
|
|||||||
+ migrate_from_legacy_sql())
|
+ migrate_from_legacy_sql())
|
||||||
|
|
||||||
|
|
||||||
def _with_delimiter(body):
|
_BLOCK_OPEN = re.compile(r"^BEGIN\b", re.I)
|
||||||
"""把过程/触发器体内语句交给 mysql CLI:整段以 $$ 结束。"""
|
_BLOCK_CLOSE = re.compile(r"^END\s*;?\s*$", re.I)
|
||||||
|
_SUB_CLOSE = re.compile(r"^END\s+(IF|LOOP|CASE)\b", re.I)
|
||||||
|
_STMT_HEAD = re.compile(
|
||||||
|
r"^(DROP|CREATE|GRANT|REVOKE|SET|SELECT|CALL|ALTER|WITH|INSERT|UPDATE|DELETE|START)\b", re.I)
|
||||||
|
# 一个 $$ 段内不应再出现的「另一条 DDL 语句头」——出现即说明两条语句被黏在了一起
|
||||||
|
_INLINE_DDL_HEAD = re.compile(
|
||||||
|
r"^\s*(DROP|CREATE)\s+(PROCEDURE|TRIGGER|EVENT|TABLE|FUNCTION)\b", re.I)
|
||||||
|
# 裸关键字残缺行:`CREATE$$` / `DROP$$`(QC #12 的致命产物形态)
|
||||||
|
_BARE_TOKEN = re.compile(r"^(CREATE|DROP|ALTER|GRANT|REVOKE)\s*\$\$\s*$", re.I)
|
||||||
|
|
||||||
|
|
||||||
|
def _with_delimiter(body, delim="$$"):
|
||||||
|
"""把过程/触发器/EVENT 正文按语句切分,逐条以 $$ 收尾,交给 mysql CLI(DELIMITER)执行。
|
||||||
|
|
||||||
|
QC #12 修复(致命缺陷:迁移资产整体不可执行):
|
||||||
|
旧实现 `re.split(r"(?<=;)\n(?=(DROP|CREATE|...))", body)` 的前瞻里带**捕获组**,
|
||||||
|
re.split 会把捕获到的文本('CREATE' / 'DROP')作为独立元素插进结果列表;这些孤立 token
|
||||||
|
随后被当成一条完整语句,拼成裸 `CREATE$$` / `DROP$$` 行写进 .sql。在 `DELIMITER $$` 下
|
||||||
|
服务端把 `CREATE` 当独立语句解析 → ERROR 1064,02/03/canonical 三个文件全部无法执行。
|
||||||
|
另外旧实现把 `DROP PROCEDURE/TRIGGER/EVENT` 留在 `;` 结尾,而 DELIMITER 已改成 `$$`,
|
||||||
|
客户端只认 `$$` 为语句结束符,`;` 结尾的 DROP 会与下一条 CREATE 黏成一条语句。
|
||||||
|
现改为按行扫描 + BEGIN/END 嵌套深度判定语句边界(不再使用带捕获组的 re.split):
|
||||||
|
· 每条语句去掉结尾 `;` 后以 `$$` 结束(含 DROP ... IF EXISTS);
|
||||||
|
· `--` 注释行原样保留、不加 `$$`(否则 `$$` 被行注释吞掉,语句永不结束);
|
||||||
|
· 结构上只可能产出以完整关键字开头的语句段,不可能产出裸 `CREATE`/`DROP`。
|
||||||
|
"""
|
||||||
out = []
|
out = []
|
||||||
for chunk in re.split(r"(?<=;)\n(?=(DROP|CREATE|GRANT|REVOKE|SELECT|--|SET))", body):
|
buf = []
|
||||||
c = chunk.strip()
|
cmts = []
|
||||||
if not c:
|
depth = [0]
|
||||||
|
|
||||||
|
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[:]
|
||||||
|
|
||||||
|
for raw in body.replace("\r\n", "\n").split("\n"):
|
||||||
|
line = raw.rstrip()
|
||||||
|
s = line.strip()
|
||||||
|
if not s:
|
||||||
continue
|
continue
|
||||||
if c.startswith("--") or c.startswith("SET ") or c.startswith("GRANT") or c.startswith("REVOKE"):
|
if not buf:
|
||||||
out.append(c if c.endswith(";") else c + ";")
|
if s.startswith("--") or s.startswith("#") or not _STMT_HEAD.match(s):
|
||||||
elif c.startswith("DROP PROCEDURE") or c.startswith("DROP TRIGGER") or c.startswith("DROP EVENT"):
|
cmts.append(line)
|
||||||
out.append(c if c.endswith(";") else c + ";")
|
continue
|
||||||
else:
|
if _BLOCK_OPEN.match(s):
|
||||||
stmt = c.rstrip(";")
|
depth[0] += 1
|
||||||
out.append(stmt + "$$")
|
elif _BLOCK_CLOSE.match(s) and depth[0] > 0:
|
||||||
|
depth[0] -= 1
|
||||||
|
buf.append(line)
|
||||||
|
if depth[0] == 0 and s.endswith(";") and not _SUB_CLOSE.match(s):
|
||||||
|
flush()
|
||||||
|
flush()
|
||||||
return "\n\n".join(out) + "\n"
|
return "\n\n".join(out) + "\n"
|
||||||
|
|
||||||
|
|
||||||
|
def _delimiter_integrity_errors(text, label=""):
|
||||||
|
"""DELIMITER 语句完整性静态断言(QC #16:PASS 要能证明「可执行」,而非只查关键字存在)。
|
||||||
|
|
||||||
|
断言项:
|
||||||
|
1) `DELIMITER x` 与 `DELIMITER ;` 严格配对,文件结束时不残留未闭合块;
|
||||||
|
2) 每个 `$$` 段首个代码行必须是**完整**语句头(关键字 + 对象类型),
|
||||||
|
禁止 `CREATE$$` / `DROP$$` 这类孤立残缺 token(服务端 ERROR 1064);
|
||||||
|
3) 一个 `$$` 段内不得再出现第二条 DDL 语句头(防止 `;` 结尾语句与下一条黏连);
|
||||||
|
4) 未以 `$$` 收尾的残段里若含可执行语句(纯注释尾巴放行)即判失败。
|
||||||
|
返回缺陷描述列表;空列表 = 结构通过。
|
||||||
|
"""
|
||||||
|
errs = []
|
||||||
|
pfx = ("[%s] " % label) if label else ""
|
||||||
|
lines = text.split("\n")
|
||||||
|
delim = [None]
|
||||||
|
seg = [[]]
|
||||||
|
|
||||||
|
def code_of(ls):
|
||||||
|
return [x.strip() for x in ls if x.strip() and not x.strip().startswith("--")
|
||||||
|
and not x.strip().startswith("#")]
|
||||||
|
|
||||||
|
def close_segment():
|
||||||
|
code = code_of(seg[0])
|
||||||
|
if not code:
|
||||||
|
seg[0] = []
|
||||||
|
return
|
||||||
|
d = delim[0] or "$$"
|
||||||
|
first_words = code[0].replace(d, "").split()
|
||||||
|
if len(first_words) < 2:
|
||||||
|
errs.append(pfx + "残缺语句 token(服务端按独立语句解析 → ERROR 1064): %r" % code[0])
|
||||||
|
for x in code[1:]:
|
||||||
|
if _INLINE_DDL_HEAD.match(x):
|
||||||
|
errs.append(pfx + "一个 %s 段内黏连了多条 DDL 语句: %r" % (d, x.strip()))
|
||||||
|
seg[0] = []
|
||||||
|
|
||||||
|
for i, ln in enumerate(lines, 1):
|
||||||
|
s = ln.strip()
|
||||||
|
if _BARE_TOKEN.match(s):
|
||||||
|
errs.append(pfx + "L%d 裸关键字$$ 残缺行: %r" % (i, s))
|
||||||
|
if s.upper().startswith("DELIMITER"):
|
||||||
|
parts = s.split(None, 1)
|
||||||
|
new = parts[1].strip() if len(parts) > 1 else ";"
|
||||||
|
if seg[0]:
|
||||||
|
close_segment()
|
||||||
|
if delim[0] is None:
|
||||||
|
if new == ";":
|
||||||
|
errs.append(pfx + "L%d 出现没有配对的 `DELIMITER ;`" % i)
|
||||||
|
else:
|
||||||
|
delim[0] = new
|
||||||
|
else:
|
||||||
|
if new != ";":
|
||||||
|
errs.append(pfx + "L%d DELIMITER %s 未闭合就切换到 %s"
|
||||||
|
% (i, delim[0], new))
|
||||||
|
delim[0] = new
|
||||||
|
else:
|
||||||
|
delim[0] = None
|
||||||
|
continue
|
||||||
|
if delim[0]:
|
||||||
|
seg[0].append(ln)
|
||||||
|
if s.endswith(delim[0]):
|
||||||
|
close_segment()
|
||||||
|
if seg[0]:
|
||||||
|
errs.append(pfx + "DELIMITER 块末尾存在未以 %s 收尾的可执行残段: %r"
|
||||||
|
% (delim[0] or "$$", code_of(seg[0])[0][:60]))
|
||||||
|
seg[0] = []
|
||||||
|
if delim[0] is not None:
|
||||||
|
errs.append(pfx + "文件结束时 DELIMITER %s 仍未闭合" % delim[0])
|
||||||
|
return errs
|
||||||
|
|
||||||
|
|
||||||
# ---------------------------------------------------------------- 迁移文件产出
|
# ---------------------------------------------------------------- 迁移文件产出
|
||||||
MIGRATION_FILES = [
|
MIGRATION_FILES = [
|
||||||
("20260919_m11b_01_create_pbl_runtime_event.sql",
|
("20260919_m11b_01_create_pbl_runtime_event.sql",
|
||||||
@ -526,6 +643,43 @@ def verify():
|
|||||||
msgs.append("FAIL DDL 非幂等(缺 IF EXISTS / DROP IF EXISTS)")
|
msgs.append("FAIL DDL 非幂等(缺 IF EXISTS / DROP IF EXISTS)")
|
||||||
else:
|
else:
|
||||||
msgs.append("PASS 幂等:IF NOT EXISTS + DROP ... IF EXISTS + 分区存在性判断")
|
msgs.append("PASS 幂等:IF NOT EXISTS + DROP ... IF EXISTS + 分区存在性判断")
|
||||||
|
|
||||||
|
# 8. DELIMITER 语句完整性(QC #16:PASS 必须能证明「可执行」,不能只查关键字存在性)
|
||||||
|
artifacts = [
|
||||||
|
("01", create_table_sql()),
|
||||||
|
("02", MIGRATION_FILES[1][1]("pbls")),
|
||||||
|
("03", MIGRATION_FILES[2][1]("pbls")),
|
||||||
|
("canonical", full_script()),
|
||||||
|
]
|
||||||
|
struct_errs = []
|
||||||
|
for label, text in artifacts:
|
||||||
|
struct_errs.extend(_delimiter_integrity_errors(text, label))
|
||||||
|
if struct_errs:
|
||||||
|
ok = False
|
||||||
|
for e in struct_errs:
|
||||||
|
msgs.append("FAIL DELIMITER 语句完整性: " + e)
|
||||||
|
else:
|
||||||
|
msgs.append("PASS DELIMITER 语句完整性: 4 个产物 DELIMITER 配对正确、%d 个 $$ 段首关键字完整、"
|
||||||
|
"无裸 CREATE$$/DROP$$ 残缺行"
|
||||||
|
% sum(len(re.findall(r"\$\$\s*$", t, re.M)) for _, t in artifacts))
|
||||||
|
|
||||||
|
# 9. 幂等/结构关键字逐产物覆盖(QC #17:完整陈述,不再跳号回避缺陷)
|
||||||
|
need_map = [
|
||||||
|
("01", ("CREATE TABLE IF NOT EXISTS", "PARTITION BY RANGE COLUMNS",
|
||||||
|
"VALUES LESS THAN (MAXVALUE)")),
|
||||||
|
("02", ("DROP PROCEDURE IF EXISTS", "DROP TRIGGER IF EXISTS",
|
||||||
|
"BEFORE UPDATE", "BEFORE DELETE")),
|
||||||
|
("03", ("DROP PROCEDURE IF EXISTS", "DROP EVENT IF EXISTS", "CREATE EVENT")),
|
||||||
|
]
|
||||||
|
by_label = dict(artifacts)
|
||||||
|
miss_all = []
|
||||||
|
for label, needs in need_map:
|
||||||
|
miss_all.extend("%s:%s" % (label, n) for n in needs if n not in by_label[label])
|
||||||
|
if miss_all:
|
||||||
|
ok = False
|
||||||
|
msgs.append("FAIL 幂等/结构关键字缺失: " + ", ".join(miss_all))
|
||||||
|
else:
|
||||||
|
msgs.append("PASS 幂等关键字逐文件覆盖: 01 建表分区 / 02 过程+双触发器 / 03 双过程+EVENT")
|
||||||
return ok, msgs
|
return ok, msgs
|
||||||
|
|
||||||
|
|
||||||
@ -632,16 +786,21 @@ def _norm_part(name):
|
|||||||
return text.strip().strip("`").strip('"').strip("'").lower()
|
return text.strip().strip("`").strip('"').strip("'").lower()
|
||||||
|
|
||||||
|
|
||||||
def _render(sql_tpl, args=(), placeholders=None):
|
def _render(sql, args=None, placeholders=None):
|
||||||
"""渲染 SQL 模板里的**标识符**占位符(表名/分区名),并对 % 转义做 fail-fast 自检。
|
"""SQL 的 % 转义统一出口:可选渲染标识符 + fail-fast 自检(QC #1 的根因处置)。
|
||||||
|
|
||||||
约定(QC #1 的根因处置):模板中由 Python 填入的标识符写 %s;要交给 DBAPI 绑参的
|
约定:模板中由 Python 侧填入的标识符(表名/分区名)写 %s;要留给 DBAPI 绑参的
|
||||||
百分号占位符一律写 %%s,经本函数渲染后变回 %s。渲染后:
|
百分号占位符一律写 %%s,标识符渲染后即变回 %s。
|
||||||
· 仍残留 '%%' → 转义写错,抛错;
|
· args 不为 None:先执行 sql % args 完成标识符渲染;
|
||||||
· '%s' 个数与预期绑参个数不符 → 抛错。
|
· args 为 None:视为调用方已完成渲染(模板就地 % 过),本函数只做转义自检。
|
||||||
|
自检两条,任一不满足立即抛 RuntimeError(而不是丢给驱动报
|
||||||
|
"unsupported format character",那会在 verify_db 里被 except 吞成一条模糊失败):
|
||||||
|
· 结果仍残留 '%%' → 转义写错;
|
||||||
|
· '%s' 个数与 placeholders 不符 → 漏写/多写绑参占位符。
|
||||||
渲染单独成行、括号显式定界,不再依赖「相邻字面量先拼接、再整体 %」的隐式优先级。
|
渲染单独成行、括号显式定界,不再依赖「相邻字面量先拼接、再整体 %」的隐式优先级。
|
||||||
"""
|
"""
|
||||||
sql = sql_tpl % tuple(args)
|
if args is not None:
|
||||||
|
sql = sql % tuple(args)
|
||||||
if "%%" in sql:
|
if "%%" in sql:
|
||||||
raise RuntimeError("SQL 渲染后仍残留 '%%',转义写法有误: " + sql[:200])
|
raise RuntimeError("SQL 渲染后仍残留 '%%',转义写法有误: " + sql[:200])
|
||||||
if placeholders is not None and sql.count("%s") != placeholders:
|
if placeholders is not None and sql.count("%s") != placeholders:
|
||||||
|
|||||||
@ -60,9 +60,7 @@ DELIMITER $$
|
|||||||
|
|
||||||
-- ---------------------------------------------------------------- B. append-only
|
-- ---------------------------------------------------------------- B. append-only
|
||||||
-- append-only 断言过程:非白名单操作一律 SIGNAL 拒绝
|
-- append-only 断言过程:非白名单操作一律 SIGNAL 拒绝
|
||||||
DROP PROCEDURE IF EXISTS `pbl_assert_append_only`;
|
DROP PROCEDURE IF EXISTS `pbl_assert_append_only`$$
|
||||||
|
|
||||||
CREATE$$
|
|
||||||
|
|
||||||
CREATE PROCEDURE `pbl_assert_append_only`(
|
CREATE PROCEDURE `pbl_assert_append_only`(
|
||||||
IN p_table VARCHAR(64), IN p_priv VARCHAR(16), IN p_detail VARCHAR(255))
|
IN p_table VARCHAR(64), IN p_priv VARCHAR(16), IN p_detail VARCHAR(255))
|
||||||
@ -77,13 +75,11 @@ BEGIN
|
|||||||
SIGNAL SQLSTATE '45000'
|
SIGNAL SQLSTATE '45000'
|
||||||
SET MESSAGE_TEXT = 'PBL_APPEND_ONLY_DENIED',
|
SET MESSAGE_TEXT = 'PBL_APPEND_ONLY_DENIED',
|
||||||
MYSQL_ERRNO = 45001;
|
MYSQL_ERRNO = 45001;
|
||||||
END;
|
END$$
|
||||||
|
|
||||||
-- BEFORE UPDATE 护栏:把违规语句原文带进错误消息,便于定位调用方
|
-- BEFORE UPDATE 护栏:把违规语句原文带进错误消息,便于定位调用方
|
||||||
DROP TRIGGER IF EXISTS `trg_pbl_runtime_event_append_only_upd`$$
|
DROP TRIGGER IF EXISTS `trg_pbl_runtime_event_append_only_upd`$$
|
||||||
|
|
||||||
CREATE$$
|
|
||||||
|
|
||||||
CREATE TRIGGER `trg_pbl_runtime_event_append_only_upd`
|
CREATE TRIGGER `trg_pbl_runtime_event_append_only_upd`
|
||||||
BEFORE UPDATE ON `pbl_runtime_event` FOR EACH ROW
|
BEFORE UPDATE ON `pbl_runtime_event` FOR EACH ROW
|
||||||
BEGIN
|
BEGIN
|
||||||
@ -105,13 +101,11 @@ BEGIN
|
|||||||
SIGNAL SQLSTATE '45000' SET MESSAGE_TEXT = 'PBL_APPEND_ONLY_DENIED_UPDATE',
|
SIGNAL SQLSTATE '45000' SET MESSAGE_TEXT = 'PBL_APPEND_ONLY_DENIED_UPDATE',
|
||||||
MYSQL_ERRNO = 45002;
|
MYSQL_ERRNO = 45002;
|
||||||
END IF;
|
END IF;
|
||||||
END;
|
END$$
|
||||||
|
|
||||||
-- BEFORE DELETE 护栏
|
-- BEFORE DELETE 护栏
|
||||||
DROP TRIGGER IF EXISTS `trg_pbl_runtime_event_append_only_del`$$
|
DROP TRIGGER IF EXISTS `trg_pbl_runtime_event_append_only_del`$$
|
||||||
|
|
||||||
CREATE$$
|
|
||||||
|
|
||||||
CREATE TRIGGER `trg_pbl_runtime_event_append_only_del`
|
CREATE TRIGGER `trg_pbl_runtime_event_append_only_del`
|
||||||
BEFORE DELETE ON `pbl_runtime_event` FOR EACH ROW
|
BEFORE DELETE ON `pbl_runtime_event` FOR EACH ROW
|
||||||
BEGIN
|
BEGIN
|
||||||
@ -133,17 +127,15 @@ BEGIN
|
|||||||
SIGNAL SQLSTATE '45000' SET MESSAGE_TEXT = 'PBL_APPEND_ONLY_DENIED_DELETE',
|
SIGNAL SQLSTATE '45000' SET MESSAGE_TEXT = 'PBL_APPEND_ONLY_DENIED_DELETE',
|
||||||
MYSQL_ERRNO = 45003;
|
MYSQL_ERRNO = 45003;
|
||||||
END IF;
|
END IF;
|
||||||
END;
|
END$$
|
||||||
|
|
||||||
-- 权限层加固(可选,需 DBA 账号执行;app 账号只保留增/查)
|
-- 权限层加固(可选,需 DBA 账号执行;app 账号只保留增/查)
|
||||||
-- GRANT INSERT, SELECT, UPDATE, DELETE ON `pbls`.`pbl_runtime_event` TO NULL; -- 占位,勿执行
|
-- GRANT INSERT, SELECT, UPDATE, DELETE ON `pbls`.`pbl_runtime_event` TO NULL; -- 占位,勿执行
|
||||||
-- REVOKE UPDATE, DELETE ON `pbls`.`pbl_runtime_event` FROM 'pbls'@'%'$$
|
-- REVOKE UPDATE, DELETE ON `pbls`.`pbl_runtime_event` FROM 'pbls'@'%';
|
||||||
|
|
||||||
-- ---------------------------------------------------------------- C. 分区维护
|
-- ---------------------------------------------------------------- C. 分区维护
|
||||||
-- 1) 幂等补建未来月分区:pmax 存在时用 REORGANIZE PARTITION 拆出新月份(不丢数据)
|
-- 1) 幂等补建未来月分区:pmax 存在时用 REORGANIZE PARTITION 拆出新月份(不丢数据)
|
||||||
DROP PROCEDURE IF EXISTS `pbl_runtime_event_ensure_partitions`;
|
DROP PROCEDURE IF EXISTS `pbl_runtime_event_ensure_partitions`$$
|
||||||
|
|
||||||
CREATE$$
|
|
||||||
|
|
||||||
CREATE PROCEDURE `pbl_runtime_event_ensure_partitions`(IN p_months_ahead INT)
|
CREATE PROCEDURE `pbl_runtime_event_ensure_partitions`(IN p_months_ahead INT)
|
||||||
MODIFIES SQL DATA
|
MODIFIES SQL DATA
|
||||||
@ -185,13 +177,11 @@ BEGIN
|
|||||||
END IF;
|
END IF;
|
||||||
END LOOP;
|
END LOOP;
|
||||||
CLOSE cur;
|
CLOSE cur;
|
||||||
END;
|
END$$
|
||||||
|
|
||||||
-- 2) 到期归档:整月 DROP PARTITION(append-only 表唯一合规的清理手段)
|
-- 2) 到期归档:整月 DROP PARTITION(append-only 表唯一合规的清理手段)
|
||||||
DROP PROCEDURE IF EXISTS `pbl_runtime_event_prune_partitions`$$
|
DROP PROCEDURE IF EXISTS `pbl_runtime_event_prune_partitions`$$
|
||||||
|
|
||||||
CREATE$$
|
|
||||||
|
|
||||||
CREATE PROCEDURE `pbl_runtime_event_prune_partitions`(IN p_keep_months INT)
|
CREATE PROCEDURE `pbl_runtime_event_prune_partitions`(IN p_keep_months INT)
|
||||||
MODIFIES SQL DATA
|
MODIFIES SQL DATA
|
||||||
BEGIN
|
BEGIN
|
||||||
@ -218,16 +208,11 @@ BEGIN
|
|||||||
DEALLOCATE PREPARE pbl_drop_st;
|
DEALLOCATE PREPARE pbl_drop_st;
|
||||||
END LOOP;
|
END LOOP;
|
||||||
CLOSE cur;
|
CLOSE cur;
|
||||||
END;
|
END$$
|
||||||
|
|
||||||
-- 3) 每月自动补分区(需 event_scheduler=ON;否则用 cron,见开发说明)
|
-- 3) 每月自动补分区(需 event_scheduler=ON;否则用 cron,见开发说明)
|
||||||
-- SET GLOBAL event_scheduler = ON$$
|
-- SET GLOBAL event_scheduler = ON;
|
||||||
|
DROP EVENT IF EXISTS `ev_pbl_runtime_event_partition_maintain`$$
|
||||||
DROP$$
|
|
||||||
|
|
||||||
DROP EVENT IF EXISTS `ev_pbl_runtime_event_partition_maintain`;
|
|
||||||
|
|
||||||
CREATE$$
|
|
||||||
|
|
||||||
CREATE EVENT IF NOT EXISTS `ev_pbl_runtime_event_partition_maintain`
|
CREATE EVENT IF NOT EXISTS `ev_pbl_runtime_event_partition_maintain`
|
||||||
ON SCHEDULE EVERY 1 MONTH
|
ON SCHEDULE EVERY 1 MONTH
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user