如何實現(xiàn)一個SQL解析器

作者:vivo 互聯(lián)網(wǎng)搜索團隊- Deng Jie

一氧急、背景

隨著技術的不斷的發(fā)展,在大數(shù)據(jù)領域出現(xiàn)了越來越多的技術框架。而為了降低大數(shù)據(jù)的學習成本和難度救氯,越來越多的大數(shù)據(jù)技術和應用開始支持SQL進行數(shù)據(jù)查詢。SQL作為一個學習成本很低的語言歌憨,支持SQL進行數(shù)據(jù)查詢可以降低用戶使用大數(shù)據(jù)的門檻着憨,讓更多的用戶能夠使用大數(shù)據(jù)。

本篇文章主要介紹如何實現(xiàn)一個SQL解析器來應用的業(yè)務當中务嫡,同時結(jié)合具體的案例來介紹SQL解析器的實踐過程甲抖。

二、為什么需要SQL解析器心铃?

在設計項目系統(tǒng)架構(gòu)時准谚,我們通常會做一些技術調(diào)研。我們會去考慮為什么需要SQL解析器去扣?怎么判斷選擇的 SQL 解析器可以滿足當前的技術要求柱衔?

2.1 傳統(tǒng)的SQL查詢

傳統(tǒng)的SQL查詢,依賴完整的數(shù)據(jù)庫協(xié)議厅篓。比如數(shù)據(jù)存儲在MySQL秀存、Oracle等關系型數(shù)據(jù)庫中,有標準的SQL語法羽氮。我們可以通過不同的SQL語句來實現(xiàn)業(yè)務需求或链,如下圖所示:


但是,在處理海量數(shù)據(jù)的時候档押,關系型數(shù)據(jù)庫是難以滿足實際的業(yè)務需求的澳盐,我們需要借助大數(shù)據(jù)生態(tài)圈的技術組件來解決實際的業(yè)務需求祈纯。

2.2 實際應用場景

在使用大數(shù)據(jù)生態(tài)圈的技術組件時,有些技術組件是自帶SQL的叼耙,比如Hive腕窥、Spark、Flink等筛婉;而有些技術組件本身是不帶SQL的簇爆,比如Kafka、HBase爽撒。下面入蛆,我們可以通過對比不帶SQL和使用SQL解析器后的場景,如下圖所示:



從上圖中硕勿,我們可以看到哨毁,圖左邊在我們使用不帶SQL的技術組件時,實現(xiàn)一個查詢時源武,需要我們編寫不同的業(yè)務邏輯接口扼褪,來與Kafka、HBase這些技術組件來進行數(shù)據(jù)交互粱栖。如果隨著這類組件的增加话浇,查詢功能復雜度的增加,那邊每套接口的復雜度也會隨之增加查排,對于后續(xù)的擴展和維護也是很不方便的凳枝。而圖右邊在我們引入SQL解析器后,只需要一套接口來完成業(yè)務邏輯跋核,對于不同的技術組件進行適配即可。

三叛买、什么是SQL解析器砂代?

在選擇SQL解析器應用到我們實際的業(yè)務場景之前,我們先來了解一下SQL解析器的核心知識點率挣。

3.1 SQL解析器包含哪些內(nèi)容刻伊?

在使用SQL解析器時,解析SQL的步驟與我們解析Java/Python程序的步驟是非常的相似的椒功,比如:

  • 在C/C++中捶箱,我們可以使用LEX和YACC來做詞法分析和語法分析
  • 在Java中,我們可以使用JavaCC或ANTLR

在我們使用解析器的過程當中动漾,通常解析器主要包括三部分丁屎,它們分別是:詞法解析、語法解析旱眯、語義解析晨川。

3.1.1 什么詞法解析证九?

如何理解詞法解析呢?詞法解析我們可以這么來進行理解共虑,在啟動詞法解析任務時愧怜,它將從左到右把字符一個個的讀取并加載到解析程序里面,然后對字節(jié)流進行掃描妈拌,接著根據(jù)構(gòu)詞規(guī)則識別字符并切割成一個個的詞條拥坛,切詞的規(guī)則是遇到空格進行分割,遇到分號時結(jié)束詞法解析尘分。比如一個簡單的SQL如下所示:

