java sqlparse ast树_Spark SQL源码解析(二)Antlr4解析Sql并生成树_薛定谔的喜欢的博客-程序员宅基地

技术标签: java sqlparse ast树  

Spark SQL原理解析前言:

这一次要开始真正介绍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语句生成的树是什么样子。

2a3a9f9e9c9370085ef0572a2ba6bd82.png

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

f7e8830df9b6da6e8fc30b3f9fcb00bf.png

这里antlr4和grun都已经存储成bat文件,所以可以直接调用,实际命令在《antlr4权威指南》说得很详细了就不介绍了。调用完后就会生成这样的语法树。

8bde7eed828ae2d831e0ae100dc34a13.png

这里,将SELECT TABLE_A.B FROM TABLE_A,转换成一棵语法树。我们可以看到这颗语法树非常复杂,这是因为SQL解析中,要适配这种SELECT语句之外,还有很多其他类型的语句,比如INSERT,ALERT等等。Spark SQL这个模块的最终目标,就是将这样的一棵语法树转换成一个可执行的Dataframe(RDD)。

我们现阶段的目标则是要先生成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阶段,这一块留到下篇介绍吧。

以上~

版权声明:本文为博主原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。
本文链接:https://blog.csdn.net/weixin_35261775/article/details/114880564

智能推荐

pytorch安装记录_要好好学习グッ!(๑•̀ㅂ•́)و✧的博客-程序员宅基地

pytorch安装记录(mx250显卡)CPU版本GPU版本新的改变功能快捷键合理的创建标题,有助于目录的生成如何改变文本的样式插入链接与图片如何插入一段漂亮的代码片生成一个适合你的列表创建一个表格设定内容居中、居左、居右SmartyPants创建一个自定义列表如何创建一个注脚注释也是必不可少的KaTeX数学公式新的甘特图功能,丰富你的文章UML 图表FLowchart流程图导出与导入导出导入CPU版本最开始安装的是CPU版本的pytorch,因为担心小破显卡不管用,实在是练习上有需要才准备安装的GPU

机器学习算法基础——决策树和随机深林_Grateful_Dead424的博客-程序员宅基地

32.决策树之信息论基础决策树决策树思想的来源非常朴素,程序设计中的条件分支结构就是if-then结构,最早的决策树就是利用这类结构分割数据的一种分类学习方法猜谁是冠军?假设有32支球队每猜一次给一块钱,告诉我是否猜对了,那么我需要掏多少钱才能知道谁是冠军?我可以把球编上号,从1到32,然后提问:冠 军在1-16号吗?依次询问,只需要五次(log32=5),就可以知道结果。信息熵“谁是世界杯冠军”的信息量应该比5比特少。香农指出,它的准确信息量应该是: H = -(p1lo.

js里的数据类型判断_吴英琦的博客-程序员宅基地

js里有很多种数据类型判断,它们有这各自的优缺点,接下来就由我来为大家一一讲解吧!一, 数据判断数据判断的方法有四种分别是typeof、instanceof、constructor、Object.prototype.toString.call()、jquery.type()1.typeofconsole.log( typeof 100, //"number" typeof 'abc', //"string" typeof false, //"boolean" .

What is FAE?_hongjiujing的博客-程序员宅基地

FAE指Field Application Engineer,就是技术支持工程师/现场应用工程师。FAE stands for: failure analysis engineer,是失效分析工程师,就是技术支持工程师。AE(偶觉得AE牛过FAE):Application Engineer,应用工程师都是牛人,因为专注的是一个方面;而FAE现场应用工程师更加专注系统,可能在解决某个具体问题的时候,

vue.js选择if(条件渲染)详解_黄小二哥的博客-程序员宅基地

vue.js选择if(条件渲染)详解一、总结一句话总结:v-if 1 <!DOCTYPE html> 2 <html lang="en"> 3 <head> 4 <meta charset="UTF-8"> 5 <title>v-if</title> 6 <link re...

堆与堆排序_陈笨蛋@的博客-程序员宅基地

堆堆是一个完全二叉树,完全二叉树就是二叉树除了最后一层外均为满的,最后一层从有向左排列。堆分为小根堆和大根堆。小根堆的根节点小于左右两个节点,大根堆相反。堆的相关操作down操作void down(int u){ int t = u; if(u*2<=size&&h[t]>h[u*2])t = u*2; if(u*2+1<=size&&h[t]>h[u*2+1])t = u*2+1; if(t!=u){ swap(h

随便推点

net.sf.json.JSONObject 和org.json.JSONObject 的差别_golden_lion的博客-程序员宅基地

net.sf.json.JSONObject 和org.json.JSONObject  的差别。一、创建json对象String str = "{\"code\":\"0000\", \"msg\":{\"availableBalance\":31503079.02}}org.json.JSONObject:JSONObject json = new JSON

开启PHP-LDAP_weixin_30436101的博客-程序员宅基地

LDAP简介:  LDAP(Lightweight Directory Access Protocol)的意思是"轻量级目录访问协议",是一个用于访问"目录服务器"(Directory Servers)的协议。这里所谓的"目录"是指一种按照树状结构存储信息的数据库。这个概念和硬盘上的目录结构类似,不过LDAP的"根目录"必须是"The world",并且其一级子目录必须是"countri...

第六章 副词(Les adverbes )_weixin_30736301的博客-程序员宅基地

副词属于不变词类,无性、数变化(tout除外),它的功能是修饰动词、形容词、副词或句子。副词的构成⇨单一副词bien tard hier mal vite tôt très souvent⇨组合副词aussitôt longtemps depuis quelquefois autour ensuite⇨复合词形的副词短语avec plaisir. de ...

《深入浅出Hadoop实战开发(HDFS实战图片、MapReduce、HBase实战微博、Hive应用)》_weixin_30611509的博客-程序员宅基地

一套名为《深入浅出Hadoop实战开发(HDFS实战图片、MapReduce、HBase实战微博、Hive应用)》视频教程献给大家,该教程一共30课时,每课时45分钟左右,以下是教程介绍和下载链接: 第1章节: > Hadoop背景 > HDFS设计目标 > HDFS不适合的场景 > HDFS架构详尽分析 > MapReduce的基本原理第2章节 > Ha...

计算机网络误区——可以通过非ARP报文(正常数据包)学习ARP表项吗?_永远是少年啊的博客-程序员宅基地

近期开始写新的内容,主要是为了解决一些计算机网络小白们的一些奇葩问题和常见误区。我们都知道,ARP协议主要是用来实现IP地址到MAC地址的解析的。一个数据包在二层网络中转发,必须知道对端的MAC地址才可以,如何通过IP地址找到对端的MAC地址呢?这就需要ARP协议了,ARP协议,即Address Resolution Protocol,地址解析协议,可以实现已知IP地址的情况下查找IP地址对应的MAC地址的问题。然而,对于一个非ARP协议的数据包,一般情况下也封装有发送者的IP地址和MAC地址,这样不就