前言
由上篇博客我們知道了SparkSql整個解析流程如下:
- sqlText 經(jīng)過 SqlParser 解析成 Unresolved LogicalPlan;
- analyzer 模塊結(jié)合catalog進行綁定,生成 resolved LogicalPlan;
- optimizer 模塊對 resolved LogicalPlan 進行優(yōu)化,生成 optimized LogicalPlan;
- SparkPlan 將 LogicalPlan 轉(zhuǎn)換成PhysicalPlan;
- prepareForExecution()將 PhysicalPlan 轉(zhuǎn)換成可執(zhí)行物理計劃;
- 使用 execute()執(zhí)行可執(zhí)行物理計劃;
詳解Parser模塊
Parser就是將SQL字符串切分成一個個Token,再根據(jù)一定語義規(guī)則解析為一棵語法樹。我們寫的sql語句只是一個字符串而已,首先需要將其通過詞法解析和語法解析生成語法樹,Spark1.x版本使用的是scala原生的parser語法解析器,從2.x后改用的是第三方語法解析工具ANTLR4, 在性能上有了較大的提升。
antlr4的使用需要定義一個語法文件,sparksql的語法文件的路徑在sql/catalyst/src/main/antlr4/org/apache/spark/sql/catalyst/parser/SqlBase.g4
antlr可以使用插件自動生成詞法解析和語法解析代碼,在SparkSQL中詞法解析器SqlBaseLexer和語法解析器SqlBaseParser,遍歷節(jié)點有兩種模式Listener和Visitor。
Listener模式是被動式遍歷,antlr生成類ParseTreeListener,這個類里面包含了所有進入語法樹中每個節(jié)點和退出每個節(jié)點時要進行的操作。我們只需要實現(xiàn)我們需要的節(jié)點事件邏輯代碼即可,再實例化一個遍歷類ParseTreeWalker,antlr會自上而下的遍歷所有節(jié)點,以完成我們的邏輯處理;
Visitor則是主動遍歷模式,需要我們顯示的控制我們的遍歷順序。該模式可以實現(xiàn)在不改變各元素的類的前提下定義作用于這些元素的新操作。SparkSql用的就是此方式來遍歷節(jié)點的。
通過詞法解析和語法解析將SQL語句解析成了ANTLR 4的語法樹結(jié)構(gòu)ParseTree。然后在parsePlan中,使用AstBuilder將ANTLR 4語法樹結(jié)構(gòu)轉(zhuǎn)換成catalyst表達式邏輯計劃logical plan。具體看源碼:
// 代碼1
val spark = SparkSession
.builder
.appName("SparkSQL Test")
.master("local[4]")
.getOrCreate()
spark.sql("select * from table").show(false)
---
// 代碼2
def sql(sqlText: String): DataFrame = {
Dataset.ofRows(self, sessionState.sqlParser.parsePlan(sqlText))
}
---
// 代碼3
override def parsePlan(sqlText: String): LogicalPlan = parse(sqlText) { parser =>
astBuilder.visitSingleStatement(parser.singleStatement()) match {
case plan: LogicalPlan => plan
case _ =>
val position = Origin(None, None)
throw new ParseException(Option(sqlText), "Unsupported SQL statement", position, position)
}
}
---
// 代碼4
protected def parse[T](command: String)(toResult: SqlBaseParser => T): T = {
logInfo(s"Parsing command: $command")
val lexer = new SqlBaseLexer(new ANTLRNoCaseStringStream(command))
lexer.removeErrorListeners()
lexer.addErrorListener(ParseErrorListener)
val tokenStream = new CommonTokenStream(lexer)
val parser = new SqlBaseParser(tokenStream)
parser.addParseListener(PostProcessor)
parser.removeErrorListeners()
parser.addErrorListener(ParseErrorListener)
try {
try {
// first, try parsing with potentially faster SLL mode
parser.getInterpreter.setPredictionMode(PredictionMode.SLL)
toResult(parser)
...
代碼2中的sqlParser為 SparkSqlParser,其成員變量val astBuilder = new SparkSqlAstBuilder(conf)
是將antlr語法結(jié)構(gòu)轉(zhuǎn)換為catalyst表達式的關鍵類。
可以看到代碼3中parsePlan方法先執(zhí)行parse方法(代碼4),在代碼4中先后實例化了分詞解析和語法解析類,最后將antlr的語法解析器parser:SqlBaseParser 傳給了代碼3中的柯里化函數(shù),使用astBuilder轉(zhuǎn)化為catalyst表達式,可以看到首先調(diào)用的是visitSingleStatement,singleStatement為語法文件中定義的最頂級節(jié)點,接下來就是利用antlr的visitor模式顯示的遍歷整個語法樹,將所有的節(jié)點都替換成了LogicalPlan 或者TableIdentifier。
通過Parser解析后的AST語法樹如圖所示: