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"