Spark-源码学习-SparkSQL-架构设计-SQL 引擎-数据结构-TreeNode-Expression
一、概述
Expression 是 Catalyst 中的表达式体系。
二、实现
在 Expression 类中,主要定义了包括基本属性、核心操作、输入输出、字符串表示和等价性判断
核心操作 $eval$ 函数实现了表达式对应的处理逻辑,也是其他模块调用该表达式的主要接口,而 $genCode$ 和 $doGenCode$ 用于生成表达式对应的 Java 代码。字符串表示用于查看该 Expression 的具体内容,如表达式名和输入参数等。
2.1. 属性
2.1.1. deterministic
标记表达式是否为确定性的,即每次执行 $eval$ 函数的输出是否都相同。如果在固定输入值的情况下返回值相同,该标记为true;如果在固定输入值的情况下返回值是不确定的,则说明该 expression 是不确定的,deterministic 参数应该为 false。
1 | lazy val deterministic: Boolean = children.forall(_.deterministic) |
2.1.2. _references
表示该 Expression 中会涉及的属性值,默认情况为所有子节点中属性值的集合。
1 | private lazy val _references: AttributeSet = AttributeSet.fromAttributeSets(children.map(_.references)) |
2.1.3. canonicalized
返回经过规范化(Canonicalize)处理后的表达式。规范化处理会在确保输出结果相同的前提下通过一些规则对表达式进行重写。
1 | lazy val canonicalized: Expression = withCanonicalizedChildren |
2.2. 方法
2.2.1. foldable
用来返回表达式能否在查询执行之前直接静态计算。目前,foldable 为 true 的情况有两种:
1 | def foldable: Boolean = false |
- 该表达式为 Literal 类型(“字面值”,例如常量等)
- 是当且仅当其子表达式中 foldable 都为 true 时。当 foldable 为 true 时,在算子树中,表达式可以预先直接处理(“折叠”)。
2.2.2. genCode()
2.2.3. semanticEquals()
判断两个表达式在语义上是否等价。基本的判断条件是两个表达式都是确定性的(deterministic 为 true)且两个表达式经过规范化处理后(Canonicalized) 仍然相同。
1 | final def semanticEquals(other: Expression): Boolean = deterministic && other.deterministic && canonicalized == other.canonicalized |
此外 Expression 本身也是 TreeNode 类的子类,因此能够调用所有 TreeNode 的方法,例如 $transform*$ 等,也可以通过多级的子 Expression 组合成复杂的 Expression。
三、继承体系
3.1. Nondeterministic 接口
具有不确定性的 Expression ,其中 deterministic 和 foldable 属性都 默认返回 false ,典型的实现包括 Rand
和 Randn
表达式等。
3.2. Unevaluable 接口
非可执行的表达式, 即调用其 $eval$ 函数会抛出异常。该接口主要用于生命周期不超过逻辑计划解析和优化阶段的表达式, 例如 Star(*)表达式在解析阶段就会被展开成具体的列集合。
3.3. CodegenFallback 接口
不支持 Java 代码生成,并回退到解释模式的 Catalyst 表达式。
$doGenCode()$
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38protected def doGenCode(ctx: CodegenContext, ev: ExprCode): ExprCode = {
// LeafNode does not need `input`
val input = if (this.isInstanceOf[LeafExpression]) "null" else ctx.INPUT_ROW
val idx = ctx.references.length
ctx.references += this
var childIndex = idx
this.foreach {
case n: Nondeterministic =>
// This might add the current expression twice, but it won't hurt.
ctx.references += n
childIndex += 1
ctx.addPartitionInitializationStatement(
s"""
|((Nondeterministic) references[$childIndex])
| .initialize(partitionIndex);
""".stripMargin)
case _ =>
}
val objectTerm = ctx.freshName("obj")
val placeHolder = ctx.registerComment(this.toString)
val javaType = CodeGenerator.javaType(this.dataType)
if (nullable) {
ev.copy(code = code"""
$placeHolder
Object $objectTerm = ((Expression) references[$idx]).eval($input);
boolean ${ev.isNull} = $objectTerm == null;
$javaType ${ev.value} = ${CodeGenerator.defaultValue(this.dataType)};
if (!${ev.isNull}) {
${ev.value} = (${CodeGenerator.boxedType(this.dataType)}) $objectTerm;
}""")
} else {
ev.copy(code = code"""
$placeHolder
Object $objectTerm = ((Expression) references[$idx]).eval($input);
$javaType ${ev.value} = (${CodeGenerator.boxedType(this.dataType)}) $objectTerm;
""", isNull = FalseLiteral)
}
}
3.4. LeafExpression
叶子节点类型的表达式,即不包含任何子节点,因此其 $children$ 方法通常 默认返回 Nil 值。
3.5. Unary/Binary/Ternary Expression
一元/二元/三元类型表达式,只含有1/2/3个子节点