SQL: Introduce IsNull node to simplify expressions (#35206)

Add `IsNull` node in parser to simplify expressions so that `<value> IS NULL` is
no longer translated internally to `NOT(<value> IS NOT NULL)`

Replace `IsNotNullProcessor` with `CheckNullProcessor` to encapsulate both
isNull and isNotNull functionality.

Closes: #34876
Fixes: #35171
This commit is contained in:
Marios Trivyzas 2018-11-09 11:32:38 +01:00 committed by GitHub
parent 529910a43c
commit 36da6e1671
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
31 changed files with 446 additions and 120 deletions

View File

@ -64,10 +64,24 @@ SELECT last_name l FROM "test_emp" WHERE emp_no < 10003 AND (gender = 'M' AND NO
// TODO: (NOT) RLIKE in particular and more NOT queries in general
whereIsNotNullAndComparison
SELECT last_name l FROM "test_emp" WHERE emp_no IS NOT NULL AND emp_no < 10005 ORDER BY emp_no;
whereIsNull
SELECT last_name l FROM "test_emp" WHERE emp_no IS NULL;
whereIsNullAndNegation
SELECT last_name l FROM "test_emp" WHERE NOT emp_no IS NULL;
whereIsNotNull
SELECT last_name l FROM "test_emp" WHERE emp_no IS NOT NULL;
whereIsNotNullAndNegation
SELECT last_name l FROM "test_emp" WHERE NOT emp_no IS NOT NULL;
whereIsNullAndComparison
SELECT last_name l FROM "test_emp" WHERE emp_no IS NULL AND emp_no < 10005 ORDER BY emp_no;
whereIsNullWithComparisonAndNegation
SELECT last_name l FROM "test_emp" WHERE (NOT emp_no IS NULL) AND emp_no < 10005 ORDER BY emp_no;
whereIsNotNullAndComparison
SELECT last_name l FROM "test_emp" WHERE emp_no IS NOT NULL AND emp_no < 10005 ORDER BY emp_no;
whereIsNotNullWithComparisonAndNegation
SELECT last_name l FROM "test_emp" WHERE (NOT emp_no IS NOT NULL) AND emp_no < 10005 ORDER BY emp_no;
whereIsNotNullAndIsNull
// tag::whereIsNotNullAndIsNull
SELECT last_name l FROM "test_emp" WHERE emp_no IS NOT NULL AND gender IS NULL;

View File

@ -72,3 +72,24 @@ castOnColumnNumberToDouble
SELECT CAST(emp_no AS DOUBLE) AS emp_no_cast FROM "test_emp" ORDER BY emp_no LIMIT 5;
castOnColumnNumberToBoolean
SELECT CAST(emp_no AS BOOL) AS emp_no_cast FROM "test_emp" ORDER BY emp_no LIMIT 5;
//
// SELECT with IS NULL and IS NOT NULL
isNullAndIsNotNull
SELECT null IS NULL AS col1, null IS NOT NULL AS col2;
isNullAndIsNotNullAndNegation
SELECT NOT(null IS NULL) AS col1, NOT(null IS NOT NULL) AS col2;
isNullAndIsNotNullOverComparison
SELECT (null = 1) IS NULL AS col1, (null = 1) IS NOT NULL AS col2;
isNullAndIsNotNullOverComparisonWithNegation
SELECT NOT((null = 1) IS NULL) AS col1, NOT((null = 1) IS NOT NULL) AS col2;
// with table columns
isNullAndIsNotNull_onTableColumns
SELECT languages IS NULL AS col1, languages IS NOT NULL AS col2 FROM "test_emp" WHERE emp_no IN (10018, 10019, 10020) ORDER BY emp_no;
isNullAndIsNotNullAndNegation_onTableColumns
SELECT NOT languages IS NULL AS col1, NOT(languages IS NOT NULL) AS col2 FROM test_emp WHERE emp_no IN (10018, 10019, 10020) ORDER BY emp_no;
isNullAndIsNotNullOverComparison_onTableColumns
SELECT (languages = 2) IS NULL AS col1, (languages = 2) IS NOT NULL AS col2 FROM test_emp WHERE emp_no IN (10018, 10019, 10020) ORDER BY emp_no;
isNullAndIsNotNullOverComparisonWithNegation_onTableColumns
SELECT NOT((languages = 2) IS NULL) AS col1, NOT((languages = 2) IS NOT NULL) AS col2 FROM test_emp WHERE emp_no IN (10018, 10019, 10020) ORDER BY emp_no;

View File

@ -72,12 +72,12 @@ public final class Expressions {
public static boolean nullable(List<? extends Expression> exps) {
for (Expression exp : exps) {
if (!exp.nullable()) {
return false;
}
}
if (exp.nullable()) {
return true;
}
}
return false;
}
public static boolean foldable(List<? extends Expression> exps) {
for (Expression exp : exps) {

View File

@ -28,7 +28,7 @@ import org.elasticsearch.xpack.sql.expression.gen.processor.Processor;
import org.elasticsearch.xpack.sql.expression.predicate.conditional.CoalesceProcessor;
import org.elasticsearch.xpack.sql.expression.predicate.logical.BinaryLogicProcessor;
import org.elasticsearch.xpack.sql.expression.predicate.logical.NotProcessor;
import org.elasticsearch.xpack.sql.expression.predicate.nulls.IsNotNullProcessor;
import org.elasticsearch.xpack.sql.expression.predicate.nulls.CheckNullProcessor;
import org.elasticsearch.xpack.sql.expression.predicate.operator.arithmetic.BinaryArithmeticProcessor;
import org.elasticsearch.xpack.sql.expression.predicate.operator.arithmetic.UnaryArithmeticProcessor;
import org.elasticsearch.xpack.sql.expression.predicate.operator.comparison.BinaryComparisonProcessor;
@ -59,8 +59,8 @@ public final class Processors {
entries.add(new Entry(Processor.class, BinaryLogicProcessor.NAME, BinaryLogicProcessor::new));
entries.add(new Entry(Processor.class, NotProcessor.NAME, NotProcessor::new));
// null
entries.add(new Entry(Processor.class, CheckNullProcessor.NAME, CheckNullProcessor::new));
entries.add(new Entry(Processor.class, CoalesceProcessor.NAME, CoalesceProcessor::new));
entries.add(new Entry(Processor.class, IsNotNullProcessor.NAME, IsNotNullProcessor::new));
// arithmetic
entries.add(new Entry(Processor.class, BinaryArithmeticProcessor.NAME, BinaryArithmeticProcessor::new));

View File

@ -21,10 +21,10 @@ import org.elasticsearch.xpack.sql.expression.function.scalar.string.LocateFunct
import org.elasticsearch.xpack.sql.expression.function.scalar.string.ReplaceFunctionProcessor;
import org.elasticsearch.xpack.sql.expression.function.scalar.string.StringProcessor.StringOperation;
import org.elasticsearch.xpack.sql.expression.function.scalar.string.SubstringFunctionProcessor;
import org.elasticsearch.xpack.sql.expression.predicate.logical.BinaryLogicProcessor.BinaryLogicOperation;
import org.elasticsearch.xpack.sql.expression.predicate.conditional.CoalesceProcessor;
import org.elasticsearch.xpack.sql.expression.predicate.logical.BinaryLogicProcessor.BinaryLogicOperation;
import org.elasticsearch.xpack.sql.expression.predicate.logical.NotProcessor;
import org.elasticsearch.xpack.sql.expression.predicate.nulls.IsNotNullProcessor;
import org.elasticsearch.xpack.sql.expression.predicate.nulls.CheckNullProcessor.CheckNullOperation;
import org.elasticsearch.xpack.sql.expression.predicate.operator.arithmetic.BinaryArithmeticProcessor.BinaryArithmeticOperation;
import org.elasticsearch.xpack.sql.expression.predicate.operator.arithmetic.UnaryArithmeticProcessor.UnaryArithmeticOperation;
import org.elasticsearch.xpack.sql.expression.predicate.operator.comparison.BinaryComparisonProcessor.BinaryComparisonOperation;
@ -117,8 +117,12 @@ public final class InternalSqlScriptUtils {
return NotProcessor.apply(expression);
}
public static Boolean notNull(Object expression) {
return IsNotNullProcessor.apply(expression);
public static Boolean isNull(Object expression) {
return CheckNullOperation.IS_NULL.apply(expression);
}
public static Boolean isNotNull(Object expression) {
return CheckNullOperation.IS_NOT_NULL.apply(expression);
}
public static Boolean in(Object value, List<Object> values) {

View File

@ -16,10 +16,6 @@ import org.elasticsearch.xpack.sql.tree.Location;
*/
public abstract class BinaryOperator<T, U, R, F extends PredicateBiFunction<T, U, R>> extends BinaryPredicate<T, U, R, F> {
public interface Negateable {
BinaryOperator<?, ?, ?, ?> negate();
}
protected BinaryOperator(Location location, Expression left, Expression right, F function) {
super(location, left, right, function);
}

View File

@ -0,0 +1,14 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License;
* you may not use this file except in compliance with the Elastic License.
*/
package org.elasticsearch.xpack.sql.expression.predicate;
import org.elasticsearch.xpack.sql.expression.function.scalar.ScalarFunction;
public interface Negatable<T extends ScalarFunction> {
T negate();
}

View File

@ -62,6 +62,11 @@ public class Coalesce extends ConditionalFunction {
return (children.isEmpty() || (children.get(0).foldable() && children.get(0).fold() != null));
}
@Override
public boolean nullable() {
return false;
}
@Override
public Object fold() {
List<Expression> children = children();

View File

@ -6,12 +6,12 @@
package org.elasticsearch.xpack.sql.expression.predicate.logical;
import org.elasticsearch.xpack.sql.expression.Expression;
import org.elasticsearch.xpack.sql.expression.predicate.BinaryOperator.Negateable;
import org.elasticsearch.xpack.sql.expression.predicate.Negatable;
import org.elasticsearch.xpack.sql.expression.predicate.logical.BinaryLogicProcessor.BinaryLogicOperation;
import org.elasticsearch.xpack.sql.tree.Location;
import org.elasticsearch.xpack.sql.tree.NodeInfo;
public class And extends BinaryLogic implements Negateable {
public class And extends BinaryLogic implements Negatable<BinaryLogic> {
public And(Location location, Expression left, Expression right) {
super(location, left, right, BinaryLogicOperation.AND);

View File

@ -11,7 +11,7 @@ import org.elasticsearch.xpack.sql.expression.Expressions.ParamOrdinal;
import org.elasticsearch.xpack.sql.expression.function.scalar.UnaryScalarFunction;
import org.elasticsearch.xpack.sql.expression.gen.processor.Processor;
import org.elasticsearch.xpack.sql.expression.gen.script.Scripts;
import org.elasticsearch.xpack.sql.expression.predicate.BinaryOperator.Negateable;
import org.elasticsearch.xpack.sql.expression.predicate.Negatable;
import org.elasticsearch.xpack.sql.tree.Location;
import org.elasticsearch.xpack.sql.tree.NodeInfo;
import org.elasticsearch.xpack.sql.type.DataType;
@ -58,8 +58,8 @@ public class Not extends UnaryScalarFunction {
@Override
protected Expression canonicalize() {
Expression canonicalChild = field().canonical();
if (canonicalChild instanceof Negateable) {
return ((Negateable) canonicalChild).negate();
if (canonicalChild instanceof Negatable) {
return ((Negatable) canonicalChild).negate();
}
return this;
}

View File

@ -6,12 +6,12 @@
package org.elasticsearch.xpack.sql.expression.predicate.logical;
import org.elasticsearch.xpack.sql.expression.Expression;
import org.elasticsearch.xpack.sql.expression.predicate.BinaryOperator.Negateable;
import org.elasticsearch.xpack.sql.expression.predicate.Negatable;
import org.elasticsearch.xpack.sql.expression.predicate.logical.BinaryLogicProcessor.BinaryLogicOperation;
import org.elasticsearch.xpack.sql.tree.Location;
import org.elasticsearch.xpack.sql.tree.NodeInfo;
public class Or extends BinaryLogic implements Negateable {
public class Or extends BinaryLogic implements Negatable<BinaryLogic> {
public Or(Location location, Expression left, Expression right) {
super(location, left, right, BinaryLogicOperation.OR);

View File

@ -0,0 +1,89 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License;
* you may not use this file except in compliance with the Elastic License.
*/
package org.elasticsearch.xpack.sql.expression.predicate.nulls;
import org.elasticsearch.common.io.stream.StreamInput;
import org.elasticsearch.common.io.stream.StreamOutput;
import org.elasticsearch.xpack.sql.expression.gen.processor.Processor;
import java.io.IOException;
import java.util.Objects;
import java.util.function.Function;
public class CheckNullProcessor implements Processor {
public enum CheckNullOperation implements Function<Object, Boolean> {
IS_NULL(Objects::isNull, "IS NULL"),
IS_NOT_NULL(Objects::nonNull, "IS NOT NULL");
private final Function<Object, Boolean> process;
private final String symbol;
CheckNullOperation(Function<Object, Boolean> process, String symbol) {
this.process = process;
this.symbol = symbol;
}
public String symbol() {
return symbol;
}
@Override
public String toString() {
return symbol;
}
@Override
public Boolean apply(Object o) {
return process.apply(o);
}
}
public static final String NAME = "nckn";
private final CheckNullOperation operation;
CheckNullProcessor(CheckNullOperation operation) {
this.operation = operation;
}
public CheckNullProcessor(StreamInput in) throws IOException {
this(in.readEnum(CheckNullOperation.class));
}
@Override
public String getWriteableName() {
return NAME;
}
@Override
public void writeTo(StreamOutput out) throws IOException {
out.writeEnum(operation);
}
@Override
public Object process(Object input) {
return operation.apply(input);
}
@Override
public boolean equals(Object o) {
if (this == o) {
return true;
}
if (o == null || getClass() != o.getClass()) {
return false;
}
CheckNullProcessor that = (CheckNullProcessor) o;
return operation == that.operation;
}
@Override
public int hashCode() {
return Objects.hash(operation);
}
}

View File

@ -9,12 +9,14 @@ import org.elasticsearch.xpack.sql.expression.Expression;
import org.elasticsearch.xpack.sql.expression.function.scalar.UnaryScalarFunction;
import org.elasticsearch.xpack.sql.expression.gen.processor.Processor;
import org.elasticsearch.xpack.sql.expression.gen.script.Scripts;
import org.elasticsearch.xpack.sql.expression.predicate.Negatable;
import org.elasticsearch.xpack.sql.expression.predicate.nulls.CheckNullProcessor.CheckNullOperation;
import org.elasticsearch.xpack.sql.tree.Location;
import org.elasticsearch.xpack.sql.tree.NodeInfo;
import org.elasticsearch.xpack.sql.type.DataType;
import org.elasticsearch.xpack.sql.type.DataTypes;
public class IsNotNull extends UnaryScalarFunction {
public class IsNotNull extends UnaryScalarFunction implements Negatable<UnaryScalarFunction> {
public IsNotNull(Location location, Expression field) {
super(location, field);
@ -37,12 +39,12 @@ public class IsNotNull extends UnaryScalarFunction {
@Override
protected Processor makeProcessor() {
return IsNotNullProcessor.INSTANCE;
return new CheckNullProcessor(CheckNullOperation.IS_NOT_NULL);
}
@Override
public String processScript(String script) {
return Scripts.formatTemplate(Scripts.SQL_SCRIPTS + ".notNull(" + script + ")");
return Scripts.formatTemplate(Scripts.SQL_SCRIPTS + ".isNotNull(" + script + ")");
}
@Override
@ -54,4 +56,9 @@ public class IsNotNull extends UnaryScalarFunction {
public DataType dataType() {
return DataType.BOOLEAN;
}
@Override
public UnaryScalarFunction negate() {
return new IsNull(location(), field());
}
}

View File

@ -1,54 +0,0 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License;
* you may not use this file except in compliance with the Elastic License.
*/
package org.elasticsearch.xpack.sql.expression.predicate.nulls;
import org.elasticsearch.common.io.stream.StreamInput;
import org.elasticsearch.common.io.stream.StreamOutput;
import org.elasticsearch.xpack.sql.expression.gen.processor.Processor;
import java.io.IOException;
public class IsNotNullProcessor implements Processor {
static final IsNotNullProcessor INSTANCE = new IsNotNullProcessor();
public static final String NAME = "ninn";
private IsNotNullProcessor() {}
public IsNotNullProcessor(StreamInput in) throws IOException {}
@Override
public String getWriteableName() {
return NAME;
}
@Override
public void writeTo(StreamOutput out) throws IOException {}
@Override
public Object process(Object input) {
return apply(input);
}
public static Boolean apply(Object input) {
return input != null ? Boolean.TRUE : Boolean.FALSE;
}
@Override
public int hashCode() {
return IsNotNullProcessor.class.hashCode();
}
@Override
public boolean equals(Object obj) {
if (this == obj) {
return true;
}
return obj == null || getClass() != obj.getClass();
}
}

View File

@ -0,0 +1,64 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License;
* you may not use this file except in compliance with the Elastic License.
*/
package org.elasticsearch.xpack.sql.expression.predicate.nulls;
import org.elasticsearch.xpack.sql.expression.Expression;
import org.elasticsearch.xpack.sql.expression.function.scalar.UnaryScalarFunction;
import org.elasticsearch.xpack.sql.expression.gen.processor.Processor;
import org.elasticsearch.xpack.sql.expression.gen.script.Scripts;
import org.elasticsearch.xpack.sql.expression.predicate.Negatable;
import org.elasticsearch.xpack.sql.expression.predicate.nulls.CheckNullProcessor.CheckNullOperation;
import org.elasticsearch.xpack.sql.tree.Location;
import org.elasticsearch.xpack.sql.tree.NodeInfo;
import org.elasticsearch.xpack.sql.type.DataType;
import org.elasticsearch.xpack.sql.type.DataTypes;
public class IsNull extends UnaryScalarFunction implements Negatable<UnaryScalarFunction> {
public IsNull(Location location, Expression field) {
super(location, field);
}
@Override
protected NodeInfo<IsNull> info() {
return NodeInfo.create(this, IsNull::new, field());
}
@Override
protected IsNull replaceChild(Expression newChild) {
return new IsNull(location(), newChild);
}
@Override
public Object fold() {
return field().fold() == null || DataTypes.isNull(field().dataType());
}
@Override
protected Processor makeProcessor() {
return new CheckNullProcessor(CheckNullOperation.IS_NULL);
}
@Override
public String processScript(String script) {
return Scripts.formatTemplate(Scripts.SQL_SCRIPTS + ".isNull(" + script + ")");
}
@Override
public boolean nullable() {
return false;
}
@Override
public DataType dataType() {
return DataType.BOOLEAN;
}
@Override
public UnaryScalarFunction negate() {
return new IsNotNull(location(), field());
}
}

View File

@ -6,12 +6,12 @@
package org.elasticsearch.xpack.sql.expression.predicate.operator.comparison;
import org.elasticsearch.xpack.sql.expression.Expression;
import org.elasticsearch.xpack.sql.expression.predicate.BinaryOperator;
import org.elasticsearch.xpack.sql.expression.predicate.Negatable;
import org.elasticsearch.xpack.sql.expression.predicate.operator.comparison.BinaryComparisonProcessor.BinaryComparisonOperation;
import org.elasticsearch.xpack.sql.tree.Location;
import org.elasticsearch.xpack.sql.tree.NodeInfo;
public class Equals extends BinaryComparison implements BinaryOperator.Negateable {
public class Equals extends BinaryComparison implements Negatable<BinaryComparison> {
public Equals(Location location, Expression left, Expression right) {
super(location, left, right, BinaryComparisonOperation.EQ);
@ -33,7 +33,7 @@ public class Equals extends BinaryComparison implements BinaryOperator.Negateabl
}
@Override
public BinaryOperator<?, ?, ?, ?> negate() {
public BinaryComparison negate() {
return new NotEquals(location(), left(), right());
}
}

View File

@ -5,13 +5,13 @@
*/
package org.elasticsearch.xpack.sql.expression.predicate.operator.comparison;
import org.elasticsearch.xpack.sql.expression.predicate.BinaryOperator.Negateable;
import org.elasticsearch.xpack.sql.expression.predicate.operator.comparison.BinaryComparisonProcessor.BinaryComparisonOperation;
import org.elasticsearch.xpack.sql.expression.Expression;
import org.elasticsearch.xpack.sql.expression.predicate.Negatable;
import org.elasticsearch.xpack.sql.expression.predicate.operator.comparison.BinaryComparisonProcessor.BinaryComparisonOperation;
import org.elasticsearch.xpack.sql.tree.Location;
import org.elasticsearch.xpack.sql.tree.NodeInfo;
public class GreaterThan extends BinaryComparison implements Negateable {
public class GreaterThan extends BinaryComparison implements Negatable<BinaryComparison> {
public GreaterThan(Location location, Expression left, Expression right) {
super(location, left, right, BinaryComparisonOperation.GT);

View File

@ -5,13 +5,13 @@
*/
package org.elasticsearch.xpack.sql.expression.predicate.operator.comparison;
import org.elasticsearch.xpack.sql.expression.predicate.BinaryOperator.Negateable;
import org.elasticsearch.xpack.sql.expression.predicate.operator.comparison.BinaryComparisonProcessor.BinaryComparisonOperation;
import org.elasticsearch.xpack.sql.expression.Expression;
import org.elasticsearch.xpack.sql.expression.predicate.Negatable;
import org.elasticsearch.xpack.sql.expression.predicate.operator.comparison.BinaryComparisonProcessor.BinaryComparisonOperation;
import org.elasticsearch.xpack.sql.tree.Location;
import org.elasticsearch.xpack.sql.tree.NodeInfo;
public class GreaterThanOrEqual extends BinaryComparison implements Negateable {
public class GreaterThanOrEqual extends BinaryComparison implements Negatable<BinaryComparison> {
public GreaterThanOrEqual(Location location, Expression left, Expression right) {
super(location, left, right, BinaryComparisonOperation.GTE);

View File

@ -69,7 +69,7 @@ public class In extends NamedExpression implements ScriptWeaver {
@Override
public boolean nullable() {
return Expressions.nullable(children());
return false;
}
@Override

View File

@ -5,13 +5,13 @@
*/
package org.elasticsearch.xpack.sql.expression.predicate.operator.comparison;
import org.elasticsearch.xpack.sql.expression.predicate.BinaryOperator.Negateable;
import org.elasticsearch.xpack.sql.expression.predicate.operator.comparison.BinaryComparisonProcessor.BinaryComparisonOperation;
import org.elasticsearch.xpack.sql.expression.Expression;
import org.elasticsearch.xpack.sql.expression.predicate.Negatable;
import org.elasticsearch.xpack.sql.expression.predicate.operator.comparison.BinaryComparisonProcessor.BinaryComparisonOperation;
import org.elasticsearch.xpack.sql.tree.Location;
import org.elasticsearch.xpack.sql.tree.NodeInfo;
public class LessThan extends BinaryComparison implements Negateable {
public class LessThan extends BinaryComparison implements Negatable<BinaryComparison> {
public LessThan(Location location, Expression left, Expression right) {
super(location, left, right, BinaryComparisonOperation.LT);

View File

@ -5,13 +5,13 @@
*/
package org.elasticsearch.xpack.sql.expression.predicate.operator.comparison;
import org.elasticsearch.xpack.sql.expression.predicate.BinaryOperator.Negateable;
import org.elasticsearch.xpack.sql.expression.predicate.operator.comparison.BinaryComparisonProcessor.BinaryComparisonOperation;
import org.elasticsearch.xpack.sql.expression.Expression;
import org.elasticsearch.xpack.sql.expression.predicate.Negatable;
import org.elasticsearch.xpack.sql.expression.predicate.operator.comparison.BinaryComparisonProcessor.BinaryComparisonOperation;
import org.elasticsearch.xpack.sql.tree.Location;
import org.elasticsearch.xpack.sql.tree.NodeInfo;
public class LessThanOrEqual extends BinaryComparison implements Negateable {
public class LessThanOrEqual extends BinaryComparison implements Negatable<BinaryComparison> {
public LessThanOrEqual(Location location, Expression left, Expression right) {
super(location, left, right, BinaryComparisonOperation.LTE);

View File

@ -6,12 +6,12 @@
package org.elasticsearch.xpack.sql.expression.predicate.operator.comparison;
import org.elasticsearch.xpack.sql.expression.Expression;
import org.elasticsearch.xpack.sql.expression.predicate.BinaryOperator;
import org.elasticsearch.xpack.sql.expression.predicate.Negatable;
import org.elasticsearch.xpack.sql.expression.predicate.operator.comparison.BinaryComparisonProcessor.BinaryComparisonOperation;
import org.elasticsearch.xpack.sql.tree.Location;
import org.elasticsearch.xpack.sql.tree.NodeInfo;
public class NotEquals extends BinaryComparison implements BinaryOperator.Negateable {
public class NotEquals extends BinaryComparison implements Negatable<BinaryComparison> {
public NotEquals(Location location, Expression left, Expression right) {
super(location, left, right, BinaryComparisonOperation.NEQ);
@ -33,7 +33,7 @@ public class NotEquals extends BinaryComparison implements BinaryOperator.Negate
}
@Override
public BinaryOperator<?, ?, ?, ?> negate() {
public BinaryComparison negate() {
return new Equals(location(), left(), right());
}
}

View File

@ -37,8 +37,8 @@ import org.elasticsearch.xpack.sql.expression.function.scalar.Cast;
import org.elasticsearch.xpack.sql.expression.function.scalar.ScalarFunction;
import org.elasticsearch.xpack.sql.expression.function.scalar.ScalarFunctionAttribute;
import org.elasticsearch.xpack.sql.expression.predicate.BinaryOperator;
import org.elasticsearch.xpack.sql.expression.predicate.BinaryOperator.Negateable;
import org.elasticsearch.xpack.sql.expression.predicate.BinaryPredicate;
import org.elasticsearch.xpack.sql.expression.predicate.Negatable;
import org.elasticsearch.xpack.sql.expression.predicate.Predicates;
import org.elasticsearch.xpack.sql.expression.predicate.Range;
import org.elasticsearch.xpack.sql.expression.predicate.conditional.Coalesce;
@ -46,6 +46,7 @@ import org.elasticsearch.xpack.sql.expression.predicate.logical.And;
import org.elasticsearch.xpack.sql.expression.predicate.logical.Not;
import org.elasticsearch.xpack.sql.expression.predicate.logical.Or;
import org.elasticsearch.xpack.sql.expression.predicate.nulls.IsNotNull;
import org.elasticsearch.xpack.sql.expression.predicate.nulls.IsNull;
import org.elasticsearch.xpack.sql.expression.predicate.operator.comparison.BinaryComparison;
import org.elasticsearch.xpack.sql.expression.predicate.operator.comparison.Equals;
import org.elasticsearch.xpack.sql.expression.predicate.operator.comparison.GreaterThan;
@ -1159,8 +1160,11 @@ public class Optimizer extends RuleExecutor<LogicalPlan> {
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
} else if (e instanceof IsNull) {
if (((IsNull) e).field().nullable() == false) {
return new Literal(e.location(), Expressions.name(e), Boolean.FALSE, DataType.BOOLEAN);
}
} else if (e instanceof In) {
In in = (In) e;
@ -1171,6 +1175,7 @@ public class Optimizer extends RuleExecutor<LogicalPlan> {
} else if (e.nullable() && Expressions.anyMatch(e.children(), Expressions::isNull)) {
return Literal.of(e, null);
}
return e;
}
}
@ -1335,8 +1340,8 @@ public class Optimizer extends RuleExecutor<LogicalPlan> {
return TRUE;
}
if (c instanceof Negateable) {
return ((Negateable) c).negate();
if (c instanceof Negatable) {
return ((Negatable) c).negate();
}
if (c instanceof Not) {

View File

@ -24,7 +24,7 @@ import org.elasticsearch.xpack.sql.expression.UnresolvedStar;
import org.elasticsearch.xpack.sql.expression.function.Function;
import org.elasticsearch.xpack.sql.expression.function.UnresolvedFunction;
import org.elasticsearch.xpack.sql.expression.function.scalar.Cast;
import org.elasticsearch.xpack.sql.expression.predicate.operator.comparison.In;
import org.elasticsearch.xpack.sql.expression.predicate.nulls.IsNull;
import org.elasticsearch.xpack.sql.expression.predicate.Range;
import org.elasticsearch.xpack.sql.expression.predicate.fulltext.MatchQueryPredicate;
import org.elasticsearch.xpack.sql.expression.predicate.fulltext.MultiMatchQueryPredicate;
@ -42,6 +42,7 @@ import org.elasticsearch.xpack.sql.expression.predicate.operator.arithmetic.Sub;
import org.elasticsearch.xpack.sql.expression.predicate.operator.comparison.Equals;
import org.elasticsearch.xpack.sql.expression.predicate.operator.comparison.GreaterThan;
import org.elasticsearch.xpack.sql.expression.predicate.operator.comparison.GreaterThanOrEqual;
import org.elasticsearch.xpack.sql.expression.predicate.operator.comparison.In;
import org.elasticsearch.xpack.sql.expression.predicate.operator.comparison.LessThan;
import org.elasticsearch.xpack.sql.expression.predicate.operator.comparison.LessThanOrEqual;
import org.elasticsearch.xpack.sql.expression.predicate.operator.comparison.NotEquals;
@ -211,8 +212,11 @@ abstract class ExpressionBuilder extends IdentifierBuilder {
break;
case SqlBaseParser.NULL:
// shortcut to avoid double negation later on (since there's no IsNull (missing in ES is a negated exists))
e = new IsNotNull(loc, exp);
return pCtx.NOT() != null ? e : new Not(loc, e);
if (pCtx.NOT() != null) {
return new IsNotNull(loc, exp);
} else {
return new IsNull(loc, exp);
}
default:
throw new ParsingException(loc, "Unknown predicate {}", pCtx.kind.getText());
}

View File

@ -31,6 +31,7 @@ import org.elasticsearch.xpack.sql.expression.function.scalar.ScalarFunction;
import org.elasticsearch.xpack.sql.expression.function.scalar.datetime.DateTimeFunction;
import org.elasticsearch.xpack.sql.expression.function.scalar.datetime.DateTimeHistogramFunction;
import org.elasticsearch.xpack.sql.expression.gen.script.ScriptTemplate;
import org.elasticsearch.xpack.sql.expression.predicate.nulls.IsNull;
import org.elasticsearch.xpack.sql.expression.predicate.Range;
import org.elasticsearch.xpack.sql.expression.predicate.fulltext.MatchQueryPredicate;
import org.elasticsearch.xpack.sql.expression.predicate.fulltext.MultiMatchQueryPredicate;
@ -109,7 +110,8 @@ final class QueryTranslator {
new Ranges(),
new BinaryLogic(),
new Nots(),
new Nulls(),
new IsNullTranslator(),
new IsNotNullTranslator(),
new Likes(),
new StringQueries(),
new Matches(),
@ -496,20 +498,41 @@ final class QueryTranslator {
}
}
static class Nulls extends ExpressionTranslator<IsNotNull> {
static class IsNotNullTranslator extends ExpressionTranslator<IsNotNull> {
@Override
protected QueryTranslation asQuery(IsNotNull inn, boolean onAggs) {
protected QueryTranslation asQuery(IsNotNull isNotNull, boolean onAggs) {
Query query = null;
AggFilter aggFilter = null;
if (onAggs) {
aggFilter = new AggFilter(inn.id().toString(), inn.asScript());
aggFilter = new AggFilter(isNotNull.id().toString(), isNotNull.asScript());
} else {
query = new ExistsQuery(inn.location(), nameOf(inn.field()));
query = new ExistsQuery(isNotNull.location(), nameOf(isNotNull.field()));
// query directly on the field
if (inn.field() instanceof NamedExpression) {
query = wrapIfNested(query, inn.field());
if (isNotNull.field() instanceof NamedExpression) {
query = wrapIfNested(query, isNotNull.field());
}
}
return new QueryTranslation(query, aggFilter);
}
}
static class IsNullTranslator extends ExpressionTranslator<IsNull> {
@Override
protected QueryTranslation asQuery(IsNull isNull, boolean onAggs) {
Query query = null;
AggFilter aggFilter = null;
if (onAggs) {
aggFilter = new AggFilter(isNull.id().toString(), isNull.asScript());
} else {
query = new NotQuery(isNull.location(), new ExistsQuery(isNull.location(), nameOf(isNull.field())));
// query directly on the field
if (isNull.field() instanceof NamedExpression) {
query = wrapIfNested(query, isNull.field());
}
}

View File

@ -24,6 +24,10 @@ public class NotQuery extends Query {
this.child = child;
}
public Query child() {
return child;
}
@Override
public boolean containsNestedField(String path, String field) {
return child.containsNestedField(path, field);

View File

@ -33,7 +33,8 @@ class org.elasticsearch.xpack.sql.expression.function.scalar.whitelist.InternalS
Boolean and(Boolean, Boolean)
Boolean or(Boolean, Boolean)
Boolean not(Boolean)
Boolean notNull(Object)
Boolean isNull(Object)
Boolean isNotNull(Object)
#
# Null

View File

@ -0,0 +1,51 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License;
* you may not use this file except in compliance with the Elastic License.
*/
package org.elasticsearch.xpack.sql.expression.predicate.nulls;
import org.elasticsearch.common.io.stream.NamedWriteableRegistry;
import org.elasticsearch.common.io.stream.Writeable.Reader;
import org.elasticsearch.test.AbstractWireSerializingTestCase;
import org.elasticsearch.xpack.sql.expression.function.scalar.Processors;
import org.elasticsearch.xpack.sql.expression.gen.processor.ConstantProcessor;
import org.elasticsearch.xpack.sql.expression.gen.processor.Processor;
public class CheckNullProcessorTests extends AbstractWireSerializingTestCase<CheckNullProcessor> {
private static final Processor FALSE = new ConstantProcessor(false);
private static final Processor TRUE = new ConstantProcessor(true);
private static final Processor NULL = new ConstantProcessor((Object) null);
public static CheckNullProcessor randomProcessor() {
return new CheckNullProcessor(randomFrom(CheckNullProcessor.CheckNullOperation.values()));
}
@Override
protected CheckNullProcessor createTestInstance() {
return randomProcessor();
}
@Override
protected Reader<CheckNullProcessor> instanceReader() {
return CheckNullProcessor::new;
}
@Override
protected NamedWriteableRegistry getNamedWriteableRegistry() {
return new NamedWriteableRegistry(Processors.getNamedWriteables());
}
public void testIsNull() {
assertEquals(true, new CheckNullProcessor(CheckNullProcessor.CheckNullOperation.IS_NULL).process(null));
assertEquals(false, new CheckNullProcessor(CheckNullProcessor.CheckNullOperation.IS_NULL).process("foo"));
assertEquals(false, new CheckNullProcessor(CheckNullProcessor.CheckNullOperation.IS_NULL).process(1));
}
public void testIsNotNull() {
assertEquals(false, new CheckNullProcessor(CheckNullProcessor.CheckNullOperation.IS_NOT_NULL).process(null));
assertEquals(true, new CheckNullProcessor(CheckNullProcessor.CheckNullOperation.IS_NOT_NULL).process("foo"));
assertEquals(true, new CheckNullProcessor(CheckNullProcessor.CheckNullOperation.IS_NOT_NULL).process(1));
}
}

View File

@ -35,6 +35,7 @@ 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.nulls.IsNull;
import org.elasticsearch.xpack.sql.expression.predicate.Range;
import org.elasticsearch.xpack.sql.expression.predicate.conditional.Coalesce;
import org.elasticsearch.xpack.sql.expression.predicate.logical.And;
@ -384,8 +385,16 @@ public class OptimizerTests extends ESTestCase {
return ((Literal) new ConstantFolding().rule(b)).value();
}
public void testNullFoldingIsNull() {
FoldNull foldNull = new FoldNull();
assertEquals(true, foldNull.rule(new IsNull(EMPTY, Literal.NULL)).fold());
assertEquals(false, foldNull.rule(new IsNull(EMPTY, Literal.TRUE)).fold());
}
public void testNullFoldingIsNotNull() {
assertEquals(Literal.TRUE, new FoldNull().rule(new IsNotNull(EMPTY, Literal.TRUE)));
FoldNull foldNull = new FoldNull();
assertEquals(true, foldNull.rule(new IsNotNull(EMPTY, Literal.TRUE)).fold());
assertEquals(false, foldNull.rule(new IsNotNull(EMPTY, Literal.NULL)).fold());
}
public void testGenericNullableExpression() {
@ -467,6 +476,12 @@ public class OptimizerTests extends ESTestCase {
assertEquals(FIVE, eq.right());
}
public void testBoolSimplifyNotIsNullAndNotIsNotNull() {
BooleanSimplification simplification = new BooleanSimplification();
assertTrue(simplification.rule(new Not(EMPTY, new IsNull(EMPTY, ONE))) instanceof IsNotNull);
assertTrue(simplification.rule(new Not(EMPTY, new IsNotNull(EMPTY, ONE))) instanceof IsNull);
}
public void testBoolSimplifyOr() {
BooleanSimplification simplification = new BooleanSimplification();

View File

@ -65,9 +65,14 @@ public class QueryFolderTests extends ESTestCase {
assertThat(ee.output().get(0).toString(), startsWith("keyword{f}#"));
}
public void testFoldingToLocalExecBooleanAndNull_WhereClause2() {
PhysicalPlan p = plan("SELECT true OR null");
public void testFoldingOfIsNull() {
PhysicalPlan p = plan("SELECT keyword FROM test WHERE (keyword IS NOT NULL) IS NULL");
assertEquals(LocalExec.class, p.getClass());
LocalExec ee = (LocalExec) p;
assertEquals(1, ee.output().size());
assertThat(ee.output().get(0).toString(), startsWith("keyword{f}#"));
}
public void testFoldingToLocalExecBooleanAndNull_WhereClause() {
PhysicalPlan p = plan("SELECT keyword FROM test WHERE int > 10 AND null AND true");
assertEquals(LocalExec.class, p.getClass());

View File

@ -19,6 +19,8 @@ import org.elasticsearch.xpack.sql.plan.logical.LogicalPlan;
import org.elasticsearch.xpack.sql.plan.logical.Project;
import org.elasticsearch.xpack.sql.planner.QueryTranslator.QueryTranslation;
import org.elasticsearch.xpack.sql.querydsl.agg.AggFilter;
import org.elasticsearch.xpack.sql.querydsl.query.ExistsQuery;
import org.elasticsearch.xpack.sql.querydsl.query.NotQuery;
import org.elasticsearch.xpack.sql.querydsl.query.Query;
import org.elasticsearch.xpack.sql.querydsl.query.RangeQuery;
import org.elasticsearch.xpack.sql.querydsl.query.ScriptQuery;
@ -161,6 +163,62 @@ public class QueryTranslatorTests extends ESTestCase {
assertEquals("Scalar function (LTRIM(keyword)) not allowed (yet) as arguments for LIKE", ex.getMessage());
}
public void testTranslateIsNullExpression_WhereClause() {
LogicalPlan p = plan("SELECT * FROM test WHERE keyword IS NULL");
assertTrue(p instanceof Project);
assertTrue(p.children().get(0) instanceof Filter);
Expression condition = ((Filter) p.children().get(0)).condition();
assertFalse(condition.foldable());
QueryTranslation translation = QueryTranslator.toQuery(condition, false);
assertTrue(translation.query instanceof NotQuery);
NotQuery tq = (NotQuery) translation.query;
assertTrue(tq.child() instanceof ExistsQuery);
ExistsQuery eq = (ExistsQuery) tq.child();
assertEquals("{\"exists\":{\"field\":\"keyword\",\"boost\":1.0}}",
eq.asBuilder().toString().replaceAll("\\s+", ""));
}
public void testTranslateIsNotNullExpression_WhereClause() {
LogicalPlan p = plan("SELECT * FROM test WHERE keyword IS NOT NULL");
assertTrue(p instanceof Project);
assertTrue(p.children().get(0) instanceof Filter);
Expression condition = ((Filter) p.children().get(0)).condition();
assertFalse(condition.foldable());
QueryTranslation translation = QueryTranslator.toQuery(condition, false);
assertTrue(translation.query instanceof ExistsQuery);
ExistsQuery eq = (ExistsQuery) translation.query;
assertEquals("{\"exists\":{\"field\":\"keyword\",\"boost\":1.0}}",
eq.asBuilder().toString().replaceAll("\\s+", ""));
}
public void testTranslateIsNullExpression_HavingClause_Painless() {
LogicalPlan p = plan("SELECT keyword, max(int) FROM test GROUP BY keyword HAVING max(int) IS NULL");
assertTrue(p instanceof Project);
assertTrue(p.children().get(0) instanceof Filter);
Expression condition = ((Filter) p.children().get(0)).condition();
assertFalse(condition.foldable());
QueryTranslation translation = QueryTranslator.toQuery(condition, true);
assertNull(translation.query);
AggFilter aggFilter = translation.aggFilter;
assertEquals("InternalSqlScriptUtils.nullSafeFilter(InternalSqlScriptUtils.isNull(params.a0))",
aggFilter.scriptTemplate().toString());
assertThat(aggFilter.scriptTemplate().params().toString(), startsWith("[{a=MAX(int){a->"));
}
public void testTranslateIsNotNullExpression_HavingClause_Painless() {
LogicalPlan p = plan("SELECT keyword, max(int) FROM test GROUP BY keyword HAVING max(int) IS NOT NULL");
assertTrue(p instanceof Project);
assertTrue(p.children().get(0) instanceof Filter);
Expression condition = ((Filter) p.children().get(0)).condition();
assertFalse(condition.foldable());
QueryTranslation translation = QueryTranslator.toQuery(condition, true);
assertNull(translation.query);
AggFilter aggFilter = translation.aggFilter;
assertEquals("InternalSqlScriptUtils.nullSafeFilter(InternalSqlScriptUtils.isNotNull(params.a0))",
aggFilter.scriptTemplate().toString());
assertThat(aggFilter.scriptTemplate().params().toString(), startsWith("[{a=MAX(int){a->"));
}
public void testTranslateInExpression_WhereClause() {
LogicalPlan p = plan("SELECT * FROM test WHERE keyword IN ('foo', 'bar', 'lala', 'foo', concat('la', 'la'))");
assertTrue(p instanceof Project);