自拍偷在线精品自拍偷,亚洲欧美中文日韩v在线观看不卡

如何實(shí)現(xiàn)一個(gè)SQL解析器

大數(shù)據(jù)
本篇文章主要介紹如何實(shí)現(xiàn)一個(gè)SQL解析器來(lái)應(yīng)用的業(yè)務(wù)當(dāng)中,同時(shí)結(jié)合具體的案例來(lái)介紹SQL解析器的實(shí)踐過(guò)程。

一、背景

隨著技術(shù)的不斷的發(fā)展,在大數(shù)據(jù)領(lǐng)域出現(xiàn)了越來(lái)越多的技術(shù)框架。而為了降低大數(shù)據(jù)的學(xué)習(xí)成本和難度,越來(lái)越多的大數(shù)據(jù)技術(shù)和應(yīng)用開(kāi)始支持SQL進(jìn)行數(shù)據(jù)查詢。SQL作為一個(gè)學(xué)習(xí)成本很低的語(yǔ)言,支持SQL進(jìn)行數(shù)據(jù)查詢可以降低用戶使用大數(shù)據(jù)的門檻,讓更多的用戶能夠使用大數(shù)據(jù)。

本篇文章主要介紹如何實(shí)現(xiàn)一個(gè)SQL解析器來(lái)應(yīng)用的業(yè)務(wù)當(dāng)中,同時(shí)結(jié)合具體的案例來(lái)介紹SQL解析器的實(shí)踐過(guò)程。

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

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

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

傳統(tǒng)的SQL查詢,依賴完整的數(shù)據(jù)庫(kù)協(xié)議。比如數(shù)據(jù)存儲(chǔ)在MySQL、Oracle等關(guān)系型數(shù)據(jù)庫(kù)中,有標(biāo)準(zhǔn)的SQL語(yǔ)法。我們可以通過(guò)不同的SQL語(yǔ)句來(lái)實(shí)現(xiàn)業(yè)務(wù)需求,如下圖所示:

但是,在處理海量數(shù)據(jù)的時(shí)候,關(guān)系型數(shù)據(jù)庫(kù)是難以滿足實(shí)際的業(yè)務(wù)需求的,我們需要借助大數(shù)據(jù)生態(tài)圈的技術(shù)組件來(lái)解決實(shí)際的業(yè)務(wù)需求。

2.2 實(shí)際應(yīng)用場(chǎng)景

在使用大數(shù)據(jù)生態(tài)圈的技術(shù)組件時(shí),有些技術(shù)組件是自帶SQL的,比如Hive、Spark、Flink等;而有些技術(shù)組件本身是不帶SQL的,比如Kafka、HBase。下面,我們可以通過(guò)對(duì)比不帶SQL和使用SQL解析器后的場(chǎng)景,如下圖所示:

從上圖中,我們可以看到,圖左邊在我們使用不帶SQL的技術(shù)組件時(shí),實(shí)現(xiàn)一個(gè)查詢時(shí),需要我們編寫不同的業(yè)務(wù)邏輯接口,來(lái)與Kafka、HBase這些技術(shù)組件來(lái)進(jìn)行數(shù)據(jù)交互。如果隨著這類組件的增加,查詢功能復(fù)雜度的增加,那邊每套接口的復(fù)雜度也會(huì)隨之增加,對(duì)于后續(xù)的擴(kuò)展和維護(hù)也是很不方便的。而圖右邊在我們引入SQL解析器后,只需要一套接口來(lái)完成業(yè)務(wù)邏輯,對(duì)于不同的技術(shù)組件進(jìn)行適配即可。

三、什么是SQL解析器?

在選擇SQL解析器應(yīng)用到我們實(shí)際的業(yè)務(wù)場(chǎng)景之前,我們先來(lái)了解一下SQL解析器的核心知識(shí)點(diǎn)。

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

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

  • 在C/C++中,我們可以使用LEX和YACC來(lái)做詞法分析和語(yǔ)法分析
  • 在Java中,我們可以使用JavaCC或ANTLR

