SparkSQL: select name from student where age > 18;
UnresolvedRelation(LogicalPlan)叶节点表示未解析的relation(表),包含参数tableIdentifier: TableIdentifier。
Filter(LogicalPlan)节点表示过滤节点,参数condition为过滤表达式Expression,child为子LogicalPlan(本例中即UnresolvedRelation)。
Filter中condition为GreaterThan expression表达式,其接收left和right两个expression作为自己的children expression,比较两个子expression的计算结果,作为自己的计算结果。
Project(LogicalPlan)节点,包含projectList参数表示需要选择的列(SQL中Select后面跟的列),这里就是Seq(UnresolvecdAttribute(Seq(“NAME“)),child表示子LogicalPlan,这里就是Filter节点。
在Analyzed中会调用execute()方法,将此rule应用到逻辑算子树中。execute()中会调用rule(plan),这表示调用object ResolveRelations.apply(plan: LogicalPlan)方法。
apply方法中调用LogicalPlan 的resolveOperatorsUp方法并将后面的偏函数rule作为参数。自下而上的应用于逻辑算子树上的每一个LogicalPlan节点。
其中mapChildren调用的是继承自TreeNode的方法,会将偏函数rule应用到每一个子节点之上。
rule.applyOrElse(self, identity[LogicalPlan])将偏函数应用到LogicalPlan。偏函数会对传入的参数进行模式匹配,只有匹配成功的参数才会进行处理。在rule偏函数中可以看出,如果LogicalPlan是UnresolvedRelation类型,则调用resolveRelation(u)方法。
最后将应用过偏函数rule的LogicalPlan节点返回。
resolveRelation函数中会对传入的LogicalPlan再次进行模式匹配。如果是UnresolvedRelation,会调用lookupTableFromCatalog(u, defaultDatabase)返回foundRelation。
lookupTableFromCatalog调用Analyzed中的SessionCatalog对象catalog.lookupRelation(tableIdentWithDb)。
lookupRelation会在globalTempViewManager和externalCatalog查找对应的表,将结果包装在View节点中,View节点再包装在SubqueryAlias节点中,这两个节点均为LogicalPlan的子类型。
最后将SubqueryAlias节点返回。
由于rule.applyOrElse(self, identity[LogicalPlan]),rule是偏函数,只会应用到符合条件的LogicalPlan,所以ResolveRelations只会应用到UnresolvedRlation节点。结果如下:
ResolveRelations解析过程为:
Analyzed同样会调用其apply方法,将偏函数rule传入LogicalPlan的resolveOperatorsUp函数,对逻辑算子树自下而上的应用。
这个偏函数rule的匹配很长,我们只关注第一个case和最后一个case。
对于第一个case匹配情况:
如果LogicalPlan.childrenResolved为false,则放弃对该LogicalPlan应用rule,直接返回本身。
在LogicalPlan中children是继承至TreeNode的函数,返回其所有的LogicalPlan子节点。childrenResolved即为判断所有的子节点是否已经resolved。
当LogicalPlan中所有的expression处于resloved状态以及其子节点也处于resloved状态时,该节点resloved才为true。
在Expression中,children也是继承至TreeNode的函数,返回其所有的Expression子节点。当所有的expression子节点为resolved状态(如果子节点为Nill,则children.forall(_.resolved)返回true),且输入类型检查通过时(默认通过),该expression的resloved为True。所以对于一些UnreolvedExpression,其resolved状态为false。
如果Expression没有重写resolved,如果有子节点,则由子节点的resloved状态决定,如果没有子节点,则默认为true。
对于最后一个case情况:
调用LogicalPlan的mapExpressions函数,并将resolve偏函数传入作为解析规则。先看mapExpressions函数,其继承至QueryPlan,其将resolve解析规则应用到LogicalPlan的所有expression。
transformExpression函数表示调用传入的偏函数reslove应用到expression中,生成新的expression。
recursiveTransform函数表示对传入的参数中的所有元素循环应用(应对Seq类型)transformExpression函数。
mapProductIterator函数将recursiveTransform传入作为参数。mapProductIterator是继承自TreeNode的方法,对每一个productElement调用recursiveTransform函数。
productElement继承自Product接口,对于case类的函数,其表示构造函数中的每个参数。所以recursiveTransform会应用到具体调用mapexpression的LogicalPlan子类的构造函数的各个参数中,最后返回应用过解析规则的新的构造参数。比如对于Fliter节点:
recursiveTransform会应用到condition这个expression中,child直接返回本身。
mapProductIterator将recursiveTransform函数应用到所有的构造参数(LogicalPlan的expression是通过构造函数传进来的)中,返回解析之后的构造参数,利用新的构造参数,创建新的LogicalPlan返回。
如果处理的expression是UnresolvedAttribute(nameParts),则会调用LogicalPlan的resolveChildren函数进行解析。nameParts: Seq[String]是未解析属性的字符串。Resolver是一个字符串比较器,其定义于SQLConf中,由下可以看出,resolver就是用来比较字符串的。
如果处理的expression是其他类型不能直接进行解析,会调用mapChildren函数,将resolve偏函数传入,遍历expression的子节点,对其应用resolve进行解析。
LogicalPlan的resolveChildren函数,利用所有子节点的ouput属性(output表示LogicalPlan输出的Attributes),将传入的未解析的属性字符串解析为NamedExpression(实际类型为AttributeReference)。
output表示LogicalPlan输出的Attributes。例如Filter节点的子节点为SubqueryAlias,其重写了output方法:
其会调用child的output方法,即View节点的output。
case Class利用构造函数中的ouput参数重写了output方法,返回传入的Seq[Attribute],由第一步我们可知View节点中的output是由catalog中获取到的表的元数据转换来的Attributes。
在SubqueryAlias中,会对View的output调用withQualifier方法,传入的参数是别名。这个withQualifier没太认真看,但是应该是根据别名,返回AttributeReference。
上一小节只是说明了childAttributes是由其LoicalPlan的子节点的output输出Attributes组成的Seq。其中Attribute为AttributeReference类型。
LoicalPlan的resolveChildren函数会利用childAttributes对传入的未解析的属性的字符串解析成NamedExpression。这个方法就不展开分析了,LogicalPlan中的UnReslovedAttribute(nameParts:Seq[String])必定使用的是其子节点output的属性。
所以利用childAttributes可将UnresolvedAttribute进行解析。
上面一小节只讨论为了ResolveReferences中的resolve方法的case UnresolvedAttribute(nameParts)。对于LogicalPlan中的UnresolvedAttribute你可以直接对其进行解析,但是有些LogicalPlan中并不直接包含UnresolvedAttribute expression,比如Filter中包含GreaterThan expression,但是其包含类型为UnresolvedAttribute的expression子节点。必须要对这种expression进行遍历处理。
第四个case,调用Expression的mapChildren方法,将resovle函数传入,注意将当前LogicalPlan作为参数传入了resovle函数,因为resolve函数需要使用当前LogicalPlan的childAttributes。
mapChildren继承至TreeNode函数,会将resolve函数应用于每一个子节点。
children是定义在TreeNode中的虚函数。由于Expression和QueryPlan(LogicalPlan和SparkPlan是QueryPlan的子节点)都是其子节点,所以Expression和LogicalPlan中均含有children函数。所以Expression和QueryPlan调用继承至mapChildren时,其遍历的都是定义在自身内部的children函数。
对于Expression,其分为三大类LeafExpression、UnaryExpression、BinaryExpression分别表示含有0、1、2个子节点。其内部都重新定义了children函数。由下可以看出children函数都返回一个Seq,其包含的都是内部定义的虚函数。这些函数需要具体子类去实现。
在具体实现的子类当中,其子类的构造函数中会包含children的内部成员。
GreaterThan(left: Expression, right: Expression)是继承自BinaryExpression的类,其用case修饰,所以构造函数传入的left: Expression, right:
Expression自动重写了BinaryExpression类中的 def left: Expression和def right: Expression虚函数,所以GreaterThan expression调用children函数返回的是其构造函数中传入的left和right子节点。
对于LogicalPlan体系,chidren函数的调用是类似的。
关于为什么Project节点中的name没有被解析,这是因为Filter节点中的18还未被规则解析为字面量,所以Filter节点的resolved状态还为false,所以不能对Project节点进行解析,需要等待下一轮解析。
ResolveReference解析如下:
参考:《Spark SQL内核剖析》
以上个人理解,如果有误,请指正!
因篇幅问题不能全部显示,请点此查看更多更全内容
Copyright © 2019- hids.cn 版权所有 赣ICP备2024042780号-1
违法及侵权请联系:TEL:199 1889 7713 E-MAIL:2724546146@qq.com
本站由北京市万商天勤律师事务所王兴未律师提供法律服务