fmpp java_基于Calcite自定义SQL解析器

本文介绍了如何扩展Apache Calcite的SQL解析器,以支持自定义语法。通过修改配置文件、模板文件和创建解析类,实现了关键字`jackyjob`的解析。详细步骤包括构建Maven工程、编写解析类、修改配置和模板文件,以及测试解析器。文章提供了一个实际操作的案例,并给出了相关资源链接。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

这本应该是《我也能写数据库》系列文章中的一篇,但是最近一直在反思这个系列标题是不是有点不亲民,所以,暂时放弃这个系列标题了。

本文会介绍如何扩展Calcite的SQL解析器使之更符合你的业务需求,或是特殊的语法需求,以前的文章里我们介绍过如何撰写UDF,其实这些都是对SQL进行扩展,只是我们今天会对SQL的结构进行扩展。用一句简单的话说,就是如何定义属于你自己的SQL语法。

Calcite 使用 javacc作为语法解析器,并且使用freemarker作为模板引擎,在编译的时候,freemarker会将配置文件与模板语法文件以及附加文件整体生成最终的语法文件,并通过javacc编译,形成calcite的语法文件。其整个过程如下图所示

下面,我们将从一个简单案例入手,select ids, name from test where id < 5

是一条正常的SQL,我们要加入关键字 jacky job ,形成一个新的sql语法jacky job 'select ids, name from test where id < 5'

并且,使之可以正常解析。

构建maven工程

这里注意,需要将编译插件配置好,主要包括freemarker和javacc,否则会出现文件找不到,或是类找不到等奇怪问题,下面是我的pom文件片段

org.apache.maven.plugins

maven-compiler-plugin

3.2

1.8

1.8

org.codehaus.mojo

javacc-maven-plugin

javacc

javacc

${project.build.directory}/generated-sources/fmpp

**/Parser.jj

2

false

javacc-test

generate-test-sources

javacc

${project.build.directory}/generated-test-sources/fmpp

${project.build.directory}/generated-test-sources/javacc

**/Parser.jj

2

false

org.apache.drill.tools

drill-fmpp-maven-plugin

src/main/codegen/config.fmpp

${project.build.directory}/generated-sources/fmpp

src/main/codegen/templates

generate-fmpp-sources

validate

generate

复制模板文件

从calcite源码包中,将code\src\main\codegen下所有文件复制到自己的代码路径下

写解析类

创建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 accept(SqlVisitor 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;

}

{

stringNode = StringLiteral()

{

return new SqlJacky(getPos(), token.image);

}

}

接下来找到声明语句的方法SqlNode SqlStmt() :

将|

stmt = SqlJacky()

加入到适当的位置。

最后在 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.youkuaiyun.com/ccllcaochong1/article/details/93367343

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

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

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值