SQL示例

SELECT name FROM tab;

通過詞法解析后猜惋,結(jié)果如下所示:


3.1.2 什么是語法解析?

如何理解語法解析呢音诫?語法解析我們可以這么來進行理解惨奕,在啟動語法解析任務時,語法分析的任務會在詞法分析的結(jié)果上將詞條序列組合成不同語法短句竭钝,組成的語法短句將與相應的語法規(guī)則進行適配梨撞,若適配成功則生成對應的抽象語法樹,否則報會拋出語法錯誤異常香罐。比如如下SQL語句:

SQL示例

SELECT name FROM tab WHERE id=1001;

約定規(guī)則如下:


上表中卧波,紅色的內(nèi)容通常表示終結(jié)符,它們一般是大寫的關鍵字或者符號等庇茫,小寫的內(nèi)容是非終結(jié)符港粱,一般用作規(guī)則的命名,比如字段旦签、表名等筷频。具體AST數(shù)據(jù)結(jié)構(gòu)如下圖所示:


3.1.3 什么是語義解析识颊?

如何理解語義解析呢?語義解析我們可以這么來進行理解,語義分析的任務是對語法解析得到的抽象語法樹進行有效的校驗惩猫,比如字段们豌、字段類型悍抑、函數(shù)吴叶、表等進行檢查。比如如下語句:

SQL示例

SELECT name FROM tab WHERE id=1001;

上述SQL語句竿秆,語義分析任務會做如下檢查:

  • SQL語句中表名是否存在启摄;
  • 字段name是否存在于表tab中;
  • WHERE條件中的id字段類型是否可以與1001進行比較操作幽钢。

上述檢查結(jié)束后歉备,語義解析會生成對應的表達式供優(yōu)化器去使用。

四搅吁、 如何選擇SQL解析器威创?

在了解了解析器的核心知識點后落午,如何選擇合適的SQL解析器來應用到我們的實際業(yè)務當中呢?下面肚豺,我們來對比一下主流的兩種SQL解析器溃斋。它們分別是ANTLR和Calcite。

4.1 ANTLR

ANTLR是一款功能強大的語法分析器生成器吸申,可以用來讀取梗劫、處理、執(zhí)行和轉(zhuǎn)換結(jié)構(gòu)化文本或者二進制文件截碴。在大數(shù)據(jù)的一些SQL框架里面有有廣泛的應用梳侨,比如Hive的詞法文件是ANTLR3寫的,Presto詞法文件也是ANTLR4實現(xiàn)的日丹,SparkSQLambda詞法文件也是用Presto的詞法文件改寫的走哺,另外還有HBase的SQL工具Phoenix也是用ANTLR工具進行SQL解析的。

使用ANTLR來實現(xiàn)一條SQL哲虾,執(zhí)行或者實現(xiàn)的過程大致是這樣的丙躏,實現(xiàn)詞法文件(.g4),生成詞法分析器和語法分析器束凑,生成抽象語法樹(也就是我常說的AST)晒旅,然后再遍歷抽象語法樹,生成語義樹汪诉,訪問統(tǒng)計信息废恋,優(yōu)化器生成邏輯執(zhí)行計劃,再生成物理執(zhí)行計劃去執(zhí)行扒寄。


官網(wǎng)示例:

ANTLR表達式

assign : ID '=' expr ';' ;

解析器的代碼類似于下面這樣:

ANTLR解析器代碼

void assign() {
  match(ID);
  match('=');
  expr();
  match(';');
}

4.1.1 Parser

Parser是用來識別語言的程序鱼鼓,其本身包含兩個部分:詞法分析器和語法分析器。詞法分析階段主要解決的問題是關鍵字以及各種標識符该编,比如INT(類型關鍵字)和ID(變量標識符)蚓哩。語法分析主要是基于詞法分析的結(jié)果,構(gòu)造一顆語法分析數(shù)上渴,流程大致如下:

