[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语法树如图所示:

本文参与腾讯云自媒体分享计划,欢迎正在阅读的你也加入,一起分享。

发表于

我来说两句

0 条评论
登录 后参与评论

相关文章

来自专栏Spark学习技巧

戳破 | hive on spark 调优点

微信交流群里有人问浪尖hive on spark如何调优,当时浪尖时间忙没时间回答,这里就给出一篇文章详细聊聊。强调一下资源设置调优,这个强经验性质的,这里给出...

26930
来自专栏华章科技

以卖香蕉为例,从4个方面了解SQL的数据汇总

导读:面对一个新数据集时,人们往往会关心数据中的异常值、数据的分布形式、行列之间的关系等。SQL是一种专为数据计算设计的语言,其中已经内置了许多数据汇总函数,也...

10830
来自专栏一个会写诗的程序员的博客

mongodb 中的like 怎么使用LIKE模糊查询userName包含A字母的数据(%A%)LIKE模糊查询userName以字母A开头的数据(A%)

12730
来自专栏Java架构沉思录

如何优雅地过滤敏感词

敏感词过滤功能在很多地方都会用到,理论上在Web应用中,只要涉及用户输入的地方,都需要进行文本校验,如:XSS校验、SQL注入检验、敏感词过滤等。今天着重讲讲如...

55010
来自专栏swag code

JDBC一般流程

1)注册驱动,在static代码块中注册,这里用Class.forName方法来检查驱动类的方式:

18260
来自专栏一个会写诗的程序员的博客

SQL中的DML、DDL以及DCL是什么?

DML(data manipulation language)是数据操纵语言:它们是SELECT、UPDATE、INSERT、DELETE,就象它的名字一样,这...

10740
来自专栏一个会写诗的程序员的博客

JPA 原生CRUD 语句

@Modifying @Query(value = "delete from t_sys_org_user where org_id=?1 and user_...

10320
来自专栏Spark学习技巧

陌陌:使用Spark SQL和Alluxio加速Ad Hoc查询

星球里经常有人问,如何保存sparkstreaming状态,回答的时候也会说道Alluxio。可能很多公司并没有去做Alluxio相关的使用。希望通过本文,大家...

33330
来自专栏飞总聊IT

谷歌又傻X之BigQuery ML

最近工作忙,又努力在写干活,没怎么关注互联网行业的发展。周末好不容易补补课,就发现了谷歌在其非常成功的云产品BigQuery上发布了BigQuery ML。说白...

16620
来自专栏Java架构沉思录

Mybatis的SqlSession是如何运行的

SqlSession是Mybatis最重要的构建之一,可以简单的认为Mybatis一系列的配置目的是生成类似JDBC生成的Connection对象的SqlSes...

20720

扫码关注云+社区

领取腾讯云代金券

年度创作总结 领取年终奖励