Calcite中定制自已SQL解析器

不想看文章直接訪問https://github.com/yuqi1129/schema/tree/master/mysql-protocol
(Java版本的Mysql)、https://github.com/yuqi1129/calcite-test,這里有關于JavaCC使用具體用例

1. 什么Sql Parser

所謂Sql Parser, 就是根據(jù)某種特定的定義而生成的Sql 語法解析器。 打個比方: 在計算器中輸入 1 + 2 = 之所以可以得到結(jié)果3是因為是計算器可以準確地根據(jù)上述字符解析出相應的輸入?yún)?shù)與算法,進而計算到最終的結(jié)果。 如果輸入的是1 +- 2 = 計算器可能就會提示錯誤, 這其實就是類似于SQL中提示語法錯誤,而在處理SQL的過程與處理上面的例子很類似, 可見我們需要定制相應的語法規(guī)則進而解析SQL。

2. Java CC

熟悉ANTRL的同學應該知道.g文件的作用, 在Calcite中與之對就是JavaCC(關于什么是JavaCC,可以自行Google), 通過JavaCC文件Calcite可以定義如何去解析傳入的SQL語法

3. Calcite 內(nèi)置語法解析

現(xiàn)在就以一上簡單的例子介紹一下Calcite 默認語法解析

    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)置關于parser的過程,詳細代碼見代碼

現(xiàn)在簡要的介紹以上代碼:

  • parserConfig() 是設置ParserFactory, calcite內(nèi)置Parser類為SqlParserImpl, 這個類的代碼全部是由JavaCC生成,比較大,大約在7w行左右,不要試圖去看懂這個類,因為基本上不會有人會看懂(如果有人看懂了,私下交流請你吃飯),也沒有必要,后面我們會介紹如何用JavaCC生成對應的Parser類

  • 語法參數(shù)設置

    • setCaseSensitive() 大小是寫否敏感,比如說列名、表名、函數(shù)名
    • setQuoting() 設置引用一個標識符,比如說MySQL中的是``, Oracle中的""
    • setQuotedCasing Quoting策略,不變,變大寫或變成小寫,代碼中的全部設置成變大寫
    • setUnquotedCasing 當標識符沒有被Quoting后的策略,值同上

    更多可以更以參考Calcite類Lex, 你也可以直接設置成MySQL、Oracle、MySQL_ANSI語法,如果需要定制化的話可以單獨設置上面4個參數(shù)

  • ParserConfig中其它需要注意的參數(shù)

    • setIdentifierMaxLength() 設置標識符的最大長度,如果你的列名、表較長可以相應的加大這個值
    • setConformance() 特定語法支持,比如是否支持差集等

日常使用中,一般使用默認配置即可, 除非對語法有特殊需求

注意: Parser只會解析SQL, 不會去驗證SQL是否正確,可能這么說有點矛盾,有人會想parser難道不會檢查語法正確與否嗎?我的回答是、也不是。上面的例子如果有人執(zhí)行了之后發(fā)現(xiàn)居然可以通過, 而在代碼中我們并沒有明確表名、列名、列信息之類,為什么不會報錯?
因為 Calcite parser 只會識別關鍵字(Keyword)與標識符(Identifier), 上面Sql關鍵字有select、from、where、<、=,其他為標識符,即Parsr會規(guī)定關鍵字與標識符的相對位置是否正確,不會關心標識符的值是否存在、是否正確, 至于什么時候會檢查標識符--會在Validator階段

4. 創(chuàng)建自已parser

在3中我們使用Calcite內(nèi)置的Parser Class, 假如有這樣一個需求,要支持"submit job as 'select * from test'", 如果仍使用默認Parser,上述代碼就會執(zhí)行有問題,見代碼, 那么如何支持該語法?

第一步: 工程中引入Calcite 的JavaCC文件parser.jj, 如下圖

parser.jj

修改config.fmpp中關class 名為自已近parser class 名,如YuqiSqlParserImpl

第二步: 添加對應的SqlSubmit SqlNode, 關于如何擴展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文件

第五步:在代碼引入剛剛設置的parser 類

import org.apache.calcite.sql.parser.impl.YuqiSqlParserImpl;
...

public class ParserTest {
...
    .setParserFactory(YuqiSqlParserImpl.FACTORY))
}


第六步:編譯整個項目,最終可以在Target目錄下可以看到以下文件, 然后將javacc 目錄設置成Generated Source Root, 現(xiàn)在你可以愉快的進行測試了


生成的類

最終的結(jié)果可以參考文件, 運行時請先mvn編譯一下,以后只要修改了Parser.jj文件都要重新編譯才能生效

5. 相關問題說明

  1. 由于知道JavaCC讀者可以比較少,關于JavaCC,我會專門針對這個出一個分享,如何在Calcite使用JavaCC
  2. 全部的代碼在我的github項目中,有需要的讀者請自行去fork與閱讀(覺得本文有用不要忘了star一下哈)
  3. 由于本人使用Calcite時間不長,其中難免有錯誤之處,請讀者不吝指出,相互學習,也歡迎來交流Calcite, 本人郵件: yuqi4733@gmail.com
最后編輯于
?著作權歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務。

友情鏈接更多精彩內(nèi)容