因此,為了讓詞法分析和語法分析能夠正常工作喜颁,在使用ANTLR4的時候稠氮,需要定義語法(Grammar)。

我們可以把字符流(CharStream)半开,轉(zhuǎn)換成一棵語法分析樹隔披,字符流經(jīng)過詞法分析會變成Token流。Token流再最終組裝成一棵語法分析樹寂拆,其中包含葉子節(jié)點(TerminalNode)和非葉子節(jié)點(RuleNode)奢米。具體語法分析樹如下圖所示:

4.1.2 Grammar

ANTLR官方提供了很多常用的語言的語法文件抓韩,可以進行修改后直接進行復用:https://github.com/antlr/grammars-v4

在使用語法的時候,需要注意以下事項:

  • 語法名稱和文件名要一致鬓长;
  • 語法分析器規(guī)則以小寫字母開始谒拴;
  • 詞法分析器規(guī)則以大寫字母開始;
  • 用'string'單引號引出字符串涉波;
  • 不需要指定開始符號英上;
  • 規(guī)則以分號結(jié)束;
  • ...

4.1.3 ANTLR4實現(xiàn)簡單計算功能

下面通過簡單示例啤覆,說明ANTLR4的用法苍日,需要實現(xiàn)的功能效果如下:

ANTLR示例

1+2 => 1+2=3
1+2*4 => 1+2*4=9
1+2*4-5 => 1+2*4-5=4
1+2*4-5+20/5 => 1+2*4-5+20/5=8
(1+2)*4 => (1+2)*4=12

通過ANTLR處理流程如下圖所示:

整體來說一個原則,遞歸下降窗声。即定義一個表達式(如expr)相恃,可以循環(huán)調(diào)用直接也可以調(diào)用其他表達式,但是最終肯定會有一個最核心的表達式不能再繼續(xù)往下調(diào)用了笨觅。

步驟一:定義詞法規(guī)則文件(CommonLexerRules.g4)

CommonLexerRules.g4

// 定義詞法規(guī)則
lexer grammar CommonLexerRules;
 
//////// 定義詞法
// 匹配ID
ID     : [a-zA-Z]+ ;
// 匹配INT
INT    : [0-9]+    ;
// 匹配換行符
NEWLINE: '\n'('\r'?);
// 跳過空格拦耐、跳格、換行符
WS     : [ \t\n\r]+ -> skip;
 
//////// 運算符
DIV:'/';
MUL:'*';
ADD:'+';
SUB:'-';
EQU:'=';

步驟二:定義語法規(guī)則文件(LibExpr.g4)

LibExpr.g4

// 定于語法規(guī)則
grammar LibExpr;
 
// 導入詞法規(guī)則
import CommonLexerRules;
 
// 詞法根
prog:stat+ EOF?;
 
// 定義聲明
stat:expr (NEWLINE)?         # printExpr
    | ID '=' expr (NEWLINE)? # assign
    | NEWLINE                # blank
    ;
 
// 定義表達式
expr:expr op=('*'|'/') expr # MulDiv
    |expr op=('+'|'-') expr # AddSub
    |'(' expr ')'           # Parens
    |ID                     # Id
    |INT                    # Int
    ;

步驟三:編譯生成文件

如果是Maven工程屋摇,這里在pom文件中添加如下依賴:

ANTLR依賴JAR

<dependencies>
    <dependency>
        <groupId>org.antlr</groupId>
        <artifactId>antlr4</artifactId>
        <version>4.9.3</version>
    </dependency>
    <dependency>
        <groupId>org.antlr</groupId>
        <artifactId>antlr4-runtime</artifactId>
        <version>4.9.3</version>
    </dependency>
</dependencies>

然后揩魂,執(zhí)行Maven編譯命令即可:

Maven編譯命令

mvn generate-sources

步驟四:編寫簡單的示例代碼

待預算的示例文本:

示例文本

1+2
1+2*4
1+2*4-5
1+2*4-5+20/5
(1+2)*4