在我們使用解析器的過(guò)程當(dāng)中,通常解析器主要包括三部分,它們分別是:詞法解析、語(yǔ)法解析、語(yǔ)義解析。

3.1.1 什么詞法解析?

如何理解詞法解析呢?詞法解析我們可以這么來(lái)進(jìn)行理解,在啟動(dòng)詞法解析任務(wù)時(shí),它將從左到右把字符一個(gè)個(gè)的讀取并加載到解析程序里面,然后對(duì)字節(jié)流進(jìn)行掃描,接著根據(jù)構(gòu)詞規(guī)則識(shí)別字符并切割成一個(gè)個(gè)的詞條,切詞的規(guī)則是遇到空格進(jìn)行分割,遇到分號(hào)時(shí)結(jié)束詞法解析。比如一個(gè)簡(jiǎn)單的SQL如下所示:

SQL示例

SELECT name FROM tab;

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

3.1.2 什么是語(yǔ)法解析?

如何理解語(yǔ)法解析呢?語(yǔ)法解析我們可以這么來(lái)進(jìn)行理解,在啟動(dòng)語(yǔ)法解析任務(wù)時(shí),語(yǔ)法分析的任務(wù)會(huì)在詞法分析的結(jié)果上將詞條序列組合成不同語(yǔ)法短句,組成的語(yǔ)法短句將與相應(yīng)的語(yǔ)法規(guī)則進(jìn)行適配,若適配成功則生成對(duì)應(yīng)的抽象語(yǔ)法樹(shù),否則報(bào)會(huì)拋出語(yǔ)法錯(cuò)誤異常。比如如下SQL語(yǔ)句:

SQL示例

SELECT name FROM tab WHERE id=1001;

約定規(guī)則如下:

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

3.1.3 什么是語(yǔ)義解析?

如何理解語(yǔ)義解析呢?語(yǔ)義解析我們可以這么來(lái)進(jìn)行理解,語(yǔ)義分析的任務(wù)是對(duì)語(yǔ)法解析得到的抽象語(yǔ)法樹(shù)進(jìn)行有效的校驗(yàn),比如字段、字段類型、函數(shù)、表等進(jìn)行檢查。比如如下語(yǔ)句:

SQL示例

SELECT name FROM tab WHERE id=1001;

上述SQL語(yǔ)句,語(yǔ)義分析任務(wù)會(huì)做如下檢查:

  • SQL語(yǔ)句中表名是否存在;
  • 字段name是否存在于表tab中;
  • WHERE條件中的id字段類型是否可以與1001進(jìn)行比較操作。

上述檢查結(jié)束后,語(yǔ)義解析會(huì)生成對(duì)應(yīng)的表達(dá)式供優(yōu)化器去使用。

四、 如何選擇SQL解析器?

在了解了解析器的核心知識(shí)點(diǎn)后,如何選擇合適的SQL解析器來(lái)應(yīng)用到我們的實(shí)際業(yè)務(wù)當(dāng)中呢?下面,我們來(lái)對(duì)比一下主流的兩種SQL解析器。它們分別是ANTLR和Calcite。

4.1 ANTLR

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

使用ANTLR來(lái)實(shí)現(xiàn)一條SQL,執(zhí)行或者實(shí)現(xiàn)的過(guò)程大致是這樣的,實(shí)現(xiàn)詞法文件(.g4),生成詞法分析器和語(yǔ)法分析器,生成抽象語(yǔ)法樹(shù)(也就是我常說(shuō)的AST),然后再遍歷抽象語(yǔ)法樹(shù),生成語(yǔ)義樹(shù),訪問(wèn)統(tǒng)計(jì)信息,優(yōu)化器生成邏輯執(zhí)行計(jì)劃,再生成物理執(zhí)行計(jì)劃去執(zhí)行。

官網(wǎng)示例:

ANTLR表達(dá)式

assign : ID '=' expr ';' ;

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

ANTLR解析器代碼

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

4.1.1 Parser

