基於Calcite自定義SQL解析器

這本應該是《 我也能寫數據庫》系列文章中的一篇,可是最近一直在反思這個系列標題是否是有點不親民,因此,暫時放棄這個系列標題了。


本文會介紹如何擴展Calcite的SQL解析器使之更符合你的業務需求,或是特殊的語法需求,之前的文章裏咱們介紹過如何撰寫UDF,其實這些都是對SQL進行擴展,只是咱們今天會對SQL的結構進行擴展。用一句簡單的話說,就是如何定義屬於你本身的SQL語法。java



Calcite 使用 javacc做爲語法解析器,而且使用freemarker做爲模板引擎,在編譯的時候,freemarker會將配置文件與模板語法文件以及附加文件總體生成最終的語法文件,並經過javacc編譯,造成calcite的語法文件。其整個過程以下圖所示
git


下面,咱們將從一個簡單案例入手,github

select ids, name from test where id < 5複製代碼

是一條正常的SQL,咱們要加入關鍵字 jacky job ,造成一個新的sql語法
sql

jacky job  'select ids, name from test where id < 5'複製代碼

而且,使之能夠正常解析。
數據庫



構建maven工程apache


這裏注意,須要將編譯插件配置好,主要包括freemarker和javacc,不然會出現文件找不到,或是類找不到等奇怪問題,下面是個人pom文件片斷bash


<build>    <plugins>        <plugin>            <groupId>org.apache.maven.plugins</groupId>            <artifactId>maven-compiler-plugin</artifactId>            <version>3.2</version>            <configuration>                <source>1.8</source>                <target>1.8</target>            </configuration>        </plugin>        <plugin>            <groupId>org.codehaus.mojo</groupId>            <artifactId>javacc-maven-plugin</artifactId>            <executions>                <execution>                    <id>javacc</id>                    <goals>                        <goal>javacc</goal>                    </goals>                    <configuration>                        <sourceDirectory>${project.build.directory}/generated-sources/fmpp</sourceDirectory>                        <includes>                            <include>**/Parser.jj</include>                        </includes>                        <lookAhead>2</lookAhead>                        <isStatic>false</isStatic>                    </configuration>                </execution>                <execution>                    <id>javacc-test</id>                    <phase>generate-test-sources</phase>                    <goals>                        <goal>javacc</goal>                    </goals>                    <configuration>                        <sourceDirectory>${project.build.directory}/generated-test-sources/fmpp</sourceDirectory>                        <outputDirectory>${project.build.directory}/generated-test-sources/javacc</outputDirectory>                        <includes>                            <include>**/Parser.jj</include>                        </includes>                        <lookAhead>2</lookAhead>                        <isStatic>false</isStatic>                    </configuration>                </execution>            </executions>        </plugin>        <plugin>            <groupId>org.apache.drill.tools</groupId>            <artifactId>drill-fmpp-maven-plugin</artifactId>            <executions>                <execution>                    <configuration>                        <config>src/main/codegen/config.fmpp</config>                        <output>${project.build.directory}/generated-sources/fmpp</output>                        <templates>src/main/codegen/templates</templates>                    </configuration>                    <id>generate-fmpp-sources</id>                    <phase>validate</phase>                    <goals>                        <goal>generate</goal>                    </goals>                </execution>            </executions>        </plugin>    </plugins></build>複製代碼




複製模板文件maven

從calcite源碼包中,將code\src\main\codegen下全部文件複製到本身的代碼路徑下
ide



寫解析類函數

建立SqlJacky類,包路徑爲 org.apache.calcite.sql 由於,SqlJacky須要繼承SqlNode類,而該類沒有public構造函數。


package org.apache.calcite.sql;import org.apache.calcite.sql.parser.SqlParserPos;import org.apache.calcite.sql.util.SqlVisitor;import org.apache.calcite.sql.validate.SqlValidator;import org.apache.calcite.sql.validate.SqlValidatorScope;import org.apache.calcite.util.Litmus;public class SqlJacky extends SqlNode {    private String jackyString;    private SqlParserPos pos;    public  SqlJacky(SqlParserPos pos, String jackyString){        super(pos);        this.pos = pos;        this.jackyString = jackyString;    }    public String getJackyString(){        System.out.println("getJackyString");        return this.jackyString;    }    @Override    public SqlNode clone(SqlParserPos sqlParserPos) {        System.out.println("clone");        return null;    }    @Override    public void unparse(SqlWriter sqlWriter, int i, int i1) {        sqlWriter.keyword("jacky");        sqlWriter.keyword("job");        sqlWriter.print("\n");        sqlWriter.keyword("" + jackyString + "");    }    @Override    public void validate(SqlValidator sqlValidator, SqlValidatorScope sqlValidatorScope) {        System.out.println("validate");    }    @Override    public <R> R accept(SqlVisitor<R> sqlVisitor) {        System.out.println("accept");        return null;    }    @Override    public boolean equalsDeep(SqlNode sqlNode, Litmus litmus) {        System.out.println("equalsDeep");        return false;    }}複製代碼