加減乘除邏輯類:

邏輯實現(xiàn)類

package com.vivo.learn.sql;
 
import java.util.HashMap;
import java.util.Map;
 
/**
 * 重寫訪問器規(guī)則,實現(xiàn)數(shù)據(jù)計算功能
 * 目標:
 *     1+2 => 1+2=3
 *     1+2*4 => 1+2*4=9
 *     1+2*4-5 => 1+2*4-5=4
 *     1+2*4-5+20/5 => 1+2*4-5+20/5=8
 *     (1+2)*4 => (1+2)*4=12
 */
public class LibExprVisitorImpl extends LibExprBaseVisitor<Integer> {
    // 定義數(shù)據(jù)
    Map<String,Integer> data = new HashMap<String,Integer>();
 
    // expr (NEWLINE)?         # printExpr
    @Override
    public Integer visitPrintExpr(LibExprParser.PrintExprContext ctx) {
        System.out.println(ctx.expr().getText()+"="+visit(ctx.expr()));
        return visit(ctx.expr());
    }
 
    // ID '=' expr (NEWLINE)? # assign
    @Override
    public Integer visitAssign(LibExprParser.AssignContext ctx) {
        // 獲取id
        String id = ctx.ID().getText();
        // // 獲取value
        int value = Integer.valueOf(visit(ctx.expr()));
 
        // 緩存ID數(shù)據(jù)
        data.put(id,value);
 
        // 打印日志
        System.out.println(id+"="+value);
 
        return value;
    }
 
    // NEWLINE                # blank
    @Override
    public Integer visitBlank(LibExprParser.BlankContext ctx) {
        return 0;
    }
 
    // expr op=('*'|'/') expr # MulDiv
    @Override
    public Integer visitMulDiv(LibExprParser.MulDivContext ctx) {
        // 左側(cè)數(shù)字
        int left = Integer.valueOf(visit(ctx.expr(0)));
        // 右側(cè)數(shù)字
        int right = Integer.valueOf(visit(ctx.expr(1)));
        // 操作符號
        int opType = ctx.op.getType();
 
        // 調(diào)試
        // System.out.println("visitMulDiv>>>>> left:"+left+",opType:"+opType+",right:"+right);
 
        // 判斷是否為乘法
        if(LibExprParser.MUL==opType){
            return left*right;
        }
 
        // 判斷是否為除法
        return left/right;
 
    }
 
    // expr op=('+'|'-') expr # AddSub
    @Override
    public Integer visitAddSub(LibExprParser.AddSubContext ctx) {
        // 獲取值和符號
 
        // 左側(cè)數(shù)字
        int left = Integer.valueOf(visit(ctx.expr(0)));
        // 右側(cè)數(shù)字
        int right = Integer.valueOf(visit(ctx.expr(1)));
        // 操作符號
        int opType = ctx.op.getType();
 
        // 調(diào)試
        // System.out.println("visitAddSub>>>>> left:"+left+",opType:"+opType+",right:"+right);
 
        // 判斷是否為加法
        if(LibExprParser.ADD==opType){
            return left+right;
        }
 
        // 判斷是否為減法
        return left-right;
 
    }
 
    // '(' expr ')'           # Parens
    @Override
    public Integer visitParens(LibExprParser.ParensContext ctx) {
        // 遞歸下調(diào)
        return visit(ctx.expr());
    }
 
    // ID                     # Id
    @Override
    public Integer visitId(LibExprParser.IdContext ctx) {
        // 獲取id
        String id = ctx.ID().getText();
        // 判斷ID是否被定義
        if(data.containsKey(id)){
            // System.out.println("visitId>>>>> id:"+id+",value:"+data.get(id));
            return data.get(id);
        }
        return 0;
    }
 
    // INT                    # Int
    @Override
    public Integer visitInt(LibExprParser.IntContext ctx) {
        // System.out.println("visitInt>>>>> int:"+ctx.INT().getText());
        return Integer.valueOf(ctx.INT().getText());
    }
 
}

Main函數(shù)打印輸出結(jié)果類:

