SQL: Optimizer rule for folding nullable expressions (#35080)

Add optimization for folding nullable expressions with a NULL 
argument. This is a variant of folding for the NULL case.

Fix #34826
This commit is contained in:
Costin Leau 2018-10-30 20:17:20 +02:00 committed by GitHub
parent 512319cef7
commit 6abddd8ca6
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
9 changed files with 110 additions and 7 deletions

View File

@ -78,6 +78,7 @@ public abstract class Expression extends Node<Expression> implements Resolvable
throw new SqlIllegalArgumentException("Should not fold expression"); throw new SqlIllegalArgumentException("Should not fold expression");
} }
// whether the expression becomes null if at least one param/input is null
public abstract boolean nullable(); public abstract boolean nullable();
// the references/inputs/leaves of the expression tree // the references/inputs/leaves of the expression tree

View File

@ -161,7 +161,11 @@ public class Literal extends NamedExpression {
if (name == null) { if (name == null) {
name = foldable instanceof NamedExpression ? ((NamedExpression) foldable).name() : String.valueOf(fold); name = foldable instanceof NamedExpression ? ((NamedExpression) foldable).name() : String.valueOf(fold);
} }
return new Literal(foldable.location(), name, fold, foldable.dataType()); return new Literal(foldable.location(), name, fold, foldable.dataType());
} }
public static Literal of(Expression source, Object value) {
String name = source instanceof NamedExpression ? ((NamedExpression) source).name() : String.valueOf(value);
return new Literal(source.location(), name, value, source.dataType());
}
} }

View File

@ -46,7 +46,7 @@ public abstract class Function extends NamedExpression {
@Override @Override
public boolean nullable() { public boolean nullable() {
return false; return Expressions.nullable(children());
} }
@Override @Override

View File

@ -49,6 +49,11 @@ public class Concat extends BinaryScalarFunction {
return new ConcatFunctionPipe(location(), this, Expressions.pipe(left()), Expressions.pipe(right())); return new ConcatFunctionPipe(location(), this, Expressions.pipe(left()), Expressions.pipe(right()));
} }
@Override
public boolean nullable() {
return left().nullable() && right().nullable();
}
@Override @Override
public boolean foldable() { public boolean foldable() {
return left().foldable() && right().foldable(); return left().foldable() && right().foldable();

View File

@ -33,4 +33,9 @@ public abstract class BinaryLogic extends BinaryOperator<Boolean, Boolean, Boole
protected Pipe makePipe() { protected Pipe makePipe() {
return new BinaryLogicPipe(location(), this, Expressions.pipe(left()), Expressions.pipe(right()), function()); return new BinaryLogicPipe(location(), this, Expressions.pipe(left()), Expressions.pipe(right()), function());
} }
@Override
public boolean nullable() {
return left().nullable() && right().nullable();
}
} }

View File

