看这篇就够了丨基于calcite框架的sql语法扩展探索

数栈DTinsight 数栈DTinsight     2023-01-13     578

关键词:

Calcite在大数据系统中有着广泛的运用, 比如Apache Flink, Apache Drill等都大量使用了Calcite,理解Calcite的原理可以说已经成为理解大数据系统中SQL访问层实现原理的必备条件之一。

但是不少人在学习Calcite的过程中都发现关于Calcite的实践案例其实很少,本文就将为大家详细介绍如何基于Calcite框架的SQL语法扩展探索使之更符合你的业务需求,以及扩展SQL在数栈产品的应用实践。

Calcite介绍及用途

Calcite介绍

Apache Calcite是一个动态的数据管理框架,本身不涉及任何物理存储信息,而是专注在SQL解析、基于关系代数的查询优化,通过扩展方式来对接底层存储。

目前Apache Calcite被应用在广泛的数据开源系统中,比如Apache Hive、Apache Phoenix、Apache Flink等。

Calcite的用途

Calcite提供了ANSI标准SQL的解析,以及各种SQL 方言,针对来自于不同数据源的复杂SQL,在Calcite中会把SQL解析成SqlNode语法树结构,然后根据得到的语法树转换成自定义Node,通过自定义Node解析获取到表的字段信息、以及表信息、血缘等相关信息。

下图展示了一部分对外提供的接口信息:

sqlparser 解析模块主要提供了以下几种功能 :

• 解析SQL包含的所有表、字段信息

• 解析SQL的udf函数

• 解析SQL的血缘信息,包括表级血缘、字段血缘

• 解析自定义SqlNode

• api服务变量解析替换

SQL语法扩展

了解完Calcite是什么以及用途后,下面为大家分享Calcite SQL语法扩展的相关内容。

SQL语法扩展背景

在 sqlparser 中进行sql解析的场景中,有两种情况需要使用到自定义扩展,一是Calcite不支持的一些语法;二是在一些场景中存在sql中带有$var自定义变量语法。

那么针对上面的这两种情况,Calcite的自定义扩展是如何实现的呢?自定义扩展主要涉及到以下三个文件:

• Parser.jj:Parser.jj是一个Calcite核心的语法和词法文件,基于Apache FreeMaker模版,该模版包含着变量,这些变量在编译时可以被替换

• parserImpl.ftl:提供自定义SQL语句、literals、dataType的实现方法

• config.fmpp:该文件是FMPP的配置文件,提供了SQL语句、literals、dataType的接口扩展入口

Calcite使用javacc作为语法解析器,freemaker作为模版,把parserImpls.ftl、config.fmpp、Parser.jj模版合成最终的语法词法文件,最终通过javacc编译成自定义的解析器源码,整体流程如下图所示:

扩展SQL实现

● 工程目录

● 扩展sql实现案例

支持以下limit相关语法以及数字可以写成$var形式:

-> limit count, limit start count

-> limit count offset start

-> offset start limit count

在原生的Calcite解析是支持limit count语法的,但是由于返回SqlOrderBy对象内部类Operator的unparse方法在SQL输出过程中对原始SQL进行了改写,因此需要使用扩展SQL得到正确的SQL。

下面介绍一个limit offset语法扩展样例,扩展SQL如下:

select id, name from test where id > 3 order by id desc limit 1 offset $offset_val

整体流程如下:

01

Parser.jj 定义$var变量的token词法DOLLAR_VARIABLE:


02

Parser.jj 扩展的变量方法接入,下面方法会在解析到limit、offset关键字后面的一个词时进行调用:


03

Parser.jj limit offset在select语法的核心处理逻辑:

-> 定义变量


主要定义了三个boolean类型的变量,isOffsetLimit表示offset limit 语法,isLimitOffset表示limit offset语法,isOnlyLimit表示limit count、limit start count语法。

-> 定义处理逻辑

-> 返回自定义SqlNode


针对符合上面的三个boolean条件时,使用自定义ExtendSqlOrderBy的扩展类。

04

parserImpl.ftl 定义扩展的SqlNode ExtendDollarVariable:


05

config.fmpp 定义包以及扩展实现类的import:


06

扩展SqlNode实现:

-> 变量实现sqlNode

