-
Notifications
You must be signed in to change notification settings - Fork 28.1k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
[SPARK-38868][SQL] Don't propagate exceptions from filter predicate when optimizing outer joins #36230
[SPARK-38868][SQL] Don't propagate exceptions from filter predicate when optimizing outer joins #36230
Conversation
val message = Literal(UTF8String fromString("Bad value"), StringType) | ||
val originalQuery = | ||
x.join(y, LeftOuter, Option("x.a".attr === "y.d".attr)) | ||
.where(If("y.d".attr > 0, true, RaiseError(message)).isNull) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I didn't do AssertTrue
here because RuntimeReplaceable
doesn't evaluate, so I just put in what AssertTrue
gets replaced with.
@@ -144,6 +144,8 @@ object EliminateOuterJoin extends Rule[LogicalPlan] with PredicateHelper { | |||
val emptyRow = new GenericInternalRow(attributes.length) | |||
val boundE = BindReferences.bindReference(e, attributes) | |||
if (boundE.exists(_.isInstanceOf[Unevaluable])) return false | |||
// don't evaluate an expression that might purposefully throw an exception | |||
if (boundE.exists(_.isInstanceOf[RaiseError])) return false |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
In fact, we should filter out all expressions that do not inherit NoThrow
(and mark all other no-throw expressions). But that's incomplete now. Given that it already optimized all other expressions before, I am fine with this band-aid fix.
cc @cloud-fan FYI |
@@ -144,6 +144,8 @@ object EliminateOuterJoin extends Rule[LogicalPlan] with PredicateHelper { | |||
val emptyRow = new GenericInternalRow(attributes.length) | |||
val boundE = BindReferences.bindReference(e, attributes) | |||
if (boundE.exists(_.isInstanceOf[Unevaluable])) return false | |||
// don't evaluate an expression that might purposefully throw an exception | |||
if (boundE.exists(_.isInstanceOf[RaiseError])) return false | |||
val v = boundE.eval(emptyRow) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
can we do a try-catch and swallow the error here? RaiseError
doesn't match all the expressions that may fail during evaluation.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
can we do a try-catch and swallow the error here?
Looking...
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
RaiseError doesn't match all the expressions that may fail during evaluation.
I see what you mean. This query also fails in EliminateOuterJoin
(because you can't have a null key):
select *
from (select id, id as b from range(0, 10)) l
left outer join (select id, id + 1 as c from range(0, 10)) r
on l.id = r.id
where map(c, '2') is not null;
I will add a try/catch.
cc @sigmod |
RaiseError
exceptions while optimizing outer joinsval v = boundE.eval(emptyRow) | ||
v == null || v == false | ||
} catch { | ||
case e: Exception => |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Let' follow the convention and catch NonFatal(e)
.
val x = testRelation.subquery(Symbol("x")) | ||
val y = testRelation1.subquery(Symbol("y")) | ||
|
||
val message = Literal(UTF8String fromString("Bad value"), StringType) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
val message = Literal(UTF8String fromString("Bad value"), StringType) | |
val message = Literal(UTF8String.fromString("Bad value"), StringType) |
…hen optimizing outer joins ### What changes were proposed in this pull request? Change `EliminateOuterJoin#canFilterOutNull` to return `false` when a `where` condition throws an exception. ### Why are the changes needed? Consider this query: ``` select * from (select id, id as b from range(0, 10)) l left outer join (select id, id + 1 as c from range(0, 10)) r on l.id = r.id where assert_true(c > 0) is null; ``` The query should succeed, but instead fails with ``` java.lang.RuntimeException: '(c#1L > cast(0 as bigint))' is not true! ``` This happens even though there is no row where `c > 0` is false. The `EliminateOuterJoin` rule checks if it can convert the outer join to a inner join based on the expression in the where clause, which in this case is ``` assert_true(c > 0) is null ``` `EliminateOuterJoin#canFilterOutNull` evaluates that expression with `c` set to `null` to see if the result is `null` or `false`. That rule doesn't expect the result to be a `RuntimeException`, but in this case it always is. That is, the assertion is failing during optimization, not at run time. ### Does this PR introduce _any_ user-facing change? No. ### How was this patch tested? New unit test. Closes #36230 from bersprockets/outer_join_eval_assert_issue. Authored-by: Bruce Robbins <bersprockets@gmail.com> Signed-off-by: Wenchen Fan <wenchen@databricks.com> (cherry picked from commit e2930b8) Signed-off-by: Wenchen Fan <wenchen@databricks.com>
thanks, merging to master/3.3! |
@bersprockets do you want to open backport PRs for 3.2/3.1? |
Will do, thanks! |
…hen optimizing outer joins Change `EliminateOuterJoin#canFilterOutNull` to return `false` when a `where` condition throws an exception. Consider this query: ``` select * from (select id, id as b from range(0, 10)) l left outer join (select id, id + 1 as c from range(0, 10)) r on l.id = r.id where assert_true(c > 0) is null; ``` The query should succeed, but instead fails with ``` java.lang.RuntimeException: '(c#1L > cast(0 as bigint))' is not true! ``` This happens even though there is no row where `c > 0` is false. The `EliminateOuterJoin` rule checks if it can convert the outer join to a inner join based on the expression in the where clause, which in this case is ``` assert_true(c > 0) is null ``` `EliminateOuterJoin#canFilterOutNull` evaluates that expression with `c` set to `null` to see if the result is `null` or `false`. That rule doesn't expect the result to be a `RuntimeException`, but in this case it always is. That is, the assertion is failing during optimization, not at run time. No. New unit test. Closes apache#36230 from bersprockets/outer_join_eval_assert_issue. Authored-by: Bruce Robbins <bersprockets@gmail.com> Signed-off-by: Wenchen Fan <wenchen@databricks.com>
…ate when optimizing outer joins Backport of #36230 ### What changes were proposed in this pull request? Change `EliminateOuterJoin#canFilterOutNull` to return `false` when a `where` condition throws an exception. ### Why are the changes needed? Consider this query: ``` select * from (select id, id as b from range(0, 10)) l left outer join (select id, id + 1 as c from range(0, 10)) r on l.id = r.id where assert_true(c > 0) is null; ``` The query should succeed, but instead fails with ``` java.lang.RuntimeException: '(c#1L > cast(0 as bigint))' is not true! ``` This happens even though there is no row where `c > 0` is false. The `EliminateOuterJoin` rule checks if it can convert the outer join to a inner join based on the expression in the where clause, which in this case is ``` assert_true(c > 0) is null ``` `EliminateOuterJoin#canFilterOutNull` evaluates that expression with `c` set to `null` to see if the result is `null` or `false`. That rule doesn't expect the result to be a `RuntimeException`, but in this case it always is. That is, the assertion is failing during optimization, not at run time. ### Does this PR introduce _any_ user-facing change? No. ### How was this patch tested? New unit test. Closes #36341 from bersprockets/outer_join_eval_assert_issue_32. Authored-by: Bruce Robbins <bersprockets@gmail.com> Signed-off-by: Wenchen Fan <wenchen@databricks.com>
…ate when optimizing outer joins Backport of #36230 ### What changes were proposed in this pull request? Change `EliminateOuterJoin#canFilterOutNull` to return `false` when a `where` condition throws an exception. ### Why are the changes needed? Consider this query: ``` select * from (select id, id as b from range(0, 10)) l left outer join (select id, id + 1 as c from range(0, 10)) r on l.id = r.id where assert_true(c > 0) is null; ``` The query should succeed, but instead fails with ``` java.lang.RuntimeException: '(c#1L > cast(0 as bigint))' is not true! ``` This happens even though there is no row where `c > 0` is false. The `EliminateOuterJoin` rule checks if it can convert the outer join to a inner join based on the expression in the where clause, which in this case is ``` assert_true(c > 0) is null ``` `EliminateOuterJoin#canFilterOutNull` evaluates that expression with `c` set to `null` to see if the result is `null` or `false`. That rule doesn't expect the result to be a `RuntimeException`, but in this case it always is. That is, the assertion is failing during optimization, not at run time. ### Does this PR introduce _any_ user-facing change? No. ### How was this patch tested? New unit test. Closes #36341 from bersprockets/outer_join_eval_assert_issue_32. Authored-by: Bruce Robbins <bersprockets@gmail.com> Signed-off-by: Wenchen Fan <wenchen@databricks.com> (cherry picked from commit 3690c8c) Signed-off-by: Wenchen Fan <wenchen@databricks.com>
…ate when optimizing outer joins Backport of apache#36230 ### What changes were proposed in this pull request? Change `EliminateOuterJoin#canFilterOutNull` to return `false` when a `where` condition throws an exception. ### Why are the changes needed? Consider this query: ``` select * from (select id, id as b from range(0, 10)) l left outer join (select id, id + 1 as c from range(0, 10)) r on l.id = r.id where assert_true(c > 0) is null; ``` The query should succeed, but instead fails with ``` java.lang.RuntimeException: '(c#1L > cast(0 as bigint))' is not true! ``` This happens even though there is no row where `c > 0` is false. The `EliminateOuterJoin` rule checks if it can convert the outer join to a inner join based on the expression in the where clause, which in this case is ``` assert_true(c > 0) is null ``` `EliminateOuterJoin#canFilterOutNull` evaluates that expression with `c` set to `null` to see if the result is `null` or `false`. That rule doesn't expect the result to be a `RuntimeException`, but in this case it always is. That is, the assertion is failing during optimization, not at run time. ### Does this PR introduce _any_ user-facing change? No. ### How was this patch tested? New unit test. Closes apache#36341 from bersprockets/outer_join_eval_assert_issue_32. Authored-by: Bruce Robbins <bersprockets@gmail.com> Signed-off-by: Wenchen Fan <wenchen@databricks.com> (cherry picked from commit 3690c8c)
What changes were proposed in this pull request?
Change
EliminateOuterJoin#canFilterOutNull
to returnfalse
when awhere
condition throws an exception.Why are the changes needed?
Consider this query:
The query should succeed, but instead fails with
This happens even though there is no row where
c > 0
is false.The
EliminateOuterJoin
rule checks if it can convert the outer join to a inner join based on the expression in the where clause, which in this case isEliminateOuterJoin#canFilterOutNull
evaluates that expression withc
set tonull
to see if the result isnull
orfalse
. That rule doesn't expect the result to be aRuntimeException
, but in this case it always is.That is, the assertion is failing during optimization, not at run time.
Does this PR introduce any user-facing change?
No.
How was this patch tested?
New unit test.