package com.vivo.learn.sql;
 
import org.antlr.v4.runtime.tree.ParseTree;
 
import java.io.FileNotFoundException;
import java.io.IOException;
import org.antlr.v4.runtime.*;
 
/**
 * 打印語法樹
 */
public class TestLibExprPrint {
 
    // 打印語法樹 input -> lexer -> tokens -> parser -> tree -> print
    public static void main(String args[]){
        printTree("E:\\smartloli\\hadoop\\sql-parser-example\\src\\main\\resources\\testCase.txt");
    }
 
 
    /**
     * 打印語法樹 input -> lexer -> token -> parser -> tree
     * @param fileName
     */
    private static void printTree(String fileName){
        // 定義輸入流
        ANTLRInputStream input = null;
 
        // 判斷文件名是否為空,若不為空炮温,則讀取文件內(nèi)容火脉,若為空,則讀取輸入流
        if(fileName!=null){
            try{
                input = new ANTLRFileStream(fileName);
            }catch(FileNotFoundException fnfe){
                System.out.println("文件不存在柒啤,請檢查后重試倦挂!");
            }catch(IOException ioe){
                System.out.println("文件讀取異常,請檢查后重試担巩!");
            }
        }else{
            try{
                input = new ANTLRInputStream(System.in);
            }catch(FileNotFoundException fnfe){
                System.out.println("文件不存在方援,請檢查后重試!");
 
            }catch(IOException ioe){
                System.out.println("文件讀取異常涛癌,請檢查后重試犯戏!");
            }
        }
 
        // 定義詞法規(guī)則分析器
        LibExprLexer lexer = new LibExprLexer(input);
 
        // 生成通用字符流
        CommonTokenStream tokens = new CommonTokenStream(lexer);
 
        // 語法解析
        LibExprParser parser = new LibExprParser(tokens);
 
        // 生成語法樹
        ParseTree tree = parser.prog();
 
        // 打印語法樹
        // System.out.println(tree.toStringTree(parser));
 
        // 生命訪問器
        LibExprVisitorImpl visitor = new LibExprVisitorImpl();
        visitor.visit(tree);
 
    }
 
}

執(zhí)行代碼,最終輸出結(jié)果如下圖所示:

4.2 Calcite

上述ANTLR內(nèi)容演示了詞法分析和語法分析的簡單流程拳话,但是由于ANTLR要實現(xiàn)SQL查詢先匪,需要自己定義詞法和語法相關文件,然后再使用ANTLR的插件對文件進行編譯弃衍,然后再生成代碼(與Thrift的使用類似呀非,也是先定義接口,然后編譯成對應的語言文件,最后再繼承或者實現(xiàn)這些生成好的類或者接口)岸裙。

4.2.1 原理及優(yōu)勢

而Apache Calcite的出現(xiàn)猖败,大大簡化了這些復雜的工程。Calcite可以讓用戶很方便的給自己的系統(tǒng)套上一個SQL的外殼降允,并且提供足夠高效的查詢性能優(yōu)化恩闻。

  • query language;
  • query optimization拟糕;
  • query execution判呕;
  • data management;
  • data storage送滞;

上述這五個功能侠草,通常是數(shù)據(jù)庫系統(tǒng)包含的常用功能。Calcite在設計的時候就確定了自己只關注綠色的三個部分犁嗅,而把下面數(shù)據(jù)管理和數(shù)據(jù)存儲留給各個外部的存儲或計算引擎边涕。

數(shù)據(jù)管理和數(shù)據(jù)存儲,尤其是數(shù)據(jù)存儲是很復雜的褂微,也會由于數(shù)據(jù)本身的特性導致實現(xiàn)上的多樣性功蜓。Calcite拋棄這兩部分的設計,而是專注于上層更加通用的模塊宠蚂,使得自己能夠足夠的輕量化式撼,系統(tǒng)復雜性得到控制,開發(fā)人員的精力也不至于耗費的太多求厕。

