擴(kuò)展 Spark SQL 解析

大家好久不見了,今天跟大家分享下Spark吧,談?wù)勅绾涡薷腟park SQL解析,讓其更符合你的業(yè)務(wù)邏輯。好,我們開始吧...
理論基礎(chǔ)
ANTLR
Antlr4是一款開源的語法分析器生成工具,能夠根據(jù)語法規(guī)則文件生成對(duì)應(yīng)的語法分析器?,F(xiàn)在很多流行的應(yīng)用和開源項(xiàng)目里都有使用,比如Hadoop、Hive以及Spark等都在使用ANTLR來做語法分析。
ANTLR 語法識(shí)別一般分為二個(gè)階段:
1.詞法分析階段 (lexical analysis)
對(duì)應(yīng)的分析程序叫做 lexer ,負(fù)責(zé)將符號(hào)(token)分組成符號(hào)類(token class or token type)
2.解析階段
根據(jù)詞法,構(gòu)建出一棵分析樹(parse tree)或叫語法樹(syntax tree)

ANTLR的語法文件,非常像電路圖,從入口到出口,每個(gè)Token就像電阻,連接線就是短路點(diǎn)。

語法文件(*.g4)
上面截圖對(duì)應(yīng)的語法文件片段,定義了兩部分語法,一部分是顯示表達(dá)式和賦值,另外一部分是運(yùn)算和表達(dá)式定義。
stat: ? expr NEWLINE ? ? ? ? ? ? ? # printExpr
? | ? ID '=' expr NEWLINE ? ? ? ? # assign
? | ? NEWLINE ? ? ? ? ? ? ? ? ? ? # blank
? ;
expr: ? expr op=('*'|'/') expr ? ? # MulDiv
? | ? expr op=('+'|'-') expr ? ? # AddSub
? | ? INT ? ? ? ? ? ? ? ? ? ? ? ? # int
? | ? ID ? ? ? ? ? ? ? ? ? ? ? ? # id
? | ? '(' expr ')' ? ? ? ? ? ? ? # parens
? ;
接下來,加上定義詞法部分,就能形成完整的語法文件。
完整語法文件:
grammar LabeledExpr; // rename to distinguish from Expr.g4
prog: ? stat+ ;
stat: ? expr NEWLINE ? ? ? ? ? ? ? # printExpr
? | ? ID '=' expr NEWLINE ? ? ? ? # assign
? | ? NEWLINE ? ? ? ? ? ? ? ? ? ? # blank
? ;
expr: ? expr op=('*'|'/') expr ? ? # MulDiv
? | ? expr op=('+'|'-') expr ? ? # AddSub
? | ? INT ? ? ? ? ? ? ? ? ? ? ? ? # int
? | ? ID ? ? ? ? ? ? ? ? ? ? ? ? # id
? | ? '(' expr ')' ? ? ? ? ? ? ? # parens
? ;
MUL : ? '*' ; // assigns token name to '*' used above in grammar
DIV : ? '/' ;
ADD : ? '+' ;
SUB : ? '-' ;
ID : ? [a-zA-Z]+ ; ? ? // match identifiers
INT : ? [0-9]+ ; ? ? ? ? // match integers
NEWLINE:'\r'? '\n' ; ? ? // return newlines to parser (is end-statement signal)
WS : ? [ \t]+ -> skip ; // toss out whitespace
SqlBase.g4
Spark的語法文件,在sql下的catalyst模塊里,如下圖:

擴(kuò)展語法定義
一條正常SQL,例如 Select t.id,t.name from t ?, 現(xiàn)在我們?yōu)槠涮砑右粋€(gè) JACKY表達(dá)式,令其出現(xiàn)在 Select 后面 ,形成一條語句
Select t.id,t.name JACKY(2) from t
我們先看一下正常的語法規(guī)則:

現(xiàn)在我們添加一個(gè) jackyExpression

jackExpression 本身的規(guī)則就是 JACKY加上括號(hào)包裹的一個(gè)數(shù)字

將 JACKY 添加為token

修改語法文件 如下:
jackyExpression
? : JACKY'(' number ')'
? //expression
? ;
namedExpression
? : expression (AS? (identifier | identifierList))?
? ;
namedExpressionSeq
? : namedExpression (',' namedExpression | jackyExpression )*
? ;
擴(kuò)展邏輯計(jì)劃
經(jīng)過上面的修改,就可以測(cè)試語法規(guī)則,是不是符合預(yù)期了,下面是一顆解析樹,我們可以看到j(luò)ackyExpression已經(jīng)可以正常解析了。

Spark 執(zhí)行流程
這里引用一張經(jīng)典的Spark SQL架構(gòu)圖

我們輸入的 SQL語句 首先被解析成 Unresolved Logical Pan ,對(duì)應(yīng)的是

給邏輯計(jì)劃添加遍歷方法:
?override def visitJackyExpression(ctx: JackyExpressionContext): String = withOrigin(ctx) {
? ?println("this is astbuilder jacky = "+ctx.number().getText)
? ?this.jacky = ctx.number().getText.toInt
? ?ctx.number().getText
}
再處理namedExpression的時(shí)候,添加jackyExpression處理
? ?// Expressions.
? ?val expressions = Option(namedExpressionSeq).toSeq
? ? .flatMap(_.namedExpression.asScala)
? ? .map(typedVisit[Expression])
//jackyExpression 處理
? ?if(namedExpressionSeq().jackyExpression()!=null && namedExpressionSeq().jackyExpression().size() > 0){
? ? ?visitJackyExpression(namedExpressionSeq().jackyExpression().get(0))
? }
好了,到這里從邏輯計(jì)劃處理就完成了,有了邏輯計(jì)劃,就可以在后續(xù)物理計(jì)劃中添加相應(yīng)的處理邏輯就可以了(還沒研究明白... Orz)。
測(cè)試
測(cè)試用例
public class Case4 {
? ?public static void main(String[] args) {
? ? ? ?CharStream ca = CharStreams.fromString("SELECT `b`.`id`,`b`.`class` JACKY(2) FROM `b` LIMIT 10");
? ? ? ?SqlBaseLexer lexer = new SqlBaseLexer(ca);
? ? ? ?SqlBaseParser sqlBaseParser = new SqlBaseParser(new CommonTokenStream(lexer));
? ? ? ?ParseTree parseTree = sqlBaseParser.singleStatement();
? ? ? ?AstBuilder astBuilder = new AstBuilder();
? ? ? ?astBuilder.visit(parseTree);
? ? ? ?System.out.println(parseTree.toStringTree(sqlBaseParser));
? ? ? ?System.out.println(astBuilder.jacky());
? }
}
執(zhí)行結(jié)果

關(guān)注 【?麒思妙想】解鎖更多硬核。
歷史文章導(dǎo)讀:
如果文章對(duì)您有那么一點(diǎn)點(diǎn)幫助,我將倍感榮幸
歡迎??關(guān)注、在看、點(diǎn)贊、轉(zhuǎn)發(fā)?
