SQL: Fix issue with always false filter involving functions (#36830)
When a filter is evaluated to false then it becomes a LocalRelation with an EmptyExecutable. The LocalRelation in turn, becomes a LocalExec and the the SkipQueryIfFoldingProjection was wrongly converting it to a SingletonExecutable. Moreover made a change, so that the queries without FROM clause, which are supposed to return a single row, to become a LocalRelation with a SingletonExecutable instead of EmptyExecutable to avoid mixing up with the ones operating on a table but with a filter that evaluates to false. Fixes: #35980
This commit is contained in:
parent
a7f344cc7f
commit
6221d6be49
|
@ -68,6 +68,7 @@ import org.elasticsearch.xpack.sql.plan.logical.LogicalPlan;
|
||||||
import org.elasticsearch.xpack.sql.plan.logical.OrderBy;
|
import org.elasticsearch.xpack.sql.plan.logical.OrderBy;
|
||||||
import org.elasticsearch.xpack.sql.plan.logical.Project;
|
import org.elasticsearch.xpack.sql.plan.logical.Project;
|
||||||
import org.elasticsearch.xpack.sql.plan.logical.SubQueryAlias;
|
import org.elasticsearch.xpack.sql.plan.logical.SubQueryAlias;
|
||||||
|
import org.elasticsearch.xpack.sql.plan.logical.UnaryPlan;
|
||||||
import org.elasticsearch.xpack.sql.rule.Rule;
|
import org.elasticsearch.xpack.sql.rule.Rule;
|
||||||
import org.elasticsearch.xpack.sql.rule.RuleExecutor;
|
import org.elasticsearch.xpack.sql.rule.RuleExecutor;
|
||||||
import org.elasticsearch.xpack.sql.session.EmptyExecutable;
|
import org.elasticsearch.xpack.sql.session.EmptyExecutable;
|
||||||
|
@ -1849,14 +1850,15 @@ public class Optimizer extends RuleExecutor<LogicalPlan> {
|
||||||
if (plan instanceof Project) {
|
if (plan instanceof Project) {
|
||||||
Project p = (Project) plan;
|
Project p = (Project) plan;
|
||||||
List<Object> values = extractConstants(p.projections());
|
List<Object> values = extractConstants(p.projections());
|
||||||
if (values.size() == p.projections().size() && !(p.child() instanceof EsRelation)) {
|
if (values.size() == p.projections().size() && !(p.child() instanceof EsRelation) &&
|
||||||
|
isNotQueryWithFromClauseAndFilterFoldedToFalse(p)) {
|
||||||
return new LocalRelation(p.location(), new SingletonExecutable(p.output(), values.toArray()));
|
return new LocalRelation(p.location(), new SingletonExecutable(p.output(), values.toArray()));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (plan instanceof Aggregate) {
|
if (plan instanceof Aggregate) {
|
||||||
Aggregate a = (Aggregate) plan;
|
Aggregate a = (Aggregate) plan;
|
||||||
List<Object> values = extractConstants(a.aggregates());
|
List<Object> values = extractConstants(a.aggregates());
|
||||||
if (values.size() == a.aggregates().size()) {
|
if (values.size() == a.aggregates().size() && isNotQueryWithFromClauseAndFilterFoldedToFalse(a)) {
|
||||||
return new LocalRelation(a.location(), new SingletonExecutable(a.output(), values.toArray()));
|
return new LocalRelation(a.location(), new SingletonExecutable(a.output(), values.toArray()));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1875,6 +1877,15 @@ public class Optimizer extends RuleExecutor<LogicalPlan> {
|
||||||
}
|
}
|
||||||
return values;
|
return values;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Check if the plan doesn't model a query with FROM clause on a table
|
||||||
|
* that its filter (WHERE clause) is folded to FALSE.
|
||||||
|
*/
|
||||||
|
private static boolean isNotQueryWithFromClauseAndFilterFoldedToFalse(UnaryPlan plan) {
|
||||||
|
return (!(plan.child() instanceof LocalRelation) || (plan.child() instanceof LocalRelation &&
|
||||||
|
!(((LocalRelation) plan.child()).executable() instanceof EmptyExecutable)));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -41,7 +41,7 @@ import org.elasticsearch.xpack.sql.plan.logical.SubQueryAlias;
|
||||||
import org.elasticsearch.xpack.sql.plan.logical.UnresolvedRelation;
|
import org.elasticsearch.xpack.sql.plan.logical.UnresolvedRelation;
|
||||||
import org.elasticsearch.xpack.sql.plan.logical.With;
|
import org.elasticsearch.xpack.sql.plan.logical.With;
|
||||||
import org.elasticsearch.xpack.sql.proto.SqlTypedParamValue;
|
import org.elasticsearch.xpack.sql.proto.SqlTypedParamValue;
|
||||||
import org.elasticsearch.xpack.sql.session.EmptyExecutable;
|
import org.elasticsearch.xpack.sql.session.SingletonExecutable;
|
||||||
import org.elasticsearch.xpack.sql.type.DataType;
|
import org.elasticsearch.xpack.sql.type.DataType;
|
||||||
|
|
||||||
import java.util.LinkedHashMap;
|
import java.util.LinkedHashMap;
|
||||||
|
@ -104,7 +104,7 @@ abstract class LogicalPlanBuilder extends ExpressionBuilder {
|
||||||
public LogicalPlan visitQuerySpecification(QuerySpecificationContext ctx) {
|
public LogicalPlan visitQuerySpecification(QuerySpecificationContext ctx) {
|
||||||
LogicalPlan query;
|
LogicalPlan query;
|
||||||
if (ctx.fromClause() == null) {
|
if (ctx.fromClause() == null) {
|
||||||
query = new LocalRelation(source(ctx), new EmptyExecutable(emptyList()));
|
query = new LocalRelation(source(ctx), new SingletonExecutable());
|
||||||
} else {
|
} else {
|
||||||
query = plan(ctx.fromClause());
|
query = plan(ctx.fromClause());
|
||||||
}
|
}
|
||||||
|
|
|
@ -9,6 +9,7 @@ import org.elasticsearch.action.ActionListener;
|
||||||
import org.elasticsearch.xpack.sql.expression.Attribute;
|
import org.elasticsearch.xpack.sql.expression.Attribute;
|
||||||
import org.elasticsearch.xpack.sql.util.Check;
|
import org.elasticsearch.xpack.sql.util.Check;
|
||||||
|
|
||||||
|
import java.util.Collections;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
|
||||||
public class SingletonExecutable implements Executable {
|
public class SingletonExecutable implements Executable {
|
||||||
|
@ -16,6 +17,10 @@ public class SingletonExecutable implements Executable {
|
||||||
private final List<Attribute> output;
|
private final List<Attribute> output;
|
||||||
private final Object[] values;
|
private final Object[] values;
|
||||||
|
|
||||||
|
public SingletonExecutable() {
|
||||||
|
this(Collections.emptyList());
|
||||||
|
}
|
||||||
|
|
||||||
public SingletonExecutable(List<Attribute> output, Object... values) {
|
public SingletonExecutable(List<Attribute> output, Object... values) {
|
||||||
Check.isTrue(output.size() == values.length, "Attributes {} and values {} are out of sync", output, values);
|
Check.isTrue(output.size() == values.length, "Attributes {} and values {} are out of sync", output, values);
|
||||||
this.output = output;
|
this.output = output;
|
||||||
|
|
|
@ -18,6 +18,7 @@ import org.elasticsearch.xpack.sql.plan.physical.EsQueryExec;
|
||||||
import org.elasticsearch.xpack.sql.plan.physical.LocalExec;
|
import org.elasticsearch.xpack.sql.plan.physical.LocalExec;
|
||||||
import org.elasticsearch.xpack.sql.plan.physical.PhysicalPlan;
|
import org.elasticsearch.xpack.sql.plan.physical.PhysicalPlan;
|
||||||
import org.elasticsearch.xpack.sql.session.EmptyExecutable;
|
import org.elasticsearch.xpack.sql.session.EmptyExecutable;
|
||||||
|
import org.elasticsearch.xpack.sql.session.SingletonExecutable;
|
||||||
import org.elasticsearch.xpack.sql.stats.Metrics;
|
import org.elasticsearch.xpack.sql.stats.Metrics;
|
||||||
import org.elasticsearch.xpack.sql.type.EsField;
|
import org.elasticsearch.xpack.sql.type.EsField;
|
||||||
import org.elasticsearch.xpack.sql.type.TypesTests;
|
import org.elasticsearch.xpack.sql.type.TypesTests;
|
||||||
|
@ -68,6 +69,48 @@ public class QueryFolderTests extends ESTestCase {
|
||||||
assertThat(ee.output().get(0).toString(), startsWith("keyword{f}#"));
|
assertThat(ee.output().get(0).toString(), startsWith("keyword{f}#"));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void testLocalExecWithPrunedFilterWithFunction() {
|
||||||
|
PhysicalPlan p = plan("SELECT E() FROM test WHERE PI() = 5");
|
||||||
|
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("E{c}#"));
|
||||||
|
}
|
||||||
|
|
||||||
|
public void testLocalExecWithPrunedFilterWithFunctionAndAggregation() {
|
||||||
|
PhysicalPlan p = plan("SELECT E() FROM test WHERE PI() = 5 GROUP BY 1");
|
||||||
|
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("E{c}#"));
|
||||||
|
}
|
||||||
|
|
||||||
|
public void testLocalExecWithoutFromClause() {
|
||||||
|
PhysicalPlan p = plan("SELECT E(), 'foo', abs(10)");
|
||||||
|
assertEquals(LocalExec.class, p.getClass());
|
||||||
|
LocalExec le = (LocalExec) p;
|
||||||
|
assertEquals(SingletonExecutable.class, le.executable().getClass());
|
||||||
|
SingletonExecutable ee = (SingletonExecutable) le.executable();
|
||||||
|
assertEquals(3, ee.output().size());
|
||||||
|
assertThat(ee.output().get(0).toString(), startsWith("E{c}#"));
|
||||||
|
assertThat(ee.output().get(1).toString(), startsWith("foo{c}#"));
|
||||||
|
assertThat(ee.output().get(2).toString(), startsWith("ABS(10){c}#"));
|
||||||
|
}
|
||||||
|
|
||||||
|
public void testLocalExecWithoutFromClauseWithPrunedFilter() {
|
||||||
|
PhysicalPlan p = plan("SELECT E() WHERE PI() = 5");
|
||||||
|
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("E{c}#"));
|
||||||
|
}
|
||||||
|
|
||||||
public void testFoldingOfIsNull() {
|
public void testFoldingOfIsNull() {
|
||||||
PhysicalPlan p = plan("SELECT keyword FROM test WHERE (keyword IS NOT NULL) IS NULL");
|
PhysicalPlan p = plan("SELECT keyword FROM test WHERE (keyword IS NOT NULL) IS NULL");
|
||||||
assertEquals(LocalExec.class, p.getClass());
|
assertEquals(LocalExec.class, p.getClass());
|
||||||
|
|
Loading…
Reference in New Issue