split up Expr.java (#10333)

This commit is contained in:
Clint Wylie 2020-08-31 12:51:53 -07:00 committed by GitHub
parent 8ab1979304
commit 475d86a4f7
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
8 changed files with 1725 additions and 1542 deletions

View File

@ -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);
}
}

View File

@ -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;
}
}

View File

@ -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);
}

View File

@ -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);
}
}

File diff suppressed because it is too large Load Diff

View File

@ -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);
}
}

View File

@ -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);
}
}

View File

@ -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);
}
}