diff --git a/common/pom.xml b/common/pom.xml index c9dcc57cb1a..5df810e4f62 100644 --- a/common/pom.xml +++ b/common/pom.xml @@ -51,6 +51,10 @@ commons-codec commons-codec + + commons-lang + commons-lang + org.apache.commons commons-dbcp2 diff --git a/common/src/main/antlr4/io/druid/math/expr/antlr/Expr.g4 b/common/src/main/antlr4/io/druid/math/expr/antlr/Expr.g4 index f478911dd55..d87197db657 100644 --- a/common/src/main/antlr4/io/druid/math/expr/antlr/Expr.g4 +++ b/common/src/main/antlr4/io/druid/math/expr/antlr/Expr.g4 @@ -11,16 +11,22 @@ expr : ('-'|'!') expr # unaryOpExpr | IDENTIFIER # identifierExpr | DOUBLE # doubleExpr | LONG # longExpr + | STRING # string ; fnArgs : expr (',' expr)* # functionArgs ; -IDENTIFIER : [_$a-zA-Z][_$a-zA-Z0-9]* ; +IDENTIFIER : [_$a-zA-Z][_$a-zA-Z0-9]* | '"' [_$a-zA-Z][_$a-zA-Z0-9]* '"'; LONG : [0-9]+ ; DOUBLE : [0-9]+ '.' [0-9]* ; WS : [ \t\r\n]+ -> skip ; +STRING : '\'' (ESC | ~ [\'\\])* '\''; +fragment ESC : '\\' ([\'\\/bfnrt] | UNICODE) ; +fragment UNICODE : 'u' HEX HEX HEX HEX ; +fragment HEX : [0-9a-fA-F] ; + MINUS : '-' ; NOT : '!' ; POW : '^' ; diff --git a/common/src/main/java/io/druid/math/expr/Expr.java b/common/src/main/java/io/druid/math/expr/Expr.java index 9c986084f0b..7fca695f914 100644 --- a/common/src/main/java/io/druid/math/expr/Expr.java +++ b/common/src/main/java/io/druid/math/expr/Expr.java @@ -19,15 +19,18 @@ package io.druid.math.expr; +import com.google.common.base.Strings; import com.google.common.math.LongMath; +import io.druid.java.util.common.IAE; import java.util.List; +import java.util.Objects; /** */ public interface Expr { - Number eval(ObjectBinding bindings); + ExprEval eval(ObjectBinding bindings); interface ObjectBinding { @@ -67,10 +70,32 @@ class LongExpr extends ConstantExpr } @Override - public Number eval(ObjectBinding bindings) + public ExprEval eval(ObjectBinding bindings) + { + return ExprEval.of(value); + } +} + +class StringExpr extends ConstantExpr +{ + private final String value; + + public StringExpr(String value) + { + this.value = value; + } + + @Override + public String toString() { return value; } + + @Override + public ExprEval eval(ObjectBinding bindings) + { + return ExprEval.of(value); + } } class DoubleExpr extends ConstantExpr @@ -89,9 +114,9 @@ class DoubleExpr extends ConstantExpr } @Override - public Number eval(ObjectBinding bindings) + public ExprEval eval(ObjectBinding bindings) { - return value; + return ExprEval.of(value); } } @@ -111,14 +136,9 @@ class IdentifierExpr extends ConstantExpr } @Override - public Number eval(ObjectBinding bindings) + public ExprEval eval(ObjectBinding bindings) { - Number val = bindings.get(value); - if (val == null) { - return null; - } else { - return val instanceof Long ? val : val.doubleValue(); - } + return ExprEval.bestEffortOf(bindings.get(value)); } } @@ -140,7 +160,7 @@ class FunctionExpr implements Expr } @Override - public Number eval(ObjectBinding bindings) + public ExprEval eval(ObjectBinding bindings) { return Parser.func.get(name.toLowerCase()).apply(args, bindings); } @@ -180,14 +200,16 @@ class UnaryMinusExpr extends UnaryExpr } @Override - public Number eval(ObjectBinding bindings) + public ExprEval eval(ObjectBinding bindings) { - Number valObj = expr.eval(bindings); - if (valObj instanceof Long) { - return -1 * valObj.longValue(); - } else { - return -1 * valObj.doubleValue(); + ExprEval ret = expr.eval(bindings); + 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 @@ -212,10 +234,16 @@ class UnaryNotExpr extends UnaryExpr } @Override - public Number eval(ObjectBinding bindings) + public ExprEval eval(ObjectBinding bindings) { - Number valObj = expr.eval(bindings); - return valObj.doubleValue() > 0 ? 0.0d : 1.0d; + ExprEval ret = expr.eval(bindings); + if (ret.type() == ExprType.LONG) { + return ExprEval.of(ret.asBoolean() ? 0L : 1L); + } + if (ret.type() == ExprType.DOUBLE) { + return ExprEval.of(ret.asBoolean() ? 0.0d :1.0d); + } + throw new IllegalArgumentException("unsupported type " + ret.type()); } @Override @@ -238,16 +266,6 @@ abstract class BinaryOpExprBase implements Expr this.right = right; } - protected boolean isNull(Number left, Number right) - { - return left == null || right == null; - } - - protected boolean isLong(Number left, Number right) - { - return left instanceof Long && right instanceof Long; - } - @Override public void visit(Visitor visitor) { @@ -263,346 +281,348 @@ abstract class BinaryOpExprBase implements Expr } } -class BinMinusExpr extends BinaryOpExprBase +abstract class BinaryEvalOpExprBase extends BinaryOpExprBase { + public 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); + if (leftVal.isNull() || rightVal.isNull()) { + return ExprEval.of(null); + } + if (leftVal.type() == ExprType.STRING || rightVal.type() == ExprType.STRING) { + return evalString(leftVal.asString(), rightVal.asString()); + } + if (leftVal.type() == ExprType.LONG && rightVal.type() == ExprType.LONG) { + return ExprEval.of(evalLong(leftVal.asLong(), rightVal.asLong())); + } + return ExprEval.of(evalDouble(leftVal.asDouble(), rightVal.asDouble())); + } + + protected ExprEval evalString(String left, 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 - public Number eval(ObjectBinding bindings) + protected final long evalLong(long left, long right) { - Number leftVal = left.eval(bindings); - Number rightVal = right.eval(bindings); - if (isNull(leftVal, rightVal)) { - return null; - } else if (isLong(leftVal, rightVal)) { - return leftVal.longValue() - rightVal.longValue(); - } else { - return leftVal.doubleValue() - rightVal.doubleValue(); - } + return left - right; + } + + @Override + protected final double evalDouble(double left, double right) + { + return left - right; } } -class BinPowExpr extends BinaryOpExprBase +class BinPowExpr extends BinaryEvalOpExprBase { - BinPowExpr(String op, Expr left, Expr right) { super(op, left, right); } @Override - public Number eval(ObjectBinding bindings) + protected final long evalLong(long left, long right) { - Number leftVal = left.eval(bindings); - Number rightVal = right.eval(bindings); - if (isNull(leftVal, rightVal)) { - return null; - } else if (isLong(leftVal, rightVal)) { - return LongMath.pow(leftVal.longValue(), rightVal.intValue()); - } else { - return Math.pow(leftVal.doubleValue(), rightVal.doubleValue()); - } + return LongMath.pow(left, (int)right); + } + + @Override + protected final double evalDouble(double left, double right) + { + return Math.pow(left, right); } } -class BinMulExpr extends BinaryOpExprBase +class BinMulExpr extends BinaryEvalOpExprBase { - BinMulExpr(String op, Expr left, Expr right) { super(op, left, right); } @Override - public Number eval(ObjectBinding bindings) + protected final long evalLong(long left, long right) { - Number leftVal = left.eval(bindings); - Number rightVal = right.eval(bindings); - if (isNull(leftVal, rightVal)) { - return null; - } else if (isLong(leftVal, rightVal)) { - return leftVal.longValue() * rightVal.longValue(); - } else { - return leftVal.doubleValue() * rightVal.doubleValue(); - } + return left * right; + } + + @Override + protected final double evalDouble(double left, double right) + { + return left * right; } } -class BinDivExpr extends BinaryOpExprBase +class BinDivExpr extends BinaryEvalOpExprBase { - BinDivExpr(String op, Expr left, Expr right) { super(op, left, right); } @Override - public Number eval(ObjectBinding bindings) + protected final long evalLong(long left, long right) { - Number leftVal = left.eval(bindings); - Number rightVal = right.eval(bindings); - if (isLong(leftVal, rightVal)) { - return leftVal.longValue() / rightVal.longValue(); - } else { - return leftVal.doubleValue() / rightVal.doubleValue(); - } + return left / right; + } + + @Override + protected final double evalDouble(double left, double right) + { + return left / right; } } -class BinModuloExpr extends BinaryOpExprBase +class BinModuloExpr extends BinaryEvalOpExprBase { - BinModuloExpr(String op, Expr left, Expr right) { super(op, left, right); } @Override - public Number eval(ObjectBinding bindings) + protected final long evalLong(long left, long right) { - Number leftVal = left.eval(bindings); - Number rightVal = right.eval(bindings); - if (isNull(leftVal, rightVal)) { - return null; - } else if (isLong(leftVal, rightVal)) { - return leftVal.longValue() % rightVal.longValue(); - } else { - return leftVal.doubleValue() % rightVal.doubleValue(); - } + return left % right; + } + + @Override + protected final double evalDouble(double left, double right) + { + return left % right; } } -class BinPlusExpr extends BinaryOpExprBase +class BinPlusExpr extends BinaryEvalOpExprBase { - BinPlusExpr(String op, Expr left, Expr right) { super(op, left, right); } @Override - public Number eval(ObjectBinding bindings) + protected ExprEval evalString(String left, String right) { - Number leftVal = left.eval(bindings); - Number rightVal = right.eval(bindings); - if (isNull(leftVal, rightVal)) { - return null; - } else if (isLong(leftVal, rightVal)) { - return leftVal.longValue() + rightVal.longValue(); - } else { - return leftVal.doubleValue() + rightVal.doubleValue(); - } + return ExprEval.of(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 BinLtExpr extends BinaryOpExprBase +class BinLtExpr extends BinaryEvalOpExprBase { - BinLtExpr(String op, Expr left, Expr right) { super(op, left, right); } @Override - public Number eval(ObjectBinding bindings) + protected ExprEval evalString(String left, String right) { - Number leftVal = left.eval(bindings); - Number rightVal = right.eval(bindings); - if (isNull(leftVal, rightVal)) { - return null; - } else if (isLong(leftVal, rightVal)) { - return leftVal.longValue() < rightVal.longValue() ? 1 : 0; - } else { - return leftVal.doubleValue() < rightVal.doubleValue() ? 1.0d : 0.0d; - } + return ExprEval.of(left.compareTo(right) < 0 ? 1L : 0L); + } + + @Override + protected final long evalLong(long left, long right) + { + return left < right ? 1L : 0L; + } + + @Override + protected final double evalDouble(double left, double right) + { + return left < right ? 1.0d : 0.0d; } } -class BinLeqExpr extends BinaryOpExprBase +class BinLeqExpr extends BinaryEvalOpExprBase { - BinLeqExpr(String op, Expr left, Expr right) { super(op, left, right); } @Override - public Number eval(ObjectBinding bindings) + protected ExprEval evalString(String left, String right) { - Number leftVal = left.eval(bindings); - Number rightVal = right.eval(bindings); - if (isNull(leftVal, rightVal)) { - return null; - } else if (isLong(leftVal, rightVal)) { - return leftVal.longValue() <= rightVal.longValue() ? 1 : 0; - } else { - return leftVal.doubleValue() <= rightVal.doubleValue() ? 1.0d : 0.0d; - } + return ExprEval.of(left.compareTo(right) <= 0 ? 1L : 0L); + } + + @Override + protected final long evalLong(long left, long right) + { + return left <= right ? 1L : 0L; + } + + @Override + protected final double evalDouble(double left, double right) + { + return left <= right ? 1.0d : 0.0d; } } -class BinGtExpr extends BinaryOpExprBase +class BinGtExpr extends BinaryEvalOpExprBase { - BinGtExpr(String op, Expr left, Expr right) { super(op, left, right); } @Override - public Number eval(ObjectBinding bindings) + protected ExprEval evalString(String left, String right) { - Number leftVal = left.eval(bindings); - Number rightVal = right.eval(bindings); - if (isNull(leftVal, rightVal)) { - return null; - } else if (isLong(leftVal, rightVal)) { - return leftVal.longValue() > rightVal.longValue() ? 1 : 0; - } else { - return leftVal.doubleValue() > rightVal.doubleValue() ? 1.0d : 0.0d; - } + return ExprEval.of(left.compareTo(right) > 0 ? 1L : 0L); + } + + @Override + protected final long evalLong(long left, long right) + { + return left > right ? 1L : 0L; + } + + @Override + protected final double evalDouble(double left, double right) + { + return left > right ? 1.0d : 0.0d; } } -class BinGeqExpr extends BinaryOpExprBase +class BinGeqExpr extends BinaryEvalOpExprBase { - BinGeqExpr(String op, Expr left, Expr right) { super(op, left, right); } @Override - public Number eval(ObjectBinding bindings) + protected ExprEval evalString(String left, String right) { - Number leftVal = left.eval(bindings); - Number rightVal = right.eval(bindings); - if (isNull(leftVal, rightVal)) { - return null; - } else if (isLong(leftVal, rightVal)) { - return leftVal.longValue() >= rightVal.longValue() ? 1 : 0; - } else { - return leftVal.doubleValue() >= rightVal.doubleValue() ? 1.0d : 0.0d; - } + return ExprEval.of(left.compareTo(right) >= 0 ? 1L : 0L); + } + + @Override + protected final long evalLong(long left, long right) + { + return left >= right ? 1L : 0L; + } + + @Override + protected final double evalDouble(double left, double right) + { + return left >= right ? 1.0d : 0.0d; } } -class BinEqExpr extends BinaryOpExprBase +class BinEqExpr extends BinaryEvalOpExprBase { - BinEqExpr(String op, Expr left, Expr right) { super(op, left, right); } @Override - public Number eval(ObjectBinding bindings) + protected ExprEval evalString(String left, String right) { - Number leftVal = left.eval(bindings); - Number rightVal = right.eval(bindings); - if (isNull(leftVal, rightVal)) { - return null; - } else if (isLong(leftVal, rightVal)) { - return leftVal.longValue() == rightVal.longValue() ? 1 : 0; - } else { - return leftVal.doubleValue() == rightVal.doubleValue() ? 1.0d : 0.0d; - } + return ExprEval.of(left.equals(right) ? 1L : 0L); + } + + @Override + protected final long evalLong(long left, long right) + { + return left == right ? 1L : 0L; + } + + @Override + protected final double evalDouble(double left, double right) + { + return left == right ? 1.0d : 0.0d; } } -class BinNeqExpr extends BinaryOpExprBase +class BinNeqExpr extends BinaryEvalOpExprBase { - BinNeqExpr(String op, Expr left, Expr right) { super(op, left, right); } @Override - public Number eval(ObjectBinding bindings) + protected ExprEval evalString(String left, String right) { - Number leftVal = left.eval(bindings); - Number rightVal = right.eval(bindings); - if (isNull(leftVal, rightVal)) { - return null; - } else if (isLong(leftVal, rightVal)) { - return leftVal.longValue() != rightVal.longValue() ? 1 : 0; - } else { - return leftVal.doubleValue() != rightVal.doubleValue() ? 1.0d : 0.0d; - } + return ExprEval.of(!Objects.equals(left, right) ? 1L : 0L); + } + + @Override + protected final long evalLong(long left, long right) + { + return left != right ? 1L : 0L; + } + + @Override + protected final double evalDouble(double left, double right) + { + return left != right ? 1.0d : 0.0d; } } class BinAndExpr extends BinaryOpExprBase { - BinAndExpr(String op, Expr left, Expr right) { super(op, left, right); } @Override - public Number eval(ObjectBinding bindings) + public ExprEval eval(ObjectBinding bindings) { - Number leftVal = left.eval(bindings); - Number rightVal = right.eval(bindings); - if (isNull(leftVal, rightVal)) { - return null; - } else if (isLong(leftVal, rightVal)) { - long lval = leftVal.longValue(); - if (lval > 0) { - long rval = rightVal.longValue(); - return rval > 0 ? 1 : 0; - } else { - return 0; - } - } else { - double lval = leftVal.doubleValue(); - if (lval > 0) { - double rval = rightVal.doubleValue(); - return rval > 0 ? 1.0d : 0.0d; - } else { - return 0.0d; - } - } + 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 - public Number eval(ObjectBinding bindings) + public ExprEval eval(ObjectBinding bindings) { - Number leftVal = left.eval(bindings); - Number rightVal = right.eval(bindings); - if (isNull(leftVal, rightVal)) { - return null; - } else if (isLong(leftVal, rightVal)) { - long lval = leftVal.longValue(); - if (lval > 0) { - return 1; - } else { - long rval = rightVal.longValue(); - return rval > 0 ? 1 : 0; - } - } else { - double lval = leftVal.doubleValue(); - if (lval > 0) { - return 1.0d; - } else { - double rval = rightVal.doubleValue(); - return rval > 0 ? 1.0d : 0.0d; - } - } + ExprEval leftVal = left.eval(bindings); + return leftVal.asBoolean() ? leftVal : right.eval(bindings); } } diff --git a/common/src/main/java/io/druid/math/expr/ExprEval.java b/common/src/main/java/io/druid/math/expr/ExprEval.java new file mode 100644 index 00000000000..1012a24f08b --- /dev/null +++ b/common/src/main/java/io/druid/math/expr/ExprEval.java @@ -0,0 +1,262 @@ +/* + * Licensed to Metamarkets Group Inc. (Metamarkets) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. Metamarkets 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 io.druid.math.expr; + +import com.google.common.base.Strings; +import io.druid.java.util.common.IAE; + +/** + */ +public abstract class ExprEval +{ + public static ExprEval of(long longValue) + { + return new LongExprEval(longValue); + } + + public static ExprEval of(double longValue) + { + return new DoubleExprEval(longValue); + } + + public static ExprEval of(String stringValue) + { + return new StringExprEval(stringValue); + } + + public static ExprEval of(boolean value, ExprType type) + { + switch (type) { + case DOUBLE: + return ExprEval.of(value ? 1D : 0D); + case LONG: + return ExprEval.of(value ? 1L : 0L); + case STRING: + return ExprEval.of(String.valueOf(value)); + default: + throw new IllegalArgumentException("invalid type " + type); + } + } + + public static ExprEval bestEffortOf(Object val) + { + if (val instanceof ExprEval) { + return (ExprEval) val; + } + if (val instanceof Number) { + if (val instanceof Float || val instanceof Double) { + return new DoubleExprEval((Number)val); + } + return new LongExprEval((Number)val); + } + return new StringExprEval(val == null ? null : String.valueOf(val)); + } + + final T value; + + private ExprEval(T value) + { + this.value = value; + } + + public abstract ExprType type(); + + public Object value() + { + return value; + } + + public boolean isNull() + { + return value == null; + } + + public Number numericValue() + { + return (Number) value; + } + + public abstract int asInt(); + + public abstract long asLong(); + + public abstract double asDouble(); + + public String asString() + { + return value == null ? null : String.valueOf(value); + } + + public abstract boolean asBoolean(); + + public abstract ExprEval castTo(ExprType castTo); + + private static abstract class NumericExprEval extends ExprEval { + + private NumericExprEval(Number value) + { + super(value); + } + + @Override + public final int asInt() + { + return value.intValue(); + } + + @Override + public final long asLong() + { + return value.longValue(); + } + + @Override + public final double asDouble() + { + return value.doubleValue(); + } + } + + private static class DoubleExprEval extends NumericExprEval + { + private DoubleExprEval(Number value) + { + super(value); + } + + @Override + public final ExprType type() + { + return ExprType.DOUBLE; + } + + @Override + public final boolean asBoolean() + { + return asDouble() > 0; + } + + @Override + public final ExprEval castTo(ExprType castTo) + { + switch (castTo) { + case DOUBLE: + return this; + case LONG: + return ExprEval.of(asLong()); + case STRING: + return ExprEval.of(asString()); + } + throw new IAE("invalid type " + castTo); + } + } + + private static class LongExprEval extends NumericExprEval + { + private LongExprEval(Number value) + { + super(value); + } + + @Override + public final ExprType type() + { + return ExprType.LONG; + } + + @Override + public final boolean asBoolean() + { + return asLong() > 0; + } + + @Override + public final ExprEval castTo(ExprType castTo) + { + switch (castTo) { + case DOUBLE: + return ExprEval.of(asDouble()); + case LONG: + return this; + case STRING: + return ExprEval.of(asString()); + } + throw new IAE("invalid type " + castTo); + } + } + + private static class StringExprEval extends ExprEval + { + private StringExprEval(String value) + { + super(value); + } + + @Override + public final ExprType type() + { + return ExprType.STRING; + } + + @Override + public final boolean isNull() + { + return Strings.isNullOrEmpty(value); + } + + @Override + public final int asInt() + { + return Integer.parseInt(value); + } + + @Override + public final long asLong() + { + return Long.parseLong(value); + } + + @Override + public final double asDouble() + { + return Double.parseDouble(value); + } + + @Override + public final boolean asBoolean() + { + return Boolean.valueOf(value); + } + + @Override + public final ExprEval castTo(ExprType castTo) + { + switch (castTo) { + case DOUBLE: + return ExprEval.of(asDouble()); + case LONG: + return ExprEval.of(asLong()); + case STRING: + return this; + } + throw new IAE("invalid type " + castTo); + } + } +} diff --git a/common/src/main/java/io/druid/math/expr/ExprListenerImpl.java b/common/src/main/java/io/druid/math/expr/ExprListenerImpl.java index 39fecc779c9..c2d2fdadbcc 100644 --- a/common/src/main/java/io/druid/math/expr/ExprListenerImpl.java +++ b/common/src/main/java/io/druid/math/expr/ExprListenerImpl.java @@ -23,6 +23,7 @@ import io.druid.math.expr.antlr.ExprBaseListener; import io.druid.math.expr.antlr.ExprParser; import org.antlr.v4.runtime.tree.ParseTree; import org.antlr.v4.runtime.tree.TerminalNode; +import org.apache.commons.lang.StringEscapeUtils; import java.util.ArrayList; import java.util.Collections; @@ -148,6 +149,15 @@ public class ExprListenerImpl extends ExprBaseListener nodes.put(ctx, nodes.get(ctx.getChild(1))); } + @Override + public void exitString(ExprParser.StringContext ctx) + { + String text = ctx.getText(); + String unquoted = text.substring(1, text.length() - 1); + String unescaped = unquoted.indexOf('\\') >= 0 ? StringEscapeUtils.unescapeJava(unquoted) : unquoted; + nodes.put(ctx, new StringExpr(unescaped)); + } + @Override public void exitLogicalOpExpr(ExprParser.LogicalOpExprContext ctx) { @@ -289,9 +299,13 @@ public class ExprListenerImpl extends ExprBaseListener @Override public void exitIdentifierExpr(ExprParser.IdentifierExprContext ctx) { + String text = ctx.getText(); + if (text.charAt(0) == '"' && text.charAt(text.length() - 1) == '"') { + text = text.substring(1, text.length() - 1); + } nodes.put( ctx, - new IdentifierExpr(ctx.getText()) + new IdentifierExpr(text) ); } diff --git a/common/src/main/java/io/druid/math/expr/ExprType.java b/common/src/main/java/io/druid/math/expr/ExprType.java new file mode 100644 index 00000000000..1a4759aab3d --- /dev/null +++ b/common/src/main/java/io/druid/math/expr/ExprType.java @@ -0,0 +1,27 @@ +/* + * Licensed to Metamarkets Group Inc. (Metamarkets) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. Metamarkets 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 io.druid.math.expr; + +/** + */ +public enum ExprType +{ + DOUBLE, LONG, STRING +} diff --git a/common/src/main/java/io/druid/math/expr/Function.java b/common/src/main/java/io/druid/math/expr/Function.java index f6953dcde30..a27e3c55fb1 100644 --- a/common/src/main/java/io/druid/math/expr/Function.java +++ b/common/src/main/java/io/druid/math/expr/Function.java @@ -19,6 +19,12 @@ package io.druid.math.expr; +import io.druid.java.util.common.IAE; +import org.joda.time.DateTime; +import org.joda.time.format.DateTimeFormat; +import org.joda.time.format.DateTimeFormatter; +import org.joda.time.format.ISODateTimeFormat; + import java.util.List; /** @@ -27,40 +33,90 @@ interface Function { String name(); - Number apply(List args, Expr.ObjectBinding bindings); + ExprEval apply(List args, Expr.ObjectBinding bindings); abstract class SingleParam implements Function { @Override - public Number apply(List args, Expr.ObjectBinding bindings) + public ExprEval apply(List args, Expr.ObjectBinding bindings) { if (args.size() != 1) { - throw new RuntimeException("function '" + name() + "' needs 1 argument"); + throw new IAE("function '%s' needs 1 argument", name()); } Expr expr = args.get(0); return eval(expr.eval(bindings)); } - protected abstract Number eval(Number x); + protected abstract ExprEval eval(ExprEval param); } abstract class DoubleParam implements Function { @Override - public Number apply(List args, Expr.ObjectBinding bindings) + public ExprEval apply(List args, Expr.ObjectBinding bindings) { if (args.size() != 2) { - throw new RuntimeException("function '" + name() + "' needs 1 argument"); + throw new IAE("function '%s' needs 2 arguments", name()); } Expr expr1 = args.get(0); Expr expr2 = args.get(1); return eval(expr1.eval(bindings), expr2.eval(bindings)); } - protected abstract Number eval(Number x, Number y); + protected abstract ExprEval eval(ExprEval x, ExprEval y); } - class Abs extends SingleParam + abstract class SingleParamMath extends SingleParam + { + @Override + protected final ExprEval eval(ExprEval param) + { + if (param.type() == ExprType.LONG) { + return eval(param.asLong()); + } else if (param.type() == ExprType.DOUBLE) { + return eval(param.asDouble()); + } + return ExprEval.of(null); + } + + protected ExprEval eval(long param) + { + return eval((double) param); + } + + protected ExprEval eval(double param) + { + return eval((long) param); + } + } + + abstract class DoubleParamMath extends DoubleParam + { + @Override + protected final ExprEval eval(ExprEval x, ExprEval y) + { + if (x.type() == ExprType.STRING || y.type() == ExprType.STRING) { + return ExprEval.of(null); + } + if (x.type() == ExprType.LONG && y.type() == ExprType.LONG) { + return eval(x.asLong(), y.asLong()); + } else { + return eval(x.asDouble(), y.asDouble()); + } + } + + protected ExprEval eval(long x, long y) + { + return eval((double) x, (double) y); + } + + protected ExprEval eval(double x, double y) + { + return eval((long) x, (long) y); + } + } + + class Abs extends SingleParamMath { @Override public String name() @@ -69,13 +125,19 @@ interface Function } @Override - protected Number eval(Number x) + protected ExprEval eval(long param) { - return x instanceof Long ? Math.abs(x.longValue()) : Math.abs(x.doubleValue()); + return ExprEval.of(Math.abs(param)); + } + + @Override + protected ExprEval eval(double param) + { + return ExprEval.of(Math.abs(param)); } } - class Acos extends SingleParam + class Acos extends SingleParamMath { @Override public String name() @@ -84,13 +146,13 @@ interface Function } @Override - protected Number eval(Number x) + protected ExprEval eval(double param) { - return Math.acos(x.doubleValue()); + return ExprEval.of(Math.acos(param)); } } - class Asin extends SingleParam + class Asin extends SingleParamMath { @Override public String name() @@ -99,13 +161,13 @@ interface Function } @Override - protected Number eval(Number x) + protected ExprEval eval(double param) { - return Math.asin(x.doubleValue()); + return ExprEval.of(Math.asin(param)); } } - class Atan extends SingleParam + class Atan extends SingleParamMath { @Override public String name() @@ -114,13 +176,13 @@ interface Function } @Override - protected Number eval(Number x) + protected ExprEval eval(double param) { - return Math.atan(x.doubleValue()); + return ExprEval.of(Math.atan(param)); } } - class Cbrt extends SingleParam + class Cbrt extends SingleParamMath { @Override public String name() @@ -129,13 +191,13 @@ interface Function } @Override - protected Number eval(Number x) + protected ExprEval eval(double param) { - return Math.cbrt(x.doubleValue()); + return ExprEval.of(Math.cbrt(param)); } } - class Ceil extends SingleParam + class Ceil extends SingleParamMath { @Override public String name() @@ -144,13 +206,13 @@ interface Function } @Override - protected Number eval(Number x) + protected ExprEval eval(double param) { - return Math.ceil(x.doubleValue()); + return ExprEval.of(Math.ceil(param)); } } - class Cos extends SingleParam + class Cos extends SingleParamMath { @Override public String name() @@ -159,13 +221,13 @@ interface Function } @Override - protected Number eval(Number x) + protected ExprEval eval(double param) { - return Math.cos(x.doubleValue()); + return ExprEval.of(Math.cos(param)); } } - class Cosh extends SingleParam + class Cosh extends SingleParamMath { @Override public String name() @@ -174,13 +236,13 @@ interface Function } @Override - protected Number eval(Number x) + protected ExprEval eval(double param) { - return Math.cosh(x.doubleValue()); + return ExprEval.of(Math.cosh(param)); } } - class Exp extends SingleParam + class Exp extends SingleParamMath { @Override public String name() @@ -189,13 +251,13 @@ interface Function } @Override - protected Number eval(Number x) + protected ExprEval eval(double param) { - return Math.exp(x.doubleValue()); + return ExprEval.of(Math.exp(param)); } } - class Expm1 extends SingleParam + class Expm1 extends SingleParamMath { @Override public String name() @@ -204,13 +266,13 @@ interface Function } @Override - protected Number eval(Number x) + protected ExprEval eval(double param) { - return Math.expm1(x.doubleValue()); + return ExprEval.of(Math.expm1(param)); } } - class Floor extends SingleParam + class Floor extends SingleParamMath { @Override public String name() @@ -219,13 +281,13 @@ interface Function } @Override - protected Number eval(Number x) + protected ExprEval eval(double param) { - return Math.floor(x.doubleValue()); + return ExprEval.of(Math.floor(param)); } } - class GetExponent extends SingleParam + class GetExponent extends SingleParamMath { @Override public String name() @@ -234,13 +296,13 @@ interface Function } @Override - protected Number eval(Number x) + protected ExprEval eval(double param) { - return Math.getExponent(x.doubleValue()); + return ExprEval.of(Math.getExponent(param)); } } - class Log extends SingleParam + class Log extends SingleParamMath { @Override public String name() @@ -249,13 +311,13 @@ interface Function } @Override - protected Number eval(Number x) + protected ExprEval eval(double param) { - return Math.log(x.doubleValue()); + return ExprEval.of(Math.log(param)); } } - class Log10 extends SingleParam + class Log10 extends SingleParamMath { @Override public String name() @@ -264,13 +326,13 @@ interface Function } @Override - protected Number eval(Number x) + protected ExprEval eval(double param) { - return Math.log10(x.doubleValue()); + return ExprEval.of(Math.log10(param)); } } - class Log1p extends SingleParam + class Log1p extends SingleParamMath { @Override public String name() @@ -279,13 +341,13 @@ interface Function } @Override - protected Number eval(Number x) + protected ExprEval eval(double param) { - return Math.log1p(x.doubleValue()); + return ExprEval.of(Math.log1p(param)); } } - class NextUp extends SingleParam + class NextUp extends SingleParamMath { @Override public String name() @@ -294,13 +356,13 @@ interface Function } @Override - protected Number eval(Number x) + protected ExprEval eval(double param) { - return Math.nextUp(x.doubleValue()); + return ExprEval.of(Math.nextUp(param)); } } - class Rint extends SingleParam + class Rint extends SingleParamMath { @Override public String name() @@ -309,13 +371,13 @@ interface Function } @Override - protected Number eval(Number x) + protected ExprEval eval(double param) { - return Math.rint(x.doubleValue()); + return ExprEval.of(Math.rint(param)); } } - class Round extends SingleParam + class Round extends SingleParamMath { @Override public String name() @@ -324,13 +386,13 @@ interface Function } @Override - protected Number eval(Number x) + protected ExprEval eval(double param) { - return Math.round(x.doubleValue()); + return ExprEval.of(Math.round(param)); } } - class Signum extends SingleParam + class Signum extends SingleParamMath { @Override public String name() @@ -339,13 +401,13 @@ interface Function } @Override - protected Number eval(Number x) + protected ExprEval eval(double param) { - return Math.signum(x.doubleValue()); + return ExprEval.of(Math.signum(param)); } } - class Sin extends SingleParam + class Sin extends SingleParamMath { @Override public String name() @@ -354,13 +416,13 @@ interface Function } @Override - protected Number eval(Number x) + protected ExprEval eval(double param) { - return Math.sin(x.doubleValue()); + return ExprEval.of(Math.sin(param)); } } - class Sinh extends SingleParam + class Sinh extends SingleParamMath { @Override public String name() @@ -369,13 +431,13 @@ interface Function } @Override - protected Number eval(Number x) + protected ExprEval eval(double param) { - return Math.sinh(x.doubleValue()); + return ExprEval.of(Math.sinh(param)); } } - class Sqrt extends SingleParam + class Sqrt extends SingleParamMath { @Override public String name() @@ -384,13 +446,13 @@ interface Function } @Override - protected Number eval(Number x) + protected ExprEval eval(double param) { - return Math.sqrt(x.doubleValue()); + return ExprEval.of(Math.sqrt(param)); } } - class Tan extends SingleParam + class Tan extends SingleParamMath { @Override public String name() @@ -399,13 +461,13 @@ interface Function } @Override - protected Number eval(Number x) + protected ExprEval eval(double param) { - return Math.tan(x.doubleValue()); + return ExprEval.of(Math.tan(param)); } } - class Tanh extends SingleParam + class Tanh extends SingleParamMath { @Override public String name() @@ -414,13 +476,13 @@ interface Function } @Override - protected Number eval(Number x) + protected ExprEval eval(double param) { - return Math.tanh(x.doubleValue()); + return ExprEval.of(Math.tanh(param)); } } - class ToDegrees extends SingleParam + class ToDegrees extends SingleParamMath { @Override public String name() @@ -429,13 +491,13 @@ interface Function } @Override - protected Number eval(Number x) + protected ExprEval eval(double param) { - return Math.toDegrees(x.doubleValue()); + return ExprEval.of(Math.toDegrees(param)); } } - class ToRadians extends SingleParam + class ToRadians extends SingleParamMath { @Override public String name() @@ -444,13 +506,13 @@ interface Function } @Override - protected Number eval(Number x) + protected ExprEval eval(double param) { - return Math.toRadians(x.doubleValue()); + return ExprEval.of(Math.toRadians(param)); } } - class Ulp extends SingleParam + class Ulp extends SingleParamMath { @Override public String name() @@ -459,13 +521,13 @@ interface Function } @Override - protected Number eval(Number x) + protected ExprEval eval(double param) { - return Math.ulp(x.doubleValue()); + return ExprEval.of(Math.ulp(param)); } } - class Atan2 extends DoubleParam + class Atan2 extends DoubleParamMath { @Override public String name() @@ -474,13 +536,13 @@ interface Function } @Override - protected Number eval(Number x, Number y) + protected ExprEval eval(double y, double x) { - return Math.atan2(x.doubleValue(), y.doubleValue()); + return ExprEval.of(Math.atan2(y, x)); } } - class CopySign extends DoubleParam + class CopySign extends DoubleParamMath { @Override public String name() @@ -489,13 +551,13 @@ interface Function } @Override - protected Number eval(Number x, Number y) + protected ExprEval eval(double x, double y) { - return Math.copySign(x.doubleValue(), y.doubleValue()); + return ExprEval.of(Math.copySign(x, y)); } } - class Hypot extends DoubleParam + class Hypot extends DoubleParamMath { @Override public String name() @@ -504,13 +566,13 @@ interface Function } @Override - protected Number eval(Number x, Number y) + protected ExprEval eval(double x, double y) { - return Math.hypot(x.doubleValue(), y.doubleValue()); + return ExprEval.of(Math.hypot(x, y)); } } - class Remainder extends DoubleParam + class Remainder extends DoubleParamMath { @Override public String name() @@ -519,13 +581,13 @@ interface Function } @Override - protected Number eval(Number x, Number y) + protected ExprEval eval(double x, double y) { - return Math.IEEEremainder(x.doubleValue(), y.doubleValue()); + return ExprEval.of(Math.IEEEremainder(x, y)); } } - class Max extends DoubleParam + class Max extends DoubleParamMath { @Override public String name() @@ -534,16 +596,19 @@ interface Function } @Override - protected Number eval(Number x, Number y) + protected ExprEval eval(long x, long y) { - if (x instanceof Long && y instanceof Long) { - return Math.max(x.longValue(), y.longValue()); - } - return Double.compare(x.doubleValue(), y.doubleValue()) >= 0 ? x : y; + return ExprEval.of(Math.max(x, y)); + } + + @Override + protected ExprEval eval(double x, double y) + { + return ExprEval.of(Math.max(x, y)); } } - class Min extends DoubleParam + class Min extends DoubleParamMath { @Override public String name() @@ -552,16 +617,19 @@ interface Function } @Override - protected Number eval(Number x, Number y) + protected ExprEval eval(long x, long y) { - if (x instanceof Long && y instanceof Long) { - return Math.min(x.longValue(), y.longValue()); - } - return Double.compare(x.doubleValue(), y.doubleValue()) <= 0 ? x : y; + return ExprEval.of(Math.min(x, y)); + } + + @Override + protected ExprEval eval(double x, double y) + { + return ExprEval.of(Math.min(x, y)); } } - class NextAfter extends DoubleParam + class NextAfter extends DoubleParamMath { @Override public String name() @@ -570,13 +638,13 @@ interface Function } @Override - protected Number eval(Number x, Number y) + protected ExprEval eval(double x, double y) { - return Math.nextAfter(x.doubleValue(), y.doubleValue()); + return ExprEval.of(Math.nextAfter(x, y)); } } - class Pow extends DoubleParam + class Pow extends DoubleParamMath { @Override public String name() @@ -585,9 +653,9 @@ interface Function } @Override - protected Number eval(Number x, Number y) + protected ExprEval eval(double x, double y) { - return Math.pow(x.doubleValue(), y.doubleValue()); + return ExprEval.of(Math.pow(x, y)); } } @@ -600,9 +668,9 @@ interface Function } @Override - protected Number eval(Number x, Number y) + protected ExprEval eval(ExprEval x, ExprEval y) { - return Math.scalb(x.doubleValue(), y.intValue()); + return ExprEval.of(Math.scalb(x.asDouble(), y.asInt())); } } @@ -615,18 +683,113 @@ interface Function } @Override - public Number apply(List args, Expr.ObjectBinding bindings) + public ExprEval apply(List args, Expr.ObjectBinding bindings) { if (args.size() != 3) { - throw new RuntimeException("function 'if' needs 3 argument"); + throw new IAE("function 'if' needs 3 arguments"); } - Number x = args.get(0).eval(bindings); - if (x instanceof Long) { - return x.longValue() > 0 ? args.get(1).eval(bindings) : args.get(2).eval(bindings); - } else { - return x.doubleValue() > 0 ? args.get(1).eval(bindings) : args.get(2).eval(bindings); + ExprEval x = args.get(0).eval(bindings); + return x.asBoolean() ? args.get(1).eval(bindings) : args.get(2).eval(bindings); + } + } + + class CastFunc extends DoubleParam + { + @Override + public String name() + { + return "cast"; + } + + @Override + protected ExprEval eval(ExprEval x, ExprEval y) + { + ExprType castTo; + try { + castTo = ExprType.valueOf(y.asString().toUpperCase()); } + catch (IllegalArgumentException e) { + throw new IAE("invalid type '%s'", y.asString()); + } + return x.castTo(castTo); + } + } + + class TimestampFromEpochFunc implements Function + { + @Override + public String name() + { + return "timestamp"; + } + + @Override + public ExprEval apply(List args, Expr.ObjectBinding bindings) + { + if (args.size() != 1 && args.size() != 2) { + throw new IAE("function '%s' needs 1 or 2 arguments", name()); + } + ExprEval value = args.get(0).eval(bindings); + if (value.type() != ExprType.STRING) { + throw new IAE("first argument should be string type but got %s type", value.type()); + } + + DateTimeFormatter formatter = ISODateTimeFormat.dateOptionalTimeParser(); + if (args.size() > 1) { + ExprEval format = args.get(1).eval(bindings); + if (format.type() != ExprType.STRING) { + throw new IAE("second argument should be string type but got %s type", format.type()); + } + formatter = DateTimeFormat.forPattern(format.asString()); + } + DateTime date; + try { + date = DateTime.parse(value.asString(), formatter); + } + catch (IllegalArgumentException e) { + throw new IAE(e, "invalid value %s", value.asString()); + } + return toValue(date); + } + + protected ExprEval toValue(DateTime date) + { + return ExprEval.of(date.getMillis()); + } + } + + class UnixTimestampFunc extends TimestampFromEpochFunc + { + @Override + public String name() + { + return "unix_timestamp"; + } + + @Override + protected final ExprEval toValue(DateTime date) + { + return ExprEval.of(date.getMillis() / 1000); + } + } + + class NvlFunc implements Function + { + @Override + public String name() + { + return "nvl"; + } + + @Override + public ExprEval apply(List args, Expr.ObjectBinding bindings) + { + if (args.size() != 2) { + throw new IAE("function 'nvl' needs 2 arguments"); + } + final ExprEval eval = args.get(0).eval(bindings); + return eval.isNull() ? args.get(1).eval(bindings) : eval; } } } diff --git a/common/src/main/java/io/druid/math/expr/Parser.java b/common/src/main/java/io/druid/math/expr/Parser.java index ea9bfc37158..80ab1adb8ad 100644 --- a/common/src/main/java/io/druid/math/expr/Parser.java +++ b/common/src/main/java/io/druid/math/expr/Parser.java @@ -103,7 +103,11 @@ public class Parser @Override public Number get(String name) { - return (Number)bindings.get(name); + Number number = (Number)bindings.get(name); + if (number == null && !bindings.containsKey(name)) { + throw new RuntimeException("No binding found for " + name); + } + return number; } }; } diff --git a/common/src/test/java/io/druid/math/expr/EvalTest.java b/common/src/test/java/io/druid/math/expr/EvalTest.java index f64bc4373f6..fefbce0fb62 100644 --- a/common/src/test/java/io/druid/math/expr/EvalTest.java +++ b/common/src/test/java/io/druid/math/expr/EvalTest.java @@ -19,114 +19,128 @@ package io.druid.math.expr; -import com.google.common.base.Supplier; +import com.google.common.collect.ImmutableMap; import org.junit.Assert; import org.junit.Test; -import java.util.HashMap; -import java.util.Map; - /** */ public class EvalTest { - private Supplier constantSupplier(final Number number) + private long evalLong(String x, Expr.ObjectBinding bindings) { - return new Supplier() - { - @Override - public Number get() - { - return number; - } - }; + ExprEval ret = eval(x, bindings); + Assert.assertEquals(ExprType.LONG, ret.type()); + return ret.asLong(); + } + + private double evalDouble(String x, Expr.ObjectBinding bindings) + { + ExprEval ret = eval(x, bindings); + Assert.assertEquals(ExprType.DOUBLE, ret.type()); + return ret.asDouble(); + } + + private ExprEval eval(String x, Expr.ObjectBinding bindings) + { + return Parser.parse(x).eval(bindings); } @Test public void testDoubleEval() { - Map> bindings = new HashMap<>(); - bindings.put( "x", constantSupplier(2.0d)); + Expr.ObjectBinding bindings = Parser.withMap(ImmutableMap.of("x", 2.0d)); + Assert.assertEquals(2.0, evalDouble("x", bindings), 0.0001); + Assert.assertEquals(2.0, evalDouble("\"x\"", bindings), 0.0001); + Assert.assertEquals(304.0, evalDouble("300 + \"x\" * 2", bindings), 0.0001); - Assert.assertEquals(2.0, evaluate("x", bindings).doubleValue(), 0.0001); + Assert.assertFalse(evalDouble("1.0 && 0.0", bindings) > 0.0); + Assert.assertTrue(evalDouble("1.0 && 2.0", bindings) > 0.0); - Assert.assertFalse(evaluate("1.0 && 0.0", bindings).doubleValue() > 0.0); - Assert.assertTrue(evaluate("1.0 && 2.0", bindings).doubleValue() > 0.0); + Assert.assertTrue(evalDouble("1.0 || 0.0", bindings) > 0.0); + Assert.assertFalse(evalDouble("0.0 || 0.0", bindings) > 0.0); - Assert.assertTrue(evaluate("1.0 || 0.0", bindings).doubleValue() > 0.0); - Assert.assertFalse(evaluate("0.0 || 0.0", bindings).doubleValue() > 0.0); + Assert.assertTrue(evalDouble("2.0 > 1.0", bindings) > 0.0); + Assert.assertTrue(evalDouble("2.0 >= 2.0", bindings) > 0.0); + Assert.assertTrue(evalDouble("1.0 < 2.0", bindings) > 0.0); + Assert.assertTrue(evalDouble("2.0 <= 2.0", bindings) > 0.0); + Assert.assertTrue(evalDouble("2.0 == 2.0", bindings) > 0.0); + Assert.assertTrue(evalDouble("2.0 != 1.0", bindings) > 0.0); - Assert.assertTrue(evaluate("2.0 > 1.0", bindings).doubleValue() > 0.0); - Assert.assertTrue(evaluate("2.0 >= 2.0", bindings).doubleValue() > 0.0); - Assert.assertTrue(evaluate("1.0 < 2.0", bindings).doubleValue() > 0.0); - Assert.assertTrue(evaluate("2.0 <= 2.0", bindings).doubleValue() > 0.0); - Assert.assertTrue(evaluate("2.0 == 2.0", bindings).doubleValue() > 0.0); - Assert.assertTrue(evaluate("2.0 != 1.0", bindings).doubleValue() > 0.0); + Assert.assertEquals(3.5, evalDouble("2.0 + 1.5", bindings), 0.0001); + Assert.assertEquals(0.5, evalDouble("2.0 - 1.5", bindings), 0.0001); + Assert.assertEquals(3.0, evalDouble("2.0 * 1.5", bindings), 0.0001); + Assert.assertEquals(4.0, evalDouble("2.0 / 0.5", bindings), 0.0001); + Assert.assertEquals(0.2, evalDouble("2.0 % 0.3", bindings), 0.0001); + Assert.assertEquals(8.0, evalDouble("2.0 ^ 3.0", bindings), 0.0001); + Assert.assertEquals(-1.5, evalDouble("-1.5", bindings), 0.0001); - Assert.assertEquals(3.5, evaluate("2.0 + 1.5", bindings).doubleValue(), 0.0001); - Assert.assertEquals(0.5, evaluate("2.0 - 1.5", bindings).doubleValue(), 0.0001); - Assert.assertEquals(3.0, evaluate("2.0 * 1.5", bindings).doubleValue(), 0.0001); - Assert.assertEquals(4.0, evaluate("2.0 / 0.5", bindings).doubleValue(), 0.0001); - Assert.assertEquals(0.2, evaluate("2.0 % 0.3", bindings).doubleValue(), 0.0001); - Assert.assertEquals(8.0, evaluate("2.0 ^ 3.0", bindings).doubleValue(), 0.0001); - Assert.assertEquals(-1.5, evaluate("-1.5", bindings).doubleValue(), 0.0001); + Assert.assertTrue(evalDouble("!-1.0", bindings) > 0.0); + Assert.assertTrue(evalDouble("!0.0", bindings) > 0.0); + Assert.assertFalse(evalDouble("!2.0", bindings) > 0.0); - Assert.assertTrue(evaluate("!-1.0", bindings).doubleValue() > 0.0); - Assert.assertTrue(evaluate("!0.0", bindings).doubleValue() > 0.0); - Assert.assertFalse(evaluate("!2.0", bindings).doubleValue() > 0.0); - - Assert.assertEquals(2.0, evaluate("sqrt(4.0)", bindings).doubleValue(), 0.0001); - Assert.assertEquals(2.0, evaluate("if(1.0, 2.0, 3.0)", bindings).doubleValue(), 0.0001); - Assert.assertEquals(3.0, evaluate("if(0.0, 2.0, 3.0)", bindings).doubleValue(), 0.0001); - } - - private Number evaluate(String in, Map> bindings) { - return Parser.parse(in).eval(Parser.withSuppliers(bindings)); + Assert.assertEquals(2.0, evalDouble("sqrt(4.0)", bindings), 0.0001); + Assert.assertEquals(2.0, evalDouble("if(1.0, 2.0, 3.0)", bindings), 0.0001); + Assert.assertEquals(3.0, evalDouble("if(0.0, 2.0, 3.0)", bindings), 0.0001); } @Test public void testLongEval() { - Map> bindings = new HashMap<>(); - bindings.put("x", constantSupplier(9223372036854775807L)); + Expr.ObjectBinding bindings = Parser.withMap(ImmutableMap.of("x", 9223372036854775807L)); - Assert.assertEquals(9223372036854775807L, evaluate("x", bindings).longValue()); + Assert.assertEquals(9223372036854775807L, evalLong("x", bindings)); + Assert.assertEquals(9223372036854775807L, evalLong("\"x\"", bindings)); + Assert.assertEquals(92233720368547759L, evalLong("\"x\" / 100 + 1", bindings)); - Assert.assertFalse(evaluate("9223372036854775807 && 0", bindings).longValue() > 0); - Assert.assertTrue(evaluate("9223372036854775807 && 9223372036854775806", bindings).longValue() > 0); + Assert.assertFalse(evalLong("9223372036854775807 && 0", bindings) > 0); + Assert.assertTrue(evalLong("9223372036854775807 && 9223372036854775806", bindings) > 0); - Assert.assertTrue(evaluate("9223372036854775807 || 0", bindings).longValue() > 0); - Assert.assertFalse(evaluate("-9223372036854775807 || -9223372036854775807", bindings).longValue() > 0); - Assert.assertTrue(evaluate("-9223372036854775807 || 9223372036854775807", bindings).longValue() > 0); - Assert.assertFalse(evaluate("0 || 0", bindings).longValue() > 0); + Assert.assertTrue(evalLong("9223372036854775807 || 0", bindings) > 0); + Assert.assertFalse(evalLong("-9223372036854775807 || -9223372036854775807", bindings) > 0); + Assert.assertTrue(evalLong("-9223372036854775807 || 9223372036854775807", bindings) > 0); + Assert.assertFalse(evalLong("0 || 0", bindings) > 0); - Assert.assertTrue(evaluate("9223372036854775807 > 9223372036854775806", bindings).longValue() > 0); - Assert.assertTrue(evaluate("9223372036854775807 >= 9223372036854775807", bindings).longValue() > 0); - Assert.assertTrue(evaluate("9223372036854775806 < 9223372036854775807", bindings).longValue() > 0); - Assert.assertTrue(evaluate("9223372036854775807 <= 9223372036854775807", bindings).longValue() > 0); - Assert.assertTrue(evaluate("9223372036854775807 == 9223372036854775807", bindings).longValue() > 0); - Assert.assertTrue(evaluate("9223372036854775807 != 9223372036854775806", bindings).longValue() > 0); + Assert.assertTrue(evalLong("9223372036854775807 > 9223372036854775806", bindings) > 0); + Assert.assertTrue(evalLong("9223372036854775807 >= 9223372036854775807", bindings) > 0); + Assert.assertTrue(evalLong("9223372036854775806 < 9223372036854775807", bindings) > 0); + Assert.assertTrue(evalLong("9223372036854775807 <= 9223372036854775807", bindings) > 0); + Assert.assertTrue(evalLong("9223372036854775807 == 9223372036854775807", bindings) > 0); + Assert.assertTrue(evalLong("9223372036854775807 != 9223372036854775806", bindings) > 0); - Assert.assertEquals(9223372036854775807L, evaluate("9223372036854775806 + 1", bindings).longValue()); - Assert.assertEquals(9223372036854775806L, evaluate("9223372036854775807 - 1", bindings).longValue()); - Assert.assertEquals(9223372036854775806L, evaluate("4611686018427387903 * 2", bindings).longValue()); - Assert.assertEquals(4611686018427387903L, evaluate("9223372036854775806 / 2", bindings).longValue()); - Assert.assertEquals(7L, evaluate("9223372036854775807 % 9223372036854775800", bindings).longValue()); - Assert.assertEquals( 9223372030926249001L, evaluate("3037000499 ^ 2", bindings).longValue()); - Assert.assertEquals(-9223372036854775807L, evaluate("-9223372036854775807", bindings).longValue()); + Assert.assertEquals(9223372036854775807L, evalLong("9223372036854775806 + 1", bindings)); + Assert.assertEquals(9223372036854775806L, evalLong("9223372036854775807 - 1", bindings)); + Assert.assertEquals(9223372036854775806L, evalLong("4611686018427387903 * 2", bindings)); + Assert.assertEquals(4611686018427387903L, evalLong("9223372036854775806 / 2", bindings)); + Assert.assertEquals(7L, evalLong("9223372036854775807 % 9223372036854775800", bindings)); + Assert.assertEquals(9223372030926249001L, evalLong("3037000499 ^ 2", bindings)); + Assert.assertEquals(-9223372036854775807L, evalLong("-9223372036854775807", bindings)); - Assert.assertTrue(evaluate("!-9223372036854775807", bindings).longValue() > 0); - Assert.assertTrue(evaluate("!0", bindings).longValue() > 0); - Assert.assertFalse(evaluate("!9223372036854775807", bindings).longValue() > 0); + Assert.assertTrue(evalLong("!-9223372036854775807", bindings) > 0); + Assert.assertTrue(evalLong("!0", bindings) > 0); + Assert.assertFalse(evalLong("!9223372036854775807", bindings) > 0); - Assert.assertEquals(3037000499L, evaluate("sqrt(9223372036854775807)", bindings).longValue()); - Assert.assertEquals(9223372036854775807L, evaluate( - "if(9223372036854775807, 9223372036854775807, 9223372036854775806)", - bindings - ).longValue()); - Assert.assertEquals(9223372036854775806L, evaluate( - "if(0, 9223372036854775807, 9223372036854775806)", - bindings - ).longValue()); + Assert.assertEquals(3037000499L, evalLong("cast(sqrt(9223372036854775807), 'long')", bindings)); + Assert.assertEquals( + 1L, evalLong("if(x == 9223372036854775807, 1, 0)", bindings) + ); + Assert.assertEquals( + 0L, evalLong("if(x - 1 == 9223372036854775807, 1, 0)", bindings) + ); + + Assert.assertEquals(1271030400000L, evalLong("timestamp('2010-04-12')", bindings)); + Assert.assertEquals(1270998000000L, evalLong("timestamp('2010-04-12T+09:00')", bindings)); + Assert.assertEquals(1271055781000L, evalLong("timestamp('2010-04-12T07:03:01')", bindings)); + Assert.assertEquals(1271023381000L, evalLong("timestamp('2010-04-12T07:03:01+09:00')", bindings)); + Assert.assertEquals(1271023381419L, evalLong("timestamp('2010-04-12T07:03:01.419+09:00')", bindings)); + + Assert.assertEquals(1271030400L, evalLong("unix_timestamp('2010-04-12')", bindings)); + Assert.assertEquals(1270998000L, evalLong("unix_timestamp('2010-04-12T+09:00')", bindings)); + Assert.assertEquals(1271055781L, evalLong("unix_timestamp('2010-04-12T07:03:01')", bindings)); + Assert.assertEquals(1271023381L, evalLong("unix_timestamp('2010-04-12T07:03:01+09:00')", bindings)); + Assert.assertEquals(1271023381L, evalLong("unix_timestamp('2010-04-12T07:03:01.419+09:00')", bindings)); + + Assert.assertEquals("NULL", eval("nvl(if(x == 9223372036854775807, '', 'x'), 'NULL')", bindings).asString()); + Assert.assertEquals("x", eval("nvl(if(x == 9223372036854775806, '', 'x'), 'NULL')", bindings).asString()); } } diff --git a/docs/content/misc/math-expr.md b/docs/content/misc/math-expr.md index ff8565e4e65..0af0a177b69 100644 --- a/docs/content/misc/math-expr.md +++ b/docs/content/misc/math-expr.md @@ -13,21 +13,26 @@ This expression language supports the following operators (listed in decreasing |<, <=, >, >=, ==, !=|Binary Comparison| |&&,\|\||Binary Logical AND, OR| -Long and double data types are supported. If a number contains a dot, it is interpreted as a double, otherwise it is interpreted as a long. That means, always add a '.' to your number if you want it intepreted as a double value. +Long, double and string data types are supported. If a number contains a dot, it is interpreted as a double, otherwise it is interpreted as a long. That means, always add a '.' to your number if you want it interpreted as a double value. String literal should be quoted by single quotation marks. -Expressions can contain variables. Variable names may contain letters, digits, '\_' and '$'. Variable names must not begin with a digit. +Expressions can contain variables. Variable names may contain letters, digits, '\_' and '$'. Variable names must not begin with a digit. To escape other special characters, user can quote it with double quotation marks. -For logical operators, a number is true if and only if it is positive. (0 means false) +For logical operators, a number is true if and only if it is positive (0 or minus value means false). For string type, it's evaluation result of 'Boolean.valueOf(string)'. -Also, the following in-built functions are supported. +Also, the following built-in functions are supported. |name|description| |----|-----------| -|sqrt|sqrt(x) would return square root of x| -|if|if(predicate,then,else) returns 'then' if 'predicate' evaluates to a positive number, otherwise it returns 'else'| +|cast|cast(expr,'LONG' or 'DOUBLE' or 'STRING') returns expr with specified type. exception can be thrown | +|if|if(predicate,then,else) returns 'then' if 'predicate' evaluates to a positive number, otherwise it returns 'else' | +|nvl|nvl(expr,expr-for-null) returns 'expr-for-null' if 'expr' is null (or empty string for string type) | +|timestamp|timestamp(expr[,format-string]) parses string expr into date then returns milli-seconds from java epoch. without 'format-string' it's regarded as ISO datetime format | +|unix_timestamp|same with 'timestamp' function but returns seconds instead | -Built-in Math functions. See javadoc of java.lang.Math for detailed explanation for each function. +And built-in math functions. See javadoc of java.lang.Math for detailed explanation for each function. +|name|description| +|----|-----------| |abs|abs(x) would return the absolute value of x| |acos|acos(x) would return the arc cosine of x| |asin|asin(x) would return the arc sine of x| diff --git a/processing/src/main/java/io/druid/query/aggregation/post/ExpressionPostAggregator.java b/processing/src/main/java/io/druid/query/aggregation/post/ExpressionPostAggregator.java index 477f4add86f..b917869cd52 100644 --- a/processing/src/main/java/io/druid/query/aggregation/post/ExpressionPostAggregator.java +++ b/processing/src/main/java/io/druid/query/aggregation/post/ExpressionPostAggregator.java @@ -95,7 +95,7 @@ public class ExpressionPostAggregator implements PostAggregator @Override public Object compute(Map values) { - return parsed.eval(Parser.withMap(values)); + return parsed.eval(Parser.withMap(values)).value(); } @Override diff --git a/processing/src/main/java/io/druid/query/groupby/epinephelinae/RowBasedGrouperHelper.java b/processing/src/main/java/io/druid/query/groupby/epinephelinae/RowBasedGrouperHelper.java index 0b1b3ea2938..01c9fb870aa 100644 --- a/processing/src/main/java/io/druid/query/groupby/epinephelinae/RowBasedGrouperHelper.java +++ b/processing/src/main/java/io/druid/query/groupby/epinephelinae/RowBasedGrouperHelper.java @@ -681,7 +681,7 @@ public class RowBasedGrouperHelper @Override public Number get() { - return parsed.eval(binding); + return parsed.eval(binding).numericValue(); } }; } diff --git a/processing/src/main/java/io/druid/segment/QueryableIndexStorageAdapter.java b/processing/src/main/java/io/druid/segment/QueryableIndexStorageAdapter.java index 87bf743e281..24bddc89ea1 100644 --- a/processing/src/main/java/io/druid/segment/QueryableIndexStorageAdapter.java +++ b/processing/src/main/java/io/druid/segment/QueryableIndexStorageAdapter.java @@ -853,7 +853,7 @@ public class QueryableIndexStorageAdapter implements StorageAdapter @Override public Number get() { - return parsed.eval(binding); + return parsed.eval(binding).numericValue(); } }; } diff --git a/processing/src/main/java/io/druid/segment/incremental/IncrementalIndex.java b/processing/src/main/java/io/druid/segment/incremental/IncrementalIndex.java index 65a51f5de0c..ba954ddf0e6 100644 --- a/processing/src/main/java/io/druid/segment/incremental/IncrementalIndex.java +++ b/processing/src/main/java/io/druid/segment/incremental/IncrementalIndex.java @@ -323,7 +323,7 @@ public abstract class IncrementalIndex implements Iterable, @Override public Number get() { - return parsed.eval(binding); + return parsed.eval(binding).numericValue(); } }; } diff --git a/processing/src/main/java/io/druid/segment/incremental/IncrementalIndexStorageAdapter.java b/processing/src/main/java/io/druid/segment/incremental/IncrementalIndexStorageAdapter.java index eb84fa7d572..0c3d892ec25 100644 --- a/processing/src/main/java/io/druid/segment/incremental/IncrementalIndexStorageAdapter.java +++ b/processing/src/main/java/io/druid/segment/incremental/IncrementalIndexStorageAdapter.java @@ -589,7 +589,7 @@ public class IncrementalIndexStorageAdapter implements StorageAdapter @Override public Number get() { - return parsed.eval(binding); + return parsed.eval(binding).numericValue(); } }; } diff --git a/processing/src/test/java/io/druid/query/aggregation/post/ArithmeticPostAggregatorTest.java b/processing/src/test/java/io/druid/query/aggregation/post/ArithmeticPostAggregatorTest.java index e6fa9f3b86c..a35dca14f51 100644 --- a/processing/src/test/java/io/druid/query/aggregation/post/ArithmeticPostAggregatorTest.java +++ b/processing/src/test/java/io/druid/query/aggregation/post/ArithmeticPostAggregatorTest.java @@ -52,7 +52,7 @@ public class ArithmeticPostAggregatorTest List postAggregatorList = Lists.newArrayList( new ConstantPostAggregator( - "roku", 6 + "roku", 6D ), new FieldAccessPostAggregator( "rows", "rows" @@ -96,7 +96,7 @@ public class ArithmeticPostAggregatorTest List postAggregatorList = Lists.newArrayList( new ConstantPostAggregator( - "roku", 6 + "roku", 6D ), new FieldAccessPostAggregator( "rows", "rows"