Skip to content
Closed
Show file tree
Hide file tree
Changes from 3 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -161,7 +161,8 @@ abstract class Optimizer(catalogManager: CatalogManager)
// LocalRelation and does not trigger many rules.
Batch("LocalRelation early", fixedPoint,
ConvertToLocalRelation,
PropagateEmptyRelation) ::
PropagateEmptyRelation,
UpdateAttributeNullability) ::
Comment on lines +164 to +167

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can you leave a comment above explaining why we run UpdateAttributeNullability here?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Sure, added in the latest commit.

Batch("Pullup Correlated Expressions", Once,
PullupCorrelatedPredicates) ::
// Subquery batch applies the optimizer rules recursively. Therefore, it makes no sense
Expand Down Expand Up @@ -198,7 +199,8 @@ abstract class Optimizer(catalogManager: CatalogManager)
ReassignLambdaVariableID) :+
Batch("LocalRelation", fixedPoint,
ConvertToLocalRelation,
PropagateEmptyRelation) :+
PropagateEmptyRelation,
UpdateAttributeNullability) :+
// The following batch should be executed after batch "Join Reorder" and "LocalRelation".
Batch("Check Cartesian Products", Once,
CheckCartesianProducts) :+
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -50,8 +50,24 @@ object PropagateEmptyRelation extends Rule[LogicalPlan] with PredicateHelper wit
override def conf: SQLConf = SQLConf.get

def apply(plan: LogicalPlan): LogicalPlan = plan transformUp {
case p: Union if p.children.forall(isEmptyLocalRelation) =>
empty(p)
case p @ Union(children) if children.exists(isEmptyLocalRelation) =>
val newChildren = children.filterNot(isEmptyLocalRelation)
if (newChildren.isEmpty) {
empty(p)
} else {
Comment thread
dongjoon-hyun marked this conversation as resolved.
val newPlan = if (newChildren.size > 1) Union(newChildren) else newChildren.head
val outputs = newPlan.output.zip(p.output)
// the original Union may produce different output attributes than the new one so we alias
// them if needed
if (outputs.forall { case (newAttr, oldAttr) => newAttr.exprId == oldAttr.exprId }) {
newPlan
} else {
val outputAliases = outputs.map { case (newAttr, oldAttr) =>
Alias(newAttr, oldAttr.name)(oldAttr.exprId)

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Since this is introducing a new Alias, I'm wondering if we need to worry about explicitMetadata in this case.

@peter-toth peter-toth Jul 13, 2020

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You are right, we can lose it in some cases. Fixed in cea0a48

}
Project(outputAliases, newPlan)
}
}

// Joins on empty LocalRelations generated from streaming sources are not eliminated
// as stateful streaming joins need to perform other state management operations other than
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,7 @@ class PropagateEmptyRelationSuite extends PlanTest {

val testRelation1 = LocalRelation.fromExternalRows(Seq('a.int), data = Seq(Row(1)))
val testRelation2 = LocalRelation.fromExternalRows(Seq('b.int), data = Seq(Row(1)))
val testRelation3 = LocalRelation.fromExternalRows(Seq('c.int.notNull), data = Seq(Row(1)))

test("propagate empty relation through Union") {
val query = testRelation1
Expand All @@ -67,6 +68,34 @@ class PropagateEmptyRelationSuite extends PlanTest {
comparePlans(optimized, correctAnswer)
}

test("SPARK-32241: remove empty relation children from Union") {

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

can we add a test to check the nullability update?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

A simple test case like here: b454cd7 is ok? Or you want me to test that UpdateAttributeNullability kicks in and fixes some nullability after PropagateEmptyRelation?

@jinhai-cloud jinhai-cloud Aug 9, 2023

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This test case does not apply the UpdateAttributeNullability rules in Optimizer. Do you need to add a test case?

val query = testRelation1.union(testRelation2.where(false))
val optimized = Optimize.execute(query.analyze)
val correctAnswer = testRelation1
comparePlans(optimized, correctAnswer)

val query2 = testRelation1.where(false).union(testRelation2)
val optimized2 = Optimize.execute(query2.analyze)
val correctAnswer2 = testRelation2.select('b.as('a)).analyze
comparePlans(optimized2, correctAnswer2)

val query3 = testRelation1.union(testRelation2.where(false)).union(testRelation3)
val optimized3 = Optimize.execute(query3.analyze)
val correctAnswer3 = testRelation1.union(testRelation3)
comparePlans(optimized3, correctAnswer3)

val query4 = testRelation1.where(false).union(testRelation2).union(testRelation3)
val optimized4 = Optimize.execute(query4.analyze)
val correctAnswer4 = testRelation2.union(testRelation3).select('b.as('a)).analyze
comparePlans(optimized4, correctAnswer4)

// Nullability can change from nullable to non-nullable
val query5 = testRelation1.where(false).union(testRelation3).select('a.as('x)).analyze
val optimized5 = Optimize.execute(query5.analyze)
assert(query5.output.head.nullable, "Original output should be nullable")
assert(!optimized5.output.head.nullable, "The new output should be non-nullable")
}

test("propagate empty relation through Join") {
// Testcases are tuples of (left predicate, right predicate, joinType, correct answer)
// Note that `None` is used to compare with OptimizeWithoutPropagateEmptyRelation.
Expand Down