|
17 | 17 |
|
18 | 18 | package org.apache.spark.sql.catalyst.analysis
|
19 | 19 |
|
20 |
| -import org.apache.spark.sql.catalyst.expressions.{AliasHelper, EvalHelper, Expression} |
| 20 | +import scala.collection.mutable |
| 21 | + |
| 22 | +import org.apache.spark.sql.catalyst.TableIdentifier |
| 23 | +import org.apache.spark.sql.catalyst.expressions.{AliasHelper, EvalHelper, Expression, SubqueryExpression, VariableReference} |
21 | 24 | import org.apache.spark.sql.catalyst.parser.CatalystSqlParser
|
22 |
| -import org.apache.spark.sql.catalyst.plans.logical.LogicalPlan |
| 25 | +import org.apache.spark.sql.catalyst.plans.logical.{CreateView, LogicalPlan} |
23 | 26 | import org.apache.spark.sql.catalyst.rules.Rule
|
24 | 27 | import org.apache.spark.sql.catalyst.trees.TreePattern.UNRESOLVED_IDENTIFIER
|
| 28 | +import org.apache.spark.sql.connector.catalog.CatalogV2Util.isSessionCatalog |
| 29 | +import org.apache.spark.sql.errors.QueryCompilationErrors |
25 | 30 | import org.apache.spark.sql.types.StringType
|
26 | 31 |
|
27 | 32 | /**
|
28 | 33 | * Resolves the identifier expressions and builds the original plans/expressions.
|
29 | 34 | */
|
30 | 35 | object ResolveIdentifierClause extends Rule[LogicalPlan] with AliasHelper with EvalHelper {
|
31 | 36 |
|
32 |
| - override def apply(plan: LogicalPlan): LogicalPlan = plan.resolveOperatorsUpWithPruning( |
33 |
| - _.containsPattern(UNRESOLVED_IDENTIFIER)) { |
| 37 | + override def apply(plan: LogicalPlan): LogicalPlan = { |
| 38 | + val referredTempVars = new mutable.ArrayBuffer[Seq[String]] |
| 39 | + val analyzedPlan = apply0(plan, referredTempVars) |
| 40 | + |
| 41 | + analyzedPlan match { |
| 42 | + case cv @ CreateView( |
| 43 | + ResolvedIdentifierInSessionCatalog(ident), _, _, _, _, _, _, _, _, _) => |
| 44 | + if (referredTempVars.nonEmpty) { |
| 45 | + throw QueryCompilationErrors.notAllowedToCreatePermanentViewByReferencingTempVarError( |
| 46 | + ident, |
| 47 | + referredTempVars.head.quoted |
| 48 | + ) |
| 49 | + } |
| 50 | + cv |
| 51 | + case _ => analyzedPlan |
| 52 | + } |
| 53 | + } |
| 54 | + |
| 55 | + object ResolvedIdentifierInSessionCatalog{ |
| 56 | + def unapply(resolved: LogicalPlan): Option[TableIdentifier] = resolved match { |
| 57 | + case ResolvedIdentifier(catalog, ident) if isSessionCatalog(catalog) => |
| 58 | + if (ident.namespace().length != 1) { |
| 59 | + throw QueryCompilationErrors |
| 60 | + .requiresSinglePartNamespaceError(ident.namespace().toImmutableArraySeq) |
| 61 | + } |
| 62 | + Some(TableIdentifier(ident.name, Some(ident.namespace.head), Some(catalog.name))) |
| 63 | + case _ => None |
| 64 | + } |
| 65 | + } |
| 66 | + |
| 67 | + private def apply0( |
| 68 | + plan: LogicalPlan, |
| 69 | + referredTempVars: mutable.ArrayBuffer[Seq[String]]): LogicalPlan = |
| 70 | + plan.resolveOperatorsUpWithPruning(_.containsPattern(UNRESOLVED_IDENTIFIER)) { |
34 | 71 | case p: PlanWithUnresolvedIdentifier if p.identifierExpr.resolved && p.childrenResolved =>
|
| 72 | + |
| 73 | + referredTempVars ++= collectTemporaryVariablesInLogicalPlan(p) |
| 74 | + |
35 | 75 | p.planBuilder.apply(evalIdentifierExpr(p.identifierExpr), p.children)
|
36 | 76 | case other =>
|
37 | 77 | other.transformExpressionsWithPruning(_.containsAnyPattern(UNRESOLVED_IDENTIFIER)) {
|
38 | 78 | case e: ExpressionWithUnresolvedIdentifier if e.identifierExpr.resolved =>
|
| 79 | + |
| 80 | + referredTempVars ++= collectTemporaryVariablesInExpressionTree(e) |
| 81 | + |
39 | 82 | e.exprBuilder.apply(evalIdentifierExpr(e.identifierExpr), e.otherExprs)
|
40 | 83 | }
|
41 | 84 | }
|
42 | 85 |
|
| 86 | + /** |
| 87 | + * Collect all temporary SQL variables and return the identifiers separately. |
| 88 | + */ |
| 89 | + private def collectTemporaryVariablesInLogicalPlan(child: LogicalPlan): Seq[Seq[String]] = { |
| 90 | + def collectTempVars(child: LogicalPlan): Seq[Seq[String]] = { |
| 91 | + child.flatMap { plan => |
| 92 | + plan.expressions.flatMap(_.flatMap { |
| 93 | + case e: SubqueryExpression => collectTempVars(e.plan) |
| 94 | + case r: VariableReference => Seq(r.originalNameParts) |
| 95 | + case _ => Seq.empty |
| 96 | + }) |
| 97 | + }.distinct |
| 98 | + } |
| 99 | + collectTempVars(child) |
| 100 | + } |
| 101 | + |
| 102 | + private def collectTemporaryVariablesInExpressionTree(child: Expression): Seq[Seq[String]] = { |
| 103 | + def collectTempVars(child: Expression): Seq[Seq[String]] = { |
| 104 | + child.flatMap { expr => |
| 105 | + expr.children.flatMap(_.flatMap { |
| 106 | + case e: SubqueryExpression => collectTemporaryVariablesInLogicalPlan(e.plan) |
| 107 | + case r: VariableReference => Seq(r.originalNameParts) |
| 108 | + case _ => Seq.empty |
| 109 | + }) |
| 110 | + }.distinct |
| 111 | + } |
| 112 | + collectTempVars(child) |
| 113 | + } |
| 114 | + |
43 | 115 | private def evalIdentifierExpr(expr: Expression): Seq[String] = {
|
44 | 116 | trimAliases(prepareForEval(expr)) match {
|
45 | 117 | case e if !e.foldable => expr.failAnalysis(
|
|
0 commit comments