Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

MySQL审核存储过程脚本时SQL片段切分不正确 #2406

Open
sjjian opened this issue May 7, 2024 · 2 comments
Open

MySQL审核存储过程脚本时SQL片段切分不正确 #2406

sjjian opened this issue May 7, 2024 · 2 comments
Assignees
Labels
bug Something isn't working
Milestone

Comments

@sjjian
Copy link
Member

sjjian commented May 7, 2024

版本信息(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)

解决方案:

变更影响面

受影响的模块或功能

外部引用的潜在问题或风险

版本兼容性

测试建议

@winfredLIN
Copy link
Collaborator

winfredLIN commented Jul 1, 2024

实现方案

新增两个结构,delimiter,splitter,和一个接口block,对两个结构体parser和scanner新增了一些方法,给splitter调用。其中splitter拥有三个成员:delimiter,parser,scanner。具体职责如下:

  1. delimiter用于识别分隔符定义语法和sql文本片段中的分隔符
  2. parser是pincap的parser,用于将sql语句解析为抽象语法树节点
  3. scanner是pincap的scanner,用于扫描sql语句中的token
  4. splitter还会调用block用于检测识别内部包含多条SQL语句的BeginEnd语句块,以及其中的流程控制语句块。

工作流程:

  1. splitter会使用scanner遍历sql文本中的token
  2. 由splitter调用allblock识别BeginEnd语句块,splitter会跳过该语句块中分隔符的识别
  3. 由delimiter识别分隔符定义语法,变更当前上下文的分隔符,并且识别分隔符
  4. 由splitter基于识别到的分隔符,将SQL文本切分为一条条SQL
  5. 最后由parser将识别到的sql文本转化为抽象语法树节点,将不能识别为sql的文本转化为UnparsedStmt
  6. 输出时,不会输出定义分隔符的mysql客户端语法,并且会把自定义的分隔符替换为默认分隔符

测试影响面

影响所有SQL审核相关的功能
影响所有涉及解析SQL文本的功能

兼容性分析

  • 不涉及修改model层,只修改driver,因此兼容新旧版本数据
  • 兼容其他调用parse的函数和方法,原因:
  1. 相比之前的parse方法,在解析SQL之前先根据分隔符切分SQL,当正确使用分隔符时,切分结果为一条条的SQL语句(对于使用自定义分隔符分隔的sql,分隔符会被替换为默认分隔符;
  2. 在此基础上,对一条条SQL进行解析,对于能够解析成功的SQL,结果与之前的parse方法相同,对于不能够解析成功的SQL,解析结果为为解析的节点,但与之前不同,新版的结果在正确使用分隔符的前提下,一定是一条SQL,而不会有多条SQL杂糅在一起。因此也能够单独执行这条SQL。
  3. 对于Begin.....End语句块,在不支持解析分隔符之前,会被错误的拆分为多条SQL,在支持解析分隔符之后,可以完整的识别为一条SQL,当然也存在一种特定情况,即出现没有定义分隔符,且只有这一条Begin...End的SQL语句,我们通过检测Begin...End并跳过中间的分隔符,来达到使该语句块能够正确切分为一条SQL的目的。

@winfredLIN
Copy link
Collaborator

winfredLIN commented Jul 4, 2024

测试

测试使用了三个文件,前两个使用的是文件模式,最后一个使用的是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

测试结果截图

测试执行的文件均能执行成功,符合预期

  • 使用文件模式
    screencapture-10-186-62-32-9999-sqle-project-700300-exec-workflow-66-files-6-sqls-2024-07-04-14_13_27
    screencapture-10-186-62-32-9999-sqle-project-700300-exec-workflow-65-files-5-sqls-2024-07-04-14_10_48
  • 使用SQL模式
    screencapture-10-186-62-32-9999-sqle-project-700300-exec-workflow-1808780996166815744-2024-07-04-16_33_21

@ColdWaterLW ColdWaterLW modified the milestones: v3.2408.0, v3.2409.0 Aug 26, 2024
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
bug Something isn't working
Projects
None yet
Development

No branches or pull requests

5 participants