1. 程式人生 > >[Spark SQL] 原始碼解析之Parser

[Spark SQL] 原始碼解析之Parser

前言

由上篇部落格我們知道了SparkSql整個解析流程如下:

  • sqlText 經過 SqlParser 解析成 Unresolved LogicalPlan;
  • analyzer 模組結合catalog進行繫結,生成 resolved LogicalPlan;
  • optimizer 模組對 resolved LogicalPlan 進行優化,生成 optimized LogicalPlan;
  • SparkPlan 將 LogicalPlan 轉換成PhysicalPlan;
  • prepareForExecution()將 PhysicalPlan 轉換成可執行物理計劃;
  • 使用 execute()執行可執行物理計劃;

詳解Parser模組

Parser就是將SQL字串切分成一個個Token,再根據一定語義規則解析為一棵語法樹。我們寫的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,遍歷節點有兩種模式Listener和Visitor。

Listener模式是被動式遍歷,antlr生成類ParseTreeListener,這個類裡面包含了所有進入語法樹中每個節點和退出每個節點時要進行的操作。我們只需要實現我們需要的節點事件邏輯程式碼即可,再例項化一個遍歷類ParseTreeWalker,antlr會自上而下的遍歷所有節點,以完成我們的邏輯處理;

Visitor則是主動遍歷模式,需要我們顯示的控制我們的遍歷順序。該模式可以實現在不改變各元素的類的前提下定義作用於這些元素的新操作。SparkSql用的就是此方式來遍歷節點的。

通過詞法解析和語法解析將SQL語句解析成了ANTLR 4的語法樹結構ParseTree。然後在parsePlan中,使用AstBuilder將ANTLR 4語法樹結構轉換成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語法結構轉換為catalyst表示式的關鍵類。

可以看到程式碼3中parsePlan方法先執行parse方法(程式碼4),在程式碼4中先後例項化了分詞解析和語法解析類,最後將antlr的語法解析器parser:SqlBaseParser 傳給了程式碼3中的柯里化函式,使用astBuilder轉化為catalyst表示式,可以看到首先呼叫的是visitSingleStatement,singleStatement為語法檔案中定義的最頂級節點,接下來就是利用antlr的visitor模式顯示的遍歷整個語法樹,將所有的節點都替換成了LogicalPlan 或者TableIdentifier。
通過Parser解析後的AST語法樹如圖所示: