SQL: Fix edge case: `<field> IN (null)` (#34802)

Handle the case when `null` is the only value in the list so that
it's translated to a `MatchNoDocsQuery`.

Followup to: #34750
This commit is contained in:
Marios Trivyzas 2018-10-25 14:24:11 +02:00 committed by GitHub
parent a69c540f12
commit bd143334d3
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
5 changed files with 41 additions and 6 deletions

View File

@ -78,11 +78,19 @@ public class In extends NamedExpression implements ScriptWeaver {
@Override @Override
public boolean foldable() { public boolean foldable() {
return Expressions.foldable(children()); return Expressions.foldable(children()) ||
(Expressions.foldable(list) && list().stream().allMatch(e -> e.dataType() == DataType.NULL));
} }
@Override @Override
public Boolean fold() { public Boolean fold() {
if (value.dataType() == DataType.NULL) {
return null;
}
if (list.size() == 1 && list.get(0).dataType() == DataType.NULL) {
return false;
}
Object foldedLeftValue = value.fold(); Object foldedLeftValue = value.fold();
Boolean result = false; Boolean result = false;
for (Expression rightValue : list) { for (Expression rightValue : list) {

View File

@ -11,23 +11,28 @@ import org.elasticsearch.xpack.sql.expression.Foldables;
import org.elasticsearch.xpack.sql.tree.Location; import org.elasticsearch.xpack.sql.tree.Location;
import org.elasticsearch.xpack.sql.type.DataType; import org.elasticsearch.xpack.sql.type.DataType;
import java.util.Collections;
import java.util.LinkedHashSet; import java.util.LinkedHashSet;
import java.util.List; import java.util.List;
import java.util.Objects; import java.util.Objects;
import java.util.Set;
import static org.elasticsearch.index.query.QueryBuilders.termsQuery; import static org.elasticsearch.index.query.QueryBuilders.termsQuery;
public class TermsQuery extends LeafQuery { public class TermsQuery extends LeafQuery {
private final String term; private final String term;
private final LinkedHashSet<Object> values; private final Set<Object> values;
public TermsQuery(Location location, String term, List<Expression> values) { public TermsQuery(Location location, String term, List<Expression> values) {
super(location); super(location);
this.term = term; this.term = term;
values.removeIf(e -> e.dataType() == DataType.NULL); values.removeIf(e -> e.dataType() == DataType.NULL);
this.values = new LinkedHashSet<>(Foldables.valuesOf(values, values.get(0).dataType())); if (values.isEmpty()) {
this.values.removeIf(Objects::isNull); this.values = Collections.emptySet();
} else {
this.values = new LinkedHashSet<>(Foldables.valuesOf(values, values.get(0).dataType()));
}
} }
@Override @Override

View File

@ -95,7 +95,7 @@ public class OptimizerTests extends ESTestCase {
private static final Literal FOUR = L(4); private static final Literal FOUR = L(4);
private static final Literal FIVE = L(5); private static final Literal FIVE = L(5);
private static final Literal SIX = L(6); private static final Literal SIX = L(6);
private static final Literal NULL = L(null);
public static class DummyBooleanExpression extends Expression { public static class DummyBooleanExpression extends Expression {
@ -323,6 +323,18 @@ public class OptimizerTests extends ESTestCase {
assertThat(Foldables.valuesOf(in.list(), DataType.INTEGER), contains(1 ,2 ,3 ,4)); assertThat(Foldables.valuesOf(in.list(), DataType.INTEGER), contains(1 ,2 ,3 ,4));
} }
public void testConstantFoldingIn_RightValueIsNull() {
In in = new In(EMPTY, getFieldAttribute(), Arrays.asList(NULL, NULL));
Literal result= (Literal) new ConstantFolding().rule(in);
assertEquals(false, result.value());
}
public void testConstantFoldingIn_LeftValueIsNull() {
In in = new In(EMPTY, NULL, Arrays.asList(ONE, TWO, THREE));
Literal result= (Literal) new ConstantFolding().rule(in);
assertNull(result.value());
}
public void testArithmeticFolding() { public void testArithmeticFolding() {
assertEquals(10, foldOperator(new Add(EMPTY, L(7), THREE))); assertEquals(10, foldOperator(new Add(EMPTY, L(7), THREE)));
assertEquals(4, foldOperator(new Sub(EMPTY, L(7), THREE))); assertEquals(4, foldOperator(new Sub(EMPTY, L(7), THREE)));

View File

@ -64,6 +64,16 @@ public class QueryFolderTests extends AbstractBuilderTestCase {
assertThat(ee.output().get(0).toString(), startsWith("keyword{f}#")); assertThat(ee.output().get(0).toString(), startsWith("keyword{f}#"));
} }
public void testFoldingToLocalExecWithProject_FoldableIn() {
PhysicalPlan p = plan("SELECT keyword FROM test WHERE int IN (null, null)");
assertEquals(LocalExec.class, p.getClass());
LocalExec le = (LocalExec) p;
assertEquals(EmptyExecutable.class, le.executable().getClass());
EmptyExecutable ee = (EmptyExecutable) le.executable();
assertEquals(1, ee.output().size());
assertThat(ee.output().get(0).toString(), startsWith("keyword{f}#"));
}
public void testFoldingToLocalExecWithProject_WithOrderAndLimit() { public void testFoldingToLocalExecWithProject_WithOrderAndLimit() {
PhysicalPlan p = plan("SELECT keyword FROM test WHERE 1 = 2 ORDER BY int LIMIT 10"); PhysicalPlan p = plan("SELECT keyword FROM test WHERE 1 = 2 ORDER BY int LIMIT 10");
assertEquals(LocalExec.class, p.getClass()); assertEquals(LocalExec.class, p.getClass());

View File

@ -173,7 +173,7 @@ public class QueryTranslatorTests extends AbstractBuilderTestCase {
assertEquals("keyword:(bar foo lala)", tq.asBuilder().toQuery(createShardContext()).toString()); assertEquals("keyword:(bar foo lala)", tq.asBuilder().toQuery(createShardContext()).toString());
} }
public void testTranslateInExpression_WhereClauseAndNullHAndling() throws IOException { public void testTranslateInExpression_WhereClauseAndNullHandling() throws IOException {
LogicalPlan p = plan("SELECT * FROM test WHERE keyword IN ('foo', null, 'lala', null, 'foo', concat('la', 'la'))"); LogicalPlan p = plan("SELECT * FROM test WHERE keyword IN ('foo', null, 'lala', null, 'foo', concat('la', 'la'))");
assertTrue(p instanceof Project); assertTrue(p instanceof Project);
assertTrue(p.children().get(0) instanceof Filter); assertTrue(p.children().get(0) instanceof Filter);