-> 扩展limit实现类ExtendSqlOrderBy,该类实现了SqlOrderBy,并在此基础扩展了limit的SqlNode,以及isOffsetLimit、isLimitOffset、isOnlyLimit三个boolean标识limit的不同语法


通过上面的这些步骤后,最后解析生成的SqlNode语法树如下所示:

扩展SQL在数栈的应用

目前袋鼠云的底层sqlparser sql解析涉及的子产品应用包括API数据服务离线开发客户数据洞察(标签)实时开发等,虽然大部分针对Calcite的SQL语法扩展相对于上层的产品应用感知不是很明显,但是扩展SQL还是解决了一些痛点,主要如下:

• 逐渐替换底层采用了多种解析工具解析的情况,使维护更简单,减少bug的产生

• 解决一些不支持的语法,避免在上层业务层做处理或者在底层做一些特殊处理

以在API数据服务后续接入的like语法改造为例为大家进行分享,目前的API数据服务中支持like $var语法,在执行测试中通过传递like语法来确定执行的模糊匹配方式,例如%xx、xx%、%xx%。

收到客户提出的优化like语法场景,袋鼠云本着客户第一的原则,这种合理的优化需求是采纳的。SQL支持like%$var、$var%、%$var%,这样在执行测试中就不需要输入%了,目前扩展SQL语法已经支持这种优化的like语法,预计在2023年上半年会接入进去,下面通过API数据服务展示当前like SQL和扩展后的SQL差异:

● 当前like $var处理

-> 生成API

-> 测试执行,模糊匹配需要输入%

● 扩展like %$var%

-> 生成API

-> 测试执行,由于在SQL阶段已经写了模糊匹配方式,因此可以直接输入值

总结规划

相信通过上面的案例后,大家对于Calcite扩展SQL语法的流程应该有了大致的了解,目前在袋鼠云的业务场景中已经扩展了许多语法,在未来还有一些工作需要进行优化:

• 丰富SQL语法,实现不同数据源扩展SQL语法的隔离

• 逐渐通过SQL语法扩展替换掉底层Calcite和druid共同解析的场景,避免维护多套相同的解析,减少线上问题产生

最后如果是初步接触Calcite SQL语法扩展的同学们,建议先熟悉javacc语法。

地址:https://javacc.github.io/javacc/

想了解或咨询更多有关袋鼠云大数据产品、行业解决方案、客户案例的朋友,浏览袋鼠云官网:https://www.dtstack.com/?src=szbky

同时,欢迎对大数据开源项目有兴趣的同学加入「袋鼠云开源框架钉钉技术qun」,交流最新开源技术信息,qun号码:30537511,项目地址:https://github.com/DTStack

javanio全面详解(看这篇就够了)

很多技术框架都使用NIO技术,学习和掌握JavaNIO技术对于高性能、高并发网络的应用是非常关键的NIO简介NIO中的N可以理解为Non-blocking,不单纯是New,是解决高并发、I/O高性能的有效方式。JavaNIO是Java1.4之后推出来的一套IO接口,NIO... 查看详情

java集合框架最全详解(看这篇就够了)

Java集合体系框架Java集合类主要由两个根接口Collection和Map派生出来的。Collection派生出了三个子接口:1)ListList代表了有序可重复集合,可直接根据元素的索引来访问2)SetSet代表无序不可重复集合,只能根据元素本身来... 查看详情

基于springboot的mysql实现读写分离,看这篇就够了!

来源:cnblogs.com/wyq178/p/13352707.html「前言」: 首先思考一个问题:在高并发的场景中,关于数据库都有哪些优化的手段?常用的有以下的实现方法:读写分离、加缓存、主从架构集群、分库分表等,在互联网应用中,大部分... 查看详情

handler原理剖析,看这篇就够了

Handler原理剖析,看这篇就够了本篇文章将会对Handler进行深层次的剖析,结合关系剖析图、代码走向剖析图以及10个常见问题,希望看完文章的同学都能有所收获,加深对Handler的了解!一、Handler运行原理剖析1.... 查看详情

搞透kafka的存储架构,看这篇就够了

阅读本文大约需要30分钟。这篇文章干货很多,希望你可以耐心读完。   从这篇文章开始,我将对 Kafka专项知识进行深度剖析, 今天我就来聊聊kafka的存储系统架构设计,说到存储系统,大家可能对MySQL比... 查看详情

