Spark SQL源码剖析(二)Antlr4剖析Sql并天生树

Spark SQL原理剖析前言:

Spark SQL源码剖析(一)SQL剖析框架Catalyst流程概述

这一次要最先真正先容Spark剖析SQL的流程,首先是从Sql Parse阶段最先,简朴点说,这个阶段就是使用Antlr4,将一条Sql语句剖析成语法树。

可能有童鞋没接触过antlr4这个内容,推荐看看《antlr4权威指南》前四章,看完最少知道antlr4能干嘛。我这里就不多先容了。

这篇首先先先容挪用spark.sql()时刻的流程,再看看antlr4在这个其中的主要功能,最后再将探讨Logical Plan究竟是什么器械。

初始流程

当你挪用spark.sql的时刻,会挪用下面的方式:

  def sql(sqlText: String): DataFrame = {
    Dataset.ofRows(self, sessionState.sqlParser.parsePlan(sqlText))
  }

parse sql阶段主要是parsePlan(sqlText)这一部门。而这里又会辗转去org.apache.spark.sql.catalyst.parser.AbstractSqlParser挪用parse方式。这里贴下要害代码。

  protected def parse[T](command: String)(toResult: SqlBaseParser => T): T = {
    logDebug(s"Parsing command: $command")

    val lexer = new SqlBaseLexer(new UpperCaseCharStream(CharStreams.fromString(command)))
    lexer.removeErrorListeners()
    lexer.addErrorListener(ParseErrorListener)
    lexer.legacy_setops_precedence_enbled = SQLConf.get.setOpsPrecedenceEnforced

    val tokenStream = new CommonTokenStream(lexer)
    val parser = new SqlBaseParser(tokenStream)
    parser.addParseListener(PostProcessor)
    parser.removeErrorListeners()
    parser.addErrorListener(ParseErrorListener)
    parser.legacy_setops_precedence_enbled = SQLConf.get.setOpsPrecedenceEnforced

    try {
      try {
        // first, try parsing with potentially faster SLL mode
        parser.getInterpreter.setPredictionMode(PredictionMode.SLL)
        toResult(parser)
      }
      catch {
        case e: ParseCancellationException =>
          // if we fail, parse with LL mode
          tokenStream.seek(0) // rewind input stream
          parser.reset()

          // Try Again.
          parser.getInterpreter.setPredictionMode(PredictionMode.LL)
          toResult(parser)
      }
    }
    catch {
      case e: ParseException if e.command.isDefined =>
        throw e
      case e: ParseException =>
        throw e.withCommand(command)
      case e: AnalysisException =>
        val position = Origin(e.line, e.startPosition)
        throw new ParseException(Option(command), e.message, position, position)
    }
  }

可以发现,这内里的处置逻辑,无论是SqlBaseLexer照样SqlBaseParser都是Antlr4的器械,包罗最后的toResult(parser)也是挪用接见者模式的类去遍历语法树来天生Logical Plan。若是对antlr4有一定领会,那么对这里这些器械一定不会生疏。那我们接下来看看Antlr4在这其中的角色。

Antlr4天生语法树

Spark提供了一个.g4文件,编译的时刻会使用Antlr凭据这个.g4天生对应的词法剖析类和语法剖析类,同时还使用了接见者模式,用以构建Logical Plan(语法树)。

接见者模式简朴说就是会去遍历天生的语法树(针对语法树中每个节点天生一个visit方式),以及返回响应的值。我们接下来看看一条简朴的select语句天生的树是什么样子。

Spark SQL源码剖析(二)Antlr4剖析Sql并天生树

这个sqlBase.g4文件我们也可以直接拿出来玩,直接复制出来,用antlr相关工具就可以天生一个天生一个剖析SQL的图了。

Spark SQL源码剖析(二)Antlr4剖析Sql并天生树

这里antlr4和grun都已经存储成bat文件,以是可以直接挪用,现实下令在《antlr4权威指南》说得很详细了就不先容了。挪用完后就会天生这样的语法树。

Spark SQL源码剖析(二)Antlr4剖析Sql并天生树

这里,将SELECT TABLE_A.B FROM TABLE_A,转换成一棵语法树。我们可以看到这颗语法树非常庞大,这是由于SQL剖析中,要适配这种SELECT语句之外,另有许多其他类型的语句,好比INSERT,ALERT等等。Spark SQL这个模块的最终目的,就是将这样的一棵语法树转换成一个可执行的Dataframe(RDD)。

