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
SQL: Introduce IsNull node to simplify expressions #35206
Conversation
Add `IsNull` node in parser to simplify expressions so that `<value> IS NULL` is no longer translated internally to `NOT(<value> IS NOT NULL)` Closes: elastic#34876
Pinging @elastic/es-search-aggs |
Integration tests for HAVING will be introduced together with the fix here: https://github.com/elastic/elasticsearch/pull/35164/files#diff-a3ba4284670bdc13a2ef764cff91a212 |
retest this please |
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 good however the change is too broad (see my comments on negatable) hence my requests for changes.
|
||
import java.io.IOException; | ||
|
||
public class IsNullProcessor implements Processor { |
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.
As we have two very similar processors, it makes sense to aggregate the isnull/isnotnull processors into a single processor class which uses an enum to differentiate between the two (as we do with the rest of other operators).
This makes things consistent and keeps the serialization name explosion to a minimum.
It's also a good reason to keep this a 6.6 feature and not backport it to 6.5 (since it's an enhancement not really a bug).
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.
If that's the case and the change isn't too big, it's worth backporting yes.
new ConstantFolding(), | ||
// boolean | ||
new BooleanSimplification(), | ||
new BooleanLiteralsOnTheRight(), | ||
new BinaryComparisonSimplification(), | ||
// need to occur after simplification |
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.
need to occur after simplification
Why? It should get picked up on the next cycle in the worse case scenario.
@@ -58,8 +57,8 @@ public String processScript(String script) { | |||
@Override | |||
protected Expression canonicalize() { | |||
Expression canonicalChild = field().canonical(); | |||
if (canonicalChild instanceof Negateable) { | |||
return ((Negateable) canonicalChild).negate(); | |||
if (canonicalChild instanceof UnaryNegateable) { |
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 doesn't look good as it essentially removes NOT
over binary logical negateable NOT (x AND y)
.
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.
That's a mistake for sure, thx for catching!
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 add some tests please with NOT
in HAVING
over AND and IS NULL to check this automatically in the future?
@@ -19,6 +19,10 @@ | |||
|
|||
public abstract class UnaryScalarFunction extends ScalarFunction { | |||
|
|||
public interface UnaryNegateable { |
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.
Regardless of the number of children, there should be a negatable
(I believe that's the correct syntax) interface common to all. In other words, Negatable should be moved up from BinaryOperator
to potentially a standalone interface and likely have its generic signature extended to bound its return type (Negatable<T extends ScalarFunction>
).
@@ -1278,8 +1284,11 @@ private Expression simplifyNot(Not n) { | |||
return TRUE; | |||
} | |||
|
|||
if (c instanceof Negateable) { | |||
return ((Negateable) c).negate(); | |||
if (c instanceof BinaryNegateable) { |
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.
Case in point, it doesn't make any difference internally whether an expression is BinaryNegtable
or Unary
- the result in both cases is an Expression
so why differentiate between the two.
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.
It was just because of the 2 different ifaces, but as you suggested we'd better merge them into one.
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.
Made some comments. Thanks.
package org.elasticsearch.xpack.sql.expression.function.scalar; | ||
|
||
public interface Negateable { | ||
|
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 to extract the interface so high up - just put it inside the predicate package since essentially it applies only to logical predicates. Also the correct name should be Negatable
.
Also it's worth looking into adding the generic aspect in place to have a better lock down on the class hierarchy:
Negatable<T extends ScalarFunction> { T negate() }
. IsNull/IsNotNull can lock it to UnaryScalarFunction
while the BinaryLogic
keep the BinaryOperator
in place.
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.
But i didn't change its name: Negate
+ able
= Negateable
:) but the correct word is negatable, so I'll change.
public enum CheckNullOperation implements Function<Object, Boolean> { | ||
|
||
IS_NULL(Objects::isNull, "IS NULL"), | ||
IS_NOT_NULL(o -> !Objects.isNull(o), "IS NOT 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.
Use Objects::nonNull
(the main reason these methods exist on Objects).
new ConstantFolding(), | ||
new FoldNull(), |
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.
Again, why was FoldNull
moved?
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 reason, missed the initial place, will change.
retest this please |
1 similar comment
retest this please |
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
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. Looking great
Backported to |
Backported to |
Add
IsNull
node in parser to simplify expressions so that<value> IS NULL
isno longer translated internally to
NOT(<value> IS NOT NULL)
Replace
IsNotNullProcessor
withCheckNullProcessor
to encapsulate bothisNull and isNotNull functionality.
Closes: #34876
Fixes: #35171