不想看文章直接訪問https://github.com/yuqi1129/schema/tree/master/mysql-protocol
(Java版本的Mysql)涡相、https://github.com/yuqi1129/calcite-test,這里有關(guān)于JavaCC使用具體用例
1. 什么Sql Parser
所謂Sql Parser, 就是根據(jù)某種特定的定義而生成的Sql 語法解析器拆宛。 打個比方: 在計算器中輸入 1 + 2 =
之所以可以得到結(jié)果3是因為是計算器可以準(zhǔn)確地根據(jù)上述字符解析出相應(yīng)的輸入?yún)?shù)與算法,進而計算到最終的結(jié)果惭适。 如果輸入的是1 +- 2 =
計算器可能就會提示錯誤狈究, 這其實就是類似于SQL中提示語法錯誤亮钦,而在處理SQL的過程與處理上面的例子很類似, 可見我們需要定制相應(yīng)的語法規(guī)則進而解析SQL掩完。
2. Java CC
熟悉ANTRL的同學(xué)應(yīng)該知道.g文件的作用噪漾, 在Calcite中與之對就是JavaCC(關(guān)于什么是JavaCC,可以自行Google), 通過JavaCC文件Calcite可以定義如何去解析傳入的SQL語法
3. Calcite 內(nèi)置語法解析
現(xiàn)在就以一上簡單的例子介紹一下Calcite 默認(rèn)語法解析
SchemaPlus rootSchema = Frameworks.createRootSchema(true);
final FrameworkConfig config = Frameworks.newConfigBuilder()
.parserConfig(SqlParser.configBuilder()
.setParserFactory(SqlParserImpl.FACTORY)
.setCaseSensitive(false)
.setQuoting(Quoting.BACK_TICK)
.setQuotedCasing(Casing.TO_UPPER)
.setUnquotedCasing(Casing.TO_UPPER)
.setConformance(SqlConformanceEnum.ORACLE_12)
.build())
.build();
String sql = "select ids, name from test where id < 5 and name = 'zhang'";
SqlParser parser = SqlParser.create(sql, config.getParserConfig());
try {
SqlNode sqlNode = parser.parseStmt();
System.out.println(sqlNode.toString());
} catch (Exception e) {
e.printStackTrace();
}
以上為Calcite 內(nèi)置關(guān)于parser的過程且蓬,詳細代碼見代碼
現(xiàn)在簡要的介紹以上代碼:
parserConfig() 是設(shè)置ParserFactory, calcite內(nèi)置Parser類為SqlParserImpl欣硼, 這個類的代碼全部是由JavaCC生成,比較大恶阴,大約在7w行左右诈胜,不要試圖去看懂這個類,因為基本上不會有人會看懂(如果有人看懂了冯事,私下交流請你吃飯)耘斩,也沒有必要,后面我們會介紹如何用JavaCC生成對應(yīng)的Parser類
-
語法參數(shù)設(shè)置
- setCaseSensitive() 大小是寫否敏感桅咆,比如說列名括授、表名、函數(shù)名
- setQuoting() 設(shè)置引用一個標(biāo)識符岩饼,比如說MySQL中的是``, Oracle中的""
- setQuotedCasing Quoting策略荚虚,不變,變大寫或變成小寫籍茧,代碼中的全部設(shè)置成變大寫
- setUnquotedCasing 當(dāng)標(biāo)識符沒有被Quoting后的策略版述,值同上
更多可以更以參考Calcite類
Lex
, 你也可以直接設(shè)置成MySQL、Oracle寞冯、MySQL_ANSI語法渴析,如果需要定制化的話可以單獨設(shè)置上面4個參數(shù) -
ParserConfig中其它需要注意的參數(shù)
- setIdentifierMaxLength() 設(shè)置標(biāo)識符的最大長度,如果你的列名吮龄、表較長可以相應(yīng)的加大這個值
- setConformance() 特定語法支持俭茧,比如是否支持差集等
日常使用中,一般使用默認(rèn)配置即可, 除非對語法有特殊需求
注意: Parser只會解析SQL, 不會去驗證SQL是否正確漓帚,可能這么說有點矛盾母债,有人會想parser難道不會檢查語法正確與否嗎?我的回答是尝抖、也不是毡们。上面的例子如果有人執(zhí)行了之后發(fā)現(xiàn)居然可以通過, 而在代碼中我們并沒有明確表名昧辽、列名衙熔、列信息之類,為什么不會報錯搅荞?
因為 Calcite parser 只會識別關(guān)鍵字(Keyword
)與標(biāo)識符(Identifier
)红氯, 上面Sql關(guān)鍵字有select框咙、from、where脖隶、<、=
暇检,其他為標(biāo)識符产阱,即Parsr會規(guī)定關(guān)鍵字與標(biāo)識符的相對位置是否正確,不會關(guān)心標(biāo)識符的值是否存在块仆、是否正確构蹬, 至于什么時候會檢查標(biāo)識符--會在Validator階段
4. 創(chuàng)建自已parser
在3中我們使用Calcite內(nèi)置的Parser Class, 假如有這樣一個需求,要支持"submit job as 'select * from test'", 如果仍使用默認(rèn)Parser悔据,上述代碼就會執(zhí)行有問題庄敛,見代碼, 那么如何支持該語法?
第一步: 工程中引入Calcite 的JavaCC文件parser.jj
, 如下圖
修改config.fmpp中關(guān)class 名為自已近parser class 名,如YuqiSqlParserImpl
第二步: 添加對應(yīng)的SqlSubmit SqlNode, 關(guān)于如何擴展SqlNode, 請仔細讀閱讀 SqlSelect等SqlNode類
public class SqlSubmit extends SqlNode {
String jobString;
public SqlSubmit(SqlParserPos pos, String jobString) {
super(pos);
this.jobString = jobString;
}
public String getJobString() {
return jobString;
}
}
第三步: 修改parser.jj 文件, 添加以下內(nèi)容
...
import org.apache.calcite.sql.SqlSubmit;
...
...
SqlNode SqlSubmit() :
{
SqlNode stringNode;
}
{
<SUBMIT> <JOB> <AS>
stringNode = StringLiteral()
{
return new SqlSubmit(getPos(), token.image);
}
}
...
SqlNode SqlStmt() :
{
SqlNode stmt;
}
{
...
| stmt = SqlSubmit()
...
}
<DEFAULT, DQID, BTID> TOKEN :
{
...
| <SUBMIT: "SUBMIT">
| <JOB: "JOB">
...
}
第四步: 引入JavaCC編譯插件
詳細參考代碼中的pom文件
第五步:在代碼引入剛剛設(shè)置的parser 類
import org.apache.calcite.sql.parser.impl.YuqiSqlParserImpl;
...
public class ParserTest {
...
.setParserFactory(YuqiSqlParserImpl.FACTORY))
}
第六步:編譯整個項目科汗,最終可以在Target目錄下可以看到以下文件, 然后將javacc 目錄設(shè)置成Generated Source Root, 現(xiàn)在你可以愉快的進行測試了
最終的結(jié)果可以參考文件, 運行時請先mvn編譯一下藻烤,以后只要修改了Parser.jj文件都要重新編譯才能生效
5. 相關(guān)問題說明
- 由于知道JavaCC讀者可以比較少,關(guān)于JavaCC头滔,我會專門針對這個出一個分享,如何在Calcite使用JavaCC
- 全部的代碼在我的github項目中怖亭,有需要的讀者請自行去fork與閱讀(覺得本文有用不要忘了star一下哈)
- 由于本人使用Calcite時間不長,其中難免有錯誤之處坤检,請讀者不吝指出兴猩,相互學(xué)習(xí),也歡迎來交流Calcite早歇, 本人郵件: yuqi4733@gmail.com