SQL: Fix issue with wrong NULL optimization (#37124)

Logical operators OR and AND as well as conditional functions
(COALESCE, LEAST, GREATEST, etc.) cannot be folded to NULL if one
of their children is NULL as is the case for most of the functions.
Therefore, their nullable() implementation cannot return true. On
the other hand they cannot return false as if they're wrapped within
an IS NULL or IS NOT NULL expression, the expression will be folded
to false and true respectively leading to wrong results.

Change the signature of nullable() method and add a third value UKNOWN
to handle these cases.

Fixes: #35872
This commit is contained in:
Marios Trivyzas 2019-01-06 18:29:34 +02:00 committed by GitHub
parent b34e7d4f19
commit da3d8fb5b7
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
40 changed files with 272 additions and 148 deletions

View File

@ -76,7 +76,7 @@ public class Alias extends NamedExpression {
}
@Override
public boolean nullable() {
public Nullability nullable() {
return child.nullable();
}

View File

@ -7,8 +7,8 @@ package org.elasticsearch.xpack.sql.expression;
import org.elasticsearch.xpack.sql.SqlIllegalArgumentException;
import org.elasticsearch.xpack.sql.expression.gen.script.ScriptTemplate;
import org.elasticsearch.xpack.sql.tree.Source;
import org.elasticsearch.xpack.sql.tree.NodeInfo;
import org.elasticsearch.xpack.sql.tree.Source;
import java.util.List;
import java.util.Objects;
@ -42,20 +42,20 @@ public abstract class Attribute extends NamedExpression {
private final String qualifier;
// can the attr be null - typically used in JOINs
private final boolean nullable;
private final Nullability nullability;
public Attribute(Source source, String name, String qualifier, ExpressionId id) {
this(source, name, qualifier, true, id);
this(source, name, qualifier, Nullability.TRUE, id);
}
public Attribute(Source source, String name, String qualifier, boolean nullable, ExpressionId id) {
this(source, name, qualifier, nullable, id, false);
public Attribute(Source source, String name, String qualifier, Nullability nullability, ExpressionId id) {
this(source, name, qualifier, nullability, id, false);
}
public Attribute(Source source, String name, String qualifier, boolean nullable, ExpressionId id, boolean synthetic) {
public Attribute(Source source, String name, String qualifier, Nullability nullability, ExpressionId id, boolean synthetic) {
super(source, name, emptyList(), id, synthetic);
this.qualifier = qualifier;
this.nullable = nullable;
this.nullability = nullability;
}
@Override
@ -77,8 +77,8 @@ public abstract class Attribute extends NamedExpression {
}
@Override
public boolean nullable() {
return nullable;
public Nullability nullable() {
return nullability;
}
@Override
@ -94,11 +94,11 @@ public abstract class Attribute extends NamedExpression {
return Objects.equals(qualifier(), qualifier) ? this : clone(source(), name(), qualifier, nullable(), id(), synthetic());
}
public Attribute withNullability(boolean nullable) {
return Objects.equals(nullable(), nullable) ? this : clone(source(), name(), qualifier(), nullable, id(), synthetic());
public Attribute withNullability(Nullability nullability) {
return Objects.equals(nullable(), nullability) ? this : clone(source(), name(), qualifier(), nullability, id(), synthetic());
}
protected abstract Attribute clone(Source source, String name, String qualifier, boolean nullable, ExpressionId id,
protected abstract Attribute clone(Source source, String name, String qualifier, Nullability nullability, ExpressionId id,
boolean synthetic);
@Override
@ -123,7 +123,7 @@ public abstract class Attribute extends NamedExpression {
@Override
public int hashCode() {
return Objects.hash(super.hashCode(), qualifier, nullable);
return Objects.hash(super.hashCode(), qualifier, nullability);
}
@Override
@ -131,7 +131,7 @@ public abstract class Attribute extends NamedExpression {
if (super.equals(obj)) {
Attribute other = (Attribute) obj;
return Objects.equals(qualifier, other.qualifier)
&& Objects.equals(nullable, other.nullable);
&& Objects.equals(nullability, other.nullability);
}
return false;

View File

@ -36,7 +36,7 @@ public class Exists extends SubQueryExpression {
}
@Override
public boolean nullable() {
return false;
public Nullability nullable() {
return Nullability.FALSE;
}
}

View File

@ -78,8 +78,7 @@ public abstract class Expression extends Node<Expression> implements Resolvable
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 Nullability nullable();
// the references/inputs/leaves of the expression tree
public AttributeSet references() {

View File

@ -79,13 +79,8 @@ public final class Expressions {
return false;
}
public static boolean nullable(List<? extends Expression> exps) {
for (Expression exp : exps) {
if (exp.nullable()) {
return true;
}
}
return false;
public static Nullability nullable(List<? extends Expression> exps) {
return Nullability.and(exps.stream().map(Expression::nullable).toArray(Nullability[]::new));
}
public static boolean foldable(List<? extends Expression> exps) {

View File

@ -6,8 +6,8 @@
package org.elasticsearch.xpack.sql.expression;
import org.elasticsearch.common.Strings;
import org.elasticsearch.xpack.sql.tree.Source;
import org.elasticsearch.xpack.sql.tree.NodeInfo;
import org.elasticsearch.xpack.sql.tree.Source;
import org.elasticsearch.xpack.sql.type.DataType;
import org.elasticsearch.xpack.sql.type.EsField;
import org.elasticsearch.xpack.sql.util.StringUtils;
@ -34,12 +34,12 @@ public class FieldAttribute extends TypedAttribute {
}
public FieldAttribute(Source source, FieldAttribute parent, String name, EsField field) {
this(source, parent, name, field, null, true, null, false);
this(source, parent, name, field, null, Nullability.TRUE, null, false);
}
public FieldAttribute(Source source, FieldAttribute parent, String name, EsField field, String qualifier,
boolean nullable, ExpressionId id, boolean synthetic) {
super(source, name, field.getDataType(), qualifier, nullable, id, synthetic);
Nullability nullability, ExpressionId id, boolean synthetic) {
super(source, name, field.getDataType(), qualifier, nullability, id, synthetic);
this.path = parent != null ? parent.name() : StringUtils.EMPTY;
this.parent = parent;
this.field = field;
@ -98,13 +98,14 @@ public class FieldAttribute extends TypedAttribute {
@Override
protected Expression canonicalize() {
return new FieldAttribute(source(), null, "<none>", field, null, true, id(), false);
return new FieldAttribute(source(), null, "<none>", field, null, Nullability.TRUE, id(), false);
}
@Override
protected Attribute clone(Source source, String name, String qualifier, boolean nullable, ExpressionId id, boolean synthetic) {
protected Attribute clone(Source source, String name, String qualifier, Nullability nullability,
ExpressionId id, boolean synthetic) {
FieldAttribute qualifiedParent = parent != null ? (FieldAttribute) parent.withQualifier(qualifier) : null;
return new FieldAttribute(source, qualifiedParent, name, field, qualifier, nullable, id, synthetic);
return new FieldAttribute(source, qualifiedParent, name, field, qualifier, nullability, id, synthetic);
}
@Override

View File

@ -8,8 +8,8 @@ package org.elasticsearch.xpack.sql.expression;
import org.elasticsearch.xpack.sql.SqlIllegalArgumentException;
import org.elasticsearch.xpack.sql.expression.gen.script.Params;
import org.elasticsearch.xpack.sql.expression.gen.script.ScriptTemplate;
import org.elasticsearch.xpack.sql.tree.Source;
import org.elasticsearch.xpack.sql.tree.NodeInfo;
import org.elasticsearch.xpack.sql.tree.Source;
import org.elasticsearch.xpack.sql.type.DataType;
import org.elasticsearch.xpack.sql.type.DataTypeConversion;
import org.elasticsearch.xpack.sql.type.DataTypes;
@ -56,8 +56,8 @@ public class Literal extends NamedExpression {
}
@Override
public boolean nullable() {
return value == null;
public Nullability nullable() {
return value == null ? Nullability.TRUE : Nullability.FALSE;
}
@Override
@ -77,7 +77,7 @@ public class Literal extends NamedExpression {
@Override
public Attribute toAttribute() {
return new LiteralAttribute(source(), name(), null, false, id(), false, dataType, this);
return new LiteralAttribute(source(), name(), null, Nullability.FALSE, id(), false, dataType, this);
}
@Override

View File

@ -6,17 +6,17 @@
package org.elasticsearch.xpack.sql.expression;
import org.elasticsearch.xpack.sql.expression.gen.pipeline.Pipe;
import org.elasticsearch.xpack.sql.tree.Source;
import org.elasticsearch.xpack.sql.tree.NodeInfo;
import org.elasticsearch.xpack.sql.tree.Source;
import org.elasticsearch.xpack.sql.type.DataType;
public class LiteralAttribute extends TypedAttribute {
private final Literal literal;
public LiteralAttribute(Source source, String name, String qualifier, boolean nullable, ExpressionId id, boolean synthetic,
DataType dataType, Literal literal) {
super(source, name, dataType, qualifier, nullable, id, synthetic);
public LiteralAttribute(Source source, String name, String qualifier, Nullability nullability, ExpressionId id, boolean synthetic,
DataType dataType, Literal literal) {
super(source, name, dataType, qualifier, nullability, id, synthetic);
this.literal = literal;
}
@ -27,9 +27,9 @@ public class LiteralAttribute extends TypedAttribute {
}
@Override
protected LiteralAttribute clone(Source source, String name, String qualifier, boolean nullable,
protected LiteralAttribute clone(Source source, String name, String qualifier, Nullability nullability,
ExpressionId id, boolean synthetic) {
return new LiteralAttribute(source, name, qualifier, nullable, id, synthetic, dataType(), literal);
return new LiteralAttribute(source, name, qualifier, nullability, id, synthetic, dataType(), literal);
}
@Override

View File

@ -0,0 +1,38 @@
/*
* 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;
public enum Nullability {
TRUE, // Whether the expression can become null
FALSE, // The expression can never become null
UNKNOWN; // Cannot determine if the expression supports possible null folding
/**
* Return the logical AND of a list of {@code Nullability}
* <pre>
* UNKNOWN AND TRUE/FALSE/UNKNOWN = UNKNOWN
* FALSE AND FALSE = FALSE
* TRUE AND FALSE/TRUE = TRUE
* </pre>
*/
public static Nullability and(Nullability... nullabilities) {
Nullability value = null;
for (Nullability n: nullabilities) {
switch (n) {
case UNKNOWN:
return UNKNOWN;
case TRUE:
value = TRUE;
break;
case FALSE:
if (value == null) {
value = FALSE;
}
}
}
return value != null ? value : FALSE;
}
}

View File

@ -41,8 +41,8 @@ public class Order extends Expression {
}
@Override
public boolean nullable() {
return false;
public Nullability nullable() {
return Nullability.FALSE;
}
@Override

View File

@ -36,7 +36,7 @@ public class ScalarSubquery extends SubQueryExpression {
}
@Override
public boolean nullable() {
return true;
public Nullability nullable() {
return Nullability.TRUE;
}
}

View File

@ -14,13 +14,9 @@ public abstract class TypedAttribute extends Attribute {
private final DataType dataType;
protected TypedAttribute(Source source, String name, DataType dataType) {
this(source, name, dataType, null, true, null, false);
}
protected TypedAttribute(Source source, String name, DataType dataType, String qualifier, boolean nullable,
protected TypedAttribute(Source source, String name, DataType dataType, String qualifier, Nullability nullability,
ExpressionId id, boolean synthetic) {
super(source, name, qualifier, nullable, id, synthetic);
super(source, name, qualifier, nullability, id, synthetic);
this.dataType = dataType;
}

View File

@ -41,7 +41,7 @@ public abstract class UnaryExpression extends Expression {
}
@Override
public boolean nullable() {
public Nullability nullable() {
return child.nullable();
}

View File

@ -46,7 +46,7 @@ public class UnresolvedAlias extends UnresolvedNamedExpression {
}
@Override
public boolean nullable() {
public Nullability nullable() {
throw new UnresolvedException("nullable", this);
}

View File

@ -7,8 +7,8 @@ package org.elasticsearch.xpack.sql.expression;
import org.elasticsearch.xpack.sql.capabilities.Unresolvable;
import org.elasticsearch.xpack.sql.capabilities.UnresolvedException;
import org.elasticsearch.xpack.sql.tree.Source;
import org.elasticsearch.xpack.sql.tree.NodeInfo;
import org.elasticsearch.xpack.sql.tree.Source;
import org.elasticsearch.xpack.sql.type.DataType;
import org.elasticsearch.xpack.sql.util.CollectionUtils;
@ -65,7 +65,8 @@ public class UnresolvedAttribute extends Attribute implements Unresolvable {
}
@Override
protected Attribute clone(Source source, String name, String qualifier, boolean nullable, ExpressionId id, boolean synthetic) {
protected Attribute clone(Source source, String name, String qualifier, Nullability nullability,
ExpressionId id, boolean synthetic) {
return this;
}

View File

@ -35,7 +35,7 @@ public class UnresolvedStar extends UnresolvedNamedExpression {
}
@Override
public boolean nullable() {
public Nullability nullable() {
throw new UnresolvedException("nullable", this);
}

View File

@ -9,6 +9,7 @@ import org.elasticsearch.xpack.sql.expression.Expression;
import org.elasticsearch.xpack.sql.expression.ExpressionId;
import org.elasticsearch.xpack.sql.expression.Expressions;
import org.elasticsearch.xpack.sql.expression.NamedExpression;
import org.elasticsearch.xpack.sql.expression.Nullability;
import org.elasticsearch.xpack.sql.tree.Source;
import org.elasticsearch.xpack.sql.util.StringUtils;
@ -45,7 +46,7 @@ public abstract class Function extends NamedExpression {
}
@Override
public boolean nullable() {
public Nullability nullable() {
return Expressions.nullable(children());
}

View File

@ -6,6 +6,7 @@
package org.elasticsearch.xpack.sql.expression.function;
import org.elasticsearch.xpack.sql.expression.ExpressionId;
import org.elasticsearch.xpack.sql.expression.Nullability;
import org.elasticsearch.xpack.sql.expression.TypedAttribute;
import org.elasticsearch.xpack.sql.tree.Source;
import org.elasticsearch.xpack.sql.type.DataType;
@ -16,9 +17,9 @@ public abstract class FunctionAttribute extends TypedAttribute {
private final String functionId;
protected FunctionAttribute(Source source, String name, DataType dataType, String qualifier, boolean nullable, ExpressionId id,
boolean synthetic, String functionId) {
super(source, name, dataType, qualifier, nullable, id, synthetic);
protected FunctionAttribute(Source source, String name, DataType dataType, String qualifier, Nullability nullability,
ExpressionId id, boolean synthetic, String functionId) {
super(source, name, dataType, qualifier, nullability, id, synthetic);
this.functionId = functionId;
}

View File

@ -7,12 +7,15 @@ package org.elasticsearch.xpack.sql.expression.function;
import org.elasticsearch.xpack.sql.expression.Attribute;
import org.elasticsearch.xpack.sql.expression.ExpressionId;
import org.elasticsearch.xpack.sql.expression.Nullability;
import org.elasticsearch.xpack.sql.expression.gen.pipeline.Pipe;
import org.elasticsearch.xpack.sql.expression.gen.pipeline.ScorePipe;
import org.elasticsearch.xpack.sql.tree.Source;
import org.elasticsearch.xpack.sql.tree.NodeInfo;
import org.elasticsearch.xpack.sql.tree.Source;
import org.elasticsearch.xpack.sql.type.DataType;
import static org.elasticsearch.xpack.sql.expression.Nullability.FALSE;
/**
* {@link Attribute} that represents Elasticsearch's {@code _score}.
*/
@ -21,15 +24,15 @@ public class ScoreAttribute extends FunctionAttribute {
* Constructor for normal use.
*/
public ScoreAttribute(Source source) {
this(source, "SCORE()", DataType.FLOAT, null, false, null, false);
this(source, "SCORE()", DataType.FLOAT, null, FALSE, null, false);
}
/**
* Constructor for {@link #clone()}
*/
private ScoreAttribute(Source source, String name, DataType dataType, String qualifier, boolean nullable, ExpressionId id,
private ScoreAttribute(Source source, String name, DataType dataType, String qualifier, Nullability nullability, ExpressionId id,
boolean synthetic) {
super(source, name, dataType, qualifier, nullable, id, synthetic, "SCORE");
super(source, name, dataType, qualifier, nullability, id, synthetic, "SCORE");
}
@Override
@ -38,8 +41,9 @@ public class ScoreAttribute extends FunctionAttribute {
}
@Override
protected Attribute clone(Source source, String name, String qualifier, boolean nullable, ExpressionId id, boolean synthetic) {
return new ScoreAttribute(source, name, dataType(), qualifier, nullable, id, synthetic);
protected Attribute clone(Source source, String name, String qualifier, Nullability nullability,
ExpressionId id, boolean synthetic) {
return new ScoreAttribute(source, name, dataType(), qualifier, nullability, id, synthetic);
}
@Override

View File

@ -10,6 +10,7 @@ import org.elasticsearch.xpack.sql.capabilities.UnresolvedException;
import org.elasticsearch.xpack.sql.expression.Attribute;
import org.elasticsearch.xpack.sql.expression.Expression;
import org.elasticsearch.xpack.sql.expression.Literal;
import org.elasticsearch.xpack.sql.expression.Nullability;
import org.elasticsearch.xpack.sql.expression.gen.script.ScriptTemplate;
import org.elasticsearch.xpack.sql.session.Configuration;
import org.elasticsearch.xpack.sql.tree.Source;
@ -135,7 +136,7 @@ public class UnresolvedFunction extends Function implements Unresolvable {
}
@Override
public boolean nullable() {
public Nullability nullable() {
throw new UnresolvedException("nullable", this);
}

View File

@ -8,9 +8,10 @@ package org.elasticsearch.xpack.sql.expression.function.aggregate;
import org.elasticsearch.xpack.sql.expression.Attribute;
import org.elasticsearch.xpack.sql.expression.Expression;
import org.elasticsearch.xpack.sql.expression.ExpressionId;
import org.elasticsearch.xpack.sql.expression.Nullability;
import org.elasticsearch.xpack.sql.expression.function.FunctionAttribute;
import org.elasticsearch.xpack.sql.tree.Source;
import org.elasticsearch.xpack.sql.tree.NodeInfo;
import org.elasticsearch.xpack.sql.tree.Source;
import org.elasticsearch.xpack.sql.type.DataType;
import java.util.Objects;
@ -21,12 +22,12 @@ public class AggregateFunctionAttribute extends FunctionAttribute {
AggregateFunctionAttribute(Source source, String name, DataType dataType, ExpressionId id,
String functionId, String propertyPath) {
this(source, name, dataType, null, false, id, false, functionId, propertyPath);
this(source, name, dataType, null, Nullability.FALSE, id, false, functionId, propertyPath);
}
public AggregateFunctionAttribute(Source source, String name, DataType dataType, String qualifier,
boolean nullable, ExpressionId id, boolean synthetic, String functionId, String propertyPath) {
super(source, name, dataType, qualifier, nullable, id, synthetic, functionId);
Nullability nullability, ExpressionId id, boolean synthetic, String functionId, String propertyPath) {
super(source, name, dataType, qualifier, nullability, id, synthetic, functionId);
this.propertyPath = propertyPath;
}
@ -42,14 +43,14 @@ public class AggregateFunctionAttribute extends FunctionAttribute {
@Override
protected Expression canonicalize() {
return new AggregateFunctionAttribute(source(), "<none>", dataType(), null, true, id(), false, "<none>", null);
return new AggregateFunctionAttribute(source(), "<none>", dataType(), null, Nullability.TRUE, id(), false, "<none>", null);
}
@Override
protected Attribute clone(Source source, String name, String qualifier, boolean nullable, ExpressionId id, boolean synthetic) {
protected Attribute clone(Source source, String name, String qualifier, Nullability nullability, ExpressionId id, boolean synthetic) {
// this is highly correlated with QueryFolder$FoldAggregate#addFunction (regarding the function name within the querydsl)
// that is the functionId is actually derived from the expression id to easily track it across contexts
return new AggregateFunctionAttribute(source, name, dataType(), qualifier, nullable, id, synthetic, functionId(), propertyPath);
return new AggregateFunctionAttribute(source, name, dataType(), qualifier, nullability, id, synthetic, functionId(), propertyPath);
}
public AggregateFunctionAttribute withFunctionId(String functionId, String propertyPath) {

View File

@ -8,20 +8,21 @@ package org.elasticsearch.xpack.sql.expression.function.grouping;
import org.elasticsearch.xpack.sql.expression.Attribute;
import org.elasticsearch.xpack.sql.expression.Expression;
import org.elasticsearch.xpack.sql.expression.ExpressionId;
import org.elasticsearch.xpack.sql.expression.Nullability;
import org.elasticsearch.xpack.sql.expression.function.FunctionAttribute;
import org.elasticsearch.xpack.sql.tree.Source;
import org.elasticsearch.xpack.sql.tree.NodeInfo;
import org.elasticsearch.xpack.sql.tree.Source;
import org.elasticsearch.xpack.sql.type.DataType;
public class GroupingFunctionAttribute extends FunctionAttribute {
GroupingFunctionAttribute(Source source, String name, DataType dataType, ExpressionId id, String functionId) {
this(source, name, dataType, null, false, id, false, functionId);
this(source, name, dataType, null, Nullability.FALSE, id, false, functionId);
}
public GroupingFunctionAttribute(Source source, String name, DataType dataType, String qualifier,
boolean nullable, ExpressionId id, boolean synthetic, String functionId) {
super(source, name, dataType, qualifier, nullable, id, synthetic, functionId);
Nullability nullability, ExpressionId id, boolean synthetic, String functionId) {
super(source, name, dataType, qualifier, nullability, id, synthetic, functionId);
}
@Override
@ -32,14 +33,15 @@ public class GroupingFunctionAttribute extends FunctionAttribute {
@Override
protected Expression canonicalize() {
return new GroupingFunctionAttribute(source(), "<none>", dataType(), null, true, id(), false, "<none>");
return new GroupingFunctionAttribute(source(), "<none>", dataType(), null, Nullability.TRUE, id(), false, "<none>");
}
@Override
protected Attribute clone(Source source, String name, String qualifier, boolean nullable, ExpressionId id, boolean synthetic) {
protected Attribute clone(Source source, String name, String qualifier, Nullability nullability,
ExpressionId id, boolean synthetic) {
// this is highly correlated with QueryFolder$FoldAggregate#addFunction (regarding the function name within the querydsl)
// that is the functionId is actually derived from the expression id to easily track it across contexts
return new GroupingFunctionAttribute(source, name, dataType(), qualifier, nullable, id, synthetic, functionId());
return new GroupingFunctionAttribute(source, name, dataType(), qualifier, nullability, id, synthetic, functionId());
}
public GroupingFunctionAttribute withFunctionId(String functionId, String propertyPath) {

View File

@ -6,6 +6,7 @@
package org.elasticsearch.xpack.sql.expression.function.scalar;
import org.elasticsearch.xpack.sql.expression.Expression;
import org.elasticsearch.xpack.sql.expression.Nullability;
import org.elasticsearch.xpack.sql.expression.gen.processor.Processor;
import org.elasticsearch.xpack.sql.expression.gen.script.ScriptTemplate;
import org.elasticsearch.xpack.sql.tree.Source;
@ -62,8 +63,11 @@ public class Cast extends UnaryScalarFunction {
}
@Override
public boolean nullable() {
return field().nullable() || DataTypes.isNull(from());
public Nullability nullable() {
if (DataTypes.isNull(from())) {
return Nullability.TRUE;
}
return field().nullable();
}
@Override

View File

@ -7,6 +7,7 @@
package org.elasticsearch.xpack.sql.expression.function.scalar;
import org.elasticsearch.xpack.sql.expression.Expression;
import org.elasticsearch.xpack.sql.expression.Nullability;
import org.elasticsearch.xpack.sql.expression.gen.script.ScriptTemplate;
import org.elasticsearch.xpack.sql.session.Configuration;
import org.elasticsearch.xpack.sql.tree.Source;
@ -42,8 +43,8 @@ public abstract class ConfigurationFunction extends ScalarFunction {
}
@Override
public boolean nullable() {
return false;
public Nullability nullable() {
return Nullability.FALSE;
}
@Override

View File

@ -8,11 +8,12 @@ package org.elasticsearch.xpack.sql.expression.function.scalar;
import org.elasticsearch.xpack.sql.expression.Attribute;
import org.elasticsearch.xpack.sql.expression.Expression;
import org.elasticsearch.xpack.sql.expression.ExpressionId;
import org.elasticsearch.xpack.sql.expression.Nullability;
import org.elasticsearch.xpack.sql.expression.function.FunctionAttribute;
import org.elasticsearch.xpack.sql.expression.gen.pipeline.Pipe;
import org.elasticsearch.xpack.sql.expression.gen.script.ScriptTemplate;
import org.elasticsearch.xpack.sql.tree.Source;
import org.elasticsearch.xpack.sql.tree.NodeInfo;
import org.elasticsearch.xpack.sql.tree.Source;
import org.elasticsearch.xpack.sql.type.DataType;
import java.util.Objects;
@ -25,13 +26,13 @@ public class ScalarFunctionAttribute extends FunctionAttribute {
ScalarFunctionAttribute(Source source, String name, DataType dataType, ExpressionId id,
String functionId, ScriptTemplate script, Expression orderBy, Pipe processorDef) {
this(source, name, dataType, null, true, id, false, functionId, script, orderBy, processorDef);
this(source, name, dataType, null, Nullability.TRUE, id, false, functionId, script, orderBy, processorDef);
}
public ScalarFunctionAttribute(Source source, String name, DataType dataType, String qualifier,
boolean nullable, ExpressionId id, boolean synthetic, String functionId, ScriptTemplate script,
Expression orderBy, Pipe pipe) {
super(source, name, dataType, qualifier, nullable, id, synthetic, functionId);
Nullability nullability, ExpressionId id, boolean synthetic, String functionId, ScriptTemplate script,
Expression orderBy, Pipe pipe) {
super(source, name, dataType, qualifier, nullability, id, synthetic, functionId);
this.script = script;
this.orderBy = orderBy;
@ -60,14 +61,15 @@ public class ScalarFunctionAttribute extends FunctionAttribute {
@Override
protected Expression canonicalize() {
return new ScalarFunctionAttribute(source(), "<none>", dataType(), null, true, id(), false,
return new ScalarFunctionAttribute(source(), "<none>", dataType(), null, Nullability.TRUE, id(), false,
functionId(), script, orderBy, pipe);
}
@Override
protected Attribute clone(Source source, String name, String qualifier, boolean nullable, ExpressionId id, boolean synthetic) {
return new ScalarFunctionAttribute(source, name, dataType(), qualifier, nullable, id, synthetic,
functionId(), script, orderBy, pipe);
protected Attribute clone(Source source, String name, String qualifier, Nullability nullability,
ExpressionId id, boolean synthetic) {
return new ScalarFunctionAttribute(source, name, dataType(), qualifier, nullability,
id, synthetic, functionId(), script, orderBy, pipe);
}
@Override

View File

@ -9,6 +9,7 @@ import org.elasticsearch.xpack.sql.expression.Expression;
import org.elasticsearch.xpack.sql.expression.Expressions;
import org.elasticsearch.xpack.sql.expression.Expressions.ParamOrdinal;
import org.elasticsearch.xpack.sql.expression.FieldAttribute;
import org.elasticsearch.xpack.sql.expression.Nullability;
import org.elasticsearch.xpack.sql.expression.function.scalar.BinaryScalarFunction;
import org.elasticsearch.xpack.sql.expression.gen.pipeline.Pipe;
import org.elasticsearch.xpack.sql.expression.gen.script.ScriptTemplate;
@ -50,8 +51,8 @@ public class Concat extends BinaryScalarFunction {
}
@Override
public boolean nullable() {
return false;
public Nullability nullable() {
return Nullability.FALSE;
}
@Override

View File

@ -8,6 +8,7 @@ package org.elasticsearch.xpack.sql.expression.predicate;
import org.elasticsearch.xpack.sql.expression.Expression;
import org.elasticsearch.xpack.sql.expression.Expressions;
import org.elasticsearch.xpack.sql.expression.Literal;
import org.elasticsearch.xpack.sql.expression.Nullability;
import org.elasticsearch.xpack.sql.expression.function.scalar.ScalarFunction;
import org.elasticsearch.xpack.sql.expression.gen.pipeline.Pipe;
import org.elasticsearch.xpack.sql.expression.gen.script.Params;
@ -119,8 +120,8 @@ public class Range extends ScalarFunction {
}
@Override
public boolean nullable() {
return value.nullable() && lower.nullable() && upper.nullable();
public Nullability nullable() {
return Nullability.and(value.nullable(), lower.nullable(), upper.nullable());
}
@Override

View File

@ -8,6 +8,7 @@ package org.elasticsearch.xpack.sql.expression.predicate.conditional;
import org.elasticsearch.xpack.sql.expression.Expression;
import org.elasticsearch.xpack.sql.expression.Expressions;
import org.elasticsearch.xpack.sql.expression.Nullability;
import org.elasticsearch.xpack.sql.expression.function.scalar.ScalarFunction;
import org.elasticsearch.xpack.sql.tree.Source;
import org.elasticsearch.xpack.sql.type.DataType;
@ -36,7 +37,7 @@ public abstract class ConditionalFunction extends ScalarFunction {
}
@Override
public boolean nullable() {
return false;
public Nullability nullable() {
return Nullability.UNKNOWN;
}
}

View File

@ -8,6 +8,7 @@ package org.elasticsearch.xpack.sql.expression.predicate.conditional;
import org.elasticsearch.xpack.sql.expression.Expression;
import org.elasticsearch.xpack.sql.expression.Expressions;
import org.elasticsearch.xpack.sql.expression.Nullability;
import org.elasticsearch.xpack.sql.expression.gen.pipeline.Pipe;
import org.elasticsearch.xpack.sql.expression.gen.script.ParamsBuilder;
import org.elasticsearch.xpack.sql.expression.gen.script.ScriptTemplate;
@ -57,8 +58,8 @@ public class NullIf extends ConditionalFunction {
}
@Override
public boolean nullable() {
return true;
public Nullability nullable() {
return Nullability.UNKNOWN;
}
@Override

View File

@ -6,6 +6,7 @@
package org.elasticsearch.xpack.sql.expression.predicate.fulltext;
import org.elasticsearch.xpack.sql.expression.Expression;
import org.elasticsearch.xpack.sql.expression.Nullability;
import org.elasticsearch.xpack.sql.tree.Source;
import org.elasticsearch.xpack.sql.type.DataType;
@ -56,8 +57,8 @@ public abstract class FullTextPredicate extends Expression {
}
@Override
public boolean nullable() {
return false;
public Nullability nullable() {
return Nullability.FALSE;
}
@Override

View File

@ -7,6 +7,7 @@ package org.elasticsearch.xpack.sql.expression.predicate.logical;
import org.elasticsearch.xpack.sql.expression.Expression;
import org.elasticsearch.xpack.sql.expression.Expressions;
import org.elasticsearch.xpack.sql.expression.Nullability;
import org.elasticsearch.xpack.sql.expression.gen.pipeline.Pipe;
import org.elasticsearch.xpack.sql.expression.predicate.BinaryOperator;
import org.elasticsearch.xpack.sql.expression.predicate.logical.BinaryLogicProcessor.BinaryLogicOperation;
@ -35,8 +36,8 @@ public abstract class BinaryLogic extends BinaryOperator<Boolean, Boolean, Boole
}
@Override
public boolean nullable() {
public Nullability nullable() {
// Cannot fold null due to 3vl, constant folding will do any possible folding.
return false;
return Nullability.UNKNOWN;
}
}

View File

@ -6,6 +6,7 @@
package org.elasticsearch.xpack.sql.expression.predicate.nulls;
import org.elasticsearch.xpack.sql.expression.Expression;
import org.elasticsearch.xpack.sql.expression.Nullability;
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;
@ -48,8 +49,8 @@ public class IsNotNull extends UnaryScalarFunction implements Negatable<UnarySca
}
@Override
public boolean nullable() {
return false;
public Nullability nullable() {
return Nullability.FALSE;
}
@Override

View File

@ -6,6 +6,7 @@
package org.elasticsearch.xpack.sql.expression.predicate.nulls;
import org.elasticsearch.xpack.sql.expression.Expression;
import org.elasticsearch.xpack.sql.expression.Nullability;
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;
@ -48,8 +49,8 @@ public class IsNull extends UnaryScalarFunction implements Negatable<UnaryScalar
}
@Override
public boolean nullable() {
return false;
public Nullability nullable() {
return Nullability.FALSE;
}
@Override

View File

@ -8,6 +8,7 @@ package org.elasticsearch.xpack.sql.expression.predicate.operator.comparison;
import org.elasticsearch.xpack.sql.expression.Expression;
import org.elasticsearch.xpack.sql.expression.Expressions;
import org.elasticsearch.xpack.sql.expression.Foldables;
import org.elasticsearch.xpack.sql.expression.Nullability;
import org.elasticsearch.xpack.sql.expression.function.scalar.ScalarFunction;
import org.elasticsearch.xpack.sql.expression.gen.pipeline.Pipe;
import org.elasticsearch.xpack.sql.expression.gen.script.ScriptTemplate;
@ -64,8 +65,8 @@ public class In extends ScalarFunction {
}
@Override
public boolean nullable() {
return false;
public Nullability nullable() {
return Nullability.UNKNOWN;
}
@Override

View File

@ -6,6 +6,7 @@
package org.elasticsearch.xpack.sql.expression.predicate.operator.comparison;
import org.elasticsearch.xpack.sql.expression.Expression;
import org.elasticsearch.xpack.sql.expression.Nullability;
import org.elasticsearch.xpack.sql.expression.predicate.operator.comparison.BinaryComparisonProcessor.BinaryComparisonOperation;
import org.elasticsearch.xpack.sql.tree.Source;
import org.elasticsearch.xpack.sql.tree.NodeInfo;
@ -35,7 +36,7 @@ public class NullEquals extends BinaryComparison {
}
@Override
public boolean nullable() {
return false;
public Nullability nullable() {
return Nullability.FALSE;
}
}

View File

@ -7,6 +7,7 @@
package org.elasticsearch.xpack.sql.expression.predicate.regex;
import org.elasticsearch.xpack.sql.expression.Expression;
import org.elasticsearch.xpack.sql.expression.Nullability;
import org.elasticsearch.xpack.sql.expression.function.scalar.UnaryScalarFunction;
import org.elasticsearch.xpack.sql.expression.gen.processor.Processor;
import org.elasticsearch.xpack.sql.expression.predicate.regex.RegexProcessor.RegexOperation;
@ -28,8 +29,11 @@ public abstract class RegexMatch extends UnaryScalarFunction {
}
@Override
public boolean nullable() {
return field().nullable() && pattern != null;
public Nullability nullable() {
if (pattern == null) {
return Nullability.TRUE;
}
return field().nullable();
}
@Override

View File

@ -18,6 +18,7 @@ import org.elasticsearch.xpack.sql.expression.Expressions;
import org.elasticsearch.xpack.sql.expression.FieldAttribute;
import org.elasticsearch.xpack.sql.expression.Literal;
import org.elasticsearch.xpack.sql.expression.NamedExpression;
import org.elasticsearch.xpack.sql.expression.Nullability;
import org.elasticsearch.xpack.sql.expression.Order;
import org.elasticsearch.xpack.sql.expression.function.Function;
import org.elasticsearch.xpack.sql.expression.function.FunctionAttribute;
@ -44,7 +45,6 @@ import org.elasticsearch.xpack.sql.expression.predicate.Predicates;
import org.elasticsearch.xpack.sql.expression.predicate.Range;
import org.elasticsearch.xpack.sql.expression.predicate.conditional.ArbitraryConditionalFunction;
import org.elasticsearch.xpack.sql.expression.predicate.conditional.Coalesce;
import org.elasticsearch.xpack.sql.expression.predicate.conditional.NullIf;
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;
@ -1097,12 +1097,12 @@ public class Optimizer extends RuleExecutor<LogicalPlan> {
@Override
protected Expression rule(Expression e) {
if (e instanceof IsNotNull) {
if (((IsNotNull) e).field().nullable() == false) {
if (((IsNotNull) e).field().nullable() == Nullability.FALSE) {
return new Literal(e.source(), Expressions.name(e), Boolean.TRUE, DataType.BOOLEAN);
}
} else if (e instanceof IsNull) {
if (((IsNull) e).field().nullable() == false) {
if (((IsNull) e).field().nullable() == Nullability.FALSE) {
return new Literal(e.source(), Expressions.name(e), Boolean.FALSE, DataType.BOOLEAN);
}
@ -1112,10 +1112,7 @@ public class Optimizer extends RuleExecutor<LogicalPlan> {
return Literal.of(in, null);
}
} else if (e instanceof NullIf) {
return e;
} else if (e.nullable() && Expressions.anyMatch(e.children(), Expressions::isNull)) {
} else if (e.nullable() == Nullability.TRUE && Expressions.anyMatch(e.children(), Expressions::isNull)) {
return Literal.of(e, null);
}
@ -1314,7 +1311,7 @@ public class Optimizer extends RuleExecutor<LogicalPlan> {
// true for equality
if (bc instanceof Equals || bc instanceof GreaterThanOrEqual || bc instanceof LessThanOrEqual) {
if (!l.nullable() && !r.nullable() && l.semanticEquals(r)) {
if (l.nullable() == Nullability.FALSE && r.nullable() == Nullability.FALSE && l.semanticEquals(r)) {
return TRUE;
}
}
@ -1329,7 +1326,7 @@ public class Optimizer extends RuleExecutor<LogicalPlan> {
// false for equality
if (bc instanceof NotEquals || bc instanceof GreaterThan || bc instanceof LessThan) {
if (!l.nullable() && !r.nullable() && l.semanticEquals(r)) {
if (l.nullable() == Nullability.FALSE && r.nullable() == Nullability.FALSE && l.semanticEquals(r)) {
return FALSE;
}
}

View File

@ -5,17 +5,17 @@
*/
package org.elasticsearch.xpack.sql.plan.logical;
import org.elasticsearch.xpack.sql.expression.Attribute;
import org.elasticsearch.xpack.sql.expression.Expression;
import org.elasticsearch.xpack.sql.expression.Nullability;
import org.elasticsearch.xpack.sql.tree.NodeInfo;
import org.elasticsearch.xpack.sql.tree.Source;
import org.elasticsearch.xpack.sql.type.DataType;
import java.util.List;
import java.util.Objects;
import org.elasticsearch.xpack.sql.expression.Attribute;
import org.elasticsearch.xpack.sql.expression.Expression;
import org.elasticsearch.xpack.sql.tree.Source;
import org.elasticsearch.xpack.sql.tree.NodeInfo;
import org.elasticsearch.xpack.sql.type.DataType;
import static java.util.stream.Collectors.toList;
import static org.elasticsearch.xpack.sql.util.CollectionUtils.combine;
public class Join extends BinaryPlan {
@ -78,7 +78,7 @@ public class Join extends BinaryPlan {
private static List<Attribute> makeNullable(List<Attribute> output) {
return output.stream()
.map(a -> a.withNullability(true))
.map(a -> a.withNullability(Nullability.TRUE))
.collect(toList());
}

View File

@ -0,0 +1,35 @@
/*
* 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;
import org.elasticsearch.test.ESTestCase;
import static org.elasticsearch.xpack.sql.expression.Nullability.FALSE;
import static org.elasticsearch.xpack.sql.expression.Nullability.TRUE;
import static org.elasticsearch.xpack.sql.expression.Nullability.UNKNOWN;
public class NullabilityTests extends ESTestCase {
public void testLogicalAndOfNullabilities() {
assertEquals(FALSE, Nullability.and());
assertEquals(TRUE, Nullability.and(TRUE));
assertEquals(FALSE, Nullability.and(FALSE));
assertEquals(UNKNOWN, Nullability.and(UNKNOWN));
assertEquals(UNKNOWN, Nullability.and(UNKNOWN, UNKNOWN));
assertEquals(UNKNOWN, Nullability.and(UNKNOWN, TRUE));
assertEquals(UNKNOWN, Nullability.and(UNKNOWN, FALSE));
assertEquals(FALSE, Nullability.and(FALSE, FALSE));
assertEquals(TRUE, Nullability.and(FALSE, TRUE));
assertEquals(UNKNOWN, Nullability.and(FALSE, UNKNOWN));
assertEquals(TRUE, Nullability.and(TRUE, TRUE));
assertEquals(TRUE, Nullability.and(TRUE, FALSE));
assertEquals(UNKNOWN, Nullability.and(TRUE, UNKNOWN));
}
}

View File

@ -14,6 +14,7 @@ import org.elasticsearch.xpack.sql.expression.FieldAttribute;
import org.elasticsearch.xpack.sql.expression.Foldables;
import org.elasticsearch.xpack.sql.expression.Literal;
import org.elasticsearch.xpack.sql.expression.NamedExpression;
import org.elasticsearch.xpack.sql.expression.Nullability;
import org.elasticsearch.xpack.sql.expression.Order;
import org.elasticsearch.xpack.sql.expression.Order.OrderDirection;
import org.elasticsearch.xpack.sql.expression.function.Function;
@ -134,8 +135,8 @@ public class OptimizerTests extends ESTestCase {
}
@Override
public boolean nullable() {
return false;
public Nullability nullable() {
return Nullability.FALSE;
}
@Override
@ -393,6 +394,8 @@ public class OptimizerTests extends ESTestCase {
return ((Literal) new ConstantFolding().rule(b)).value();
}
// Null folding
public void testNullFoldingIsNull() {
FoldNull foldNull = new FoldNull();
assertEquals(true, foldNull.rule(new IsNull(EMPTY, Literal.NULL)).fold());
@ -422,6 +425,34 @@ public class OptimizerTests extends ESTestCase {
assertNullLiteral(rule.rule(new RLike(EMPTY, Literal.NULL, "123")));
}
public void testNullFoldingDoesNotApplyOnLogicalExpressions() {
FoldNull rule = new FoldNull();
Or or = new Or(EMPTY, Literal.NULL, Literal.TRUE);
assertEquals(or, rule.rule(or));
or = new Or(EMPTY, Literal.NULL, Literal.NULL);
assertEquals(or, rule.rule(or));
And and = new And(EMPTY, Literal.NULL, Literal.TRUE);
assertEquals(and, rule.rule(and));
and = new And(EMPTY, Literal.NULL, Literal.NULL);
assertEquals(and, rule.rule(and));
}
public void testNullFoldingDoesNotApplyOnConditionals() {
FoldNull rule = new FoldNull();
Coalesce coalesce = new Coalesce(EMPTY, Arrays.asList(Literal.NULL, ONE, TWO));
assertEquals(coalesce, rule.rule(coalesce));
coalesce = new Coalesce(EMPTY, Arrays.asList(Literal.NULL, NULL, NULL));
assertEquals(coalesce, rule.rule(coalesce));
Greatest greatest = new Greatest(EMPTY, Arrays.asList(Literal.NULL, ONE, TWO));
assertEquals(greatest, rule.rule(greatest));
greatest = new Greatest(EMPTY, Arrays.asList(Literal.NULL, ONE, TWO));
assertEquals(greatest, rule.rule(greatest));
}
public void testSimplifyCoalesceNulls() {
Expression e = new SimplifyConditional().rule(new Coalesce(EMPTY, asList(Literal.NULL, Literal.NULL)));
assertEquals(Coalesce.class, e.getClass());