同時著隆,Calcite也沒有重復去早輪子,能復用的東西呀癣,都是直接拿來復用美浦。這也是讓開發(fā)者能夠接受去使用它的一個原因。比如项栏,如下兩個例子:

  • 例子1:作為一個SQL解析器浦辨,關鍵的SQL解析,Calcite沒有重復造輪子沼沈,而是直接使用了開源的JavaCC流酬,來將SQL語句轉(zhuǎn)化為Java代碼,然后進一步轉(zhuǎn)化成一棵抽象語法樹(AST)以供下一階段使用列另;
  • 例子2:為了支持后面會提到的靈活的元數(shù)據(jù)功能康吵,Calcite需要支持運行時編譯Java代碼。默認的JavaC太重访递,需要一個更輕量級的編譯器,Calcite同樣沒有選擇造輪子同辣,而是使用了開源了Janino方案拷姿。

上面的圖是Calcite官方給出的架構(gòu)圖惭载,從圖中我們可以獲取到的信息是,一方面印證了我們上面提到的响巢,Calcite足夠的簡單描滔,沒有做自己不該做的事情;另一方面踪古,也是更重要的含长,Calcite被設計的足夠模塊化和可插拔。

  • 【JDBC Client】:這個模塊用來支持使用JDBC Client的應用伏穆;
  • 【SQL Parser and Validator】:該模塊用來做SQL解析和校驗拘泞;
  • 【Expressions Builder】:用來支持自己做SQL解析和校驗的框架對接;
  • 【Operator Expressions】:該模塊用來處理關系表達式枕扫;
  • 【Metadata Provider】:該模塊用來支持外部自定義元數(shù)據(jù)陪腌;
  • 【Pluggable Rules】:該模塊用來定義優(yōu)化規(guī)則;
  • 【Query Optimizer】:最核心的模塊烟瞧,專注于查詢優(yōu)化诗鸭。

功能模塊的劃分足夠合理,也足夠獨立参滴,使得不用完整集成强岸,而是可以只選擇其中的一部分使用,而基本上每個模塊都支持自定義砾赔,也使得用戶能夠更多的定制系統(tǒng)蝌箍。


上面列舉的這些大數(shù)據(jù)常用的組件都Calcite均有集成,可以看到Hive就是自己做了SQL解析过蹂,只使用了Calcite的查詢優(yōu)化功能十绑。而像Flink則是從解析到優(yōu)化都直接使用了Calcite。

上面介紹的Calcite集成方法酷勺,都是把Calcite的模塊當做庫來使用本橙。如果覺得太重量級,可以選擇更簡單的適配器功能脆诉。通過類似Spark這些框架里自定義的Source或Sink的方式甚亭,來實現(xiàn)和外部系統(tǒng)的數(shù)據(jù)交互操作。


上圖就是比較典型的適配器用法击胜,比如通過Kafka的適配器就能直接在應用層通過SQL亏狰,而底層自動轉(zhuǎn)換成Java和Kafka進行數(shù)據(jù)交互(后面部分有個案例操作)。

4.2.2 Calcite實現(xiàn)KSQL查詢Kafk

參考了EFAK(原Kafka Eagle開源項目)的SQL實現(xiàn)偶摔,來查詢Kafka中Topic里面的數(shù)據(jù)暇唾。

1.常規(guī)SQL查詢

SQL查詢

select * from video_search_query where partition in (0) limit 10

預覽截圖:


2.UDF查詢

SQL查詢

select JSON(msg,'query') as query,JSON(msg,'pv') as pv from video_search_query where `partition` in (0) limit 10

預覽截圖:

4.3 ANTLR4 和 Calcite SQL解析對比

4.3.1 ANTLR4解析SQL

ANTLR4解析SQL的主要流程包含:定義詞法和語法文件、編寫SQL解析邏輯類、主服務調(diào)用SQL邏輯類策州。

1.定義詞法和語法文件

可參考官網(wǎng)提供的開源地址:詳情

2.編寫SQL解析邏輯類

這里瘸味,我們編寫一個實現(xiàn)解析SQL表名的類,具體實現(xiàn)代碼如下所示:

