Skip to content
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

Closed

Conversation

bersprockets
Copy link
Contributor

@bersprockets bersprockets commented Apr 17, 2022

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.

@github-actions github-actions bot added the SQL label Apr 17, 2022
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)
Copy link
Contributor Author

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
Copy link
Member

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.

@HyukjinKwon
Copy link
Member

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)
Copy link
Contributor

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.

Copy link
Contributor Author

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...

Copy link
Contributor Author

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.

@cloud-fan
Copy link
Contributor

cc @sigmod

@bersprockets bersprockets changed the title [SPARK-38868][SQL] Avoid RaiseError exceptions while optimizing outer joins [SPARK-38868][SQL] Don't propagate exceptions from filter predicate when optimizing outer joins Apr 21, 2022
val v = boundE.eval(emptyRow)
v == null || v == false
} catch {
case e: Exception =>
Copy link
Contributor

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)
Copy link
Contributor

Choose a reason for hiding this comment

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

Suggested change
val message = Literal(UTF8String fromString("Bad value"), StringType)
val message = Literal(UTF8String.fromString("Bad value"), StringType)

@cloud-fan cloud-fan closed this in e2930b8 Apr 25, 2022
cloud-fan pushed a commit that referenced this pull request Apr 25, 2022
…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>
@cloud-fan
Copy link
Contributor

thanks, merging to master/3.3!

@cloud-fan
Copy link
Contributor

cloud-fan commented Apr 25, 2022

@bersprockets do you want to open backport PRs for 3.2/3.1?

@bersprockets
Copy link
Contributor Author

do you want to open backport PRs for 3.2/3.1?

Will do, thanks!

bersprockets added a commit to bersprockets/spark that referenced this pull request Apr 25, 2022
…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>
cloud-fan pushed a commit that referenced this pull request Apr 27, 2022
…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>
cloud-fan pushed a commit that referenced this pull request Apr 27, 2022
…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>
@bersprockets bersprockets deleted the outer_join_eval_assert_issue branch August 10, 2022 18:11
kazuyukitanimura pushed a commit to kazuyukitanimura/spark that referenced this pull request Aug 10, 2022
…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)
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Projects
None yet
3 participants