Parser是用來(lái)識(shí)別語(yǔ)言的程序,其本身包含兩個(gè)部分:詞法分析器和語(yǔ)法分析器。詞法分析階段主要解決的問(wèn)題是關(guān)鍵字以及各種標(biāo)識(shí)符,比如INT(類型關(guān)鍵字)和ID(變量標(biāo)識(shí)符)。語(yǔ)法分析主要是基于詞法分析的結(jié)果,構(gòu)造一顆語(yǔ)法分析數(shù),流程大致如下:

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

我們可以把字符流(CharStream),轉(zhuǎn)換成一棵語(yǔ)法分析樹(shù),字符流經(jīng)過(guò)詞法分析會(huì)變成Token流。Token流再最終組裝成一棵語(yǔ)法分析樹(shù),其中包含葉子節(jié)點(diǎn)(TerminalNode)和非葉子節(jié)點(diǎn)(RuleNode)。具體語(yǔ)法分析樹(shù)如下圖所示:

4.1.2 Grammar

ANTLR官方提供了很多常用的語(yǔ)言的語(yǔ)法文件,可以進(jìn)行修改后直接進(jìn)行復(fù)用:

??https://github.com/antlr/grammars-v4??

在使用語(yǔ)法的時(shí)候,需要注意以下事項(xiàng):

  • 語(yǔ)法名稱和文件名要一致;
  • 語(yǔ)法分析器規(guī)則以小寫字母開(kāi)始;
  • 詞法分析器規(guī)則以大寫字母開(kāi)始;
  • 用'string'單引號(hào)引出字符串;
  • 不需要指定開(kāi)始符號(hào);
  • 規(guī)則以分號(hào)結(jié)束;
  • ...

4.1.3 ANTLR4實(shí)現(xiàn)簡(jiǎn)單計(jì)算功能

下面通過(guò)簡(jiǎn)單示例,說(shuō)明ANTLR4的用法,需要實(shí)現(xiàn)的功能效果如下:

ANTLR示例

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

通過(guò)ANTLR處理流程如下圖所示:

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

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

CommonLexerRules.g4

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

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

LibExpr.g4
// 定于語(yǔ)法規(guī)則
grammar LibExpr;
// 導(dǎo)入詞法規(guī)則
import CommonLexerRules;
// 詞法根
prog:stat+ EOF?;
// 定義聲明
stat:expr (NEWLINE)? # printExpr
| ID '=' expr (NEWLINE)? # assign
| NEWLINE # blank
;
// 定義表達(dá)式
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

步驟四:編寫簡(jiǎn)單的示例代碼

待預(yù)算的示例文本:

示例文本

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

加減乘除邏輯類:

邏輯實(shí)現(xiàn)類

