基于javacc与基于java SQL解析器实现的一点心得背景由于项目中mysql的日志式只能选用MIXED式(binlog存在一
基于javacc与基于java SQL解析器实现的一点心得
背景
由于项目中mysql的日志格式只能选用MIXED格式(binlog存在一定的局限性,请参考),因此需要解析SQL语句。
在查找的SQL语句处理器中,都有一定的局限性,而所选中,其中一个基于javacc实现的解析器JSqlParser,使用起来方便,而且代码结构模块很好,基于其重构也比较简单。
虽然项目很久没更新与维护了,基于其改造也可以接受,因此选用了JSqlParser,而这也有了后面的SQL解析器的改造。
javacc简单的介绍JavaCC 是一个代码生成器,可以根据输入的语言定义输出一个词法分析器和解析器,JavaCC 输出的代码是合法的可编译Java代码.解析器和词法分析器本身就是一个冗长而复杂的组件,手工编写一个这样的程序需要仔细考虑各条件的相互作用,例如分析C语言时,处理整数的代码不可能和处理浮点的代码相互独立,因为整数和浮点数的开头都是数字。而使用像JavaCC 这样的分析器生成器时处理整数的规则和处理浮点数的规则是分开书写的,而两者之间的共享代码在代码生成是被自动添加了.这就增强了程序的模块性,同时也意味着定义文件较手工编写的Java 代码来说更加易于编写,阅读和更改.
上面是一段javacc的简单介绍,总的来说,通过javacc完成一些字符串的分析,还是比较方便,现在普遍使用AST了。
javacc相关的资料很少了,(官网:http://javacc.java.net/)
JSqlParser存在的问题及解决JSqlParser是一个SQL语句的解析器,包括常用的一些SQL语句,insert,update,select,delete等,但兼容的语法有限,比如括号,或者一些复杂的结构等。
JSqlPaerser实现是基于javacc完成对传入SQL语句的词法分析与解析,解析流程按分支一步步实现,相应的代码结构引入是基于visitor设计模式,javacc的定义文件结构很清晰。
但是对于binlog中读取到的SQL语句很多不能兼容的结构解析,最常用的有:
1.对于插号结构兼容,JSqlparser对于解析属性是否有括号的结构中容易出错,由于我们对于insert与update两类SQL有些需要,相应的结构都修改了定义文件。
2.对于转义字符的处理,对于字符串TOKEN:S_CHAR_LITERAL定义时,对于转义字符兼容很差,解析都会报错。对于转义字符的处理,有太多要说的了,不管是JSqlparser,还有常用的开源的SQL源码中,对于转义字符都会有些问题。BINLOG中对于转义了符都会在SQL语句中体现,如:
"'aaaaa\\\'bbbb'",此次一个字符串属性,其中中间有单引号,因此在binlog sql语句中能过转义字符处理,因此对于转义字符与单引号,在前后结尾的部分等,会引起很多问题,很难处理。
javacc中对于TOKEN的判断,类似于正则表达式的处理,但又不同。此为转义字符最主要的问题。
对于我们现在使用的字符串token,如下限制:
<S_CHAR_LITERAL:"'"( ( (~["'","\\"]) | ("\\"( ["n","t","b","r","f","0","\\","'","\""])) | ("\\"(~["'"])) )* )"'">
对于字符串,单引号开始,然后中间非单号与转义符,出现单引号时,匹配所有的转义字符,如果不是转义字符,则转义字符加任意非单引号字符都可以,然后再以单引号结束。
3.对于二进制的处理,在SQL语句,二进制会被转成十六进,我们在JSqlparser中完成了对二进制的解析处理。
还有其它相关的一些,主要是SQL语句中对于解析处理的问题。
纯java写解析器,以及正则表达式实现基于以上JSqlpaerser的解析器,对于binlog的SQL语句都能处理了。 由于是线上环境,为了避免还有其它可能解析的问题,会引起解析出错,我们需要第二个简单的SQL解析器处理异常的情况,主要是insert操作。
基于这个想法,在查找了很多,但都太重了,还是决定自己来实现。
想想对于insert处理,还是比较简单,语句匹配为insert结构,然后解析对应的属性了段,以及对应的值,还我需要正是这些字段与其对应的属性的Map.
对于insert结构,通过正则来实现(ps:正则也用熟练了~)
REGEX = "^insert[ ]+[^\\(]+\\(([^\\)]*)\\)[ ]+((?i)values)[ ]*\\((.*)";
Pattern.compile(REGEX).matcher(sql)
columnsName = matcher.group(1);
columnsValue = matcher.group(3).substring(0,matcher.group(3).length() - 1);
对于一条SQL语句,通过以上,我可以得到对应的属性字段值, 以及对应的值,当然这只是(a,b,c) (v1,v2,v3)两段获取。
得到以上信息后,split各属性值,分隔符是逗号,会得到各了段的值,
但是对于value,很有可能属性中存在逗号,因此需要分割后,对于截断的字符串需要组装,另外对于binlog中sql的转义字符,需要去除。
通过以上实现,基于java的纯SQL解析器可完成了,也可以完成对于SQL语句的解析。
算个半成品解析器,但是对于项目 需求却足够了。
比较两者的解析性能,对于相同的insert语句,业务属性的SQL语句比较有27个属性,基于javacc的一般在0.2-0.3 ms之间,还基于纯java的解析器一般在0.1MS左右。
正则表达式学习教程
很少用到正则表达式,通过上面的教程,在实现过程中可以写出自己需求的正则表达式~
最近在业余时间要写一个对于redis-monitor指令的监控,主要是对线上redis服务的监控,有现成的redis-live,但感觉依赖太多太重,不太符合需求。
打算自己写一个。考虑了下,还是用python来实现。