blob: 1cd7d6253ff8ec9fdf92f6e86acf963090116b99 [file] [log] [blame]
/*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
package org.apache.druid.math.expr;
import com.google.common.base.Joiner;
import com.google.common.base.Preconditions;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableSet;
import com.google.common.collect.Sets;
import com.google.common.math.LongMath;
import com.google.common.primitives.Ints;
import org.apache.commons.lang.StringEscapeUtils;
import org.apache.druid.annotations.SubclassesMustOverrideEqualsAndHashCode;
import org.apache.druid.common.config.NullHandling;
import org.apache.druid.java.util.common.IAE;
import org.apache.druid.java.util.common.ISE;
import org.apache.druid.java.util.common.StringUtils;
import org.apache.druid.java.util.common.guava.Comparators;
import javax.annotation.Nullable;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashSet;
import java.util.List;
import java.util.Objects;
import java.util.Set;
import java.util.stream.Collectors;
/**
* Base interface of Druid expression language abstract syntax tree nodes. All {@link Expr} implementations are
* immutable.
*/
@SubclassesMustOverrideEqualsAndHashCode
public interface Expr
{
String NULL_LITERAL = "null";
Joiner ARG_JOINER = Joiner.on(", ");
/**
* Indicates expression is a constant whose literal value can be extracted by {@link Expr#getLiteralValue()},
* making evaluating with arguments and bindings unecessary
*/
default boolean isLiteral()
{
// Overridden by things that are literals.
return false;
}
/**
* Returns the value of expr if expr is a literal, or throws an exception otherwise.
*
* @return {@link ConstantExpr}'s literal value
*
* @throws IllegalStateException if expr is not a literal
*/
@Nullable
default Object getLiteralValue()
{
// Overridden by things that are literals.
throw new ISE("Not a literal");
}
/**
* Returns an {@link IdentifierExpr} if it is one, else null
*/
@Nullable
default IdentifierExpr getIdentifierExprIfIdentifierExpr()
{
return null;
}
/**
* Returns the string identifier of an {@link IdentifierExpr}, else null. Use this method to analyze an {@link Expr}
* tree when trying to distinguish between different {@link IdentifierExpr} with the same
* {@link IdentifierExpr#binding}. Do NOT use this method to analyze the input binding (e.g. backing column name),
* use {@link #getBindingIfIdentifier} instead.
*/
@Nullable
default String getIdentifierIfIdentifier()
{
// overridden by things that are identifiers
return null;
}
/**
* Returns the string key to use to get a value from {@link Expr.ObjectBinding} of an {@link IdentifierExpr},
* else null. Use this method to analyze the inputs required to an {@link Expr} tree (e.g. backing column name).
*/
@Nullable
default String getBindingIfIdentifier()
{
// overridden by things that are identifiers
return null;
}
/**
* Evaluate the {@link Expr} with the bindings which supply {@link IdentifierExpr} with their values, producing an
* {@link ExprEval} with the result.
*/
ExprEval eval(ObjectBinding bindings);
/**
* Convert the {@link Expr} back into parseable string that when parsed with
* {@link Parser#parse(String, ExprMacroTable)} will produce an equivalent {@link Expr}.
*/
String stringify();
/**
* Programmatically inspect the {@link Expr} tree with a {@link Visitor}. Each {@link Expr} is responsible for
* ensuring the {@link Visitor} can visit all of its {@link Expr} children before visiting itself
*/
void visit(Visitor visitor);
/**
* Programatically rewrite the {@link Expr} tree with a {@link Shuttle}.Each {@link Expr} is responsible for
* ensuring the {@link Shuttle} can visit all of its {@link Expr} children, as well as updating its children
* {@link Expr} with the results from the {@link Shuttle}, before finally visiting an updated form of itself.
*/
Expr visit(Shuttle shuttle);
/**
* Examine the usage of {@link IdentifierExpr} children of an {@link Expr}, constructing a {@link BindingDetails}
*/
BindingDetails analyzeInputs();
/**
* Mechanism to supply values to back {@link IdentifierExpr} during expression evaluation
*/
interface ObjectBinding
{
/**
* Get value binding for string identifier of {@link IdentifierExpr}
*/
@Nullable
Object get(String name);
}
/**
* Mechanism to inspect an {@link Expr}, implementing a {@link Visitor} allows visiting all children of an
* {@link Expr}
*/
interface Visitor
{
/**
* Provide the {@link Visitor} with an {@link Expr} to inspect
*/
void visit(Expr expr);
}
/**
* Mechanism to rewrite an {@link Expr}, implementing a {@link Shuttle} allows visiting all children of an
* {@link Expr}, and replacing them as desired.
*/
interface Shuttle
{
/**
* Provide the {@link Shuttle} with an {@link Expr} to inspect and potentially rewrite.
*/
Expr visit(Expr expr);
}
/**
* Information about the context in which {@link IdentifierExpr} are used in a greater {@link Expr}, listing
* the 'free variables' (total set of required input columns or values) and distinguishing between which identifiers
* are used as scalar inputs and which are used as array inputs.
*
* This type is primarily used at query time when creating expression column selectors to decide if an expression
* can properly deal with a multi-valued column input, and also to determine if certain optimizations can be taken.
*
* Current implementations of {@link #analyzeInputs()} provide context about {@link Function} and
* {@link ApplyFunction} arguments which are direct children {@link IdentifierExpr} as scalar or array typed.
* This is defined by {@link Function#getScalarInputs(List)}, {@link Function#getArrayInputs(List)} and
* {@link ApplyFunction#getArrayInputs(List)}. Identifiers that are nested inside of argument expressions which
* are other expression types will not be considered to belong directly to that function, and so are classified by the
* context their children are using them as instead.
*
* This means in rare cases and mostly for "questionable" expressions which we still allow to function 'correctly',
* these lists might not be fully reliable without a complete type inference system in place. Due to this shortcoming,
* boolean values {@link BindingDetails#hasInputArrays()} and {@link BindingDetails#isOutputArray()} are provided to
* allow functions to explicitly declare that they utilize array typed values, used when determining if some types of
* optimizations can be applied when constructing the expression column value selector.
*
* @see Function#getScalarInputs
* @see Function#getArrayInputs
* @see ApplyFunction#getArrayInputs
* @see Parser#applyUnappliedBindings
* @see Parser#applyUnapplied
* @see Parser#liftApplyLambda
* @see org.apache.druid.segment.virtual.ExpressionSelectors#makeDimensionSelector
* @see org.apache.druid.segment.virtual.ExpressionSelectors#makeColumnValueSelector
*/
@SuppressWarnings("JavadocReference")
class BindingDetails
{
private final ImmutableSet<IdentifierExpr> freeVariables;
private final ImmutableSet<IdentifierExpr> scalarVariables;
private final ImmutableSet<IdentifierExpr> arrayVariables;
private final boolean hasInputArrays;
private final boolean isOutputArray;
BindingDetails()
{
this(ImmutableSet.of(), ImmutableSet.of(), ImmutableSet.of(), false, false);
}
BindingDetails(IdentifierExpr expr)
{
this(ImmutableSet.of(expr), ImmutableSet.of(), ImmutableSet.of(), false, false);
}
private BindingDetails(
ImmutableSet<IdentifierExpr> freeVariables,
ImmutableSet<IdentifierExpr> scalarVariables,
ImmutableSet<IdentifierExpr> arrayVariables,
boolean hasInputArrays,
boolean isOutputArray
)
{
this.freeVariables = freeVariables;
this.scalarVariables = scalarVariables;
this.arrayVariables = arrayVariables;
this.hasInputArrays = hasInputArrays;
this.isOutputArray = isOutputArray;
}
/**
* Get the list of required column inputs to evaluate an expression ({@link IdentifierExpr#binding})
*/
public List<String> getRequiredBindingsList()
{
return new ArrayList<>(getRequiredBindings());
}
/**
* Get the set of required column inputs to evaluate an expression ({@link IdentifierExpr#binding})
*/
public Set<String> getRequiredBindings()
{
return map(freeVariables, IdentifierExpr::getBindingIfIdentifier);
}
/**
* Set of {@link IdentifierExpr#binding} which are used as scalar inputs to operators and functions.
*/
Set<String> getScalarBindings()
{
return map(scalarVariables, IdentifierExpr::getBindingIfIdentifier);
}
/**
* Set of {@link IdentifierExpr#binding} which are used as array inputs to operators, functions, and apply
* functions.
*/
public Set<String> getArrayBindings()
{
return map(arrayVariables, IdentifierExpr::getBindingIfIdentifier);
}
/**
* Total set of 'free' inputs of an {@link Expr}, that are not supplied by a {@link LambdaExpr} binding
*/
public Set<IdentifierExpr> getFreeVariables()
{
return freeVariables;
}
/**
* Set of {@link IdentifierExpr#identifier} which are used as scalar inputs to operators and functions.
*/
Set<String> getScalarVariables()
{
return map(scalarVariables, IdentifierExpr::getIdentifier);
}
/**
* Set of {@link IdentifierExpr#identifier} which are used as array inputs to operators, functions, and apply
* functions.
*/
Set<String> getArrayVariables()
{
return map(arrayVariables, IdentifierExpr::getIdentifier);
}
/**
* Returns true if any expression in the expression tree has any array inputs. Note that in some cases, this can be
* true and {@link #getArrayBindings()} or {@link #getArrayVariables()} can be empty.
*
* This is because these collections contain identifiers/bindings which were classified as either scalar or array
* inputs based on the context of their usage by {@link Expr#analyzeInputs()}, where as this value and
* {@link #isOutputArray()} are set based on information reported by {@link Function#hasArrayInputs()},
* {@link Function#hasArrayOutput()}, and {@link ApplyFunction#hasArrayOutput(LambdaExpr)}, without regards to
* identifiers or anything else.
*/
public boolean hasInputArrays()
{
return hasInputArrays;
}
/**
* Returns true if any expression in this expression tree produces array outputs as reported by
* {@link Function#hasArrayOutput()} or {@link ApplyFunction#hasArrayOutput(LambdaExpr)}
*/
public boolean isOutputArray()
{
return isOutputArray;
}
/**
* Combine with {@link BindingDetails} from {@link Expr#analyzeInputs()}
*/
public BindingDetails with(Expr other)
{
return with(other.analyzeInputs());
}
/**
* Combine (union) another {@link BindingDetails}
*/
public BindingDetails with(BindingDetails other)
{
return new BindingDetails(
ImmutableSet.copyOf(Sets.union(freeVariables, other.freeVariables)),
ImmutableSet.copyOf(Sets.union(scalarVariables, other.scalarVariables)),
ImmutableSet.copyOf(Sets.union(arrayVariables, other.arrayVariables)),
hasInputArrays || other.hasInputArrays,
isOutputArray || other.isOutputArray
);
}
/**
* Add set of arguments as {@link BindingDetails#scalarVariables} that are *directly* {@link IdentifierExpr},
* else they are ignored.
*/
public BindingDetails withScalarArguments(Set<Expr> scalarArguments)
{
Set<IdentifierExpr> moreScalars = new HashSet<>();
for (Expr expr : scalarArguments) {
final boolean isIdentiferExpr = expr.getIdentifierExprIfIdentifierExpr() != null;
if (isIdentiferExpr) {
moreScalars.add((IdentifierExpr) expr);
}
}
return new BindingDetails(
ImmutableSet.copyOf(Sets.union(freeVariables, moreScalars)),
ImmutableSet.copyOf(Sets.union(scalarVariables, moreScalars)),
arrayVariables,
hasInputArrays,
isOutputArray
);
}
/**
* Add set of arguments as {@link BindingDetails#arrayVariables} that are *directly* {@link IdentifierExpr},
* else they are ignored.
*/
BindingDetails withArrayArguments(Set<Expr> arrayArguments)
{
Set<IdentifierExpr> arrayIdentifiers = new HashSet<>();
for (Expr expr : arrayArguments) {
final boolean isIdentifierExpr = expr.getIdentifierExprIfIdentifierExpr() != null;
if (isIdentifierExpr) {
arrayIdentifiers.add((IdentifierExpr) expr);
}
}
return new BindingDetails(
ImmutableSet.copyOf(Sets.union(freeVariables, arrayIdentifiers)),
scalarVariables,
ImmutableSet.copyOf(Sets.union(arrayVariables, arrayIdentifiers)),
hasInputArrays || !arrayArguments.isEmpty(),
isOutputArray
);
}
/**
* Copy, setting if an expression has array inputs
*/
BindingDetails withArrayInputs(boolean hasArrays)
{
return new BindingDetails(
freeVariables,
scalarVariables,
arrayVariables,
hasArrays || !arrayVariables.isEmpty(),
isOutputArray
);
}
/**
* Copy, setting if an expression produces an array output
*/
BindingDetails withArrayOutput(boolean isOutputArray)
{
return new BindingDetails(
freeVariables,
scalarVariables,
arrayVariables,
hasInputArrays,
isOutputArray
);
}
/**
* Remove any {@link IdentifierExpr} that are from a {@link LambdaExpr}, since the {@link ApplyFunction} will
* provide bindings for these variables.
*/
BindingDetails removeLambdaArguments(Set<String> lambda)
{
return new BindingDetails(
ImmutableSet.copyOf(freeVariables.stream().filter(x -> !lambda.contains(x.getIdentifier())).iterator()),
ImmutableSet.copyOf(scalarVariables.stream().filter(x -> !lambda.contains(x.getIdentifier())).iterator()),
ImmutableSet.copyOf(arrayVariables.stream().filter(x -> !lambda.contains(x.getIdentifier())).iterator()),
hasInputArrays,
isOutputArray
);
}
// Use this instead of streams for better performance
private static Set<String> map(
Set<IdentifierExpr> variables,
java.util.function.Function<IdentifierExpr, String> mapper
)
{
Set<String> results = Sets.newHashSetWithExpectedSize(variables.size());
for (IdentifierExpr variable : variables) {
results.add(mapper.apply(variable));
}
return results;
}
}
}
/**
* Base type for all constant expressions. {@link ConstantExpr} allow for direct value extraction without evaluating
* {@link Expr.ObjectBinding}. {@link ConstantExpr} are terminal nodes of an expression tree, and have no children
* {@link Expr}.
*/
abstract class ConstantExpr implements Expr
{
@Override
public boolean isLiteral()
{
return true;
}
@Override
public void visit(Visitor visitor)
{
visitor.visit(this);
}
@Override
public Expr visit(Shuttle shuttle)
{
return shuttle.visit(this);
}
@Override
public BindingDetails analyzeInputs()
{
return new BindingDetails();
}
@Override
public String stringify()
{
return toString();
}
}
abstract class NullNumericConstantExpr extends ConstantExpr
{
@Override
public Object getLiteralValue()
{
return null;
}
@Override
public String toString()
{
return NULL_LITERAL;
}
}
class LongExpr extends ConstantExpr
{
private final Long value;
LongExpr(Long value)
{
this.value = Preconditions.checkNotNull(value, "value");
}
@Override
public Object getLiteralValue()
{
return value;
}
@Override
public String toString()
{
return String.valueOf(value);
}
@Override
public ExprEval eval(ObjectBinding bindings)
{
return ExprEval.ofLong(value);
}
@Override
public boolean equals(Object o)
{
if (this == o) {
return true;
}
if (o == null || getClass() != o.getClass()) {
return false;
}
LongExpr longExpr = (LongExpr) o;
return Objects.equals(value, longExpr.value);
}
@Override
public int hashCode()
{
return Objects.hash(value);
}
}
class NullLongExpr extends NullNumericConstantExpr
{
@Override
public ExprEval eval(ObjectBinding bindings)
{
return ExprEval.ofLong(null);
}
@Override
public final int hashCode()
{
return NullLongExpr.class.hashCode();
}
@Override
public final boolean equals(Object obj)
{
return obj instanceof NullLongExpr;
}
}
class LongArrayExpr extends ConstantExpr
{
private final Long[] value;
LongArrayExpr(Long[] value)
{
this.value = Preconditions.checkNotNull(value, "value");
}
@Override
public Object getLiteralValue()
{
return value;
}
@Override
public String toString()
{
return Arrays.toString(value);
}
@Override
public ExprEval eval(ObjectBinding bindings)
{
return ExprEval.ofLongArray(value);
}
@Override
public String stringify()
{
if (value.length == 0) {
return "<LONG>[]";
}
return StringUtils.format("<LONG>%s", toString());
}
@Override
public boolean equals(Object o)
{
if (this == o) {
return true;
}
if (o == null || getClass() != o.getClass()) {
return false;
}
LongArrayExpr that = (LongArrayExpr) o;
return Arrays.equals(value, that.value);
}
@Override
public int hashCode()
{
return Arrays.hashCode(value);
}
}
class StringExpr extends ConstantExpr
{
@Nullable
private final String value;
StringExpr(@Nullable String value)
{
this.value = NullHandling.emptyToNullIfNeeded(value);
}
@Nullable
@Override
public Object getLiteralValue()
{
return value;
}
@Override
public String toString()
{
return value;
}
@Override
public ExprEval eval(ObjectBinding bindings)
{
return ExprEval.of(value);
}
@Override
public String stringify()
{
// escape as javascript string since string literals are wrapped in single quotes
return value == null ? NULL_LITERAL : StringUtils.format("'%s'", StringEscapeUtils.escapeJavaScript(value));
}
@Override
public boolean equals(Object o)
{
if (this == o) {
return true;
}
if (o == null || getClass() != o.getClass()) {
return false;
}
StringExpr that = (StringExpr) o;
return Objects.equals(value, that.value);
}
@Override
public int hashCode()
{
return Objects.hash(value);
}
}
class StringArrayExpr extends ConstantExpr
{
private final String[] value;
StringArrayExpr(String[] value)
{
this.value = Preconditions.checkNotNull(value, "value");
}
@Override
public Object getLiteralValue()
{
return value;
}
@Override
public String toString()
{
return Arrays.toString(value);
}
@Override
public ExprEval eval(ObjectBinding bindings)
{
return ExprEval.ofStringArray(value);
}
@Override
public String stringify()
{
if (value.length == 0) {
return "<STRING>[]";
}
return StringUtils.format(
"<STRING>[%s]",
ARG_JOINER.join(
Arrays.stream(value)
.map(s -> s == null
? NULL_LITERAL
// escape as javascript string since string literals are wrapped in single quotes
: StringUtils.format("'%s'", StringEscapeUtils.escapeJavaScript(s))
)
.iterator()
)
);
}
@Override
public boolean equals(Object o)
{
if (this == o) {
return true;
}
if (o == null || getClass() != o.getClass()) {
return false;
}
StringArrayExpr that = (StringArrayExpr) o;
return Arrays.equals(value, that.value);
}
@Override
public int hashCode()
{
return Arrays.hashCode(value);
}
}
class DoubleExpr extends ConstantExpr
{
private final Double value;
DoubleExpr(Double value)
{
this.value = Preconditions.checkNotNull(value, "value");
}
@Override
public Object getLiteralValue()
{
return value;
}
@Override
public String toString()
{
return String.valueOf(value);
}
@Override
public ExprEval eval(ObjectBinding bindings)
{
return ExprEval.ofDouble(value);
}
@Override
public boolean equals(Object o)
{
if (this == o) {
return true;
}
if (o == null || getClass() != o.getClass()) {
return false;
}
DoubleExpr that = (DoubleExpr) o;
return Objects.equals(value, that.value);
}
@Override
public int hashCode()
{
return Objects.hash(value);
}
}
class NullDoubleExpr extends NullNumericConstantExpr
{
@Override
public ExprEval eval(ObjectBinding bindings)
{
return ExprEval.ofDouble(null);
}
@Override
public final int hashCode()
{
return NullDoubleExpr.class.hashCode();
}
@Override
public final boolean equals(Object obj)
{
return obj instanceof NullDoubleExpr;
}
}
class DoubleArrayExpr extends ConstantExpr
{
private final Double[] value;
DoubleArrayExpr(Double[] value)
{
this.value = Preconditions.checkNotNull(value, "value");
}
@Override
public Object getLiteralValue()
{
return value;
}
@Override
public String toString()
{
return Arrays.toString(value);
}
@Override
public ExprEval eval(ObjectBinding bindings)
{
return ExprEval.ofDoubleArray(value);
}
@Override
public String stringify()
{
if (value.length == 0) {
return "<DOUBLE>[]";
}
return StringUtils.format("<DOUBLE>%s", toString());
}
@Override
public boolean equals(Object o)
{
if (this == o) {
return true;
}
if (o == null || getClass() != o.getClass()) {
return false;
}
DoubleArrayExpr that = (DoubleArrayExpr) o;
return Arrays.equals(value, that.value);
}
@Override
public int hashCode()
{
return Arrays.hashCode(value);
}
}
/**
* This {@link Expr} node is used to represent a variable in the expression language. At evaluation time, the string
* identifier will be used to retrieve the runtime value for the variable from {@link Expr.ObjectBinding}.
* {@link IdentifierExpr} are terminal nodes of an expression tree, and have no children {@link Expr}.
*/
class IdentifierExpr implements Expr
{
private final String identifier;
private final String binding;
/**
* Construct a identifier expression for a {@link LambdaExpr}, where the {@link #identifier} is equal to
* {@link #binding}
*/
IdentifierExpr(String value)
{
this.identifier = value;
this.binding = value;
}
/**
* Construct a normal identifier expression, where {@link #binding} is the key to fetch the backing value from
* {@link Expr.ObjectBinding} and the {@link #identifier} is a unique string that identifies this usage of the
* binding.
*/
IdentifierExpr(String identifier, String binding)
{
this.identifier = identifier;
this.binding = binding;
}
@Override
public String toString()
{
return binding;
}
/**
* Unique identifier for the binding
*/
@Nullable
public String getIdentifier()
{
return identifier;
}
/**
* Value binding, key to retrieve value from {@link Expr.ObjectBinding#get(String)}
*/
@Nullable
public String getBinding()
{
return binding;
}
@Nullable
@Override
public String getIdentifierIfIdentifier()
{
return identifier;
}
@Nullable
@Override
public String getBindingIfIdentifier()
{
return binding;
}
@Nullable
@Override
public IdentifierExpr getIdentifierExprIfIdentifierExpr()
{
return this;
}
@Override
public BindingDetails analyzeInputs()
{
return new BindingDetails(this);
}
@Override
public ExprEval eval(ObjectBinding bindings)
{
return ExprEval.bestEffortOf(bindings.get(binding));
}
@Override
public String stringify()
{
// escape as java strings since identifiers are wrapped in double quotes
return StringUtils.format("\"%s\"", StringEscapeUtils.escapeJava(binding));
}
@Override
public void visit(Visitor visitor)
{
visitor.visit(this);
}
@Override
public Expr visit(Shuttle shuttle)
{
return shuttle.visit(this);
}
@Override
public boolean equals(Object o)
{
if (this == o) {
return true;
}
if (o == null || getClass() != o.getClass()) {
return false;
}
IdentifierExpr that = (IdentifierExpr) o;
return Objects.equals(identifier, that.identifier);
}
@Override
public int hashCode()
{
return Objects.hash(identifier);
}
}
class LambdaExpr implements Expr
{
private final ImmutableList<IdentifierExpr> args;
private final Expr expr;
LambdaExpr(List<IdentifierExpr> args, Expr expr)
{
this.args = ImmutableList.copyOf(args);
this.expr = expr;
}
@Override
public String toString()
{
return StringUtils.format("(%s -> %s)", args, expr);
}
int identifierCount()
{
return args.size();
}
@Nullable
public String getIdentifier()
{
Preconditions.checkState(args.size() < 2, "LambdaExpr has multiple arguments");
if (args.size() == 1) {
return args.get(0).toString();
}
return null;
}
public List<String> getIdentifiers()
{
return args.stream().map(IdentifierExpr::toString).collect(Collectors.toList());
}
ImmutableList<IdentifierExpr> getIdentifierExprs()
{
return args;
}
public Expr getExpr()
{
return expr;
}
@Override
public ExprEval eval(ObjectBinding bindings)
{
return expr.eval(bindings);
}
@Override
public String stringify()
{
return StringUtils.format("(%s) -> %s", ARG_JOINER.join(getIdentifiers()), expr.stringify());
}
@Override
public void visit(Visitor visitor)
{
expr.visit(visitor);
visitor.visit(this);
}
@Override
public Expr visit(Shuttle shuttle)
{
List<IdentifierExpr> newArgs =
args.stream().map(arg -> (IdentifierExpr) shuttle.visit(arg)).collect(Collectors.toList());
Expr newBody = expr.visit(shuttle);
return shuttle.visit(new LambdaExpr(newArgs, newBody));
}
@Override
public BindingDetails analyzeInputs()
{
final Set<String> lambdaArgs = args.stream().map(IdentifierExpr::toString).collect(Collectors.toSet());
BindingDetails bodyDetails = expr.analyzeInputs();
return bodyDetails.removeLambdaArguments(lambdaArgs);
}
@Override
public boolean equals(Object o)
{
if (this == o) {
return true;
}
if (o == null || getClass() != o.getClass()) {
return false;
}
LambdaExpr that = (LambdaExpr) o;
return Objects.equals(args, that.args) &&
Objects.equals(expr, that.expr);
}
@Override
public int hashCode()
{
return Objects.hash(args, expr);
}
}
/**
* {@link Expr} node for a {@link Function} call. {@link FunctionExpr} has children {@link Expr} in the form of the
* list of arguments that are passed to the {@link Function} along with the {@link Expr.ObjectBinding} when it is
* evaluated.
*/
class FunctionExpr implements Expr
{
final Function function;
final ImmutableList<Expr> args;
private final String name;
FunctionExpr(Function function, String name, List<Expr> args)
{
this.function = function;
this.name = name;
this.args = ImmutableList.copyOf(args);
function.validateArguments(args);
}
@Override
public String toString()
{
return StringUtils.format("(%s %s)", name, args);
}
@Override
public ExprEval eval(ObjectBinding bindings)
{
return function.apply(args, bindings);
}
@Override
public String stringify()
{
return StringUtils.format("%s(%s)", name, ARG_JOINER.join(args.stream().map(Expr::stringify).iterator()));
}
@Override
public void visit(Visitor visitor)
{
for (Expr child : args) {
child.visit(visitor);
}
visitor.visit(this);
}
@Override
public Expr visit(Shuttle shuttle)
{
List<Expr> newArgs = args.stream().map(shuttle::visit).collect(Collectors.toList());
return shuttle.visit(new FunctionExpr(function, name, newArgs));
}
@Override
public BindingDetails analyzeInputs()
{
BindingDetails accumulator = new BindingDetails();
for (Expr arg : args) {
accumulator = accumulator.with(arg);
}
return accumulator.withScalarArguments(function.getScalarInputs(args))
.withArrayArguments(function.getArrayInputs(args))
.withArrayInputs(function.hasArrayInputs())
.withArrayOutput(function.hasArrayOutput());
}
@Override
public boolean equals(Object o)
{
if (this == o) {
return true;
}
if (o == null || getClass() != o.getClass()) {
return false;
}
FunctionExpr that = (FunctionExpr) o;
return args.equals(that.args) &&
name.equals(that.name);
}
@Override
public int hashCode()
{
return Objects.hash(args, name);
}
}
/**
* This {@link Expr} node is representative of an {@link ApplyFunction}, and has children in the form of a
* {@link LambdaExpr} and the list of {@link Expr} arguments that are combined with {@link Expr.ObjectBinding} to
* evaluate the {@link LambdaExpr}.
*/
class ApplyFunctionExpr implements Expr
{
final ApplyFunction function;
final String name;
final LambdaExpr lambdaExpr;
final ImmutableList<Expr> argsExpr;
final BindingDetails bindingDetails;
final BindingDetails lambdaBindingDetails;
final ImmutableList<BindingDetails> argsBindingDetails;
ApplyFunctionExpr(ApplyFunction function, String name, LambdaExpr expr, List<Expr> args)
{
this.function = function;
this.name = name;
this.argsExpr = ImmutableList.copyOf(args);
this.lambdaExpr = expr;
function.validateArguments(expr, args);
// apply function expressions are examined during expression selector creation, so precompute and cache the
// binding details of children
ImmutableList.Builder<BindingDetails> argBindingDetailsBuilder = ImmutableList.builder();
BindingDetails accumulator = new BindingDetails();
for (Expr arg : argsExpr) {
BindingDetails argDetails = arg.analyzeInputs();
argBindingDetailsBuilder.add(argDetails);
accumulator = accumulator.with(argDetails);
}
lambdaBindingDetails = lambdaExpr.analyzeInputs();
bindingDetails = accumulator.with(lambdaBindingDetails)
.withArrayArguments(function.getArrayInputs(argsExpr))
.withArrayInputs(true)
.withArrayOutput(function.hasArrayOutput(lambdaExpr));
argsBindingDetails = argBindingDetailsBuilder.build();
}
@Override
public String toString()
{
return StringUtils.format("(%s %s, %s)", name, lambdaExpr, argsExpr);
}
@Override
public ExprEval eval(ObjectBinding bindings)
{
return function.apply(lambdaExpr, argsExpr, bindings);
}
@Override
public String stringify()
{
return StringUtils.format(
"%s(%s, %s)",
name,
lambdaExpr.stringify(),
ARG_JOINER.join(argsExpr.stream().map(Expr::stringify).iterator())
);
}
@Override
public void visit(Visitor visitor)
{
lambdaExpr.visit(visitor);
for (Expr arg : argsExpr) {
arg.visit(visitor);
}
visitor.visit(this);
}
@Override
public Expr visit(Shuttle shuttle)
{
LambdaExpr newLambda = (LambdaExpr) lambdaExpr.visit(shuttle);
List<Expr> newArgs = argsExpr.stream().map(shuttle::visit).collect(Collectors.toList());
return shuttle.visit(new ApplyFunctionExpr(function, name, newLambda, newArgs));
}
@Override
public BindingDetails analyzeInputs()
{
return bindingDetails;
}
@Override
public boolean equals(Object o)
{
if (this == o) {
return true;
}
if (o == null || getClass() != o.getClass()) {
return false;
}
ApplyFunctionExpr that = (ApplyFunctionExpr) o;
return name.equals(that.name) &&
lambdaExpr.equals(that.lambdaExpr) &&
argsExpr.equals(that.argsExpr);
}
@Override
public int hashCode()
{
return Objects.hash(name, lambdaExpr, argsExpr);
}
}
/**
* Base type for all single argument operators, with a single {@link Expr} child for the operand.
*/
abstract class UnaryExpr implements Expr
{
final Expr expr;
UnaryExpr(Expr expr)
{
this.expr = expr;
}
abstract UnaryExpr copy(Expr expr);
@Override
public void visit(Visitor visitor)
{
expr.visit(visitor);
visitor.visit(this);
}
@Override
public Expr visit(Shuttle shuttle)
{
Expr newExpr = expr.visit(shuttle);
//noinspection ObjectEquality (checking for object equality here is intentional)
if (newExpr != expr) {
return shuttle.visit(copy(newExpr));
}
return shuttle.visit(this);
}
@Override
public BindingDetails analyzeInputs()
{
// currently all unary operators only operate on scalar inputs
return expr.analyzeInputs().withScalarArguments(ImmutableSet.of(expr));
}
@Override
public boolean equals(Object o)
{
if (this == o) {
return true;
}
if (o == null || getClass() != o.getClass()) {
return false;
}
UnaryExpr unaryExpr = (UnaryExpr) o;
return Objects.equals(expr, unaryExpr.expr);
}
@Override
public int hashCode()
{
return Objects.hash(expr);
}
}
class UnaryMinusExpr extends UnaryExpr
{
UnaryMinusExpr(Expr expr)
{
super(expr);
}
@Override
UnaryExpr copy(Expr expr)
{
return new UnaryMinusExpr(expr);
}
@Override
public ExprEval eval(ObjectBinding bindings)
{
ExprEval ret = expr.eval(bindings);
if (NullHandling.sqlCompatible() && (ret.value() == null)) {
return ExprEval.of(null);
}
if (ret.type() == ExprType.LONG) {
return ExprEval.of(-ret.asLong());
}
if (ret.type() == ExprType.DOUBLE) {
return ExprEval.of(-ret.asDouble());
}
throw new IAE("unsupported type " + ret.type());
}
@Override
public String stringify()
{
return StringUtils.format("-%s", expr.stringify());
}
@Override
public String toString()
{
return StringUtils.format("-%s", expr);
}
}
class UnaryNotExpr extends UnaryExpr
{
UnaryNotExpr(Expr expr)
{
super(expr);
}
@Override
UnaryExpr copy(Expr expr)
{
return new UnaryNotExpr(expr);
}
@Override
public ExprEval eval(ObjectBinding bindings)
{
ExprEval ret = expr.eval(bindings);
if (NullHandling.sqlCompatible() && (ret.value() == null)) {
return ExprEval.of(null);
}
// conforming to other boolean-returning binary operators
ExprType retType = ret.type() == ExprType.DOUBLE ? ExprType.DOUBLE : ExprType.LONG;
return ExprEval.of(!ret.asBoolean(), retType);
}
@Override
public String stringify()
{
return StringUtils.format("!%s", expr.stringify());
}
@Override
public String toString()
{
return StringUtils.format("!%s", expr);
}
}
/**
* Base type for all binary operators, this {@link Expr} has two children {@link Expr} for the left and right side
* operands.
*
* Note: all concrete subclass of this should have constructor with the form of <init>(String, Expr, Expr)
* if it's not possible, just be sure Evals.binaryOp() can handle that
*/
abstract class BinaryOpExprBase implements Expr
{
protected final String op;
protected final Expr left;
protected final Expr right;
BinaryOpExprBase(String op, Expr left, Expr right)
{
this.op = op;
this.left = left;
this.right = right;
}
@Override
public void visit(Visitor visitor)
{
left.visit(visitor);
right.visit(visitor);
visitor.visit(this);
}
@Override
public Expr visit(Shuttle shuttle)
{
Expr newLeft = left.visit(shuttle);
Expr newRight = right.visit(shuttle);
//noinspection ObjectEquality (checking for object equality here is intentional)
if (left != newLeft || right != newRight) {
return shuttle.visit(copy(newLeft, newRight));
}
return shuttle.visit(this);
}
@Override
public String toString()
{
return StringUtils.format("(%s %s %s)", op, left, right);
}
@Override
public String stringify()
{
return StringUtils.format("(%s %s %s)", left.stringify(), op, right.stringify());
}
protected abstract BinaryOpExprBase copy(Expr left, Expr right);
@Override
public BindingDetails analyzeInputs()
{
// currently all binary operators operate on scalar inputs
return left.analyzeInputs().with(right).withScalarArguments(ImmutableSet.of(left, right));
}
@Override
public boolean equals(Object o)
{
if (this == o) {
return true;
}
if (o == null || getClass() != o.getClass()) {
return false;
}
BinaryOpExprBase that = (BinaryOpExprBase) o;
return Objects.equals(op, that.op) &&
Objects.equals(left, that.left) &&
Objects.equals(right, that.right);
}
@Override
public int hashCode()
{
return Objects.hash(op, left, right);
}
}
/**
* Base class for numerical binary operators, with additional methods defined to evaluate primitive values directly
* instead of wrapped with {@link ExprEval}
*/
abstract class BinaryEvalOpExprBase extends BinaryOpExprBase
{
BinaryEvalOpExprBase(String op, Expr left, Expr right)
{
super(op, left, right);
}
@Override
public ExprEval eval(ObjectBinding bindings)
{
ExprEval leftVal = left.eval(bindings);
ExprEval rightVal = right.eval(bindings);
// Result of any Binary expressions is null if any of the argument is null.
// e.g "select null * 2 as c;" or "select null + 1 as c;" will return null as per Standard SQL spec.
if (NullHandling.sqlCompatible() && (leftVal.value() == null || rightVal.value() == null)) {
return ExprEval.of(null);
}
if (leftVal.type() == ExprType.STRING && rightVal.type() == ExprType.STRING) {
return evalString(leftVal.asString(), rightVal.asString());
} else if (leftVal.type() == ExprType.LONG && rightVal.type() == ExprType.LONG) {
if (NullHandling.sqlCompatible() && (leftVal.isNumericNull() || rightVal.isNumericNull())) {
return ExprEval.of(null);
}
return ExprEval.of(evalLong(leftVal.asLong(), rightVal.asLong()));
} else {
if (NullHandling.sqlCompatible() && (leftVal.isNumericNull() || rightVal.isNumericNull())) {
return ExprEval.of(null);
}
return ExprEval.of(evalDouble(leftVal.asDouble(), rightVal.asDouble()));
}
}
protected ExprEval evalString(@Nullable String left, @Nullable String right)
{
throw new IllegalArgumentException("unsupported type " + ExprType.STRING);
}
protected abstract long evalLong(long left, long right);
protected abstract double evalDouble(double left, double right);
}
class BinMinusExpr extends BinaryEvalOpExprBase
{
BinMinusExpr(String op, Expr left, Expr right)
{
super(op, left, right);
}
@Override
protected BinaryOpExprBase copy(Expr left, Expr right)
{
return new BinMinusExpr(op, left, right);
}
@Override
protected final long evalLong(long left, long right)
{
return left - right;
}
@Override
protected final double evalDouble(double left, double right)
{
return left - right;
}
}
class BinPowExpr extends BinaryEvalOpExprBase
{
BinPowExpr(String op, Expr left, Expr right)
{
super(op, left, right);
}
@Override
protected BinaryOpExprBase copy(Expr left, Expr right)
{
return new BinPowExpr(op, left, right);
}
@Override
protected final long evalLong(long left, long right)
{
return LongMath.pow(left, Ints.checkedCast(right));
}
@Override
protected final double evalDouble(double left, double right)
{
return Math.pow(left, right);
}
}
class BinMulExpr extends BinaryEvalOpExprBase
{
BinMulExpr(String op, Expr left, Expr right)
{
super(op, left, right);
}
@Override
protected BinaryOpExprBase copy(Expr left, Expr right)
{
return new BinMulExpr(op, left, right);
}
@Override
protected final long evalLong(long left, long right)
{
return left * right;
}
@Override
protected final double evalDouble(double left, double right)
{
return left * right;
}
}
class BinDivExpr extends BinaryEvalOpExprBase
{
BinDivExpr(String op, Expr left, Expr right)
{
super(op, left, right);
}
@Override
protected BinaryOpExprBase copy(Expr left, Expr right)
{
return new BinDivExpr(op, left, right);
}
@Override
protected final long evalLong(long left, long right)
{
return left / right;
}
@Override
protected final double evalDouble(double left, double right)
{
return left / right;
}
}
class BinModuloExpr extends BinaryEvalOpExprBase
{
BinModuloExpr(String op, Expr left, Expr right)
{
super(op, left, right);
}
@Override
protected BinaryOpExprBase copy(Expr left, Expr right)
{
return new BinModuloExpr(op, left, right);
}
@Override
protected final long evalLong(long left, long right)
{
return left % right;
}
@Override
protected final double evalDouble(double left, double right)
{
return left % right;
}
}
class BinPlusExpr extends BinaryEvalOpExprBase
{
BinPlusExpr(String op, Expr left, Expr right)
{
super(op, left, right);
}
@Override
protected BinaryOpExprBase copy(Expr left, Expr right)
{
return new BinPlusExpr(op, left, right);
}
@Override
protected ExprEval evalString(@Nullable String left, @Nullable String right)
{
return ExprEval.of(NullHandling.nullToEmptyIfNeeded(left)
+ NullHandling.nullToEmptyIfNeeded(right));
}
@Override
protected final long evalLong(long left, long right)
{
return left + right;
}
@Override
protected final double evalDouble(double left, double right)
{
return left + right;
}
}
class BinLtExpr extends BinaryEvalOpExprBase
{
BinLtExpr(String op, Expr left, Expr right)
{
super(op, left, right);
}
@Override
protected BinaryOpExprBase copy(Expr left, Expr right)
{
return new BinLtExpr(op, left, right);
}
@Override
protected ExprEval evalString(@Nullable String left, @Nullable String right)
{
return ExprEval.of(Comparators.<String>naturalNullsFirst().compare(left, right) < 0, ExprType.LONG);
}
@Override
protected final long evalLong(long left, long right)
{
return Evals.asLong(left < right);
}
@Override
protected final double evalDouble(double left, double right)
{
// Use Double.compare for more consistent NaN handling.
return Evals.asDouble(Double.compare(left, right) < 0);
}
}
class BinLeqExpr extends BinaryEvalOpExprBase
{
BinLeqExpr(String op, Expr left, Expr right)
{
super(op, left, right);
}
@Override
protected BinaryOpExprBase copy(Expr left, Expr right)
{
return new BinLeqExpr(op, left, right);
}
@Override
protected ExprEval evalString(@Nullable String left, @Nullable String right)
{
return ExprEval.of(Comparators.<String>naturalNullsFirst().compare(left, right) <= 0, ExprType.LONG);
}
@Override
protected final long evalLong(long left, long right)
{
return Evals.asLong(left <= right);
}
@Override
protected final double evalDouble(double left, double right)
{
// Use Double.compare for more consistent NaN handling.
return Evals.asDouble(Double.compare(left, right) <= 0);
}
}
class BinGtExpr extends BinaryEvalOpExprBase
{
BinGtExpr(String op, Expr left, Expr right)
{
super(op, left, right);
}
@Override
protected BinaryOpExprBase copy(Expr left, Expr right)
{
return new BinGtExpr(op, left, right);
}
@Override
protected ExprEval evalString(@Nullable String left, @Nullable String right)
{
return ExprEval.of(Comparators.<String>naturalNullsFirst().compare(left, right) > 0, ExprType.LONG);
}
@Override
protected final long evalLong(long left, long right)
{
return Evals.asLong(left > right);
}
@Override
protected final double evalDouble(double left, double right)
{
// Use Double.compare for more consistent NaN handling.
return Evals.asDouble(Double.compare(left, right) > 0);
}
}
class BinGeqExpr extends BinaryEvalOpExprBase
{
BinGeqExpr(String op, Expr left, Expr right)
{
super(op, left, right);
}
@Override
protected BinaryOpExprBase copy(Expr left, Expr right)
{
return new BinGeqExpr(op, left, right);
}
@Override
protected ExprEval evalString(@Nullable String left, @Nullable String right)
{
return ExprEval.of(Comparators.<String>naturalNullsFirst().compare(left, right) >= 0, ExprType.LONG);
}
@Override
protected final long evalLong(long left, long right)
{
return Evals.asLong(left >= right);
}
@Override
protected final double evalDouble(double left, double right)
{
// Use Double.compare for more consistent NaN handling.
return Evals.asDouble(Double.compare(left, right) >= 0);
}
}
class BinEqExpr extends BinaryEvalOpExprBase
{
BinEqExpr(String op, Expr left, Expr right)
{
super(op, left, right);
}
@Override
protected BinaryOpExprBase copy(Expr left, Expr right)
{
return new BinEqExpr(op, left, right);
}
@Override
protected ExprEval evalString(@Nullable String left, @Nullable String right)
{
return ExprEval.of(Objects.equals(left, right), ExprType.LONG);
}
@Override
protected final long evalLong(long left, long right)
{
return Evals.asLong(left == right);
}
@Override
protected final double evalDouble(double left, double right)
{
return Evals.asDouble(left == right);
}
}
class BinNeqExpr extends BinaryEvalOpExprBase
{
BinNeqExpr(String op, Expr left, Expr right)
{
super(op, left, right);
}
@Override
protected BinaryOpExprBase copy(Expr left, Expr right)
{
return new BinNeqExpr(op, left, right);
}
@Override
protected ExprEval evalString(@Nullable String left, @Nullable String right)
{
return ExprEval.of(!Objects.equals(left, right), ExprType.LONG);
}
@Override
protected final long evalLong(long left, long right)
{
return Evals.asLong(left != right);
}
@Override
protected final double evalDouble(double left, double right)
{
return Evals.asDouble(left != right);
}
}
class BinAndExpr extends BinaryOpExprBase
{
BinAndExpr(String op, Expr left, Expr right)
{
super(op, left, right);
}
@Override
protected BinaryOpExprBase copy(Expr left, Expr right)
{
return new BinAndExpr(op, left, right);
}
@Override
public ExprEval eval(ObjectBinding bindings)
{
ExprEval leftVal = left.eval(bindings);
return leftVal.asBoolean() ? right.eval(bindings) : leftVal;
}
}
class BinOrExpr extends BinaryOpExprBase
{
BinOrExpr(String op, Expr left, Expr right)
{
super(op, left, right);
}
@Override
protected BinaryOpExprBase copy(Expr left, Expr right)
{
return new BinOrExpr(op, left, right);
}
@Override
public ExprEval eval(ObjectBinding bindings)
{
ExprEval leftVal = left.eval(bindings);
return leftVal.asBoolean() ? leftVal : right.eval(bindings);
}
}