package com.vivo.learn.sql;
import java.util.HashMap;
import java.util.Map;
/**
* 重寫訪問(wèn)器規(guī)則,實(shí)現(xiàn)數(shù)據(jù)計(jì)算功能
* 目標(biāo):
* 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)));
// 操作符號(hào)
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) {
// 獲取值和符號(hào)

// 左側(cè)數(shù)字
int left = Integer.valueOf(visit(ctx.expr(0)));
// 右側(cè)數(shù)字
int right = Integer.valueOf(visit(ctx.expr(1)));
// 操作符號(hào)
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.*;
/**
? 打印語(yǔ)法樹(shù)
*/
public class TestLibExprPrint {
// 打印語(yǔ)法樹(shù) input -> lexer -> tokens -> parser -> tree -> print
public static void main(String args[]){
printTree("E:\\smartloli\\hadoop\\sql-parser-example\\src\\main\\resources\\testCase.txt");
}
/**? 打印語(yǔ)法樹(shù) 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("文件不存在,請(qǐng)檢查后重試!");
}catch(IOException ioe){
System.out.println("文件讀取異常,請(qǐng)檢查后重試!");
}
}else{
try{
input = new ANTLRInputStream(System.in);
}catch(FileNotFoundException fnfe){
System.out.println("文件不存在,請(qǐng)檢查后重試!");}catch(IOException ioe){

System.out.println("文件讀取異常,請(qǐng)檢查后重試!");
}
}
// 定義詞法規(guī)則分析器
LibExprLexer lexer = new LibExprLexer(input);
// 生成通用字符流
CommonTokenStream tokens = new CommonTokenStream(lexer);
// 語(yǔ)法解析
LibExprParser parser = new LibExprParser(tokens);
// 生成語(yǔ)法樹(shù)
ParseTree tree = parser.prog();
// 打印語(yǔ)法樹(shù)
// System.out.println(tree.toStringTree(parser));
// 生命訪問(wèn)器
LibExprVisitorImpl visitor = new LibExprVisitorImpl();
visitor.visit(tree);
}
}

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

4.2 Calcite

上述ANTLR內(nèi)容演示了詞法分析和語(yǔ)法分析的簡(jiǎn)單流程,但是由于ANTLR要實(shí)現(xiàn)SQL查詢,需要自己定義詞法和語(yǔ)法相關(guān)文件,然后再使用ANTLR的插件對(duì)文件進(jìn)行編譯,然后再生成代碼(與Thrift的使用類似,也是先定義接口,然后編譯成對(duì)應(yīng)的語(yǔ)言文件,最后再繼承或者實(shí)現(xiàn)這些生成好的類或者接口)。

4.2.1 原理及優(yōu)勢(shì)

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

  • query language;
  • query optimization;
  • query execution;
  • data management;
  • data storage;

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

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

同時(shí),Calcite也沒(méi)有重復(fù)去早輪子,能復(fù)用的東西,都是直接拿來(lái)復(fù)用。這也是讓開(kāi)發(fā)者能夠接受去使用它的一個(gè)原因。比如,如下兩個(gè)例子:

  • 例子1:作為一個(gè)SQL解析器,關(guān)鍵的SQL解析,Calcite沒(méi)有重復(fù)造輪子,而是直接使用了開(kāi)源的JavaCC,來(lái)將SQL語(yǔ)句轉(zhuǎn)化為Java代碼,然后進(jìn)一步轉(zhuǎn)化成一棵抽象語(yǔ)法樹(shù)(AST)以供下一階段使用;
  • 例子2:為了支持后面會(huì)提到的靈活的元數(shù)據(jù)功能,Calcite需要支持運(yùn)行時(shí)編譯Java代碼。默認(rèn)的JavaC太重,需要一個(gè)更輕量級(jí)的編譯器,Calcite同樣沒(méi)有選擇造輪子,而是使用了開(kāi)源了Janino方案。

上面的圖是Calcite官方給出的架構(gòu)圖,從圖中我們可以獲取到的信息是,一方面印證了我們上面提到的,Calcite足夠的簡(jiǎn)單,沒(méi)有做自己不該做的事情;另一方面,也是更重要的,Calcite被設(shè)計(jì)的足夠模塊化和可插拔。

  • 【JDBC Client】:這個(gè)模塊用來(lái)支持使用JDBC Client的應(yīng)用;
  • 【SQL Parser and Validator】:該模塊用來(lái)做SQL解析和校驗(yàn);
  • 【Expressions Builder】:用來(lái)支持自己做SQL解析和校驗(yàn)的框架對(duì)接;
  • 【Operator Expressions】:該模塊用來(lái)處理關(guān)系表達(dá)式;
  • 【Metadata Provider】:該模塊用來(lái)支持外部自定義元數(shù)據(jù);
  • 【Pluggable Rules】:該模塊用來(lái)定義優(yōu)化規(guī)則;
  • 【Query Optimizer】:最核心的模塊,專注于查詢優(yōu)化。

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

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

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

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

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

參考了EFAK(原Kafka Eagle開(kāi)源項(xiàng)目)的SQL實(shí)現(xiàn),來(lái)查詢Kafka中Topic里面的數(shù)據(jù)。

1).常規(guī)SQL查詢

SQL查詢

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

預(yù)覽截圖:

2).UDF查詢

SQL查詢

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

預(yù)覽截圖:

4.3 ANTLR4 和 Calcite SQL解析對(duì)比

4.3.1 ANTLR4解析SQL

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

1)..定義詞法和語(yǔ)法文件

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

2).編寫SQL解析邏輯類

這里,我們編寫一個(gè)實(shí)現(xiàn)解析SQL表名的類,具體實(shí)現(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).主服務(wù)調(diào)用SQL邏輯類

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

主服務(wù)

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是比較簡(jiǎn)單的,開(kāi)發(fā)中無(wú)需關(guān)注詞法和語(yǔ)法文件的定義和編寫,只需關(guān)注具體的業(yè)務(wù)邏輯實(shí)現(xiàn)。比如實(shí)現(xiàn)一個(gè)SQL的COUNT操作,Calcite實(shí)現(xiàn)步驟如下所示。

1).pom依賴

Calcite依賴JAR

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

2).實(shí)現(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");

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).預(yù)覽截圖

4.3.3 對(duì)比結(jié)果

綜合對(duì)比,我們從對(duì)兩種技術(shù)的學(xué)習(xí)成本、使用復(fù)雜度、以及靈活度來(lái)對(duì)比,可以優(yōu)先選擇Calcite來(lái)作為SQL解析器來(lái)處理實(shí)際的業(yè)務(wù)需求。

五、總結(jié)

另外,在單機(jī)模式的情況下,執(zhí)行計(jì)劃可以較為簡(jiǎn)單的翻譯成執(zhí)行代碼,但是在分布式領(lǐng)域中,因?yàn)橛?jì)算引擎多種多樣,因此,還需要一個(gè)更加貼近具體計(jì)算引擎的描述,也就是物理計(jì)劃。換言之,邏輯計(jì)劃只是抽象的一層描述,而物理計(jì)劃則和具體的計(jì)算引擎直接掛鉤。

滿足上述場(chǎng)景,通常都可以引入SQL解析器:

  • 給關(guān)系型數(shù)據(jù)庫(kù)(比如MySQL、Oracle)這類提供定制化的SQL來(lái)作為交互查詢;
  • 給開(kāi)發(fā)人員提供了JDBC、ODBC之類和各種數(shù)據(jù)庫(kù)的標(biāo)準(zhǔn)接口;
  • 對(duì)數(shù)據(jù)分析師等不太會(huì)編程語(yǔ)言的但又需要使用數(shù)據(jù)的人;
  • 大數(shù)據(jù)技術(shù)組件不自帶SQL的;
責(zé)任編輯:龐桂玉 來(lái)源: vivo互聯(lián)網(wǎng)技術(shù)
相關(guān)推薦

2019-07-05 08:39:39

GoSQL解析器

2023-12-30 13:33:36

Python解析器JSON

2017-02-14 10:20:43

Java Class解析器

2022-06-28 08:17:10

JSON性能反射

2023-07-25 14:24:33

元素JSX解析器

2014-05-15 09:45:58

Python解析器

2011-11-28 15:40:52

wiresharkRDP解析器

2015-02-10 14:32:37

XSS漏洞XSS

2014-05-06 09:27:54

2009-03-19 09:26:05

RSS解析器MagpieRSS

2011-04-01 16:16:27

JavaScript

2021-03-16 10:39:29

SpringBoot參數(shù)解析器

2020-12-02 10:13:45

JacksonJDK解析器

2010-02-22 13:38:50

Python解析器

2010-02-22 16:51:03

Python 解析器

2022-06-21 09:38:52

UnboundDNSLinux

2022-02-14 13:58:32

操作系統(tǒng)JSON格式鴻蒙

2017-12-12 15:24:32

Web Server單線程實(shí)現(xiàn)

2020-09-08 11:21:48

SQL生成器跨庫(kù)

2014-04-14 15:54:00

print()Web服務(wù)器
點(diǎn)贊
收藏

51CTO技術(shù)棧公眾號(hào)