解析表名

public class TableListener extends antlr4.sql.MySqlParserBaseListener {

    private String tableName = null;

    public void enterQueryCreateTable(antlr4.sql.MySqlParser.QueryCreateTableContext ctx) {
        List<MySqlParser.TableNameContext> tableSourceContexts = ctx.getRuleContexts(antlr4.sql.MySqlParser.TableNameContext.class);
        for (antlr4.sql.MySqlParser.TableNameContext tableSource : tableSourceContexts) {
            // 獲取表名
            tableName = tableSource.getText();
        }
    }

    public String getTableName() {
        return tableName;
    }
}

3.主服務調(diào)用SQL邏輯類

對實現(xiàn)SQL解析的邏輯類進行調(diào)用够挂,具體代碼如下所示:

主服務

public class AntlrClient {

    public static void main(String[] args) {
        // antlr4 格式化SQL
        antlr4.sql.MySqlLexer lexer = new antlr4.sql.MySqlLexer(CharStreams.fromString("create table table2 select tid from table1;"));
        antlr4.sql.MySqlParser parser = new antlr4.sql.MySqlParser(new CommonTokenStream(lexer));
        // 定義TableListener
        TableListener listener = new TableListener();
        ParseTreeWalker.DEFAULT.walk(listener, parser.sqlStatements());
        // 獲取表名
        String tableName= listener.getTableName();
        // 輸出表名
        System.out.println(tableName);
    }
}

4.3.2 Calcite解析SQL

Calcite解析SQL的流程相比較ANTLR是比較簡單的旁仿,開發(fā)中無需關注詞法和語法文件的定義和編寫,只需關注具體的業(yè)務邏輯實現(xiàn)孽糖。比如實現(xiàn)一個SQL的COUNT操作枯冈,Calcite實現(xiàn)步驟如下所示。

1.pom依賴

Calcite依賴JAR

<dependencies>
  <!-- 這里對Calcite適配依賴進行封裝办悟,引入下列包即可 -->
  <dependency>
    <groupId>org.smartloli</groupId>
    <artifactId>jsql-client</artifactId>
    <version>1.0.0</version>
  </dependency>
</dependencies>

2.實現(xiàn)代碼

Calcite示例代碼

package com.vivo.learn.sql.calcite;

import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONArray;
import com.alibaba.fastjson.JSONObject;
import org.smartloli.util.JSqlUtils;

public class JSqlClient {
    public static void main(String[] args) {
        JSONObject tabSchema = new JSONObject();
        tabSchema.put("id","integer");
        tabSchema.put("name","varchar");

        JSONArray datasets = JSON.parseArray("[{\"id\":1,\"name\":\"aaa\",\"age\":20},{\"id\":2,\"name\":\"bbb\",\"age\":21},{\"id\":3,\"name\":\"ccc\",\"age\":22}]");

        String tabName = "userinfo";
        String sql = "select count(*) as cnt from \"userinfo\"";
        try{
           String result = JSqlUtils.query(tabSchema,tabName,datasets,sql);
            System.out.println("result: "+result);
        }catch (Exception e){
            e.printStackTrace();
        }

    }
}

3.預覽截圖

4.3.3 對比結(jié)果

綜合對比尘奏,我們從對兩種技術的學習成本、使用復雜度誉尖、以及靈活度來對比罪既,可以優(yōu)先選擇Calcite來作為SQL解析器來處理實際的業(yè)務需求。

五铡恕、總結(jié)

另外琢感,在單機模式的情況下,執(zhí)行計劃可以較為簡單的翻譯成執(zhí)行代碼探熔,但是在分布式領域中驹针,因為計算引擎多種多樣,因此诀艰,還需要一個更加貼近具體計算引擎的描述柬甥,也就是物理計劃。換言之其垄,邏輯計劃只是抽象的一層描述苛蒲,而物理計劃則和具體的計算引擎直接掛鉤。

