Skip to content

Commit 82cabda

Browse files
committed
Generalize attribute resolution.
1 parent 74f82c7 commit 82cabda

File tree

2 files changed

+20
-7
lines changed

2 files changed

+20
-7
lines changed

sql/catalyst/src/main/scala/org/apache/spark/sql/catalyst/analysis/Analyzer.scala

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -113,7 +113,7 @@ class Analyzer(catalog: Catalog, registry: FunctionRegistry, caseSensitive: Bool
113113
q transformExpressions {
114114
case u @ UnresolvedAttribute(name) =>
115115
// Leave unchanged if resolution fails. Hopefully will be resolved next round.
116-
val result = q.resolve(name).getOrElse(u)
116+
val result = q.resolveChildren(name).getOrElse(u)
117117
logDebug(s"Resolving $u to $result")
118118
result
119119
}

sql/catalyst/src/main/scala/org/apache/spark/sql/catalyst/plans/logical/LogicalPlan.scala

Lines changed: 19 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -72,32 +72,45 @@ abstract class LogicalPlan extends QueryPlan[LogicalPlan] {
7272
def childrenResolved: Boolean = !children.exists(!_.resolved)
7373

7474
/**
75-
* Optionally resolves the given string to a [[NamedExpression]]. The attribute is expressed as
75+
* Optionally resolves the given string to a [[NamedExpression]] using the input from all child
76+
* nodes of this LogicalPlan. The attribute is expressed as
7677
* as string in the following form: `[scope].AttributeName.[nested].[fields]...`.
7778
*/
78-
def resolve(name: String): Option[NamedExpression] = {
79+
def resolveChildren(name: String): Option[NamedExpression] =
80+
resolve(name, children.flatMap(_.output))
81+
82+
/**
83+
* Optionally resolves the given string to a [[NamedExpression]] based on the output of this
84+
* LogicalPlan. The attribute is expressed as string in the following form:
85+
* `[scope].AttributeName.[nested].[fields]...`.
86+
*/
87+
def resolve(name: String): Option[NamedExpression] =
88+
resolve(name, output)
89+
90+
/** Performs attribute resolution given a name and a sequence of possible attributes. */
91+
protected def resolve(name: String, input: Seq[Attribute]): Option[NamedExpression] = {
7992
val parts = name.split("\\.")
8093
// Collect all attributes that are output by this nodes children where either the first part
8194
// matches the name or where the first part matches the scope and the second part matches the
8295
// name. Return these matches along with any remaining parts, which represent dotted access to
8396
// struct fields.
84-
val options = children.flatMap(_.output).flatMap { option =>
97+
val options = input.flatMap { option =>
8598
// If the first part of the desired name matches a qualifier for this possible match, drop it.
8699
val remainingParts =
87100
if (option.qualifiers.contains(parts.head) && parts.size > 1) parts.drop(1) else parts
88101
if (option.name == remainingParts.head) (option, remainingParts.tail.toList) :: Nil else Nil
89102
}
90103

91104
options.distinct match {
92-
case (a, Nil) :: Nil => Some(a) // One match, no nested fields, use it.
105+
case Seq((a, Nil)) => Some(a) // One match, no nested fields, use it.
93106
// One match, but we also need to extract the requested nested field.
94-
case (a, nestedFields) :: Nil =>
107+
case Seq((a, nestedFields)) =>
95108
a.dataType match {
96109
case StructType(fields) =>
97110
Some(Alias(nestedFields.foldLeft(a: Expression)(GetField), nestedFields.last)())
98111
case _ => None // Don't know how to resolve these field references
99112
}
100-
case Nil => None // No matches.
113+
case Seq() => None // No matches.
101114
case ambiguousReferences =>
102115
throw new TreeNodeException(
103116
this, s"Ambiguous references to $name: ${ambiguousReferences.mkString(",")}")

0 commit comments

Comments
 (0)