Apache Calcite解析表名和字段名信息(未进行validate)_不悔将来的博客-程序员宅基地_calcite 获取表名

技术标签: java  OLAP  大数据  

目录

 

前言

解析实现


前言

市面上有很多做SQL解析的语法引擎,如阿里的druid,ANTLR等。楼主之前在南京某宁公司使用ANTLR和阿里的druid做过表/字段级的血缘解析,也一直认为ANTLR/druid做解析做解析很方便,尤其是ANTLR的visitor模式,想要哪个要哪个。

但近期由于业务的复杂,需要将SQL语法不仅解析出来还需要做一定的转义操作。参考了spark源码是如何处理ANTLR的,结果发现太过复杂(其实是没大看懂),因此采用了最简单的replace方式,可是该方式会存在问题(你们懂的!!)

为此,研究了一下据说很牛逼的Calcite。但不得不承认,Calcite真复杂,目前还只是小白!!

 

解析实现

import org.apache.calcite.config.Lex;
import org.apache.calcite.sql.*;
import org.apache.calcite.sql.parser.SqlParser;
import org.apache.calcite.sql.parser.SqlParserPos;
import org.apache.calcite.sql.parser.impl.SqlParserImpl;
import org.apache.calcite.sql.validate.SqlConformanceEnum;
import org.apache.calcite.util.ImmutableBeans;


/**
 * Created with IntelliJ IDEA.
 * Description:
 *
 * @Author: yuanhongjun
 * DateTime: 2021-04-13 9:25
 */
public class CalciteSQLParser {

    private static SqlParser.Config config = SqlParser.configBuilder().setLex(Lex.MYSQL).setCaseSensitive(true).setConformance(SqlConformanceEnum.MYSQL_5).build();
    //.setConformance(SqlConformanceEnum.MYSQL_5)  limit 10,10
    //.setCaseSensitive(true)大小写敏感 

    private static SqlParser.Config DEFAULT = (ImmutableBeans.create(SqlParser.Config.class)).withLex(Lex.MYSQL).withIdentifierMaxLength(128).withConformance(SqlConformanceEnum.DEFAULT).withParserFactory(SqlParserImpl.FACTORY);


    public static void main(String[] args) throws Exception {


//        String sql = "select sum(b) as dd , b.c from db.d where e = x and f not in (x,d)";

//        String sql = "SELECT sum(x.dd) as xx ,2 from db.a x where id = xx and c = 'zz' group by xx order by dd limit 10 " ;
//        String sql = "SELECT sum(x.dd) as xx ,2 from db.a x where id = xx and c = 'zz' order by dd limit 10 ";
//        String sql = "SELECT sum(f) as xx,e FROM db.B left join B.dd on dd.xx=b.cc WHERE g = h";
//        String sql = "SELECT sum(f) as xx,e FROM db.B left join (select xx from B.dd union select xx from d.dddddd) as bdd on dd.xx=b.cc WHERE g = h";
        String sql = "SELECT sum(x.dd) as xx ,2 from db.a x where id = xx and c = 'zz'  " +
                "union all SELECT sum(f) as xx,e FROM db.B left join B.dd on dd.xx=b.cc WHERE g = h limit 10,10";
        //当存在子查询和order by的时候都可能需要传入到Select

        SqlParser sqlParser = SqlParser.create(sql, config);
        try {
            SqlNode sqlNode = sqlParser.parseQuery();

            System.out.println(sqlNode.toString());

            hanlerSQL(sqlNode);

        } catch (Exception e) {
            throw new RuntimeException("", e);
        }

    }

    private static void hanlerSQL(SqlNode sqlNode) {
        SqlKind kind = sqlNode.getKind();
        switch (kind) {
            case SELECT:
                hanlerSelect(sqlNode);
                break;
            case UNION:
                ((SqlBasicCall) sqlNode).getOperandList().forEach(node -> {
                    hanlerSQL(node);
                });

                break;
            case ORDER_BY:
                handlerOrderBy(sqlNode);
                break;
        }
    }

    private static void handlerOrderBy(SqlNode node) {
        SqlOrderBy sqlOrderBy = (SqlOrderBy) node;
        SqlNode query = sqlOrderBy.query;
        hanlerSQL(query);
        SqlNodeList orderList = sqlOrderBy.orderList;
        handlerField(orderList);
    }


