您现在的位置是:首页 >学无止境 >golang 切分 sql 语句网站首页学无止境

golang 切分 sql 语句

Immortal_s 2023-05-26 08:00:02
简介golang 切分 sql 语句

背景

前端传过来一个 string 类型的 sql 语句。里面可能包含多条 sql,后端要根据 sql 语句的不同类型采取不同的方案,所以要先将 sql 语句拆分为单条语句。

思考过程

如果对过程不感兴趣可以跳过这一节。

第一版

首先想到的就是可以根据分号来切割 sql 语句,一个分号就是一条语句。但是因为有注释的存在,注释里面可以存在分号,对切割有影响。第一版的思路就来了,先把注释去掉,然后使用分号切割。
去除注释的代码很简单,mysql 的注释一共有三种,# 和 -- 代表单行注释,/**/ 代表多行注释。写了一个状态机,从前到后扫 sql 语句,遇到单行注释就跳过,直到遇到 。遇到多行注释就找 */。

问题

建表语句的 COMMENT 里面其实也是注释,里面可以写任何内容,如果在这里面写了 #, -- 或 /*,那么后面的语句也会被当成注释被去掉。

create table if not exists `student` (`name` INT(9) NOT NULL COMMENT '--名字',`age` INT(9) NOT NULL COMMENT '年龄');

name 字段的 COMMENT 里的这种语句就会导致 -- 后面的所有东西都会被当成注释去掉。

第二版

既然在 COMMENT 里面出问题,那么能不能不处理 COMMENT 里面的语句,不管里面有啥,遇到 COMMENT 就无脑向后找,直到 COMMENT 结束。那第二版的思路就有了,处理注释的时候同时处理 COMMENT。
mysql 里的 COMMENT 一共有三种写法,可以使用单引号,双引号和反引号。所以在遍历的时候遇到这三个符号就标记开始 COMMENT,一直到结束标记结束 COMMENT,在处理 COMMENT 过程中不进行注释的处理。

问题

mysql 里对 COMMENT 的写法要求有点低,使用单引号的时候里面还可以加双引号,但是这个双引号是没有用处的。类似于

create table if not exists `student` (`name` INT(9) NOT NULL COMMENT '名字"student name',`age` INT(9) NOT NULL COMMENT '年龄');

Name 字段的 COMMENT 里这种单引号包裹双引号的行为就会导致 识别到的 COMMENT 提前结束。

第三版

可以优化下对 COMMENT 的识别,区分单引号开始,还是双引号开始,还是反引号开始,向下遍历的时候只能找对应开始的结束符。但是这种实在是太复杂了,还要处理注释的情况。所以换了个思考方向。

最终方案

原本是想着去掉注释,但其实注释对 sql 语句的执行完全没有任何影响。所以就有了一个简单的方案,直接跳过处理注释和 COMMENT 里面的处理,遇到有注释或 COMMENT 就直接向后找到结束,没有注释的话遇到分号就进行分割。

代码

// SplitSQL 分割 sql 语句
func SplitSQL(sql string) []string {
	var (
		startIndex int
		sqls       []string
	)
	for i := 0; i < len(sql); i++ {
		// 判断 comment
		if sql[i] == ''' {
			i += strings.Index(sql[i+1:], "'") + 1
		} else if sql[i] == '"' {
			i += strings.Index(sql[i+1:], """) + 1
		} else if sql[i] == '`' {
			i += strings.Index(sql[i+1:], "`") + 1
			// 判断注释
		} else if sql[i] == '/' && i+1 < len(sql) && sql[i+1] == '*' {
			i += strings.Index(sql[i+1:], "*/") + 1
		} else if sql[i] == '-' && i+1 < len(sql) && sql[i+1] == '-' {
			i += strings.Index(sql[i+1:], "
") + 1
		} else if sql[i] == '#' {
			i += strings.Index(sql[i+1:], "
") + 1
		} else if sql[i] == ';' {
			sqls = append(sqls, strings.TrimSpace(sql[startIndex:i+1]))
			startIndex = i + 1
		}
	}
	// 防止最后一个 sql 不以分号结尾
	if startIndex != len(sql) {
		sqls = append(sqls, strings.TrimSpace(sql[startIndex:]))
	}
	return sqls
}

前三个 if 判断单引号双引号和反引号,遇到了就向下找后面遇到的第一个对应的符号。中间三个 if 是判断 *, -- , # 遇到单行注释就向后找 ,多行注释就找 */。最后一个 if 是没有注释也没有 COMMENT 而且遇到了分号,那么就分割。

最后

golang 里面竟然没有一个好用的切分 sql 的库,差评

风语者!平时喜欢研究各种技术,目前在从事后端开发工作,热爱生活、热爱工作。