Support string type in math expression (#2836)

* Support string type in math expression

addressed comments

addressed comments

Addressed comments

* Updated math function document

* Addressed comments
This commit is contained in:
Navis Ryu 2016-11-03 12:10:48 +09:00 committed by David Lim
parent 2362effd8c
commit e10def32f2
16 changed files with 959 additions and 440 deletions

View File

@ -51,6 +51,10 @@
<groupId>commons-codec</groupId>
<artifactId>commons-codec</artifactId>
</dependency>
<dependency>
<groupId>commons-lang</groupId>
<artifactId>commons-lang</artifactId>
</dependency>
<dependency>
<groupId>org.apache.commons</groupId>
<artifactId>commons-dbcp2</artifactId>

View File

@ -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 : '^' ;

View File

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

View File

@ -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<T>
{
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<Number> {
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<String>
{
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);
}
}
}

View File

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

View File

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

View File

@ -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<Expr> args, Expr.ObjectBinding bindings);
ExprEval apply(List<Expr> args, Expr.ObjectBinding bindings);
abstract class SingleParam implements Function
{
@Override
public Number apply(List<Expr> args, Expr.ObjectBinding bindings)
public ExprEval apply(List<Expr> 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<Expr> args, Expr.ObjectBinding bindings)
public ExprEval apply(List<Expr> 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<Expr> args, Expr.ObjectBinding bindings)
public ExprEval apply(List<Expr> 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<Expr> 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<Expr> 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;
}
}
}

View File

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

View File

@ -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<Number> constantSupplier(final Number number)
private long evalLong(String x, Expr.ObjectBinding bindings)
{
return new Supplier<Number>()
{
@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<String, Supplier<Number>> 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<String, Supplier<Number>> 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<String, Supplier<Number>> 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());
}
}

View File

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

View File

@ -95,7 +95,7 @@ public class ExpressionPostAggregator implements PostAggregator
@Override
public Object compute(Map<String, Object> values)
{
return parsed.eval(Parser.withMap(values));
return parsed.eval(Parser.withMap(values)).value();
}
@Override

View File

@ -681,7 +681,7 @@ public class RowBasedGrouperHelper
@Override
public Number get()
{
return parsed.eval(binding);
return parsed.eval(binding).numericValue();
}
};
}

View File

@ -853,7 +853,7 @@ public class QueryableIndexStorageAdapter implements StorageAdapter
@Override
public Number get()
{
return parsed.eval(binding);
return parsed.eval(binding).numericValue();
}
};
}

View File

@ -323,7 +323,7 @@ public abstract class IncrementalIndex<AggregatorType> implements Iterable<Row>,
@Override
public Number get()
{
return parsed.eval(binding);
return parsed.eval(binding).numericValue();
}
};
}

View File

@ -589,7 +589,7 @@ public class IncrementalIndexStorageAdapter implements StorageAdapter
@Override
public Number get()
{
return parsed.eval(binding);
return parsed.eval(binding).numericValue();
}
};
}

View File

@ -52,7 +52,7 @@ public class ArithmeticPostAggregatorTest
List<PostAggregator> postAggregatorList =
Lists.newArrayList(
new ConstantPostAggregator(
"roku", 6
"roku", 6D
),
new FieldAccessPostAggregator(
"rows", "rows"
@ -96,7 +96,7 @@ public class ArithmeticPostAggregatorTest
List<PostAggregator> postAggregatorList =
Lists.newArrayList(
new ConstantPostAggregator(
"roku", 6
"roku", 6D
),
new FieldAccessPostAggregator(
"rows", "rows"