mirror of https://github.com/apache/druid.git
Add ROUND function in druid-sql. (#7224)
* Implement round function in druid-sql * Return value according to the type of argument * Fix codes for abnoraml inputs, updated math-expr.md * Fix assert text * Fix error messages and refactor codes * Fix compile error, update sql.md, refactor codes and format tests
This commit is contained in:
parent
7385dbc9e8
commit
7c19c92a81
|
@ -26,6 +26,8 @@ import org.apache.druid.java.util.common.StringUtils;
|
||||||
import org.joda.time.DateTime;
|
import org.joda.time.DateTime;
|
||||||
import org.joda.time.format.DateTimeFormat;
|
import org.joda.time.format.DateTimeFormat;
|
||||||
|
|
||||||
|
import java.math.BigDecimal;
|
||||||
|
import java.math.RoundingMode;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -499,7 +501,7 @@ interface Function
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
class Round extends SingleParamMath
|
class Round implements Function
|
||||||
{
|
{
|
||||||
@Override
|
@Override
|
||||||
public String name()
|
public String name()
|
||||||
|
@ -508,9 +510,42 @@ interface Function
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected ExprEval eval(double param)
|
public ExprEval apply(List<Expr> args, Expr.ObjectBinding bindings)
|
||||||
{
|
{
|
||||||
return ExprEval.of(Math.round(param));
|
if (args.size() != 1 && args.size() != 2) {
|
||||||
|
throw new IAE("Function[%s] needs 1 or 2 arguments", name());
|
||||||
|
}
|
||||||
|
|
||||||
|
ExprEval value1 = args.get(0).eval(bindings);
|
||||||
|
if (value1.type() != ExprType.LONG && value1.type() != ExprType.DOUBLE) {
|
||||||
|
throw new IAE("The first argument to the function[%s] should be integer or double type but get the %s type", name(), value1.type());
|
||||||
|
}
|
||||||
|
|
||||||
|
if (args.size() == 1) {
|
||||||
|
return eval(value1);
|
||||||
|
} else {
|
||||||
|
ExprEval value2 = args.get(1).eval(bindings);
|
||||||
|
if (value2.type() != ExprType.LONG) {
|
||||||
|
throw new IAE("The second argument to the function[%s] should be integer type but get the %s type", name(), value2.type());
|
||||||
|
}
|
||||||
|
return eval(value1, value2.asInt());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private ExprEval eval(ExprEval param)
|
||||||
|
{
|
||||||
|
return eval(param, 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
private ExprEval eval(ExprEval param, int scale)
|
||||||
|
{
|
||||||
|
if (param.type() == ExprType.LONG) {
|
||||||
|
return ExprEval.of(BigDecimal.valueOf(param.asLong()).setScale(scale, RoundingMode.HALF_UP).longValue());
|
||||||
|
} else if (param.type() == ExprType.DOUBLE) {
|
||||||
|
return ExprEval.of(BigDecimal.valueOf(param.asDouble()).setScale(scale, RoundingMode.HALF_UP).doubleValue());
|
||||||
|
} else {
|
||||||
|
return ExprEval.of(null);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -133,7 +133,7 @@ See javadoc of java.lang.Math for detailed explanation for each function.
|
||||||
|pow|pow(x, y) would return the value of the x raised to the power of y|
|
|pow|pow(x, y) would return the value of the x raised to the power of y|
|
||||||
|remainder|remainder(x, y) would return the remainder operation on two arguments as prescribed by the IEEE 754 standard|
|
|remainder|remainder(x, y) would return the remainder operation on two arguments as prescribed by the IEEE 754 standard|
|
||||||
|rint|rint(x) would return value that is closest in value to x and is equal to a mathematical integer|
|
|rint|rint(x) would return value that is closest in value to x and is equal to a mathematical integer|
|
||||||
|round|round(x) would return the closest long value to x, with ties rounding up|
|
|round|round(x, y) would return the value of the x rounded to the y decimal places. While x can be an integer or floating-point number, y must be an integer. The type of the return value is specified by that of x. y defaults to 0 if omitted. When y is negative, x is rounded on the left side of the y decimal points.|
|
||||||
|scalb|scalb(d, sf) would return d * 2^sf rounded as if performed by a single correctly rounded floating-point multiply to a member of the double value set|
|
|scalb|scalb(d, sf) would return d * 2^sf rounded as if performed by a single correctly rounded floating-point multiply to a member of the double value set|
|
||||||
|signum|signum(x) would return the signum function of the argument x|
|
|signum|signum(x) would return the signum function of the argument x|
|
||||||
|sin|sin(x) would return the trigonometric sine of an angle x|
|
|sin|sin(x) would return the trigonometric sine of an angle x|
|
||||||
|
|
|
@ -149,6 +149,7 @@ Numeric functions will return 64 bit integers or 64 bit floats, depending on the
|
||||||
|`SQRT(expr)`|Square root.|
|
|`SQRT(expr)`|Square root.|
|
||||||
|`TRUNCATE(expr[, digits])`|Truncate expr to a specific number of decimal digits. If digits is negative, then this truncates that many places to the left of the decimal point. Digits defaults to zero if not specified.|
|
|`TRUNCATE(expr[, digits])`|Truncate expr to a specific number of decimal digits. If digits is negative, then this truncates that many places to the left of the decimal point. Digits defaults to zero if not specified.|
|
||||||
|`TRUNC(expr[, digits])`|Synonym for `TRUNCATE`.|
|
|`TRUNC(expr[, digits])`|Synonym for `TRUNCATE`.|
|
||||||
|
|`ROUND(expr[, digits])`|`ROUND(x, y)` would return the value of the x rounded to the y decimal places. While x can be an integer or floating-point number, y must be an integer. The type of the return value is specified by that of x. y defaults to 0 if omitted. When y is negative, x is rounded on the left side of the y decimal points.|
|
||||||
|`x + y`|Addition.|
|
|`x + y`|Addition.|
|
||||||
|`x - y`|Subtraction.|
|
|`x - y`|Subtraction.|
|
||||||
|`x * y`|Multiplication.|
|
|`x * y`|Multiplication.|
|
||||||
|
|
|
@ -0,0 +1,59 @@
|
||||||
|
/*
|
||||||
|
* Licensed to the Apache Software Foundation (ASF) under one
|
||||||
|
* or more contributor license agreements. See the NOTICE file
|
||||||
|
* distributed with this work for additional information
|
||||||
|
* regarding copyright ownership. The ASF licenses this file
|
||||||
|
* to you under the Apache License, Version 2.0 (the
|
||||||
|
* "License"); you may not use this file except in compliance
|
||||||
|
* with the License. You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing,
|
||||||
|
* software distributed under the License is distributed on an
|
||||||
|
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||||||
|
* KIND, either express or implied. See the License for the
|
||||||
|
* specific language governing permissions and limitations
|
||||||
|
* under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package org.apache.druid.sql.calcite.expression.builtin;
|
||||||
|
|
||||||
|
import org.apache.calcite.rex.RexNode;
|
||||||
|
import org.apache.calcite.sql.SqlFunction;
|
||||||
|
import org.apache.calcite.sql.SqlFunctionCategory;
|
||||||
|
import org.apache.calcite.sql.type.ReturnTypes;
|
||||||
|
import org.apache.calcite.sql.type.SqlTypeFamily;
|
||||||
|
import org.apache.druid.sql.calcite.expression.DruidExpression;
|
||||||
|
import org.apache.druid.sql.calcite.expression.OperatorConversions;
|
||||||
|
import org.apache.druid.sql.calcite.expression.SqlOperatorConversion;
|
||||||
|
import org.apache.druid.sql.calcite.planner.PlannerContext;
|
||||||
|
import org.apache.druid.sql.calcite.table.RowSignature;
|
||||||
|
|
||||||
|
public class RoundOperatorConversion implements SqlOperatorConversion
|
||||||
|
{
|
||||||
|
private static final SqlFunction SQL_FUNCTION = OperatorConversions
|
||||||
|
.operatorBuilder("ROUND")
|
||||||
|
.operandTypes(SqlTypeFamily.NUMERIC, SqlTypeFamily.INTEGER)
|
||||||
|
.requiredOperands(1)
|
||||||
|
.returnTypeInference(ReturnTypes.ARG0)
|
||||||
|
.functionCategory(SqlFunctionCategory.NUMERIC)
|
||||||
|
.build();
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public SqlFunction calciteOperator()
|
||||||
|
{
|
||||||
|
return SQL_FUNCTION;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public DruidExpression toDruidExpression(final PlannerContext plannerContext, final RowSignature rowSignature, final RexNode rexNode)
|
||||||
|
{
|
||||||
|
return OperatorConversions.convertCall(plannerContext, rowSignature, rexNode, inputExpressions -> {
|
||||||
|
return DruidExpression.fromFunctionCall(
|
||||||
|
"round",
|
||||||
|
inputExpressions
|
||||||
|
);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
|
@ -65,6 +65,7 @@ import org.apache.druid.sql.calcite.expression.builtin.ReinterpretOperatorConver
|
||||||
import org.apache.druid.sql.calcite.expression.builtin.RepeatOperatorConversion;
|
import org.apache.druid.sql.calcite.expression.builtin.RepeatOperatorConversion;
|
||||||
import org.apache.druid.sql.calcite.expression.builtin.ReverseOperatorConversion;
|
import org.apache.druid.sql.calcite.expression.builtin.ReverseOperatorConversion;
|
||||||
import org.apache.druid.sql.calcite.expression.builtin.RightOperatorConversion;
|
import org.apache.druid.sql.calcite.expression.builtin.RightOperatorConversion;
|
||||||
|
import org.apache.druid.sql.calcite.expression.builtin.RoundOperatorConversion;
|
||||||
import org.apache.druid.sql.calcite.expression.builtin.StringFormatOperatorConversion;
|
import org.apache.druid.sql.calcite.expression.builtin.StringFormatOperatorConversion;
|
||||||
import org.apache.druid.sql.calcite.expression.builtin.StrposOperatorConversion;
|
import org.apache.druid.sql.calcite.expression.builtin.StrposOperatorConversion;
|
||||||
import org.apache.druid.sql.calcite.expression.builtin.SubstringOperatorConversion;
|
import org.apache.druid.sql.calcite.expression.builtin.SubstringOperatorConversion;
|
||||||
|
@ -159,6 +160,7 @@ public class DruidOperatorTable implements SqlOperatorTable
|
||||||
.add(new BinaryOperatorConversion(SqlStdOperatorTable.LESS_THAN_OR_EQUAL, "<="))
|
.add(new BinaryOperatorConversion(SqlStdOperatorTable.LESS_THAN_OR_EQUAL, "<="))
|
||||||
.add(new BinaryOperatorConversion(SqlStdOperatorTable.AND, "&&"))
|
.add(new BinaryOperatorConversion(SqlStdOperatorTable.AND, "&&"))
|
||||||
.add(new BinaryOperatorConversion(SqlStdOperatorTable.OR, "||"))
|
.add(new BinaryOperatorConversion(SqlStdOperatorTable.OR, "||"))
|
||||||
|
.add(new RoundOperatorConversion())
|
||||||
// time operators
|
// time operators
|
||||||
.add(new CeilOperatorConversion())
|
.add(new CeilOperatorConversion())
|
||||||
.add(new DateTruncOperatorConversion())
|
.add(new DateTruncOperatorConversion())
|
||||||
|
|
|
@ -48,6 +48,7 @@ import org.apache.druid.sql.calcite.expression.builtin.RegexpExtractOperatorConv
|
||||||
import org.apache.druid.sql.calcite.expression.builtin.RepeatOperatorConversion;
|
import org.apache.druid.sql.calcite.expression.builtin.RepeatOperatorConversion;
|
||||||
import org.apache.druid.sql.calcite.expression.builtin.ReverseOperatorConversion;
|
import org.apache.druid.sql.calcite.expression.builtin.ReverseOperatorConversion;
|
||||||
import org.apache.druid.sql.calcite.expression.builtin.RightOperatorConversion;
|
import org.apache.druid.sql.calcite.expression.builtin.RightOperatorConversion;
|
||||||
|
import org.apache.druid.sql.calcite.expression.builtin.RoundOperatorConversion;
|
||||||
import org.apache.druid.sql.calcite.expression.builtin.StringFormatOperatorConversion;
|
import org.apache.druid.sql.calcite.expression.builtin.StringFormatOperatorConversion;
|
||||||
import org.apache.druid.sql.calcite.expression.builtin.StrposOperatorConversion;
|
import org.apache.druid.sql.calcite.expression.builtin.StrposOperatorConversion;
|
||||||
import org.apache.druid.sql.calcite.expression.builtin.TimeExtractOperatorConversion;
|
import org.apache.druid.sql.calcite.expression.builtin.TimeExtractOperatorConversion;
|
||||||
|
@ -463,6 +464,82 @@ public class ExpressionsTest extends CalciteTestBase
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testRound()
|
||||||
|
{
|
||||||
|
final SqlFunction roundFunction = new RoundOperatorConversion().calciteOperator();
|
||||||
|
|
||||||
|
testExpression(
|
||||||
|
rexBuilder.makeCall(roundFunction, inputRef("a")),
|
||||||
|
DruidExpression.fromExpression("round(\"a\")"),
|
||||||
|
10L
|
||||||
|
);
|
||||||
|
|
||||||
|
testExpression(
|
||||||
|
rexBuilder.makeCall(roundFunction, inputRef("b")),
|
||||||
|
DruidExpression.fromExpression("round(\"b\")"),
|
||||||
|
25L
|
||||||
|
);
|
||||||
|
|
||||||
|
testExpression(
|
||||||
|
rexBuilder.makeCall(roundFunction, inputRef("b"), integerLiteral(-1)),
|
||||||
|
DruidExpression.fromExpression("round(\"b\",-1)"),
|
||||||
|
30L
|
||||||
|
);
|
||||||
|
|
||||||
|
testExpression(
|
||||||
|
rexBuilder.makeCall(roundFunction, inputRef("x")),
|
||||||
|
DruidExpression.fromExpression("round(\"x\")"),
|
||||||
|
2.0
|
||||||
|
);
|
||||||
|
|
||||||
|
testExpression(
|
||||||
|
rexBuilder.makeCall(roundFunction, inputRef("x"), integerLiteral(1)),
|
||||||
|
DruidExpression.fromExpression("round(\"x\",1)"),
|
||||||
|
2.3
|
||||||
|
);
|
||||||
|
|
||||||
|
testExpression(
|
||||||
|
rexBuilder.makeCall(roundFunction, inputRef("y")),
|
||||||
|
DruidExpression.fromExpression("round(\"y\")"),
|
||||||
|
3.0
|
||||||
|
);
|
||||||
|
|
||||||
|
testExpression(
|
||||||
|
rexBuilder.makeCall(roundFunction, inputRef("z")),
|
||||||
|
DruidExpression.fromExpression("round(\"z\")"),
|
||||||
|
-2.0
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testRoundWithInvalidArgument()
|
||||||
|
{
|
||||||
|
final SqlFunction roundFunction = new RoundOperatorConversion().calciteOperator();
|
||||||
|
|
||||||
|
expectedException.expect(IAE.class);
|
||||||
|
expectedException.expectMessage("The first argument to the function[round] should be integer or double type but get the STRING type");
|
||||||
|
testExpression(
|
||||||
|
rexBuilder.makeCall(roundFunction, inputRef("s")),
|
||||||
|
DruidExpression.fromExpression("round(\"s\")"),
|
||||||
|
"IAE Exception"
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testRoundWithInvalidSecondArgument()
|
||||||
|
{
|
||||||
|
final SqlFunction roundFunction = new RoundOperatorConversion().calciteOperator();
|
||||||
|
|
||||||
|
expectedException.expect(IAE.class);
|
||||||
|
expectedException.expectMessage("The second argument to the function[round] should be integer type but get the STRING type");
|
||||||
|
testExpression(
|
||||||
|
rexBuilder.makeCall(roundFunction, inputRef("x"), rexBuilder.makeLiteral("foo")),
|
||||||
|
DruidExpression.fromExpression("round(\"x\",'foo')"),
|
||||||
|
"IAE Exception"
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void testDateTrunc()
|
public void testDateTrunc()
|
||||||
{
|
{
|
||||||
|
|
Loading…
Reference in New Issue