sqle
sqle copied to clipboard
MySQL审核存储过程脚本时SQL片段切分不正确
版本信息(Version)
main
问题描述(Describe)
MySQL审核存储过程脚本时SQL片段切分不正确
截图或日志(Log)
输入的存储过程脚本如下:
DELIMITER $$
USE `fwdb`$$
DROP PROCEDURE IF EXISTS `_GS_GM_Check`$$
CREATE DEFINER=`root`@`%` PROCEDURE `_GS_GM_Check`(vi_uid INT,vi_pwd VARCHAR(32),vi_ip VARCHAR(100),OUT vo_level INT,OUT vo_code INT)
BEGIN
SET vo_level = 0;
-- JinSQ 2016-04-08 CHECK IP ADD START
IF IFNULL(vi_ip,' ')=' ' THEN
BEGIN
-- JinSQ 2016-04-08 CHECK IP ADD END
-- if exists(select 1 from T_GM_Info where f_userid = vi_uid and f_pwd = vi_pwd and F_ip = vi_ip and F_Active = 1)
IF EXISTS(SELECT 1 FROM T_GM_Info WHERE f_userid = vi_uid AND F_Active = 1)
THEN
-- select f_level into vo_level from T_GM_Info where f_userid = vi_uid AND f_pwd = vi_pwd AND F_ip = vi_ip AND F_Active = 1;
SELECT f_level INTO vo_level FROM T_GM_Info WHERE f_userid = vi_uid AND F_Active = 1;
SET vo_code = 1000; -- success
ELSE
SET vo_code = 1001; -- fail
END IF;
-- JinSQ 2016-04-08 CHECK IP ADD START
END;
ELSE
BEGIN
IF EXISTS(SELECT 1 FROM T_GM_Info WHERE f_userid = vi_uid AND F_ip = vi_ip AND F_Active = 1)
THEN
SELECT f_level INTO vo_level FROM T_GM_Info WHERE f_userid = vi_uid AND F_ip = vi_ip AND F_Active = 1;
SET vo_code = 1000; -- success
ELSE
SET vo_code = 1001; -- fail
END IF;
END;
END IF;
-- JinSQ 2016-04-08 CHECK IP ADD END
END$$
DELIMITER ;
审核后切分的片段如下:
DELIMITER $$
USE `fwdb`$$
DROP PROCEDURE IF EXISTS `_GS_GM_Check`$$
CREATE DEFINER=`root`@`%` PROCEDURE `_GS_GM_Check`(vi_uid INT,vi_pwd VARCHAR(32),vi_ip VARCHAR(100),OUT vo_level INT,OUT vo_code INT)
BEGIN
SET vo_level = 0;
-- JinSQ 2016-04-08 CHECK IP ADD START
IF IFNULL(vi_ip,' ')=' ' THEN
BEGIN
-- JinSQ 2016-04-08 CHECK IP ADD END
-- if exists(select 1 from T_GM_Info where f_userid = vi_uid and f_pwd = vi_pwd and F_ip = vi_ip and F_Active = 1)
IF EXISTS(SELECT 1 FROM T_GM_Info WHERE f_userid = vi_uid AND F_Active = 1)
THEN
-- select f_level into vo_level from T_GM_Info where f_userid = vi_uid AND f_pwd = vi_pwd AND F_ip = vi_ip AND F_Active = 1;
SELECT f_level INTO vo_level FROM T_GM_Info WHERE f_userid = vi_uid AND F_Active = 1;
SET vo_code = 1000; -- success
ELSE
SET vo_code = 1001; -- fail
END IF;
-- JinSQ 2016-04-08 CHECK IP ADD START
END;
ELSE
BEGIN
IF EXISTS(SELECT 1 FROM T_GM_Info WHERE f_userid = vi_uid AND F_ip = vi_ip AND F_Active = 1)
THEN
SELECT f_level INTO vo_level FROM T_GM_Info WHERE f_userid = vi_uid AND F_ip = vi_ip AND F_Active = 1;
SET vo_code = 1000; -- success
ELSE
SET vo_code = 1001; -- fail
END IF;
END;
END IF;
-- JinSQ 2016-04-08 CHECK IP ADD END
END$$
DELIMITER ;
如何复现(To Reproduce)
通过快捷审核随意创建一个MySQL的静态审核,输入上面的SQL脚步即可复现
实现方案
无法正确切分存储过程原因: 1、解析器对于一条存储过程语句的匹配规则是根据"BEGIN"和"END"关键字,但是复杂存储过程可以嵌套,解析器无法处理嵌套的情况(https://dev.mysql.com/doc/refman/5.7/en/begin-end.html) 2、如果一条存储过程定义中本身包含默认分隔符";",所以必须重新定义分隔符,需用到关键字delimiter,但是解析器无法解析该关键字。(https://dev.mysql.com/doc/refman/5.7/en/stored-programs-defining.html) 如:
mysql> delimiter //
mysql> CREATE PROCEDURE dorepeat(p1 INT)
-> BEGIN
-> SET @x = 0;
-> REPEAT SET @x = @x + 1; UNTIL @x > p1 END REPEAT;
-> END
-> //
Query OK, 0 rows affected (0.00 sec)
mysql> delimiter ;
mysql> CALL dorepeat(1000);
Query OK, 0 rows affected (0.00 sec)
mysql> SELECT @x;
+------+
| @x |
+------+
| 1001 |
+------+
1 row in set (0.00 sec)
解决方案:
变更影响面
受影响的模块或功能
外部引用的潜在问题或风险
版本兼容性
测试建议
实现方案
新增两个结构,delimiter,splitter,和一个接口block,对两个结构体parser和scanner新增了一些方法,给splitter调用。其中splitter拥有三个成员:delimiter,parser,scanner。具体职责如下:
- delimiter用于识别分隔符定义语法和sql文本片段中的分隔符
- parser是pincap的parser,用于将sql语句解析为抽象语法树节点
- scanner是pincap的scanner,用于扫描sql语句中的token
- splitter还会调用block用于检测识别内部包含多条SQL语句的BeginEnd语句块,以及其中的流程控制语句块。
工作流程:
- splitter会使用scanner遍历sql文本中的token
- 由splitter调用allblock识别BeginEnd语句块,splitter会跳过该语句块中分隔符的识别
- 由delimiter识别分隔符定义语法,变更当前上下文的分隔符,并且识别分隔符
- 由splitter基于识别到的分隔符,将SQL文本切分为一条条SQL
- 最后由parser将识别到的sql文本转化为抽象语法树节点,将不能识别为sql的文本转化为UnparsedStmt
- 输出时,不会输出定义分隔符的mysql客户端语法,并且会把自定义的分隔符替换为默认分隔符
测试影响面
影响所有SQL审核相关的功能 影响所有涉及解析SQL文本的功能
兼容性分析
- 不涉及修改model层,只修改driver,因此兼容新旧版本数据
- 兼容其他调用parse的函数和方法,原因:
- 相比之前的parse方法,在解析SQL之前先根据分隔符切分SQL,当正确使用分隔符时,切分结果为一条条的SQL语句(对于使用自定义分隔符分隔的sql,分隔符会被替换为默认分隔符;
- 在此基础上,对一条条SQL进行解析,对于能够解析成功的SQL,结果与之前的parse方法相同,对于不能够解析成功的SQL,解析结果为为解析的节点,但与之前不同,新版的结果在正确使用分隔符的前提下,一定是一条SQL,而不会有多条SQL杂糅在一起。因此也能够单独执行这条SQL。
- 对于Begin.....End语句块,在不支持解析分隔符之前,会被错误的拆分为多条SQL,在支持解析分隔符之后,可以完整的识别为一条SQL,当然也存在一种特定情况,即出现没有定义分隔符,且只有这一条Begin...End的SQL语句,我们通过检测Begin...End并跳过中间的分隔符,来达到使该语句块能够正确切分为一条SQL的目的。
测试
测试使用了三个文件,前两个使用的是文件模式,最后一个使用的是SQL模式。测试SQL由AI生成, 基础Prompt:
对于MySQL,请帮助我写一些SQL,必须满足:
1. 尽量包含各种类型的SQL语句
2. 其中有一些包含带有BEGIN END的语句块,这些语句块需要使用自定义分隔符来分隔
3. 由于这些数据是用于测试,在MySQL上运行完毕之后数据需要清除,因此请您在创建表、视图、存储过程等对象后,在SQL文本的最后,请给出相应的删除语句。
感谢
测试文件: test_db.employees.txt test_db.projects.txt test_db.department.txt
测试结果截图
测试执行的文件均能执行成功,符合预期
- 使用文件模式
- 使用SQL模式