在這個解析類裏面,其實咱們並無作不少工做,只是在構造器裏面,將變量保存起來。


須要注意的是這個方法,unparse ,這裏用於解析顯示用的,咱們將關鍵字輸出出來。


修改config.fmpp文件

找到

package: "org.apache.calcite.sql.parser.impl",複製代碼

將下方的class,替換成一個你本身的類名,後面會用到。例如

class: "JackySqlParserImpl",複製代碼



修改Parser.jj文件


首先須要在import的地方引入上面的解析類

import org.apache.calcite.sql.SqlJacky;複製代碼


而後再後處理代碼中加入解析邏輯

SqlNode SqlJacky() :{     SqlNode stringNode;}{    <JACKY> <JOB>    stringNode = StringLiteral()    {        return new SqlJacky(getPos(), token.image);    }}複製代碼


接下來找到聲明語句的方法

SqlNode SqlStmt() :複製代碼

|    stmt = SqlJacky()複製代碼

加入到適當的位置。

最後在

<DEFAULT, DQID, BTID> TOKEN :複製代碼

的地方將,jacky 和 job 關鍵字加入

|   < JACKY: "JACKY">|   < JOB: "JOB">複製代碼


因爲這個文件比較大,這裏就不能貼完整的代碼了,下面的鏈接中,有參考案例。


編譯


執行maven的編譯命令




測試


在構建測試的時候,注意將本身的解析解析類設置好,即在fmpp裏設置的類名

.setParserFactory(JackySqlParserImpl.FACTORY)複製代碼


完整測試代碼以下


package cn.flinkhub;import org.apache.calcite.avatica.util.Casing;import org.apache.calcite.avatica.util.Quoting;import org.apache.calcite.schema.SchemaPlus;import org.apache.calcite.sql.SqlNode;import org.apache.calcite.sql.parser.SqlParser;import org.apache.calcite.tools.FrameworkConfig;import org.apache.calcite.tools.Frameworks;import org.apache.calcite.sql.parser.impl.JackySqlParserImpl;public class CustomParser {    public static void main(String[] args) {        SchemaPlus rootSchema = Frameworks.createRootSchema(true);        final FrameworkConfig config = Frameworks.newConfigBuilder()                .parserConfig(SqlParser.configBuilder()                        //.setLex(Lex.ORACLE)                        .setParserFactory(JackySqlParserImpl.FACTORY)                        .setCaseSensitive(false)                        .setQuoting(Quoting.BACK_TICK)                        .setQuotedCasing(Casing.TO_UPPER)                        .setUnquotedCasing(Casing.TO_UPPER)                        //.setConformance(SqlConformanceEnum.ORACLE_12)                        .build())                .build();//        "jacky 'select ids, name from test where id < 5'";        String sql = "jacky job 'select ids, name from test where id < 5'";        SqlParser parser = SqlParser.create(sql, config.getParserConfig());        try {            SqlNode sqlNode = parser.parseStmt();            System.out.println(sqlNode.toString());        } catch (Exception e) {            e.printStackTrace();        }    }}複製代碼



執行結果




到這裏,解析的部分咱們就作完了,後續我計劃寫一些執行計劃相關的文章,讓這個語法用起來。


研究calcite的時間有限,有錯誤的地方歡迎你們勘誤。同時也但願對calcite有興趣的小夥伴和我交流。


鳴謝:這個demo主要參考了 餘啓大神 的代碼,受益不淺。


參考鏈接:


https://blog.csdn.net/ccllcaochong1/article/details/93367343


https://github.com/yuqi1129/calcite-test


https://github.com/quxiucheng/apache-calcite-tutorial/tree/a7d63273d0c7585fc65ad250c99a67a201bcb8b5

相關文章
相關標籤/搜索