@ -39,6 +39,8 @@ import org.elasticsearch.xpack.sql.expression.function.scalar.ScalarFunctionAttr
import org.elasticsearch.xpack.sql.expression.predicate.BinaryOperator; import org.elasticsearch.xpack.sql.expression.predicate.BinaryOperator;
import org.elasticsearch.xpack.sql.expression.predicate.BinaryOperator.Negateable; import org.elasticsearch.xpack.sql.expression.predicate.BinaryOperator.Negateable;
import org.elasticsearch.xpack.sql.expression.predicate.BinaryPredicate; import org.elasticsearch.xpack.sql.expression.predicate.BinaryPredicate;
import org.elasticsearch.xpack.sql.expression.predicate.In;
import org.elasticsearch.xpack.sql.expression.predicate.IsNotNull;
import org.elasticsearch.xpack.sql.expression.predicate.Predicates; import org.elasticsearch.xpack.sql.expression.predicate.Predicates;
import org.elasticsearch.xpack.sql.expression.predicate.Range; import org.elasticsearch.xpack.sql.expression.predicate.Range;
import org.elasticsearch.xpack.sql.expression.predicate.logical.And; import org.elasticsearch.xpack.sql.expression.predicate.logical.And;
@ -63,6 +65,7 @@ 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;
import org.elasticsearch.xpack.sql.session.SingletonExecutable; import org.elasticsearch.xpack.sql.session.SingletonExecutable;
import org.elasticsearch.xpack.sql.type.DataType;
import org.elasticsearch.xpack.sql.util.CollectionUtils; import org.elasticsearch.xpack.sql.util.CollectionUtils;
import java.util.ArrayList; import java.util.ArrayList;
@ -122,6 +125,7 @@ public class Optimizer extends RuleExecutor<LogicalPlan> {
new CombineProjections(), new CombineProjections(),
// folding // folding
new ReplaceFoldableAttributes(), new ReplaceFoldableAttributes(),
new FoldNull(),
new ConstantFolding(), new ConstantFolding(),
// boolean // boolean
new BooleanSimplification(), new BooleanSimplification(),
@ -682,8 +686,7 @@ public class Optimizer extends RuleExecutor<LogicalPlan> {
if (TRUE.equals(filter.condition())) { if (TRUE.equals(filter.condition())) {
return filter.child(); return filter.child();
} }
// TODO: add comparison with null as well if (FALSE.equals(filter.condition()) || FoldNull.isNull(filter.condition())) {
if (FALSE.equals(filter.condition())) {
return new LocalRelation(filter.location(), new EmptyExecutable(filter.output())); return new LocalRelation(filter.location(), new EmptyExecutable(filter.output()));
} }
} }
@ -1112,6 +1115,41 @@ public class Optimizer extends RuleExecutor<LogicalPlan> {
} }
} }
static class FoldNull extends OptimizerExpressionRule {
FoldNull() {
super(TransformDirection.UP);
}
private static boolean isNull(Expression ex) {
return DataType.NULL == ex.dataType() || (ex.foldable() && ex.fold() == null);
}
@Override
protected Expression rule(Expression e) {
if (e instanceof IsNotNull) {
if (((IsNotNull) e).field().nullable() == false) {
return new Literal(e.location(), Expressions.name(e), Boolean.TRUE, DataType.BOOLEAN);
}
}
// see https://github.com/elastic/elasticsearch/issues/34876
// similar for IsNull once it gets introduced
if (e instanceof In) {
In in = (In) e;
if (isNull(in.value())) {
return Literal.of(in, null);
}
}
if (e.nullable() && Expressions.anyMatch(e.children(), FoldNull::isNull)) {
return Literal.of(e, null);
}
return e;
}
}
static class ConstantFolding extends OptimizerExpressionRule { static class ConstantFolding extends OptimizerExpressionRule {
ConstantFolding() { ConstantFolding() {

View File

@ -19,6 +19,7 @@ import org.elasticsearch.xpack.sql.expression.function.Function;
import org.elasticsearch.xpack.sql.expression.function.aggregate.AggregateFunction; import org.elasticsearch.xpack.sql.expression.function.aggregate.AggregateFunction;
import org.elasticsearch.xpack.sql.expression.function.aggregate.Count; import org.elasticsearch.xpack.sql.expression.function.aggregate.Count;
import org.elasticsearch.xpack.sql.expression.function.scalar.Cast; import org.elasticsearch.xpack.sql.expression.function.scalar.Cast;
import org.elasticsearch.xpack.sql.expression.function.scalar.datetime.DayName;
import org.elasticsearch.xpack.sql.expression.function.scalar.datetime.DayOfMonth; import org.elasticsearch.xpack.sql.expression.function.scalar.datetime.DayOfMonth;
import org.elasticsearch.xpack.sql.expression.function.scalar.datetime.DayOfYear; import org.elasticsearch.xpack.sql.expression.function.scalar.datetime.DayOfYear;
import org.elasticsearch.xpack.sql.expression.function.scalar.datetime.MonthOfYear; import org.elasticsearch.xpack.sql.expression.function.scalar.datetime.MonthOfYear;
@ -28,8 +29,11 @@ import org.elasticsearch.xpack.sql.expression.function.scalar.math.ACos;
import org.elasticsearch.xpack.sql.expression.function.scalar.math.ASin; import org.elasticsearch.xpack.sql.expression.function.scalar.math.ASin;
import org.elasticsearch.xpack.sql.expression.function.scalar.math.ATan; import org.elasticsearch.xpack.sql.expression.function.scalar.math.ATan;
import org.elasticsearch.xpack.sql.expression.function.scalar.math.Abs; import org.elasticsearch.xpack.sql.expression.function.scalar.math.Abs;
import org.elasticsearch.xpack.sql.expression.function.scalar.math.Cos;
import org.elasticsearch.xpack.sql.expression.function.scalar.math.E; import org.elasticsearch.xpack.sql.expression.function.scalar.math.E;
import org.elasticsearch.xpack.sql.expression.function.scalar.math.Floor; import org.elasticsearch.xpack.sql.expression.function.scalar.math.Floor;
import org.elasticsearch.xpack.sql.expression.function.scalar.string.Ascii;
import org.elasticsearch.xpack.sql.expression.function.scalar.string.Repeat;
import org.elasticsearch.xpack.sql.expression.predicate.BinaryOperator; import org.elasticsearch.xpack.sql.expression.predicate.BinaryOperator;
import org.elasticsearch.xpack.sql.expression.predicate.In; import org.elasticsearch.xpack.sql.expression.predicate.In;
import org.elasticsearch.xpack.sql.expression.predicate.IsNotNull; import org.elasticsearch.xpack.sql.expression.predicate.IsNotNull;
@ -56,6 +60,7 @@ import org.elasticsearch.xpack.sql.optimizer.Optimizer.BooleanSimplification;
import org.elasticsearch.xpack.sql.optimizer.Optimizer.CombineBinaryComparisons; import org.elasticsearch.xpack.sql.optimizer.Optimizer.CombineBinaryComparisons;
import org.elasticsearch.xpack.sql.optimizer.Optimizer.CombineProjections; import org.elasticsearch.xpack.sql.optimizer.Optimizer.CombineProjections;
import org.elasticsearch.xpack.sql.optimizer.Optimizer.ConstantFolding; import org.elasticsearch.xpack.sql.optimizer.Optimizer.ConstantFolding;
import org.elasticsearch.xpack.sql.optimizer.Optimizer.FoldNull;
import org.elasticsearch.xpack.sql.optimizer.Optimizer.PropagateEquals; import org.elasticsearch.xpack.sql.optimizer.Optimizer.PropagateEquals;
import org.elasticsearch.xpack.sql.optimizer.Optimizer.PruneDuplicateFunctions; import org.elasticsearch.xpack.sql.optimizer.Optimizer.PruneDuplicateFunctions;
import org.elasticsearch.xpack.sql.optimizer.Optimizer.PruneSubqueryAliases; import org.elasticsearch.xpack.sql.optimizer.Optimizer.PruneSubqueryAliases;
@ -374,10 +379,36 @@ public class OptimizerTests extends ESTestCase {
return ((Literal) new ConstantFolding().rule(b)).value(); return ((Literal) new ConstantFolding().rule(b)).value();
} }
public void testNullFoldingIsNotNull() {
assertEquals(Literal.TRUE, new FoldNull().rule(new IsNotNull(EMPTY, Literal.TRUE)));
}
public void testGenericNullableExpression() {
FoldNull rule = new FoldNull();
// date-time
assertNullLiteral(rule.rule(new DayName(EMPTY, Literal.NULL, randomTimeZone())));
// math function
assertNullLiteral(rule.rule(new Cos(EMPTY, Literal.NULL)));
// string function
assertNullLiteral(rule.rule(new Ascii(EMPTY, Literal.NULL)));
assertNullLiteral(rule.rule(new Repeat(EMPTY, getFieldAttribute(), Literal.NULL)));
// arithmetic
assertNullLiteral(rule.rule(new Add(EMPTY, getFieldAttribute(), Literal.NULL)));
// comparison
assertNullLiteral(rule.rule(new GreaterThan(EMPTY, getFieldAttribute(), Literal.NULL)));
// regex
assertNullLiteral(rule.rule(new RLike(EMPTY, getFieldAttribute(), Literal.NULL)));
}
// //
// Logical simplifications // Logical simplifications
// //
private void assertNullLiteral(Expression expression) {
assertEquals(Literal.class, expression.getClass());
assertNull(((Literal) expression).fold());
}
public void testBinaryComparisonSimplification() { public void testBinaryComparisonSimplification() {
assertEquals(Literal.TRUE, new BinaryComparisonSimplification().rule(new Equals(EMPTY, FIVE, FIVE))); assertEquals(Literal.TRUE, new BinaryComparisonSimplification().rule(new Equals(EMPTY, FIVE, FIVE)));
assertEquals(Literal.TRUE, new BinaryComparisonSimplification().rule(new GreaterThanOrEqual(EMPTY, FIVE, FIVE))); assertEquals(Literal.TRUE, new BinaryComparisonSimplification().rule(new GreaterThanOrEqual(EMPTY, FIVE, FIVE)));

View File

@ -12,6 +12,7 @@ import org.elasticsearch.xpack.sql.analysis.index.IndexResolution;
import org.elasticsearch.xpack.sql.expression.function.FunctionRegistry; import org.elasticsearch.xpack.sql.expression.function.FunctionRegistry;
import org.elasticsearch.xpack.sql.optimizer.Optimizer; import org.elasticsearch.xpack.sql.optimizer.Optimizer;
import org.elasticsearch.xpack.sql.parser.SqlParser; import org.elasticsearch.xpack.sql.parser.SqlParser;
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;
@ -64,6 +65,24 @@ 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 testFoldingOfIsNotNull() {
PhysicalPlan p = plan("SELECT keyword FROM test WHERE (keyword IS NULL) IS NOT NULL");
assertEquals(EsQueryExec.class, p.getClass());
EsQueryExec ee = (EsQueryExec) p;
assertEquals(1, ee.output().size());
assertThat(ee.output().get(0).toString(), startsWith("keyword{f}#"));
}
public void testFoldingToLocalExecWithNullFilter() {
PhysicalPlan p = plan("SELECT keyword FROM test WHERE null IN (1, 2)");
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_FoldableIn() { public void testFoldingToLocalExecWithProject_FoldableIn() {
PhysicalPlan p = plan("SELECT keyword FROM test WHERE int IN (null, null)"); PhysicalPlan p = plan("SELECT keyword FROM test WHERE int IN (null, null)");
assertEquals(LocalExec.class, p.getClass()); assertEquals(LocalExec.class, p.getClass());

View File

@ -3,7 +3,7 @@
// //
nullDate nullDate
SELECT YEAR(CAST(NULL AS DATE)) d; SELECT YEAR(CAST(NULL AS DATE)) AS d;
d:i d:i
null null