基于springboot的mysql实现读写分离,看这篇就够了!(代码片段)

👇👇关注后回复 “进群” ,拉你进程序员交流群👇👇来源丨cnblogs.com/wyq178/p/13352707.html「前言」: 首先思考一个问题:在高并发的场景中,关于数据库都有哪些优化的手段?常用的有以下的实现方法:读写... 查看详情

zookeeper入门看这篇就够了!!(代码片段)

Zookeeper是什么官方文档上这么解释zookeeper,它是一个分布式服务框架,是ApacheHadoop的一个子项目,它主要是用来解决分布式应用中经常遇到的一些数据管理问题,如:统一命名服务、状态同步服务、集群管理、分布式应用配置项... 查看详情

超详细的springboot学习教程,springboot学习看这篇就够了

超详细的springBoot学习教程,springboot学习看这篇就够了https://blog.csdn.net/lin1214000999/article/details/105468338/  查看详情

handler原理剖析,看这篇就够了

Handler原理剖析,看这篇就够了本篇文章将会对Handler进行深层次的剖析,结合关系剖析图、代码走向剖析图以及10个常见问题,希望看完文章的同学都能有所收获,加深对Handler的了解!一、Handler运行原理剖析1.... 查看详情

zookeeper入门看这篇就够了(代码片段)

Zookeeper是什么官方文档上这么解释zookeeper,它是一个分布式服务框架,是ApacheHadoop下的一个子项目,它主要是用来解决分布式应用中经常遇到的一些数据管理问题,如:统一命名服务、状态同步服务、集群管... 查看详情

史上最全!用pandas读取csv,看这篇就够了

导读:pandas.read_csv接口用于读取CSV格式的数据文件,由于CSV文件使用非常频繁,功能强大,参数众多,因此在这里专门做详细介绍。作者:李庆辉来源:大数据DT(ID:hzdashuju)01语法基本语... 查看详情

jdk8线程池看这篇就够了(代码片段)

这可能是最简短的线程池分析文章了。顶层设计,定义执行接口InterfaceExecutor()voidexecute(Runnablecommand);ExecutorService,定义控制接口interfaceExecutorServiceextendsExecutor抽象实现ExecutorService中的大部分方法abstractclassAbstractExecutorServ 查看详情

promise看这篇就够了(代码片段)

一、背景  大家都知道nodejs很快,为什么会这么快呢,原因就是node采用异步回调的方式来处理需要等待的事件,使得代码会继续往下执行不用在某个地方等待着。但是也有一个不好的地方,当我们有很多回调的时候,比如这... 查看详情

uicollectionview自定义布局!看这篇就够了

微信搜索​​HelloWorld杰少​​即可关注。各位同学早上好,新的一周又开始啦!眨眼之间,我们就要与3月挥手告别了,时间过得可真快,不禁感慨道“无可奈何花落去,似曾相识燕归来”。最近,我花了很多的时间整理了UIColle... 查看详情

入门webpack,看这篇就够了

写在前面的话阅读本文之前,先看下面这个webpack的配置文件,如果每一项你都懂,那本文能带给你的收获也许就比较有限,你可以快速浏览或直接跳过;如果你和十天前的我一样,对很多选项存在着疑惑,那花一段时间慢慢阅... 查看详情

数据团队做什么,看这篇就够了!

↑↑↑关注后"星标"Datawhale每日干货 & 每月组队学习,不错过 Datawhale干货 作者:LouisedeLeyritz,译者:追风者随着企业认识到数据对实现业务目标的决定性力量,大多数企业希望将数据置于其业务和... 查看详情

java输入输出案例,看这篇就够了!!!(代码片段)

A+B(1)importjava.util.Scanner;publicclassMainpublicstaticvoidmain(String[]args)Scannersc=newScanner(System.in);while(sc.hasNext())inta=sc.nextInt();intb=sc.nextInt();System.out.prin 查看详情

java输入输出案例,看这篇就够了!!!(代码片段)

A+B(1)importjava.util.Scanner;publicclassMainpublicstaticvoidmain(String[]args)Scannersc=newScanner(System.in);while(sc.hasNext())inta=sc.nextInt();intb=sc.nextInt();System.out.println 查看详情