    private static void hanlerSelect(SqlNode select) {

        SqlSelect sqlSelect = (SqlSelect) select;

        //TODO 改写SELECT的字段信息

        SqlNodeList selectList = sqlSelect.getSelectList();
        //字段信息
        selectList.getList().forEach(list -> {
            handlerField(list);
        });

        handlerFrom(sqlSelect.getFrom());

        if (sqlSelect.hasWhere()) {
            handlerField(sqlSelect.getWhere());
        }

        if (sqlSelect.hasOrderBy()) {
            handlerField(sqlSelect.getOrderList());
        }

        SqlNodeList group = sqlSelect.getGroup();
        if (group != null) {
            group.forEach(groupField -> {
                handlerField(groupField);
            });
        }


        SqlNode fetch = sqlSelect.getFetch();
        if (fetch != null) {
            //TODO limit
        }

    }

    private static void handlerFrom(SqlNode from) {
        SqlKind kind = from.getKind();

        switch (kind) {
            case IDENTIFIER:
                //最终的表名
                SqlIdentifier sqlIdentifier = (SqlIdentifier) from;
                //TODO 表名的替换,所以在此之前就需要获取到模型的信息
                System.out.println("==tablename===" + sqlIdentifier.toString());
                break;
            case AS:
                SqlBasicCall sqlBasicCall = (SqlBasicCall) from;
                SqlNode selectNode = sqlBasicCall.getOperandList().get(0);
                hanlerSQL(selectNode);
                break;
            case JOIN:
                SqlJoin sqlJoin = (SqlJoin) from;
                SqlNode left = sqlJoin.getLeft();
                hanlerSQL(left);
                SqlNode right = sqlJoin.getRight();
                hanlerSQL(right);
                SqlNode condition = sqlJoin.getCondition();
                handlerField(condition);
                break;
            case SELECT:
                hanlerSQL(from);
                break;
        }
    }


    private static void handlerField(SqlNode field) {
        SqlKind kind = field.getKind();
        switch (kind) {
            case AS:
                SqlNode[] operands_as = ((SqlBasicCall) field).operands;
                SqlNode left_as = operands_as[0];
                handlerField(left_as);
                break;
            case IDENTIFIER:
                //表示当前为子节点
                SqlIdentifier sqlIdentifier = (SqlIdentifier) field;
                System.out.println("===field===" + sqlIdentifier.toString());
                break;
            default:
                if (field instanceof SqlBasicCall) {
                    SqlNode[] nodes = ((SqlBasicCall) field).operands;
                    for (int i = 0; i < nodes.length; i++) {
                        handlerField(nodes[i]);
                    }
                }
                if (field instanceof SqlNodeList) {
                    ((SqlNodeList) field).getList().forEach(node -> {
                        handlerField(node);
                    });
                }
                break;
        }


    }


}

代码只是完成了解析的操作,具体怎么封装没有实现,后面会继续研究Calcite的validate以及如何根据不同存储引擎转换成对应的查询语句,敬请期待……

版权声明:本文为博主原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。
本文链接:https://blog.csdn.net/junjun56/article/details/115870189

智能推荐

Labplus:Scratch创作工具的替代与进步_我是修爸的博客-程序员宅基地_scratch替代

Scratch官方工具因已知的原因停止访问更新,而国内兼容工具也逐步增多,在众多同类型工具中,结合平时的使用需求,常用工具决定选定Labplus进行案例实现。既然是替代,一方面关心它的兼容适配,不用额外增加学习适应成本,毕竟Scratch官方工具使用习惯了,一方面也需要了解Labplus 3与Scratch 3的差异与进步。·字体不再模糊字号显示正常·上图为Scratch 3官方工具界面,在字体显示适配上模糊不清,网上也提供一些偏门的方式调节字号大小,比如修改系统字体大小,但必然影响系统其他..

java java.util.Date、java.sql.Date、java.sql.Time、java.sql.Timestamp区别和联系_Coding-lover的博客-程序员宅基地

