您现在的位置是:首页 >学无止境 >golang 切分 sql 语句网站首页学无止境
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 的库,差评