滿足上述場景绿满,通常都可以引入SQL解析器:

  • 給關系型數(shù)據(jù)庫(比如MySQL臂外、Oracle)這類提供定制化的SQL來作為交互查詢;
  • 給開發(fā)人員提供了JDBC喇颁、ODBC之類和各種數(shù)據(jù)庫的標準接口漏健;
  • 對數(shù)據(jù)分析師等不太會編程語言的但又需要使用數(shù)據(jù)的人;
  • 大數(shù)據(jù)技術組件不自帶SQL的橘霎;

參考資料:

  1. https://github.com/smartloli/EFAK
  2. https://github.com/antlr/antlr4
  3. https://github.com/antlr/grammars-v4
  4. https://github.com/apache/calcite
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末蔫浆,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子姐叁,更是在濱河造成了極大的恐慌瓦盛,老刑警劉巖洗显,帶你破解...
    沈念sama閱讀 219,539評論 6 508
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異谭溉,居然都是意外死亡墙懂,警方通過查閱死者的電腦和手機,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,594評論 3 396
  • 文/潘曉璐 我一進店門扮念,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人碧库,你說我怎么就攤上這事柜与。” “怎么了嵌灰?”我有些...
    開封第一講書人閱讀 165,871評論 0 356
  • 文/不壞的土叔 我叫張陵弄匕,是天一觀的道長。 經(jīng)常有香客問我沽瞭,道長迁匠,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 58,963評論 1 295
  • 正文 為了忘掉前任驹溃,我火速辦了婚禮城丧,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘豌鹤。我一直安慰自己亡哄,他們只是感情好,可當我...
    茶點故事閱讀 67,984評論 6 393
  • 文/花漫 我一把揭開白布布疙。 她就那樣靜靜地躺著蚊惯,像睡著了一般。 火紅的嫁衣襯著肌膚如雪灵临。 梳的紋絲不亂的頭發(fā)上截型,一...
    開封第一講書人閱讀 51,763評論 1 307
  • 那天,我揣著相機與錄音儒溉,去河邊找鬼宦焦。 笑死,一個胖子當著我的面吹牛睁搭,可吹牛的內(nèi)容都是我干的赶诊。 我是一名探鬼主播,決...
    沈念sama閱讀 40,468評論 3 420
  • 文/蒼蘭香墨 我猛地睜開眼园骆,長吁一口氣:“原來是場噩夢啊……” “哼舔痪!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起锌唾,我...
    開封第一講書人閱讀 39,357評論 0 276
  • 序言:老撾萬榮一對情侶失蹤锄码,失蹤者是張志新(化名)和其女友劉穎夺英,沒想到半個月后,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體滋捶,經(jīng)...
    沈念sama閱讀 45,850評論 1 317
  • 正文 獨居荒郊野嶺守林人離奇死亡痛悯,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 38,002評論 3 338
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了重窟。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片载萌。...
    茶點故事閱讀 40,144評論 1 351
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖巡扇,靈堂內(nèi)的尸體忽然破棺而出扭仁,到底是詐尸還是另有隱情,我是刑警寧澤厅翔,帶...
    沈念sama閱讀 35,823評論 5 346
  • 正文 年R本政府宣布乖坠,位于F島的核電站,受9級特大地震影響刀闷,放射性物質(zhì)發(fā)生泄漏熊泵。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點故事閱讀 41,483評論 3 331
  • 文/蒙蒙 一甸昏、第九天 我趴在偏房一處隱蔽的房頂上張望顽分。 院中可真熱鬧,春花似錦筒扒、人聲如沸怯邪。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,026評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽悬秉。三九已至,卻和暖如春冰蘑,著一層夾襖步出監(jiān)牢的瞬間和泌,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 33,150評論 1 272
  • 我被黑心中介騙來泰國打工祠肥, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留武氓,地道東北人。 一個月前我還...
    沈念sama閱讀 48,415評論 3 373
  • 正文 我出身青樓仇箱,卻偏偏與公主長得像县恕,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子剂桥,可洞房花燭夜當晚...
    茶點故事閱讀 45,092評論 2 355

推薦閱讀更多精彩內(nèi)容