在Web开发中,避免不了对日期的操作,常用的关于时间的类有这么几个: java.util.Date、java.sql.Date、java.sql.Time、java.sql.Timestamp,这几个类在JDK的定义如下所示:java.lang.Object ….|__java.util.Date ……….|__java.sql.Date/java.sql.Timestamp/java.s

numpy的学习笔记(三)——常用函数_jinyong303的博客-程序员宅基地

第三章 常用函数1、单位矩阵:np.eye(N)     全零数列:np.zero(N)     全1数列 :np.ones(N)2、读写文件:    c, v = np.loadtxt("data.csv", delimiter=',', usecols=(6,7), unpack=True)    np.savetxt("data.txt", i2)     np.sa

React 全家桶实现一个简易备忘录_weixin_33769125的博客-程序员宅基地

前言总括: 本文采用react+redux+react-router+less+es6+webpack,以实现一个简易备忘录(todolist)为例尽可能全面的讲述使用react全家桶实现一个完整应用的过程。代码地址:React全家桶实现一个简易备忘录原文博客地址:React全家桶实现一个简易备忘录知乎专栏&amp;amp;&amp;amp;简书专题:前端进击者(知乎)&amp;amp;&amp;amp;前端进击者...

COCO API安装_周子青的博客-程序员宅基地_cocoapi安装

# COCOAPI=/path/to/clone/cocoapigit clone https://github.com/cocodataset/cocoapi.git $COCOAPIcd $COCOAPI/PythonAPI# Install into global site-packagesmake install# Alternatively, if you do not ha...

随便推点

Linux文件共享设置__Hansen_的博客-程序员宅基地_linux设置文件共享

Linux主机通过NFS实现文件目录共享,需要在服务端与客户端同步进行配置。步骤一、NFS服务端配置1、检查NFS服务端是否已安装软件包(portmap和nfs-utils):[[email protected] ~]# rpm -qa | grep -E "nfs-utils|portmap"如果没有,则需要下载软件安装,或直接通过yum安装。2、配置/etc/exports文件:[[email protected] ~]# vi /etc/exports/home/share 192.168.222.0/24...

鹏程万里------go解决mongodb import问题_苏西守护者的博客-程序员宅基地

import ( "container/list" "fmt" "github.com/360EntSecGroup-Skylar/excelize" _ "github.com/360EntSecGroup-Skylar/excelize" "github.com/garyburd/redigo/redis" "github.com/gin-gonic/gin" "net/http" "go.mongodb.org/mongo-driver/bson").

tty驱动核心(写得不错)_hushup的博客-程序员宅基地

http://www.2cto.com/os/201107/98185.html结合http://blog.csdn.net/sirzjp/article/details/6154954

前端知识篇——(七)_丸子嘻嘻的博客-程序员宅基地

目录CSS 题目:改变 placeholder 的字体颜色大小JS 题目: Set &amp;amp;amp; Map 数据结构 其他 题目: 认识动画效果 CSS题目:改变 placeholder 的字体颜色大小&amp;amp;lt;style&amp;amp;gt;input::-webkit-input-placeholder { /* WebKit browsers */ ...

LeetCode 225. 用队列实现栈 做题笔记_从零开始的数据猿的博客-程序员宅基地

题目:使用队列实现栈的下列操作:push(x) – 元素 x 入栈pop() – 移除栈顶元素top() – 获取栈顶元素empty() – 返回栈是否为空注意:你只能使用队列的基本操作-- 也就是 push to back, peek/pop from front, size, 和 is empty 这些操作是合法的。你所使用的语言也许不支持队列。 你可以使用 list 或者 deque(双端队列)来模拟一个队列 , 只要是标准的队列操作即可。你可以假设所有操作都是有效的(例如, 对一个

数据提取方法_jarvis-Wu的博客-程序员宅基地

html和xml的区别html(超文本标记语言),用来显示数据xml(可扩展标记语言),用来传输和存储数据xpath语法// 的用途//a当前html页面上的所有的abookstore//bookbookstore下的所有book元素@的使用//a/@herf所有a的hreftext()的使用//a/text()获取所有的a下的文本text()的使用//a/text()获取所有的a下的文本//a[text()=下一页]获取文本为下一页的a标签

推荐文章

热门文章

相关标签