- Notifications
You must be signed in to change notification settings - Fork 25.6k
SQL: Handle null literal for AND and OR in WHERE #35236
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
Conversation
| Pinging @elastic/es-search-aggs |
Translate `null` to a null query and since we know that we are in the WHERE clause so we can evaluate those nulls to false. Fixes: elastic#35088
costin left a comment
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.
This needs fixing in a different place.
| } | ||
| | ||
| public void testFoldingToLocalExecBooleanAndNull_WhereClause() { | ||
| PhysicalPlan p = plan("SELECT keyword FROM test WHERE int > 10 AND null"); |
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.
Looks to me like this should be handled by the optimizer - namely it should evaluate the AND to 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.
Darn it, lost my comment - the issue is with And nullable which should return true if either one of its params is nullable not if both are. Once fixed, the nullable rule should kick in, nullify the end which would become a false query which would then be equivalent to 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.
If we do that then the <expr> OR null is not foldable so the null reached the QueryTranslator and cannot be translated. The problem is also that with OR we need to partially fold only the null side and eliminate the OR, by returning the non nullable side. It's related to: #35232
as for OR we might fold one of the sides and remove OR and for AND we might need to eliminate it completly.
costin left a comment
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.
The issue could be addressed directly in Or and And nullable(), causing the existing rules to trigger.
| } | ||
| } | ||
| | ||
| static class FoldBinaryLogical implements java.util.function.Function<Expression, Expression> { |
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.
No need for a class or instance, simply move the apply(Expression) to a method inside the main class and simply call it or use a method reference.
| static FoldBinaryLogical INSTANCE = new FoldBinaryLogical(); | ||
| | ||
| @Override | ||
| public Expression apply(Expression expression) { |
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.
Since this method is about nulls it should be inside FoldNull rule. Further more, I don't think there's a need for a rule inside the optimizer, just that Or and And should have the proper nullable() implementations.
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.
The issue is that Or and And cannot be folded in the FoldNull (except if both are null) because we need the 3vl in the select clause. So for example: we cannot fold col1 or null there, because this can potentially return true or null, for and: se cannot fold col1 and null because depending on col1 in can result to false or null. But when we are inside the PruneFilter we know that now we can fold.
| } else if (e instanceof In) { | ||
| In in = (In) e; | ||
| if (Expressions.isNull(in.value())) { | ||
| if (canFoldToNullLiteral(in.value())) { |
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.
Shouldn't be needed since the fallback if should nullify the value in the first run then fold In in the second.
| } | ||
| | ||
| if (e.nullable() && Expressions.anyMatch(e.children(), Expressions::isNull)) { | ||
| } else if (e instanceof BinaryLogic) { |
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.
This shouldn't be needed if And/Or.nullable return the proper value.
| * When encountering a different Equals or non-containing {@link Range}, the conjunction becomes false. | ||
| * When encountering a containing {@link Range}, the range gets eliminated by the equality. | ||
| * | ||
| * |
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.
?
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 have my editor to trim trailing whitespaces from lines.
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 you please change that to apply only to the modified lines and not the whole fail?
Thanks.
Regardless of the rules chosen, we tend to apply formatting once per project/packages otherwise just for the stuff added.
This reduces the noise when multiple folks work on the same file (like this case).
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 have already changed it, i did it once by mistake, will revert the changes.
| } | ||
| Object eqValue = eq.right().fold(); | ||
| |
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.
Same here - what's with the whitespace changes?
astefan left a comment
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.
LGTM
costin left a comment
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.
More comments :)
| }; | ||
| } | ||
| } | ||
| |
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.
?
| } | ||
| | ||
| | ||
| static Expression foldBinaryLogic(Expression expression) { |
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 think it would be better to move this over to the PruneFilter since it's not actually used inside FoldNull.
| } | ||
| | ||
| if (e.nullable() && Expressions.anyMatch(e.children(), Expressions::isNull)) { | ||
| } else if (e.nullable() && Expressions.anyMatch(e.children(), Expressions::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.
Please add a comment or empty line to space out the } else if (...)
The ES formatting uses this style which unfortunately makes less readable the if condition
(it's also why in general I tend to not favor else if.
| | ||
| @Override | ||
| public boolean nullable() { | ||
| if (left().nullable() && right().foldable() && right().fold() == Boolean.TRUE) { |
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.
Maybe add a static package method on BinaryLogic to avoid doing left/right.foldable() && fold = ? and do if (maybeFoldsTo(right(), Boolean.TRUE) etc...
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.
yep, forgot that, extracting method to Foldables
| return true; | ||
| } | ||
| | ||
| if (right().nullable() && left().foldable() && left().fold() == Boolean.TRUE) { |
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.
While it might be the logical equivalent, I find the current statement harder to understand.
The logic for AND is: if either side is FALSE, I'm not nullable; otherwise I am if either child is.
For OR it would: if either side is TRUE, I'm not nullable, otherwise the same as above.
In both cases, if the constant checks, there's no nullability check on the children.
The method above checks for the nullability first and then for the folding for the opposite value of each side which I find counter intuitive.
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's take OR and the rule you propose:
if (Foldables.foldsAndMatches(right(), o -> o == Boolean.TRUE) || Foldables.foldsAndMatches(left(), o -> o == Boolean.TRUE)) { return false; } return left().nullable() || right().nullable(); and the following option:
<expr> OR null => we return true as right is nullable.
Then the FoldNull will check that one of the children is nullable (the right one is) and fold the whole expression to null event if the <expr> evaluates to true.
costin left a comment
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.
LGTM! 3VL!
| retest this please |
1 similar comment
| retest this please |
Change `nullable()` logic of AND and OR to false since in the Optimizer we cannot fold to null as we might be handling and expression in the SELECT clause. Introduce folding of null for AND and OR expressions in PruneFilter() since we now know that we are in HAVING or WHERE clause and we can fold `null` to `false` Fixes: #35088 Co-authored-by: Costin Leau <costin.leau@gmail.com>
| Backported to |
Change `nullable()` logic of AND and OR to false since in the Optimizer we cannot fold to null as we might be handling and expression in the SELECT clause. Introduce folding of null for AND and OR expressions in PruneFilter() since we now know that we are in HAVING or WHERE clause and we can fold `null` to `false` Fixes: #35088 Co-authored-by: Costin Leau <costin.leau@gmail.com>
| Backported to |
| @costin Thx a lot for all your help here! |
Eliminate
nullliteral in theOptimizerwhere we know that weare in the WHERE clause so we can evaluate NULL to false. Now the
nullliteral is not reaching theQueryTranslator.Fixes: #35088