Skip to content

[MySQL/splitter] 存储过程 IF (cond) THEN 被误判为 IF() 导致 SQL 拆分错误、上线 1064 #3337

Description

@LordofAvernus

问题

MySQL SQL 拆分器在扫描存储过程时,使用「IF 后紧跟 ( 即视为 IF() 函数」的启发式规则区分控制流 IF 与函数调用。

当存储过程条件带括号时,例如:

IF ( v_lng IS NULL OR v_lat IS NULL ) AND Arg0 IS NOT NULL THEN
    ...
END IF;

IF 后的下一个 token 同样是 (,被误判为 IF() 函数,IfEndIfBlock 未入栈。后续在 END IF 处错误弹出外层 BeginEndBlockEND IF;; 成为语句分隔符,最外层 END; 被切到下一条 SQL。

现象:

  • 审核/建单阶段:一条 CREATE PROCEDURE 被拆成多条(如 DROP + 残缺过程 + 孤立 END;
  • 上线阶段:第 1 条残缺 SQL 执行触发 MySQL 1064 语法错误,整单上线失败

临时规避: 使用 DELIMITER $$ ... END$$ 包裹存储过程定义。

修复

  • 移除「IF 后紧跟 ( 即函数」的脆弱启发式
  • 新增 isThenToken() 辅助函数识别 THEN 关键字
  • 新增 ifIsControlFlowStatement():从 IF 之后前向扫描,跟踪括号深度,在深度 0 处遇到 THEN 则判定为控制流 IF 并入块栈
  • 前向扫描结束后复位 scanner 游标,保证后续 END 检测逻辑正确
  • 补充 TestIfControlFlowStatementInProcedure 回归用例

涉及文件:

  • sqle/driver/mysql/splitter/block.go
  • sqle/driver/mysql/splitter/splitter.go
  • sqle/driver/mysql/splitter/splitter_test.go

方案

flowchart TD
    A["扫描到 IF token"] --> B["前向扫描: 跟踪括号深度"]
    B --> C{"深度 0 处遇到 THEN?"}
    C -->|是| D["控制流 IF: 压入 IfEndIfBlock"]
    C -->|否| E["IF() 函数: 不入栈"]
    D --> F["复位 scanner 到 IF 之后"]
    E --> F
    F --> G["继续块栈扫描, 体内 ; 不作为分隔符"]
Loading

判定覆盖:

场景 判定
IF (cond) AND ... THEN 控制流 IF(入栈)
IF x > 0 THEN 控制流 IF(入栈)
IF(a, b, c) 函数(不入栈)
IF(CASE WHEN x THEN 1 END, a, b) 函数(THEN 在深度 ≥ 1 被忽略)

测试结果

单元测试

go test ./sqle/driver/mysql/splitter/...

结果:通过。覆盖带括号条件、ELSEIF、触发器内 IF() 函数、嵌套 CASE ... THEN 参数等场景。

环境验证(DMS http://10.186.63.138:21004/,容器 dms-server-main-ee

阶段 SQL 拆分条数 上线结果
修复前 3(DROP + 残缺 CREATE + 孤立 END) 第 2 条 1064,失败
修复后 2(DROP + 完整 CREATE PROCEDURE) 两条均 succeeded,成功

换包验证后已回退环境至原 sqled 二进制。

关联 PR

#3336

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Fields

    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions