analysis
分析阶段使用的规则
规则批 | 策略 | 规则 | 说明 |
---|---|---|---|
Substitution | fixedPoint | OptimizeUpdateFields | 该规则优化了 UpdateFields 表达式链,因此看起来更像优化规则。但是,在处理深嵌套模式时,UpdateFields 表达式树可能会非常复杂,导致分析无法进行。因此,我们需要在分析开始时尽早优化 UpdateFields 。 |
CTESubstitution | 分析 WITH 节点,并根据以下条件用 CTE 引用或 CTE 定义替代子计划: 1. 如果在传统模式下,或如果查询是 SQL 命令或 DML 语句,则用 CTE 定义(即内联 CTE)替换。 2. 否则,替换为 CTE 引用 CTERelationRefs。是否内联将在查询分析后由 InlineCTE 规则决定。 替换后未内联的所有 CTE 定义将归入主查询和子查询的一个 WithCTE 节点下。任何不包含 CTE 或已内联所有 CTE 的主查询或子查询显然都不会有任何 WithCTE 节点。如果有,WithCTE 节点将与最外层的 With 节点位于同一位置。 WithCTE 节点中的 CTE 定义将按照它们被解析的顺序排列。这意味着对于任何有效的 CTE 查询,CTE 定义都能保证按照它们的依赖关系以拓扑顺序排列(即,给定 CTE 定义 A 和 B,且 B 引用 A,则 A 保证出现在 B 之前)。否则,这一定是一个无效的用户查询,稍后关系解析规则将抛出分析异常。 | ||
BindParameters | 查找 ParameterizedQuery 中所有已命名的参数,并用用户指定参数中的字面量替换它们。 | ||
WindowsSubstitution | 用 WindowSpecDefinitions 代替子计划。 WindowSpecDefinition 是窗口函数的规范。 | ||
EliminateUnions | 如果只有一个子项,则从计划中删除 Union 算子 | ||
SubstituteUnresolvedOrdinals | 用 UnresolvedOrdinal 表达式替换 “order by ”或 “group by ”中的序号。 | ||
Disable Hints | Once | DisableHints | 当设置了 spark.sql.optimizer.disableHints 时,删除所有提示。这将在分析器开始时执行,以禁用提示功能。 |
Hints | fixedPoint | ResolveJoinStrategyHints | 允许的连接策略提示列表在 JoinStrategyHint.strategies 中定义,连接策略提示可以指定关系别名序列,例如 “MERGE(a, c)”、“BROADCAST(a)”。连接策略提示计划节点将被插入任何与指定名称相匹配的关系(没有不同的别名)、子查询或公共表表达式的顶部。 提示解析的工作方式是向下递归遍历查询计划,找到与指定关系别名之一匹配的关系或子查询。遍历不会超出任何视图引用、子查询别名。 该规则必须在普通表表达式之前执行。 |
ResolveCoalesceHints | COALESCE Hint 提示接受名称 “COALESCE”、“REPARTITION ”和 “REPARTITION_BY_RANGE”。 | ||
Simple Sanity Check | Once | LookupFunctions | 检查 UnresolvedFunction 引用的函数标识符是否在函数注册表中定义。请注意,该规则不会尝试解析 UnresolvedFunction。它只是根据函数标识符执行简单的存在性检查,以快速识别未定义的函数,而不会触发关系解析,这在某些情况下可能会导致昂贵的分区/模式发现过程。为了避免重复查找外部函数,外部函数标识符将存储在本地哈希集 externalFunctionNameSet 中。 |
Keep Legacy Outputs | Once | KeepLegacyOutputs | spark.sql.legacy.keepCommandOutputSchema 为true时,保留 SQL 命令传统输出的规则。ShowTables,ShowNamespaces,DescribeNamespace,ShowTableProperties。 |
Resolution | fixedPoint | ResolveCatalogs | 解析table/view/function/namespace的名称部分目录。 |
ResolveUserSpecifiedColumns | 解析用户指定的列。当用户在 INSERT INTO 中指定列列表时,为 DSv1 提供了重新排列列顺序的特殊规则。DSv2 由 Analyzer.ResolveInsertInto 单独处理。ResolveInsertInto 单独处理。 | ||
ResolveInsertInto | 解析INSERT INTO 语句 | ||
ResolveRelations | 用catalog中的具体关系替换未解决的关系(表和视图)。 | ||
ResolvePartitionSpec | 在分区相关命令中将UnresolvedPartitionSpec 解析成ResolvedPartitionSpec 。 | ||
ResolveFieldNameAndPosition | 根据命令的大小写敏感性解析、规范化和重写字段名称的规则。 | ||
AddMetadataColumns | 当节点缺少已解析的属性时,为子关系的输出添加元数据列。 元数据列的引用是使用 LogicalPlan.metadataOutput 中的列来解析的,但在关系被替换之前,关系的输出不包括元数据列。除非此规则将元数据添加到关系的输出中,否则分析器会检测到没有产生这些列。 只有当节点已解析但缺少其子节点的输入时,该规则才会添加元数据列。这样可以确保除非使用了元数据列,否则不会将其添加到计划中。通过只检查已解析的节点,可确保 * 扩展已完成,这样元数据列就不会被 * 意外选中。此规则会向下解析操作符,以避免过早投影出元数据列。 | ||
DeduplicateRelations | LogicalPlan的关系去重 | ||
ResolveReferences | 解析查询计划中的列引用。基本上,它会自下而上地转换查询计划树,只有当一个计划节点的所有子节点都已解析,且子节点之间不存在冲突属性时,才会尝试解析该节点的引用(详见 hasConflictingAttrs)。 | ||
ResolveLateralColumnAliasReference | 该规则是解决横向列别名的第二阶段。 解析横向列别名,它引用了之前在 SELECT 列表中定义的别名。从计划角度看,它处理两种类型的操作符: 项目和聚合。- 在 “项目 ”中,将引用的横向别名下推到新创建的 “项目 ”中,解析引用这些别名的属性 - 在 “聚合 ”中,在上面插入 “项目 ”节点,并返回到 “项目 ”的解析。 | ||
ResolveExpressionsWithNamePlaceholders | 如果表达式中包含 NamePlaceholders,则解析表达式。NamePlaceholders代表的是占位符的 | ||
ResolveDeserializer | 用已解析为给定输入属性的反序列化表达式替换 UnresolvedDeserializer。 | ||
ResolveNewInstance | 如果正在构造的对象是一个内部类,则通过查找并添加外部作用域来解决 NewInstance 问题。 | ||
ResolveUpCast | 用 Cast 替换 UpCast 表达式,并在可能截断的情况下抛出异常。 | ||
ResolveGroupingAnalytics | 解析grouping函数 | ||
ResolvePivot | 解析Pivot, | ||
ResolveUnpivot | 解析Unpivot, | ||
ResolveOrdinalInOrderByAndGroupBy | 在SQL的许多方言中,在order/sort by和group by子句中使用的顺序位置是有效的。此规则用于将序号位置转换为选择列表中的相应表达式。Spark 2.0中引入了这种支持。如果排序引用或分组依据表达式不是整数而是可折叠表达式,请忽略它们。当spark.sql.orderByOrdinal/spark.sql.groupByOrdinal设置为false,也忽略位置号。 | ||
ExtractGenerator | 从Project操作符的Project列表中提取Generator,并在Project下创建Generator操作符。 在以下情况下,该规则会抛出 AnalysisException: 1. 生成器嵌套在表达式中,例如:SELECT explode(list) + 1 FROM tbl 2. 在项目列表中发现多个生成器,例如:SELECT explode(list), explode(list) FROM tbl 3. 在非 Project 或 Generate 的其他操作符中发现 Generator,例如 SELECT * FROM tbl SORT BY explode(list) | ||
ResolveGenerate | 重写表。生成表达式,这些表达式需要以下一项或多项才能解析:输出的具体属性引用。 从SELECT子句(即从Project)重新定位到Generate子句中。 输出Attribute 的名称是从封装Generator 的Alias 或MultiAlias 表达式中提取的。 | ||
ResolveFunctions | 用具体的 LogicalPlans 代替 UnresolvedFunctionNames。 用具体的表达式替换 UnresolvedFunctions。 用具体表达式替换 UnresolvedGenerators。 用具体的 LogicalPlans 代替 UnresolvedTableValuedFunctions。 | ||
ResolveAliases | 用具体的别名代替 UnresolvedAliass。 | ||
ResolveSubquery | 该规则可解析和重写表达式内部的子查询。 注:CTE 在 CTESubstitution 中处理。 | ||
ResolveSubqueryColumnAliases | 用投影替换子查询中未解决的列别名。 | ||
ResolveWindowOrder | 检查和添加顺序到AggregateWindowFunction | ||
ResolveWindowFrame | 检查并为所有窗口功能添加合适的窗口框架。 | ||
ResolveNaturalAndUsingJoin | 根据两侧的输出计算输出列,消除自然连接或使用连接,然后在普通连接上应用 Project 消除自然连接或使用连接。 | ||
ResolveOutputRelation | 根据逻辑计划中的数据解析输出表的列。该规则将 - 按名称写入时重新排列列顺序 - 在数据类型不匹配时插入转换 - 在列名不匹配时插入别名 - 检测与输出表不兼容的计划并抛出 AnalysisException | ||
ExtractWindowExpressions | 从 Project 运算符的 projectList 和 Aggregate 运算符的 aggregateExpressions 中提取 WindowExpressions,并为每个不同的 WindowSpecDefinition 创建单独的 Window 运算符。 | ||
GlobalAggregates | 将包含聚合表达式的投影转化为聚合。 | ||
ResolveAggregateFunctions | 该规则可查找不在聚合运算符中的聚合表达式。例如,HAVING 子句或 ORDER BY 子句中的表达式。这些表达式会被下推到底层的聚合运算符,然后在原始运算符后被投影掉。 在从中查找聚合函数和分组表达式之前,我们需要确保所有表达式都已完全解析。 | ||
TimeWindowing | 使用扩展运算符将时间列映射到多个时间窗口。要知道一个时间列可以映射到多少个窗口并非易事,因此我们会高估窗口的数量,并过滤掉时间列不在时间窗口内的行。 | ||
SessionWindowing | 将时间列映射到会话窗口。 | ||
ResolveWindowTime | 解析 window_time 表达式,从作为窗口聚合运算符输出的窗口列中提取正确的窗口时间。窗口列的类型为 struct { start: TimestampType, end: TimestampType }。窗口的正确代表事件时间是 window. | ||
ResolveDefaultColumns | 这是一条在 CREATE/ REPLACE TABLE 等语句中处理 DEFAULT 列的规则。 CREATE TABLE 和 ALTER TABLE 调用支持为以后的操作设置列默认值。随后的 INSERT、UPDATE 和 MERGE 命令可根据需要使用 DEFAULT 关键字引用该值。 | ||
ResolveInlineTables | 使用LocalRelation 替换UnresolvedInlineTable | ||
ResolveLambdaVariables | 解决高阶函数公开的 lambda 变量。 该规则分两步运行: [1]. 将高阶函数公开的匿名变量绑定到 lambda 函数的参数上;这样就创建了命名和类型化的 lambda 变量。在这一步中,将检查参数名称是否重复,并检查参数的数量。 [2]. 解析 lambda 函数的函数表达式树中使用的 lambda 变量。请注意,我们允许使用当前 lambda 之外的变量,这些变量可以是定义在外层作用域中的 lambda 函数,也可以是由计划的子计划产生的属性。如果名称重复,则使用最内部作用域中定义的名称。 | ||
ResolveTimeZone | 用会话本地时区的副本替换不含时区 ID 的 TimeZoneAwareExpression。 | ||
ResolveRandomSeed | 设置随机数生成的种子。 | ||
ResolveBinaryArithmetic | 解析二进制算法 对于加法 1. 如果两边都是时间间隔,则保持不变; 2. 否则,如果一边是日期,另一边是时间间隔,则将其转为 DateAddInterval; 3. 否则,如果一边是时间间隔,则将其转为 TimeAdd; 4. 否则,如果一边是日期,则将其转为 DateAdd; 5. 否则保持不变。 减法 1. 如果两边都是时间间隔,则保持不变; 2. 否则,如果左边是日期,右边是区间,则将其转为(l, -r); 3. 否则,如果右边是区间,则将其转为(l, -r); 4. 否则,如果一边是时间戳,则将其转为 SubtractTimestamps; 5. 否则,如果右边是日期,则将其转为 DateDiff/SubtractDates; 6. 否则,如果左边是日期,则将其转为 DateSub; 7. 否则改为保持不变。 乘法 1. 如果一边是区间,则将其转换为 MultiplyInterval; 2. 否则保持不变。 对于除法 1. 如果左边是区间,则将其转为 DivideInterval; 2. 否则,保持不变。 | ||
ResolveUnion | 将 Union 的不同子代解析为一组共同的列。 | ||
RewriteDeleteFromTable | 重写 DELETE 操作的规则,使用对单行或行群组进行操作的计划。 如果表实现了 SupportsDeleteV2 和 SupportsRowLevelOperations,该规则仍将重写 DELETE 操作,但优化器会检查是否可以通过向连接器传递删除筛选器来处理该特定 DELETE 语句。如果可以,优化器将放弃重写的计划,并允许数据源使用过滤器删除。 | ||
typeCoercionRules | 当spark.sql.ansi.enabled 设置为 true 的时候,采取 ANSI 的方式进行解析,这代表的是一组解析规则。 | ||
ResolveWithCTE | 使用相应 CTE 定义的解析输出属性更新 CTE 引用。 | ||
Remove TempResolvedColumn | Once | RemoveTempResolvedColumn | 主解析批次中的 ResolveReferences 规则会在 UnresolvedHaving/ Filter/ Sort 中创建 TempResolvedColumn,用于保存临时解析的带有 agg. 如果托管 TempResolvedColumn 的表达式已完全解析,则规则 ResolveAggregationFunctions 将 - 如果 TempResolvedColumn 位于聚合函数或分组表达式中,则用 AttributeReference 替换 TempResolvedColumn。- 如果 TempResolvedColumn 不在聚合函数或分组表达式中,则将其标记为已尝试,希望其他规则能重新解决它。如果 hasTried 为真,ResolveReferences 将重新解析 TempResolvedColumn,如果解析失败,则保持不变。我们应该将其转回 UnresolvedAttribute,这样分析器稍后就能报告缺少列的错误。 如果托管 TempResolvedColumn 的表达式未被解析,TempResolvedColumn 将保持 hasTried 为 false。我们应该剥离 TempResolvedColumn,这样用户就能看到表达式未解析的原因,例如类型不匹配。 |
Post-Hoc Resolution | Once | ResolveCommandsWithIfExists | 用于在未解析表或临时视图时处理命令的规则。这些命令支持 “ifExists ”标志,因此在未解析关系时不会失败。如果 “ifExists ”标志被设为 true,则该计划将被解析为 NoopCommand. |
Remove Unresolved Hints | Once | RemoveAllHints | 删除所有hits,用于删除用户提供的无效hits。必须在执行完所有其他hits规则后才能执行。 |
Nondeterministic | Once | PullOutNondeterministic | 从非 Project 或过滤器的 LogicalPlan 中提取非确定表达式,将其放入内部 Project,最后将其投射到外部 Project。 |
UDF | Once | HandleNullInputsForUDF | 通过添加额外的 If 表达式来进行空值检查,从而正确处理 UDF 的空基元输入。当用户使用基元参数定义 UDF 时,无法判断基元参数是否为空,因此我们假定基元输入为可传递的空,如果输入为空,则返回空。 |
ResolveEncodersInUDF | 通过明确给出属性来解决 UDF 的编码器问题。我们明确给出属性是为了处理输入值的数据类型与编码器内部模式不一致的情况,这可能会导致数据丢失。例如,如果实际数据类型是 Decimal(30,0),编码器不应将输入值转换为 Decimal(38,18)。 解析后的编码器将用于将内部行反序列化为 Scala 值。 | ||
UpdateNullability | Once | UpdateAttributeNullability | 通过使用子输出属性(Attributes)中相应属性的无效性,更新已解析逻辑计划(LogicalPlan)中属性的无效性。之所以需要这一步骤,是因为用户可以在数据集 API 中使用已解析的 AttributeReference,而外连接可以改变 AttributeReference 的无效性。如果没有这条规则,可空列的可空字段实际上可能会被设置为不可空,从而导致非法优化(如 NULL 传播)和错误答案。 有关这种情况的具体查询,请参见 SPARK-13484 和 SPARK-13801。 |
Subquery | Once | UpdateOuterReferences | 引用外部查询块的子查询中的聚合表达式会被推送到外部查询块进行评估。下面的规则更新了此类外部引用,如 AttributeReference 引用父查询块/外部查询块中的属性。 |
Cleanup | fixedPoint | CleanupAliases | 清除计划中不必要的别名。基本上,我们只需要在 Project(项目列表)或 Aggregate(聚合表达式)或 Window(窗口表达式)中将别名作为顶层表达式。请注意,如果一个表达式有其他不在其子表达式中的表达式参数,如 RuntimeReplaceable,则本规则中的别名转换对这些参数不起作用。 |
HandleSpecialCommand | Once | HandleSpecialCommand | 用于处理分析完成后需要通知的特殊命令的规则。该规则应在所有其他分析规则运行后运行。 |
Remove watermark for batch query | Once | EliminateEventTimeWatermark | 忽略批量查询中的事件时间水印,该功能仅在结构化数据流中支持。TODO:将此规则添加到分析器规则列表中。 |