split up Expr.java (#10333)
diff --git a/core/src/main/java/org/apache/druid/math/expr/BinaryLogicalOperatorExpr.java b/core/src/main/java/org/apache/druid/math/expr/BinaryLogicalOperatorExpr.java
new file mode 100644
index 0000000..dad35f3
--- /dev/null
+++ b/core/src/main/java/org/apache/druid/math/expr/BinaryLogicalOperatorExpr.java
@@ -0,0 +1,266 @@
+/*
+ * 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 org.apache.druid.java.util.common.guava.Comparators;
+
+import javax.annotation.Nullable;
+import java.util.Objects;
+
+// logical operators live here
+
+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);
+ }
+
+}
diff --git a/core/src/main/java/org/apache/druid/math/expr/BinaryMathOperatorExpr.java b/core/src/main/java/org/apache/druid/math/expr/BinaryMathOperatorExpr.java
new file mode 100644
index 0000000..21fadd4
--- /dev/null
+++ b/core/src/main/java/org/apache/druid/math/expr/BinaryMathOperatorExpr.java
@@ -0,0 +1,191 @@
+/*
+ * 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.math.LongMath;
+import com.google.common.primitives.Ints;
+import org.apache.druid.common.config.NullHandling;
+
+import javax.annotation.Nullable;
+
+// math operators live here
+
+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 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 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 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 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;
+ }
+}
diff --git a/core/src/main/java/org/apache/druid/math/expr/BinaryOperatorExpr.java b/core/src/main/java/org/apache/druid/math/expr/BinaryOperatorExpr.java
new file mode 100644
index 0000000..9c39058
--- /dev/null
+++ b/core/src/main/java/org/apache/druid/math/expr/BinaryOperatorExpr.java
@@ -0,0 +1,158 @@
+/*
+ * 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.collect.ImmutableSet;
+import org.apache.druid.common.config.NullHandling;
+import org.apache.druid.java.util.common.StringUtils;
+
+import javax.annotation.Nullable;
+import java.util.Objects;
+
+/**
+ * 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);
+}
diff --git a/core/src/main/java/org/apache/druid/math/expr/ConstantExpr.java b/core/src/main/java/org/apache/druid/math/expr/ConstantExpr.java
new file mode 100644
index 0000000..4f6099c
--- /dev/null
+++ b/core/src/main/java/org/apache/druid/math/expr/ConstantExpr.java
@@ -0,0 +1,457 @@
+/*
+ * 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.Preconditions;
+import org.apache.commons.lang.StringEscapeUtils;
+import org.apache.druid.common.config.NullHandling;
+import org.apache.druid.java.util.common.StringUtils;
+
+import javax.annotation.Nullable;
+import java.util.Arrays;
+import java.util.Objects;
+
+/**
+ * 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();
+ }
+}
+
+/**
+ * Base class for typed 'null' value constants (or default value, depending on {@link NullHandling#sqlCompatible})
+ */
+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);
+ }
+}
diff --git a/core/src/main/java/org/apache/druid/math/expr/Expr.java b/core/src/main/java/org/apache/druid/math/expr/Expr.java
index 1cd7d62..e0a1525 100644
--- a/core/src/main/java/org/apache/druid/math/expr/Expr.java
+++ b/core/src/main/java/org/apache/druid/math/expr/Expr.java
@@ -20,28 +20,16 @@
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
@@ -444,1533 +432,3 @@
}
}
}
-
-/**
- * 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);
- }
-
-}
-
diff --git a/core/src/main/java/org/apache/druid/math/expr/FunctionalExpr.java b/core/src/main/java/org/apache/druid/math/expr/FunctionalExpr.java
new file mode 100644
index 0000000..2b3474a
--- /dev/null
+++ b/core/src/main/java/org/apache/druid/math/expr/FunctionalExpr.java
@@ -0,0 +1,334 @@
+/*
+ * 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.Preconditions;
+import com.google.common.collect.ImmutableList;
+import org.apache.druid.java.util.common.StringUtils;
+
+import javax.annotation.Nullable;
+import java.util.List;
+import java.util.Objects;
+import java.util.Set;
+import java.util.stream.Collectors;
+
+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);
+ }
+}
diff --git a/core/src/main/java/org/apache/druid/math/expr/IdentifierExpr.java b/core/src/main/java/org/apache/druid/math/expr/IdentifierExpr.java
new file mode 100644
index 0000000..d23657a
--- /dev/null
+++ b/core/src/main/java/org/apache/druid/math/expr/IdentifierExpr.java
@@ -0,0 +1,153 @@
+/*
+ * 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 org.apache.commons.lang.StringEscapeUtils;
+import org.apache.druid.java.util.common.StringUtils;
+
+import javax.annotation.Nullable;
+import java.util.Objects;
+
+/**
+ * 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);
+ }
+}
diff --git a/core/src/main/java/org/apache/druid/math/expr/UnaryOperatorExpr.java b/core/src/main/java/org/apache/druid/math/expr/UnaryOperatorExpr.java
new file mode 100644
index 0000000..5a41e90
--- /dev/null
+++ b/core/src/main/java/org/apache/druid/math/expr/UnaryOperatorExpr.java
@@ -0,0 +1,166 @@
+/*
+ * 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.collect.ImmutableSet;
+import org.apache.druid.common.config.NullHandling;
+import org.apache.druid.java.util.common.IAE;
+import org.apache.druid.java.util.common.StringUtils;
+
+import java.util.Objects;
+
+/**
+ * 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);
+ }
+}