基于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

相关文章
相关标签/搜索