Skip to content

Conversation

@matriv
Copy link
Contributor

@matriv matriv commented Nov 4, 2018

Eliminate null literal in the Optimizer where we know that we
are in the WHERE clause so we can evaluate NULL to false. Now the
null literal is not reaching the QueryTranslator.

Fixes: #35088

@elasticmachine
Copy link
Collaborator

Pinging @elastic/es-search-aggs

@matriv matriv removed request for astefan and costin November 4, 2018 15:19
@matriv matriv added the WIP label Nov 4, 2018
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
@matriv matriv removed the WIP label Nov 4, 2018
@matriv matriv requested review from astefan and costin November 4, 2018 20:18
Copy link
Member

@costin costin left a 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");
Copy link
Member

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.

Copy link
Member

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.

Copy link
Contributor Author

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

@matriv
Copy link
Contributor Author

matriv commented Nov 6, 2018

@costin @astefan Implemented the new solution as @costin suggested. Please check again.

Copy link
Member

@costin costin left a 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> {
Copy link
Member

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

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.

Copy link
Contributor Author

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())) {
Copy link
Member

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

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

Choose a reason for hiding this comment

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

?

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 have my editor to trim trailing whitespaces from lines.

Copy link
Member

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

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 have already changed it, i did it once by mistake, will revert the changes.

}
Object eqValue = eq.right().fold();

Copy link
Member

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?

Copy link
Contributor

@astefan astefan left a comment

Choose a reason for hiding this comment

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

LGTM

Copy link
Member

@costin costin left a comment

Choose a reason for hiding this comment

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

More comments :)

};
}
}

Copy link
Member

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

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

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

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

Copy link
Contributor Author

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

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.

Copy link
Contributor Author

@matriv matriv Nov 8, 2018

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.

Copy link
Member

@costin costin left a comment

Choose a reason for hiding this comment

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

LGTM! 3VL!

@matriv
Copy link
Contributor Author

matriv commented Nov 8, 2018

retest this please

1 similar comment
@matriv
Copy link
Contributor Author

matriv commented Nov 8, 2018

retest this please

@matriv matriv merged commit c9a84fe into elastic:master Nov 8, 2018
@matriv matriv deleted the mt/fix-35088 branch November 8, 2018 22:33
matriv pushed a commit that referenced this pull request Nov 8, 2018
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>
@matriv
Copy link
Contributor Author

matriv commented Nov 8, 2018

Backported to 6.x with dec500d

matriv pushed a commit that referenced this pull request Nov 8, 2018
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>
@matriv
Copy link
Contributor Author

matriv commented Nov 8, 2018

Backported to 6.5 with 4d0aba2

@matriv
Copy link
Contributor Author

matriv commented Nov 9, 2018

@costin Thx a lot for all your help here!

@jimczi jimczi added v7.0.0-beta1 and removed v7.0.0 labels Feb 7, 2019
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment