Apache Calcite原理解析(3)--解析层
1、Calcite的解析过程
1.1、解析过程
用户的操作请求经过服务层的接收和封装被传递给calcite-core模块,对SQL的处理第一层是解析层
。在这个过程中,初始的SQL字符串会被转化为Calcite内部的语法解析节点
,为进一步的语法校验和优化做准备。
语法解析过程主要可以分为3个阶段:
1、首先字符串处理器会将源语句中的字符串转换成字符流;
2、然后词法分析器
会对字符流中的一些词法进行匹配,形成词组(Token)流;
3、最后由语法分析器
将这些词组流进行语义逻辑的理解,转变为最终的抽象语法树
。
然后在解析的过程中,还会注册两个监听器,一个是负责检查语法是否存在错误的异常监听器
;另外一个是负责维护词法和语法匹配逻辑的表格管理器
。
1.2、Calcite中的解析体系
语法解析的最终结果是从SQL字符串得到一个抽象语法树,在Calcite中使用SQLNode负责封装抽象语法树中各个节点的语义信息。
在Calcite中SqlNode的实现类有40多个,每个类都代表一个节点到源码结构的映射,其大致可以分为3类,即SqlLiteral
、SqlIdentifier
、SqlCall
。
SqlLiteral
主要是封装输入的常量,也称为字面量;
SqlIdentifier
主要是封装输入的标识符,比如表名称,字段名称;
SqlCall
主要是SQL中的每个操作,比如Select对应SqlSelect,插入对应SqlInsert。
比如select idfrom t where id > 1 解析后的SQLNode树为:
2、JavaCC实现解析逻辑
JavaCC 代码生成器,它的作用就是生成在语法解析过程中的词法分析器和语法分析器。
通过模板文件(例如.jj文件、.jjt文件以及.jtb文件)来生成Java程序(词法和语法解析器),Calcite利用这些Java程序来完成语法解析的工作。
然后对于语法的扩展都是通过config.fmpp文件配置。
3、Antlr4实现解析逻辑
Antlr4是通过.g4文件进行语法的扩展。
4、JavaCC扩展自定义语法
4.1、使用JavaCC解析器
Calcite中,JavaCC 的依赖已经被封装到 calcite-core 模块当中,如果使用 Maven 作为依赖管理工具,只需要添加对应的calcite-core模块坐标即可。
<dependency>
<groupId>org.apache.calcite</groupId>
<artifactId>calcite-core</artifactId>
<version>1.26.0</version>
</dependency>
在代码中,可以直接使用 Calcite 的 SqlParser 接口调用对应的语法解析流程,对相关的 SQL 语句进行解析。
// SQL语句
String sql = "select * from t_user where id = 1";
// 解析配置
SqlParser.Config mysqlConfig = SqlParser.config().withLex(Lex.MYSQL);
// 创建解析器
SqlParser parser = SqlParser.create(sql, mysqlConfig);
// 解析SQL语句
SqlNode sqlNode = parser.parseQuery();
System.out.println(sqlNode.toString());
4.2、自定义语法
Load操作时将数据从一种数据源导入另一种数据源中,Load操作采用的语法模板如下。
修改config.fmpp文件扩充语法支持:
LOAD sourceType:obj TO targetType:obj
(fromCol toCol (,fromCol toCol)*)
[SEPARATOR '\t']
如下展示了 Calcite 通过模板引擎添加语法逻辑相关的文件结构,其源码将 Parser.jj 这个语法文件定义为模板,将 includes 目录下的.ftl文件作为扩展文件,最后统一通过config.fmpp来配置。
4.3、汇总
总的来说,Calcite扩展自定义语法,可以分为三步:
1、编写新的 JavaCC 语法文件
:不需要修改Parser.jj文件,只需要修改includes目录下的.ftl文件,对于前文提出的Load操作,只需要在parserImpls.ftl文件里增加Load对应的语法。
2、修改config.fmpp文件,配置自定义语法
: 需要将 Calcite 源码中的 config.fmpp 文件复制到项目的 src/main/codegen 目录下,然后修改里面的内容,来声明扩展的语法部分。
3、编译模板文件和语法文件
: 在这个过程当中,需要将模板Parser.jj文件编译成真正的Parser.jj文件,然后根据Parser.jj文件生成语法解析代码。(需要maven插件来完成)