Centos安装docker+vulhub搭建

我们现阶段的目的则是要先天生Logical Plan,Spark使用Antlr4的接见者模式,天生Logical Plan。这里顺便说下怎么实现接见者模式吧,在使用antlr4下令的时刻,加上-visit参数就会天生SqlBaseBaseVisitor,内里提供了默认的接见各个节点的触发方式。我们可以通过继续这个类,重写对应节点的visit方式,实现自己的接见逻辑,而这个继续的类就是org.apache.spark.sql.catalyst.parser.AstBuilder。

通过考察这棵树,我们可以发现针对我们的SELECT语句,对照主要的一个节点,是querySpecification节点,现实上,在AstBuilder类中,visitQuerySpecification也是对照主要的一个方式(接见对应节点时触发),正是在这个方式中天生主要的Logical Plan的。

接下来重点看这个方式,以及探讨Logical Plan。

天生Logical Plan

我们先看看AstBuilder中的代码:

class AstBuilder(conf: SQLConf) extends SqlBaseBaseVisitor[AnyRef] with Logging {
  ......其他代码
  override def visitQuerySpecification(
      ctx: QuerySpecificationContext): LogicalPlan = withOrigin(ctx) {
    val from = OneRowRelation().optional(ctx.fromClause) {  //若是有FROM语句,天生对应的Logical Plan
      visitFromClause(ctx.fromClause)
    }
    withQuerySpecification(ctx, from)
  }
  ......其他代码
  

代码中会先判断是否有FROM子语句,有的话会去天生对应的Logical Plan,再挪用withQuerySpecification()方式,而withQuerySpecification()方式是对照焦点的一个方式。它会处置包罗SELECT,FILTER,GROUP BY,HAVING等子语句的逻辑。

代码对照长就不贴了,有兴趣的童鞋可以去看看,大意就是使用scala的模式匹配,匹配差别的子语句天生差别的Logical Plan。

然后再来说说最终天生的LogicalPlan,LogicalPlan实在是继续自TreeNode,以是本质上LogicalPlan就是一棵树。

而现实上,LogicalPlan另有多个子类,划分示意差别的SQL子语句。

  • LeafNode,叶子节点,一样平常用来示意用户下令
  • UnaryNode,一元节点,示意FILTER等操作
  • BinaryNode,二元节点,示意JOIN,GROUP BY等操作

这里一元二元这些都是对应关系代数方面的知识,在学数据库理论的时刻一定有接触过,不外估量都还给先生了吧(/偷笑)。不外一元二元基本上也就是用来区分详细的操作,如上面说的FILTER,或是JOIN等,也不是很庞大。这三个类都位于org.apache.spark.sql.catalyst.plans.logical.LogicalPlan中,有兴趣的童鞋可以看看。尔后,这三个类又会有多个子类,用以示意差别的情形,这里就不再赘述。

最后看看用一个测试案例,看看会天生什么吧。示例中简朴天生一个暂且的view,然后直接select查询这个view。代码如下:

    val df = Seq((1, 1)).toDF("key", "value")
    df.createOrReplaceTempView("src")
    val queryCaseWhen = sql("select key from src ")

最终经由parse SQL后会酿成如下的内容:

'Project ['key]
+- 'UnresolvedRelation `src`

这个Project是UnaryNode的一个子类(SELECT自然是一元节点),解释我们要查询的字段是key。

UnresolvedRelation是一个新的观点,这里顺便说下,我们通过SQL parse天生的这棵树,实在叫Unresolved LogicalPlan,这里的Unresolved的意思说,还不知道src是否存在,或它的元数据是什么样,只有通过Analysis阶段后,才会把Unresolved酿成Resolved LogicalPlan。这里的意思可以理解为,读取名为src的表,但这张表的情形未知,有待验证。

总的来说,我们的示例足够简朴直接,以是内容会对照少,不外拿来学习是足够了。

下一个阶段是要使用这棵树举行剖析验证了,也就是Analysis阶段,这一块留到下篇先容吧。

以上~

原创文章,作者:28x29新闻网,如若转载,请注明出处:https://www.28x29.com/archives/5781.html