基於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來實現。


發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章