From 6fb7e49b22f792623845c419e78504577d88633b Mon Sep 17 00:00:00 2001 From: Andrei Stefan Date: Thu, 20 Sep 2018 15:29:53 +0300 Subject: [PATCH] SQL: TRUNCATE and ROUND functions (#33779) * Added TRUNCATE function, modified ROUND to accept two parameters instead of one. Made the second parameter optional for both functions. * Added documentation for both functions. --- docs/reference/sql/functions/math.asciidoc | 70 ++++++- .../sql/jdbc/jdbc/JdbcDatabaseMetaData.java | 2 +- .../expression/function/FunctionRegistry.java | 34 ++-- .../scalar/math/BinaryMathProcessor.java | 34 +++- .../function/scalar/math/MathProcessor.java | 1 - .../function/scalar/math/Round.java | 66 +++++-- .../function/scalar/math/Truncate.java | 74 +++++++ .../whitelist/InternalSqlScriptUtils.java | 9 + .../sql/plugin/SqlPainlessExtension.java | 2 + .../xpack/sql/plugin/sql_whitelist.txt | 28 +-- .../scalar/math/BinaryMathProcessorTests.java | 64 ++++++ .../xpack/qa/sql/cli/ShowTestCase.java | 8 +- .../xpack/qa/sql/jdbc/CsvSpecTestCase.java | 1 + x-pack/qa/sql/src/main/resources/agg.sql-spec | 4 +- .../sql/src/main/resources/command.csv-spec | 41 ++-- .../qa/sql/src/main/resources/docs.csv-spec | 87 +++++--- .../qa/sql/src/main/resources/math.csv-spec | 185 ++++++++++++++++++ .../qa/sql/src/main/resources/math.sql-spec | 47 ++++- 18 files changed, 652 insertions(+), 105 deletions(-) create mode 100644 x-pack/plugin/sql/src/main/java/org/elasticsearch/xpack/sql/expression/function/scalar/math/Truncate.java create mode 100644 x-pack/qa/sql/src/main/resources/math.csv-spec diff --git a/docs/reference/sql/functions/math.asciidoc b/docs/reference/sql/functions/math.asciidoc index 604603f2973..6489f1bec4d 100644 --- a/docs/reference/sql/functions/math.asciidoc +++ b/docs/reference/sql/functions/math.asciidoc @@ -37,13 +37,19 @@ Same as `CEIL` https://en.wikipedia.org/wiki/E_%28mathematical_constant%29[Euler's number], returns `2.7182818284590452354` +* https://en.wikipedia.org/wiki/Exponential_function[e^x^] (`EXP`) -* https://en.wikipedia.org/wiki/Rounding#Round_half_up[Round] (`ROUND`) +["source","sql",subs="attributes,callouts,macros"] +-------------------------------------------------- +include-tagged::{sql-specs}/math.sql-spec[exp] +-------------------------------------------------- -// TODO make the example in the tests presentable - -NOTE: This rounds "half up" meaning that `ROUND(-1.5)` results in `-1`. +* https://docs.oracle.com/javase/8/docs/api/java/lang/Math.html#expm1-double-[e^x^ - 1] (`EXPM1`) +["source","sql",subs="attributes,callouts,macros"] +-------------------------------------------------- +include-tagged::{sql-specs}/math.sql-spec[expm1] +-------------------------------------------------- * https://en.wikipedia.org/wiki/Floor_and_ceiling_functions[Floor] (`FLOOR`) @@ -63,6 +69,36 @@ include-tagged::{sql-specs}/math.sql-spec[log] include-tagged::{sql-specs}/math.sql-spec[log10] -------------------------------------------------- +* `ROUND` + +.Synopsis: +[source, sql] +---- +ROUND(numeric_exp<1>[, integer_exp<2>]) +---- +*Input*: + + <1> numeric expression + <2> integer expression; optional + +*Output*: numeric + +.Description: +Returns `numeric_exp` rounded to `integer_exp` places right of the decimal point. If `integer_exp` is negative, +`numeric_exp` is rounded to |`integer_exp`| places to the left of the decimal point. If `integer_exp` is omitted, +the function will perform as if `integer_exp` would be 0. The returned numeric data type is the same as the data type +of `numeric_exp`. + +["source","sql",subs="attributes,callouts,macros"] +-------------------------------------------------- +include-tagged::{sql-specs}/docs.csv-spec[mathRoundWithPositiveParameter] +-------------------------------------------------- + +["source","sql",subs="attributes,callouts,macros"] +-------------------------------------------------- +include-tagged::{sql-specs}/docs.csv-spec[mathRoundWithNegativeParameter] +-------------------------------------------------- + * https://en.wikipedia.org/wiki/Square_root[Square root] (`SQRT`) ["source","sql",subs="attributes,callouts,macros"] @@ -70,18 +106,34 @@ include-tagged::{sql-specs}/math.sql-spec[log10] include-tagged::{sql-specs}/math.sql-spec[sqrt] -------------------------------------------------- -* https://en.wikipedia.org/wiki/Exponential_function[e^x^] (`EXP`) +* `TRUNCATE` + +.Synopsis: +[source, sql] +---- +TRUNCATE(numeric_exp<1>[, integer_exp<2>]) +---- +*Input*: + + <1> numeric expression + <2> integer expression; optional + +*Output*: numeric + +.Description: +Returns `numeric_exp` truncated to `integer_exp` places right of the decimal point. If `integer_exp` is negative, +`numeric_exp` is truncated to |`integer_exp`| places to the left of the decimal point. If `integer_exp` is omitted, +the function will perform as if `integer_exp` would be 0. The returned numeric data type is the same as the data type +of `numeric_exp`. ["source","sql",subs="attributes,callouts,macros"] -------------------------------------------------- -include-tagged::{sql-specs}/math.sql-spec[exp] +include-tagged::{sql-specs}/docs.csv-spec[mathTruncateWithPositiveParameter] -------------------------------------------------- -* https://docs.oracle.com/javase/8/docs/api/java/lang/Math.html#expm1-double-[e^x^ - 1] (`EXPM1`) - ["source","sql",subs="attributes,callouts,macros"] -------------------------------------------------- -include-tagged::{sql-specs}/math.sql-spec[expm1] +include-tagged::{sql-specs}/docs.csv-spec[mathTruncateWithNegativeParameter] -------------------------------------------------- ==== Trigonometric diff --git a/x-pack/plugin/sql/jdbc/src/main/java/org/elasticsearch/xpack/sql/jdbc/jdbc/JdbcDatabaseMetaData.java b/x-pack/plugin/sql/jdbc/src/main/java/org/elasticsearch/xpack/sql/jdbc/jdbc/JdbcDatabaseMetaData.java index 085016bc0bd..4bc4f51d874 100644 --- a/x-pack/plugin/sql/jdbc/src/main/java/org/elasticsearch/xpack/sql/jdbc/jdbc/JdbcDatabaseMetaData.java +++ b/x-pack/plugin/sql/jdbc/src/main/java/org/elasticsearch/xpack/sql/jdbc/jdbc/JdbcDatabaseMetaData.java @@ -190,7 +190,7 @@ class JdbcDatabaseMetaData implements DatabaseMetaData, JdbcWrapper { + "PI,POWER," + "RADIANS,RAND,ROUND," + "SIGN,SIN,SQRT," - + "TAN"; + + "TAN,TRUNCATE"; } @Override diff --git a/x-pack/plugin/sql/src/main/java/org/elasticsearch/xpack/sql/expression/function/FunctionRegistry.java b/x-pack/plugin/sql/src/main/java/org/elasticsearch/xpack/sql/expression/function/FunctionRegistry.java index 2daa90c7bda..84ac75e5696 100644 --- a/x-pack/plugin/sql/src/main/java/org/elasticsearch/xpack/sql/expression/function/FunctionRegistry.java +++ b/x-pack/plugin/sql/src/main/java/org/elasticsearch/xpack/sql/expression/function/FunctionRegistry.java @@ -61,6 +61,7 @@ import org.elasticsearch.xpack.sql.expression.function.scalar.math.Sin; import org.elasticsearch.xpack.sql.expression.function.scalar.math.Sinh; import org.elasticsearch.xpack.sql.expression.function.scalar.math.Sqrt; import org.elasticsearch.xpack.sql.expression.function.scalar.math.Tan; +import org.elasticsearch.xpack.sql.expression.function.scalar.math.Truncate; import org.elasticsearch.xpack.sql.expression.function.scalar.string.Ascii; import org.elasticsearch.xpack.sql.expression.function.scalar.string.BitLength; import org.elasticsearch.xpack.sql.expression.function.scalar.string.Char; @@ -114,21 +115,21 @@ public class FunctionRegistry { def(SumOfSquares.class, SumOfSquares::new), def(Skewness.class, Skewness::new), def(Kurtosis.class, Kurtosis::new), - // Scalar functions + // Scalar functions // Date + def(DayName.class, DayName::new, "DAYNAME"), def(DayOfMonth.class, DayOfMonth::new, "DAYOFMONTH", "DAY", "DOM"), def(DayOfWeek.class, DayOfWeek::new, "DAYOFWEEK", "DOW"), def(DayOfYear.class, DayOfYear::new, "DAYOFYEAR", "DOY"), def(HourOfDay.class, HourOfDay::new, "HOUR"), def(MinuteOfDay.class, MinuteOfDay::new), def(MinuteOfHour.class, MinuteOfHour::new, "MINUTE"), - def(SecondOfMinute.class, SecondOfMinute::new, "SECOND"), + def(MonthName.class, MonthName::new, "MONTHNAME"), def(MonthOfYear.class, MonthOfYear::new, "MONTH"), + def(SecondOfMinute.class, SecondOfMinute::new, "SECOND"), + def(Quarter.class, Quarter::new), def(Year.class, Year::new), def(WeekOfYear.class, WeekOfYear::new, "WEEK"), - def(DayName.class, DayName::new, "DAYNAME"), - def(MonthName.class, MonthName::new, "MONTHNAME"), - def(Quarter.class, Quarter::new), // Math def(Abs.class, Abs::new), def(ACos.class, ACos::new), @@ -159,27 +160,28 @@ public class FunctionRegistry { def(Sinh.class, Sinh::new), def(Sqrt.class, Sqrt::new), def(Tan.class, Tan::new), + def(Truncate.class, Truncate::new), // String def(Ascii.class, Ascii::new), - def(Char.class, Char::new), def(BitLength.class, BitLength::new), + def(Char.class, Char::new), def(CharLength.class, CharLength::new, "CHARACTER_LENGTH"), - def(LCase.class, LCase::new), - def(Length.class, Length::new), - def(LTrim.class, LTrim::new), - def(RTrim.class, RTrim::new), - def(Space.class, Space::new), def(Concat.class, Concat::new), def(Insert.class, Insert::new), + def(LCase.class, LCase::new), def(Left.class, Left::new), + def(Length.class, Length::new), def(Locate.class, Locate::new), + def(LTrim.class, LTrim::new), def(Position.class, Position::new), def(Repeat.class, Repeat::new), def(Replace.class, Replace::new), def(Right.class, Right::new), + def(RTrim.class, RTrim::new), + def(Space.class, Space::new), def(Substring.class, Substring::new), def(UCase.class, UCase::new), - // Special + // Special def(Score.class, Score::new))); private final Map defs = new LinkedHashMap<>(); @@ -330,13 +332,17 @@ public class FunctionRegistry { static FunctionDefinition def(Class function, BinaryFunctionBuilder ctorRef, String... aliases) { FunctionBuilder builder = (location, children, distinct, tz) -> { - if (children.size() != 2) { + boolean isBinaryOptionalParamFunction = function.isAssignableFrom(Round.class) || function.isAssignableFrom(Truncate.class); + if (isBinaryOptionalParamFunction && (children.size() > 2 || children.size() < 1)) { + throw new IllegalArgumentException("expects one or two arguments"); + } else if (!isBinaryOptionalParamFunction && children.size() != 2) { throw new IllegalArgumentException("expects exactly two arguments"); } + if (distinct) { throw new IllegalArgumentException("does not support DISTINCT yet it was specified"); } - return ctorRef.build(location, children.get(0), children.get(1)); + return ctorRef.build(location, children.get(0), children.size() == 2 ? children.get(1) : null); }; return def(function, builder, false, aliases); } diff --git a/x-pack/plugin/sql/src/main/java/org/elasticsearch/xpack/sql/expression/function/scalar/math/BinaryMathProcessor.java b/x-pack/plugin/sql/src/main/java/org/elasticsearch/xpack/sql/expression/function/scalar/math/BinaryMathProcessor.java index fca6aa5023d..5c42f62a3b4 100644 --- a/x-pack/plugin/sql/src/main/java/org/elasticsearch/xpack/sql/expression/function/scalar/math/BinaryMathProcessor.java +++ b/x-pack/plugin/sql/src/main/java/org/elasticsearch/xpack/sql/expression/function/scalar/math/BinaryMathProcessor.java @@ -7,6 +7,7 @@ package org.elasticsearch.xpack.sql.expression.function.scalar.math; import org.elasticsearch.common.io.stream.StreamInput; import org.elasticsearch.common.io.stream.StreamOutput; +import org.elasticsearch.xpack.sql.SqlIllegalArgumentException; import org.elasticsearch.xpack.sql.expression.function.scalar.math.BinaryMathProcessor.BinaryMathOperation; import org.elasticsearch.xpack.sql.expression.function.scalar.math.MathProcessor.MathOperation; import org.elasticsearch.xpack.sql.expression.function.scalar.processor.runtime.Processor; @@ -22,7 +23,38 @@ public class BinaryMathProcessor extends BinaryNumericProcessor { ATAN2((l, r) -> Math.atan2(l.doubleValue(), r.doubleValue())), - POWER((l, r) -> Math.pow(l.doubleValue(), r.doubleValue())); + POWER((l, r) -> Math.pow(l.doubleValue(), r.doubleValue())), + ROUND((l, r) -> { + if (l == null) { + return null; + } + if (r == null) { + return l; + } + if (r instanceof Float || r instanceof Double) { + throw new SqlIllegalArgumentException("An integer number is required; received [{}] as second parameter", r); + } + + double tenAtScale = Math.pow(10., r.longValue()); + double middleResult = l.doubleValue() * tenAtScale; + int sign = middleResult > 0 ? 1 : -1; + return Math.round(Math.abs(middleResult)) / tenAtScale * sign; + }), + TRUNCATE((l, r) -> { + if (l == null) { + return null; + } + if (r == null) { + return l; + } + if (r instanceof Float || r instanceof Double) { + throw new SqlIllegalArgumentException("An integer number is required; received [{}] as second parameter", r); + } + + double tenAtScale = Math.pow(10., r.longValue()); + double g = l.doubleValue() * tenAtScale; + return (((l.doubleValue() < 0) ? Math.ceil(g) : Math.floor(g)) / tenAtScale); + }); private final BiFunction process; diff --git a/x-pack/plugin/sql/src/main/java/org/elasticsearch/xpack/sql/expression/function/scalar/math/MathProcessor.java b/x-pack/plugin/sql/src/main/java/org/elasticsearch/xpack/sql/expression/function/scalar/math/MathProcessor.java index b9bf56f33a4..18d19776cc1 100644 --- a/x-pack/plugin/sql/src/main/java/org/elasticsearch/xpack/sql/expression/function/scalar/math/MathProcessor.java +++ b/x-pack/plugin/sql/src/main/java/org/elasticsearch/xpack/sql/expression/function/scalar/math/MathProcessor.java @@ -52,7 +52,6 @@ public class MathProcessor implements Processor { RANDOM((Object l) -> l != null ? new Random(((Number) l).longValue()).nextDouble() : Randomness.get().nextDouble(), true), - ROUND((DoubleFunction) Math::round), SIGN((DoubleFunction) Math::signum), SIN(Math::sin), SINH(Math::sinh), diff --git a/x-pack/plugin/sql/src/main/java/org/elasticsearch/xpack/sql/expression/function/scalar/math/Round.java b/x-pack/plugin/sql/src/main/java/org/elasticsearch/xpack/sql/expression/function/scalar/math/Round.java index 52d7bc5aeca..f008b031a34 100644 --- a/x-pack/plugin/sql/src/main/java/org/elasticsearch/xpack/sql/expression/function/scalar/math/Round.java +++ b/x-pack/plugin/sql/src/main/java/org/elasticsearch/xpack/sql/expression/function/scalar/math/Round.java @@ -6,41 +6,75 @@ package org.elasticsearch.xpack.sql.expression.function.scalar.math; import org.elasticsearch.xpack.sql.expression.Expression; -import org.elasticsearch.xpack.sql.expression.function.scalar.math.MathProcessor.MathOperation; +import org.elasticsearch.xpack.sql.expression.Literal; +import org.elasticsearch.xpack.sql.expression.function.scalar.math.BinaryMathProcessor.BinaryMathOperation; +import org.elasticsearch.xpack.sql.expression.function.scalar.processor.definition.ProcessorDefinition; +import org.elasticsearch.xpack.sql.expression.function.scalar.processor.definition.ProcessorDefinitions; +import org.elasticsearch.xpack.sql.expression.function.scalar.script.ScriptTemplate; import org.elasticsearch.xpack.sql.tree.Location; import org.elasticsearch.xpack.sql.tree.NodeInfo; import org.elasticsearch.xpack.sql.type.DataType; -import org.elasticsearch.xpack.sql.type.DataTypeConversion; + +import java.util.Locale; +import java.util.function.BiFunction; + +import static java.lang.String.format; +import static org.elasticsearch.xpack.sql.expression.function.scalar.script.ParamsBuilder.paramsBuilder; /** - * Round - * function. - * - * Note that this uses {@link Math#round(double)} which uses "half up" rounding - * for `ROUND(-1.5)` rounds to `-1`. + * Function that takes two parameters: one is the field/value itself, the other is a non-floating point numeric + * which indicates how the rounding should behave. If positive, it will round the number till that parameter + * count digits after the decimal point. If negative, it will round the number till that paramter count + * digits before the decimal point, starting at the decimal point. */ -public class Round extends MathFunction { - public Round(Location location, Expression field) { - super(location, field); +public class Round extends BinaryNumericFunction { + + public Round(Location location, Expression left, Expression right) { + super(location, left, right == null ? Literal.of(Location.EMPTY, 0) : right); } @Override protected NodeInfo info() { - return NodeInfo.create(this, Round::new, field()); + return NodeInfo.create(this, Round::new, left(), right()); } @Override - protected Round replaceChild(Expression newChild) { - return new Round(location(), newChild); + protected Round replaceChildren(Expression newLeft, Expression newRight) { + return new Round(location(), newLeft, newRight); } @Override - protected MathOperation operation() { - return MathOperation.ROUND; + protected BiFunction operation() { + return BinaryMathOperation.ROUND; } + @Override + protected ProcessorDefinition makeProcessorDefinition() { + return new BinaryMathProcessorDefinition(location(), this, + ProcessorDefinitions.toProcessorDefinition(left()), + ProcessorDefinitions.toProcessorDefinition(right()), + BinaryMathOperation.ROUND); + } + + protected TypeResolution resolveInputType(DataType inputType) { + return inputType.isNumeric() ? + TypeResolution.TYPE_RESOLVED : + new TypeResolution("'%s' requires a numeric type, received %s", mathFunction(), inputType.esType); + } + + @Override + protected ScriptTemplate asScriptFrom(ScriptTemplate leftScript, ScriptTemplate rightScript) { + return new ScriptTemplate(format(Locale.ROOT, ScriptTemplate.formatTemplate("{sql}.%s(%s,%s)"), + mathFunction(), + leftScript.template(), + rightScript.template()), + paramsBuilder() + .script(leftScript.params()).script(rightScript.params()) + .build(), dataType()); + } + @Override public DataType dataType() { - return DataTypeConversion.asInteger(field().dataType()); + return left().dataType(); } } diff --git a/x-pack/plugin/sql/src/main/java/org/elasticsearch/xpack/sql/expression/function/scalar/math/Truncate.java b/x-pack/plugin/sql/src/main/java/org/elasticsearch/xpack/sql/expression/function/scalar/math/Truncate.java new file mode 100644 index 00000000000..af7e20c1d99 --- /dev/null +++ b/x-pack/plugin/sql/src/main/java/org/elasticsearch/xpack/sql/expression/function/scalar/math/Truncate.java @@ -0,0 +1,74 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ +package org.elasticsearch.xpack.sql.expression.function.scalar.math; + +import org.elasticsearch.xpack.sql.expression.Expression; +import org.elasticsearch.xpack.sql.expression.Literal; +import org.elasticsearch.xpack.sql.expression.function.scalar.math.BinaryMathProcessor.BinaryMathOperation; +import org.elasticsearch.xpack.sql.expression.function.scalar.processor.definition.ProcessorDefinition; +import org.elasticsearch.xpack.sql.expression.function.scalar.processor.definition.ProcessorDefinitions; +import org.elasticsearch.xpack.sql.expression.function.scalar.script.ScriptTemplate; +import org.elasticsearch.xpack.sql.tree.Location; +import org.elasticsearch.xpack.sql.tree.NodeInfo; +import org.elasticsearch.xpack.sql.type.DataType; + +import java.util.Locale; +import java.util.function.BiFunction; + +import static java.lang.String.format; +import static org.elasticsearch.xpack.sql.expression.function.scalar.script.ParamsBuilder.paramsBuilder; + +/** + * Function that takes two parameters: one is the field/value itself, the other is a non-floating point numeric + * which indicates how the truncation should behave. If positive, it will truncate the number till that + * parameter count digits after the decimal point. If negative, it will truncate the number till that parameter + * count digits before the decimal point, starting at the decimal point. + */ +public class Truncate extends BinaryNumericFunction { + + public Truncate(Location location, Expression left, Expression right) { + super(location, left, right == null ? Literal.of(Location.EMPTY, 0) : right); + } + + @Override + protected NodeInfo info() { + return NodeInfo.create(this, Truncate::new, left(), right()); + } + + @Override + protected Truncate replaceChildren(Expression newLeft, Expression newRight) { + return new Truncate(location(), newLeft, newRight); + } + + @Override + protected BiFunction operation() { + return BinaryMathOperation.TRUNCATE; + } + + @Override + protected ProcessorDefinition makeProcessorDefinition() { + return new BinaryMathProcessorDefinition(location(), this, + ProcessorDefinitions.toProcessorDefinition(left()), + ProcessorDefinitions.toProcessorDefinition(right()), + BinaryMathOperation.TRUNCATE); + } + + @Override + protected ScriptTemplate asScriptFrom(ScriptTemplate leftScript, ScriptTemplate rightScript) { + return new ScriptTemplate(format(Locale.ROOT, ScriptTemplate.formatTemplate("{sql}.%s(%s,%s)"), + mathFunction(), + leftScript.template(), + rightScript.template()), + paramsBuilder() + .script(leftScript.params()).script(rightScript.params()) + .build(), dataType()); + } + + @Override + public DataType dataType() { + return left().dataType(); + } +} diff --git a/x-pack/plugin/sql/src/main/java/org/elasticsearch/xpack/sql/expression/function/scalar/whitelist/InternalSqlScriptUtils.java b/x-pack/plugin/sql/src/main/java/org/elasticsearch/xpack/sql/expression/function/scalar/whitelist/InternalSqlScriptUtils.java index f0a79f15e36..a03a8e0a325 100644 --- a/x-pack/plugin/sql/src/main/java/org/elasticsearch/xpack/sql/expression/function/scalar/whitelist/InternalSqlScriptUtils.java +++ b/x-pack/plugin/sql/src/main/java/org/elasticsearch/xpack/sql/expression/function/scalar/whitelist/InternalSqlScriptUtils.java @@ -8,6 +8,7 @@ package org.elasticsearch.xpack.sql.expression.function.scalar.whitelist; import org.elasticsearch.xpack.sql.expression.function.scalar.datetime.DateTimeFunction; import org.elasticsearch.xpack.sql.expression.function.scalar.datetime.NamedDateTimeProcessor.NameExtractor; import org.elasticsearch.xpack.sql.expression.function.scalar.datetime.QuarterProcessor; +import org.elasticsearch.xpack.sql.expression.function.scalar.math.BinaryMathProcessor.BinaryMathOperation; import org.elasticsearch.xpack.sql.expression.function.scalar.string.BinaryStringNumericProcessor.BinaryStringNumericOperation; import org.elasticsearch.xpack.sql.expression.function.scalar.string.BinaryStringStringProcessor.BinaryStringStringOperation; import org.elasticsearch.xpack.sql.expression.function.scalar.string.ConcatFunctionProcessor; @@ -121,4 +122,12 @@ public final class InternalSqlScriptUtils { public static Integer locate(String s1, String s2) { return locate(s1, s2, null); } + + public static Number round(Number v, Number s) { + return BinaryMathOperation.ROUND.apply(v, s); + } + + public static Number truncate(Number v, Number s) { + return BinaryMathOperation.TRUNCATE.apply(v, s); + } } diff --git a/x-pack/plugin/sql/src/main/java/org/elasticsearch/xpack/sql/plugin/SqlPainlessExtension.java b/x-pack/plugin/sql/src/main/java/org/elasticsearch/xpack/sql/plugin/SqlPainlessExtension.java index 426d725ac79..1846429cc80 100644 --- a/x-pack/plugin/sql/src/main/java/org/elasticsearch/xpack/sql/plugin/SqlPainlessExtension.java +++ b/x-pack/plugin/sql/src/main/java/org/elasticsearch/xpack/sql/plugin/SqlPainlessExtension.java @@ -8,6 +8,7 @@ package org.elasticsearch.xpack.sql.plugin; import org.elasticsearch.painless.spi.PainlessExtension; import org.elasticsearch.painless.spi.Whitelist; import org.elasticsearch.painless.spi.WhitelistLoader; +import org.elasticsearch.script.BucketAggregationSelectorScript; import org.elasticsearch.script.FilterScript; import org.elasticsearch.script.ScriptContext; import org.elasticsearch.script.SearchScript; @@ -30,6 +31,7 @@ public class SqlPainlessExtension implements PainlessExtension { whitelist.put(SearchScript.AGGS_CONTEXT, list); whitelist.put(SearchScript.CONTEXT, list); whitelist.put(SearchScript.SCRIPT_SORT_CONTEXT, list); + whitelist.put(BucketAggregationSelectorScript.CONTEXT, list); return whitelist; } } diff --git a/x-pack/plugin/sql/src/main/resources/org/elasticsearch/xpack/sql/plugin/sql_whitelist.txt b/x-pack/plugin/sql/src/main/resources/org/elasticsearch/xpack/sql/plugin/sql_whitelist.txt index 0f12d32d44e..1bb2802a5db 100644 --- a/x-pack/plugin/sql/src/main/resources/org/elasticsearch/xpack/sql/plugin/sql_whitelist.txt +++ b/x-pack/plugin/sql/src/main/resources/org/elasticsearch/xpack/sql/plugin/sql_whitelist.txt @@ -12,24 +12,26 @@ class org.elasticsearch.xpack.sql.expression.function.scalar.whitelist.InternalS String dayName(long, String) String monthName(long, String) Integer quarter(long, String) + Number round(Number, Number) + Number truncate(Number, Number) Integer ascii(String) Integer bitLength(String) - String character(Number) Integer charLength(String) - String lcase(String) - String ucase(String) - Integer length(String) - String rtrim(String) - String ltrim(String) - String space(Number) - String left(String, int) - String right(String, int) + String character(Number) String concat(String, String) - String repeat(String, int) - Integer position(String, String) String insert(String, int, int, String) - String substring(String, int, int) - String replace(String, String, String) + String lcase(String) + String left(String, int) + Integer length(String) Integer locate(String, String) Integer locate(String, String, Integer) + String ltrim(String) + Integer position(String, String) + String repeat(String, int) + String replace(String, String, String) + String right(String, int) + String rtrim(String) + String space(Number) + String substring(String, int, int) + String ucase(String) } \ No newline at end of file diff --git a/x-pack/plugin/sql/src/test/java/org/elasticsearch/xpack/sql/expression/function/scalar/math/BinaryMathProcessorTests.java b/x-pack/plugin/sql/src/test/java/org/elasticsearch/xpack/sql/expression/function/scalar/math/BinaryMathProcessorTests.java index 6563760d225..d42703987df 100644 --- a/x-pack/plugin/sql/src/test/java/org/elasticsearch/xpack/sql/expression/function/scalar/math/BinaryMathProcessorTests.java +++ b/x-pack/plugin/sql/src/test/java/org/elasticsearch/xpack/sql/expression/function/scalar/math/BinaryMathProcessorTests.java @@ -8,6 +8,7 @@ package org.elasticsearch.xpack.sql.expression.function.scalar.math; import org.elasticsearch.common.io.stream.NamedWriteableRegistry; import org.elasticsearch.common.io.stream.Writeable.Reader; import org.elasticsearch.test.AbstractWireSerializingTestCase; +import org.elasticsearch.xpack.sql.SqlIllegalArgumentException; import org.elasticsearch.xpack.sql.expression.Literal; import org.elasticsearch.xpack.sql.expression.function.scalar.Processors; import org.elasticsearch.xpack.sql.expression.function.scalar.processor.runtime.ConstantProcessor; @@ -47,6 +48,69 @@ public class BinaryMathProcessorTests extends AbstractWireSerializingTestCase new Round(EMPTY, l(5), l("foobarbar")).makeProcessorDefinition().asProcessor().process(null)); + assertEquals("A number is required; received foobarbar", siae.getMessage()); + siae = expectThrows(SqlIllegalArgumentException.class, + () -> new Round(EMPTY, l("bla"), l(0)).makeProcessorDefinition().asProcessor().process(null)); + assertEquals("A number is required; received bla", siae.getMessage()); + siae = expectThrows(SqlIllegalArgumentException.class, + () -> new Round(EMPTY, l(123.34), l(0.1)).makeProcessorDefinition().asProcessor().process(null)); + assertEquals("An integer number is required; received [0.1] as second parameter", siae.getMessage()); + } + + public void testTruncateWithValidInput() { + assertEquals(123.0, new Truncate(EMPTY, l(123), l(3)).makeProcessorDefinition().asProcessor().process(null)); + assertEquals(123.4, new Truncate(EMPTY, l(123.45), l(1)).makeProcessorDefinition().asProcessor().process(null)); + assertEquals(123.0, new Truncate(EMPTY, l(123.45), l(0)).makeProcessorDefinition().asProcessor().process(null)); + assertEquals(123.0, new Truncate(EMPTY, l(123.45), null).makeProcessorDefinition().asProcessor().process(null)); + assertEquals(-100.0, new Truncate(EMPTY, l(-123), l(-2)).makeProcessorDefinition().asProcessor().process(null)); + assertEquals(-120.0, new Truncate(EMPTY, l(-123.45), l(-1)).makeProcessorDefinition().asProcessor().process(null)); + assertEquals(-123.0, new Truncate(EMPTY, l(-123.5), l(0)).makeProcessorDefinition().asProcessor().process(null)); + assertEquals(-123.0, new Truncate(EMPTY, l(-123.45), null).makeProcessorDefinition().asProcessor().process(null)); + } + + public void testTruncateFunctionWithEdgeCasesInputs() { + assertNull(new Truncate(EMPTY, l(null), l(3)).makeProcessorDefinition().asProcessor().process(null)); + assertEquals(0.0, new Truncate(EMPTY, l(0), l(0)).makeProcessorDefinition().asProcessor().process(null)); + assertEquals((double) Long.MAX_VALUE, new Truncate(EMPTY, l(Long.MAX_VALUE), l(0)) + .makeProcessorDefinition().asProcessor().process(null)); + assertEquals(Double.NaN, new Truncate(EMPTY, l(123.456), l(Integer.MAX_VALUE)) + .makeProcessorDefinition().asProcessor().process(null)); + } + + public void testTruncateInputValidation() { + SqlIllegalArgumentException siae = expectThrows(SqlIllegalArgumentException.class, + () -> new Truncate(EMPTY, l(5), l("foobarbar")).makeProcessorDefinition().asProcessor().process(null)); + assertEquals("A number is required; received foobarbar", siae.getMessage()); + siae = expectThrows(SqlIllegalArgumentException.class, + () -> new Truncate(EMPTY, l("bla"), l(0)).makeProcessorDefinition().asProcessor().process(null)); + assertEquals("A number is required; received bla", siae.getMessage()); + siae = expectThrows(SqlIllegalArgumentException.class, + () -> new Truncate(EMPTY, l(123.34), l(0.1)).makeProcessorDefinition().asProcessor().process(null)); + assertEquals("An integer number is required; received [0.1] as second parameter", siae.getMessage()); + } public void testHandleNull() { assertNull(new ATan2(EMPTY, l(null), l(3)).makeProcessorDefinition().asProcessor().process(null)); diff --git a/x-pack/qa/sql/src/main/java/org/elasticsearch/xpack/qa/sql/cli/ShowTestCase.java b/x-pack/qa/sql/src/main/java/org/elasticsearch/xpack/qa/sql/cli/ShowTestCase.java index 8dbd4b187f7..f0697f553ae 100644 --- a/x-pack/qa/sql/src/main/java/org/elasticsearch/xpack/qa/sql/cli/ShowTestCase.java +++ b/x-pack/qa/sql/src/main/java/org/elasticsearch/xpack/qa/sql/cli/ShowTestCase.java @@ -49,16 +49,18 @@ public abstract class ShowTestCase extends CliIntegrationTestCase { assertThat(readLine(), RegexMatcher.matches("\\s*LOG\\s*\\|\\s*SCALAR\\s*")); assertThat(readLine(), RegexMatcher.matches("\\s*LOG10\\s*\\|\\s*SCALAR\\s*")); assertThat(readLine(), RegexMatcher.matches("\\s*LCASE\\s*\\|\\s*SCALAR\\s*")); - assertThat(readLine(), RegexMatcher.matches("\\s*LENGTH\\s*\\|\\s*SCALAR\\s*")); - assertThat(readLine(), RegexMatcher.matches("\\s*LTRIM\\s*\\|\\s*SCALAR\\s*")); assertThat(readLine(), RegexMatcher.matches("\\s*LEFT\\s*\\|\\s*SCALAR\\s*")); + assertThat(readLine(), RegexMatcher.matches("\\s*LENGTH\\s*\\|\\s*SCALAR\\s*")); assertThat(readLine(), RegexMatcher.matches("\\s*LOCATE\\s*\\|\\s*SCALAR\\s*")); + assertThat(readLine(), RegexMatcher.matches("\\s*LTRIM\\s*\\|\\s*SCALAR\\s*")); assertEquals("", readLine()); } public void testShowFunctionsLikeInfix() throws IOException { assertThat(command("SHOW FUNCTIONS LIKE '%DAY%'"), RegexMatcher.matches("\\s*name\\s*\\|\\s*type\\s*")); assertThat(readLine(), containsString("----------")); + assertThat(readLine(), RegexMatcher.matches("\\s*DAY_NAME\\s*\\|\\s*SCALAR\\s*")); + assertThat(readLine(), RegexMatcher.matches("\\s*DAYNAME\\s*\\|\\s*SCALAR\\s*")); assertThat(readLine(), RegexMatcher.matches("\\s*DAY_OF_MONTH\\s*\\|\\s*SCALAR\\s*")); assertThat(readLine(), RegexMatcher.matches("\\s*DAYOFMONTH\\s*\\|\\s*SCALAR\\s*")); assertThat(readLine(), RegexMatcher.matches("\\s*DAY\\s*\\|\\s*SCALAR\\s*")); @@ -68,8 +70,6 @@ public abstract class ShowTestCase extends CliIntegrationTestCase { assertThat(readLine(), RegexMatcher.matches("\\s*DAYOFYEAR\\s*\\|\\s*SCALAR\\s*")); assertThat(readLine(), RegexMatcher.matches("\\s*HOUR_OF_DAY\\s*\\|\\s*SCALAR\\s*")); assertThat(readLine(), RegexMatcher.matches("\\s*MINUTE_OF_DAY\\s*\\|\\s*SCALAR\\s*")); - assertThat(readLine(), RegexMatcher.matches("\\s*DAY_NAME\\s*\\|\\s*SCALAR\\s*")); - assertThat(readLine(), RegexMatcher.matches("\\s*DAYNAME\\s*\\|\\s*SCALAR\\s*")); assertEquals("", readLine()); } } diff --git a/x-pack/qa/sql/src/main/java/org/elasticsearch/xpack/qa/sql/jdbc/CsvSpecTestCase.java b/x-pack/qa/sql/src/main/java/org/elasticsearch/xpack/qa/sql/jdbc/CsvSpecTestCase.java index 4aa599290e6..a8ce308f323 100644 --- a/x-pack/qa/sql/src/main/java/org/elasticsearch/xpack/qa/sql/jdbc/CsvSpecTestCase.java +++ b/x-pack/qa/sql/src/main/java/org/elasticsearch/xpack/qa/sql/jdbc/CsvSpecTestCase.java @@ -38,6 +38,7 @@ public abstract class CsvSpecTestCase extends SpecBaseIntegrationTestCase { tests.addAll(readScriptSpec("/nulls.csv-spec", parser)); tests.addAll(readScriptSpec("/nested.csv-spec", parser)); tests.addAll(readScriptSpec("/functions.csv-spec", parser)); + tests.addAll(readScriptSpec("/math.csv-spec", parser)); return tests; } diff --git a/x-pack/qa/sql/src/main/resources/agg.sql-spec b/x-pack/qa/sql/src/main/resources/agg.sql-spec index e2213caa597..a61c825623c 100644 --- a/x-pack/qa/sql/src/main/resources/agg.sql-spec +++ b/x-pack/qa/sql/src/main/resources/agg.sql-spec @@ -370,7 +370,7 @@ SELECT MIN(salary) mi, MAX(salary) ma, MAX(salary) - MIN(salary) AS d FROM test_ aggHavingWithMultipleScalarFunctionsBasedOnAliasFromGroupBy SELECT MIN(salary) mi, MAX(salary) ma, MAX(salary) - MIN(salary) AS d FROM test_emp GROUP BY languages HAVING d - ma % mi > 0 AND AVG(salary) > 30000 ORDER BY languages; aggHavingWithMultipleScalarFunctionsBasedOnAliasFromGroupByAndAggNotInGroupBy -SELECT MIN(salary) mi, MAX(salary) ma, MAX(salary) - MIN(salary) AS d FROM test_emp GROUP BY languages HAVING ROUND(d - ABS(ma % mi)) + AVG(salary) > 0 AND AVG(salary) > 30000 ORDER BY languages; +SELECT MIN(salary) mi, MAX(salary) ma, MAX(salary) - MIN(salary) AS d FROM test_emp GROUP BY languages HAVING ROUND((d - ABS(ma % mi))) + AVG(salary) > 0 AND AVG(salary) > 30000 ORDER BY languages; aggHavingScalarOnAggFunctionsWithoutAliasesInAndNotInGroupBy SELECT MIN(salary) mi, MAX(salary) ma, MAX(salary) - MIN(salary) AS d FROM test_emp GROUP BY languages HAVING MAX(salary) % MIN(salary) + AVG(salary) > 3000 ORDER BY languages; @@ -385,7 +385,7 @@ SELECT MIN(salary) mi, MAX(salary) ma, MAX(salary) - MIN(salary) AS d FROM test_ aggMultiGroupByHavingWithMultipleScalarFunctionsBasedOnAliasFromGroupBy SELECT MIN(salary) mi, MAX(salary) ma, MAX(salary) - MIN(salary) AS d FROM test_emp GROUP BY gender, languages HAVING d - ma % mi > 0 AND AVG(salary) > 30000 ORDER BY gender, languages; aggMultiGroupByHavingWithMultipleScalarFunctionsBasedOnAliasFromGroupByAndAggNotInGroupBy -SELECT MIN(salary) mi, MAX(salary) ma, MAX(salary) - MIN(salary) AS d FROM test_emp GROUP BY gender, languages HAVING ROUND(d - ABS(ma % mi)) + AVG(salary) > 0 AND AVG(salary) > 30000 ORDER BY gender, languages; +SELECT MIN(salary) mi, MAX(salary) ma, MAX(salary) - MIN(salary) AS d FROM test_emp GROUP BY gender, languages HAVING ROUND((d - ABS(ma % mi))) + AVG(salary) > 0 AND AVG(salary) > 30000 ORDER BY gender, languages; aggMultiGroupByHavingScalarOnAggFunctionsWithoutAliasesInAndNotInGroupBy SELECT MIN(salary) mi, MAX(salary) ma, MAX(salary) - MIN(salary) AS d FROM test_emp GROUP BY gender, languages HAVING MAX(salary) % MIN(salary) + AVG(salary) > 3000 ORDER BY gender, languages; diff --git a/x-pack/qa/sql/src/main/resources/command.csv-spec b/x-pack/qa/sql/src/main/resources/command.csv-spec index 81aa18b2e84..26e94b445c7 100644 --- a/x-pack/qa/sql/src/main/resources/command.csv-spec +++ b/x-pack/qa/sql/src/main/resources/command.csv-spec @@ -19,6 +19,8 @@ PERCENTILE_RANK |AGGREGATE SUM_OF_SQUARES |AGGREGATE SKEWNESS |AGGREGATE KURTOSIS |AGGREGATE +DAY_NAME |SCALAR +DAYNAME |SCALAR DAY_OF_MONTH |SCALAR DAYOFMONTH |SCALAR DAY |SCALAR @@ -34,18 +36,16 @@ HOUR |SCALAR MINUTE_OF_DAY |SCALAR MINUTE_OF_HOUR |SCALAR MINUTE |SCALAR -SECOND_OF_MINUTE|SCALAR -SECOND |SCALAR -MONTH_OF_YEAR |SCALAR -MONTH |SCALAR -YEAR |SCALAR -WEEK_OF_YEAR |SCALAR -WEEK |SCALAR -DAY_NAME |SCALAR -DAYNAME |SCALAR MONTH_NAME |SCALAR MONTHNAME |SCALAR +MONTH_OF_YEAR |SCALAR +MONTH |SCALAR +SECOND_OF_MINUTE|SCALAR +SECOND |SCALAR QUARTER |SCALAR +YEAR |SCALAR +WEEK_OF_YEAR |SCALAR +WEEK |SCALAR ABS |SCALAR ACOS |SCALAR ASIN |SCALAR @@ -77,24 +77,25 @@ SIN |SCALAR SINH |SCALAR SQRT |SCALAR TAN |SCALAR +TRUNCATE |SCALAR ASCII |SCALAR -CHAR |SCALAR BIT_LENGTH |SCALAR +CHAR |SCALAR CHAR_LENGTH |SCALAR -CHARACTER_LENGTH|SCALAR +CHARACTER_LENGTH|SCALAR +CONCAT |SCALAR +INSERT |SCALAR LCASE |SCALAR -LENGTH |SCALAR -LTRIM |SCALAR -RTRIM |SCALAR -SPACE |SCALAR -CONCAT |SCALAR -INSERT |SCALAR LEFT |SCALAR +LENGTH |SCALAR LOCATE |SCALAR +LTRIM |SCALAR POSITION |SCALAR REPEAT |SCALAR REPLACE |SCALAR -RIGHT |SCALAR +RIGHT |SCALAR +RTRIM |SCALAR +SPACE |SCALAR SUBSTRING |SCALAR UCASE |SCALAR SCORE |SCORE @@ -133,6 +134,8 @@ showFunctionsWithLeadingPattern SHOW FUNCTIONS LIKE '%DAY%'; name:s | type:s +DAY_NAME |SCALAR +DAYNAME |SCALAR DAY_OF_MONTH |SCALAR DAYOFMONTH |SCALAR DAY |SCALAR @@ -142,8 +145,6 @@ DAY_OF_YEAR |SCALAR DAYOFYEAR |SCALAR HOUR_OF_DAY |SCALAR MINUTE_OF_DAY |SCALAR -DAY_NAME |SCALAR -DAYNAME |SCALAR ; showTables diff --git a/x-pack/qa/sql/src/main/resources/docs.csv-spec b/x-pack/qa/sql/src/main/resources/docs.csv-spec index 280e9a5edf0..dbc1b67a3e8 100644 --- a/x-pack/qa/sql/src/main/resources/docs.csv-spec +++ b/x-pack/qa/sql/src/main/resources/docs.csv-spec @@ -195,6 +195,8 @@ PERCENTILE_RANK |AGGREGATE SUM_OF_SQUARES |AGGREGATE SKEWNESS |AGGREGATE KURTOSIS |AGGREGATE +DAY_NAME |SCALAR +DAYNAME |SCALAR DAY_OF_MONTH |SCALAR DAYOFMONTH |SCALAR DAY |SCALAR @@ -210,18 +212,16 @@ HOUR |SCALAR MINUTE_OF_DAY |SCALAR MINUTE_OF_HOUR |SCALAR MINUTE |SCALAR -SECOND_OF_MINUTE|SCALAR -SECOND |SCALAR -MONTH_OF_YEAR |SCALAR -MONTH |SCALAR -YEAR |SCALAR -WEEK_OF_YEAR |SCALAR -WEEK |SCALAR -DAY_NAME |SCALAR -DAYNAME |SCALAR MONTH_NAME |SCALAR MONTHNAME |SCALAR +MONTH_OF_YEAR |SCALAR +MONTH |SCALAR +SECOND_OF_MINUTE|SCALAR +SECOND |SCALAR QUARTER |SCALAR +YEAR |SCALAR +WEEK_OF_YEAR |SCALAR +WEEK |SCALAR ABS |SCALAR ACOS |SCALAR ASIN |SCALAR @@ -253,27 +253,28 @@ SIN |SCALAR SINH |SCALAR SQRT |SCALAR TAN |SCALAR +TRUNCATE |SCALAR ASCII |SCALAR -CHAR |SCALAR BIT_LENGTH |SCALAR -CHAR_LENGTH |SCALAR -CHARACTER_LENGTH|SCALAR +CHAR |SCALAR +CHAR_LENGTH |SCALAR +CHARACTER_LENGTH|SCALAR +CONCAT |SCALAR +INSERT |SCALAR LCASE |SCALAR -LENGTH |SCALAR -LTRIM |SCALAR -RTRIM |SCALAR -SPACE |SCALAR -CONCAT |SCALAR -INSERT |SCALAR LEFT |SCALAR +LENGTH |SCALAR LOCATE |SCALAR +LTRIM |SCALAR POSITION |SCALAR REPEAT |SCALAR REPLACE |SCALAR -RIGHT |SCALAR +RIGHT |SCALAR +RTRIM |SCALAR +SPACE |SCALAR SUBSTRING |SCALAR UCASE |SCALAR -SCORE |SCORE +SCORE |SCORE // end::showFunctions ; @@ -321,6 +322,8 @@ SHOW FUNCTIONS LIKE '%DAY%'; name | type ---------------+--------------- +DAY_NAME |SCALAR +DAYNAME |SCALAR DAY_OF_MONTH |SCALAR DAYOFMONTH |SCALAR DAY |SCALAR @@ -330,8 +333,6 @@ DAY_OF_YEAR |SCALAR DAYOFYEAR |SCALAR HOUR_OF_DAY |SCALAR MINUTE_OF_DAY |SCALAR -DAY_NAME |SCALAR -DAYNAME |SCALAR // end::showFunctionsWithPattern ; @@ -548,7 +549,7 @@ M |63 groupByAndAggExpression // tag::groupByAndAggExpression -SELECT gender AS g, ROUND(MIN(salary) / 100) AS salary FROM emp GROUP BY gender; +SELECT gender AS g, ROUND((MIN(salary) / 100)) AS salary FROM emp GROUP BY gender; g | salary ---------------+--------------- @@ -1124,3 +1125,43 @@ SELECT YEAR(CAST('2018-05-19T11:23:45Z' AS TIMESTAMP)) AS year; 2018 // end::conversionStringToDateCast ; + +mathRoundWithNegativeParameter +// tag::mathRoundWithNegativeParameter +SELECT ROUND(-345.153, -1) AS rounded; + + rounded +--------------- +-350.0 +// end::mathRoundWithNegativeParameter +; + +mathRoundWithPositiveParameter +// tag::mathRoundWithPositiveParameter +SELECT ROUND(-345.153, 1) AS rounded; + + rounded +--------------- +-345.2 +// end::mathRoundWithPositiveParameter +; + +mathTruncateWithNegativeParameter +// tag::mathTruncateWithNegativeParameter +SELECT TRUNCATE(-345.153, -1) AS trimmed; + + trimmed +--------------- +-340.0 +// end::mathTruncateWithNegativeParameter +; + +mathTruncateWithPositiveParameter +// tag::mathTruncateWithPositiveParameter +SELECT TRUNCATE(-345.153, 1) AS trimmed; + + trimmed +--------------- +-345.1 +// end::mathTruncateWithPositiveParameter +; \ No newline at end of file diff --git a/x-pack/qa/sql/src/main/resources/math.csv-spec b/x-pack/qa/sql/src/main/resources/math.csv-spec new file mode 100644 index 00000000000..1c292200ccd --- /dev/null +++ b/x-pack/qa/sql/src/main/resources/math.csv-spec @@ -0,0 +1,185 @@ +// this one doesn't work in H2 at all +truncateWithAsciiHavingAndOrderBy +SELECT TRUNCATE(ASCII(LEFT(first_name, 1)), 1), COUNT(*) count FROM test_emp GROUP BY ASCII(LEFT(first_name, 1)) HAVING COUNT(*) > 5 ORDER BY TRUNCATE(ASCII(LEFT(first_name, 1)), 1) DESC; + +TRUNCATE(ASCII(LEFT(first_name,1)),1):i| count:l +---------------------------------------+--------------- +65 |9 +66 |8 +72 |7 +75 |8 +77 |9 +80 |6 +83 |11 +; + +truncateWithNoSecondParameterWithAsciiHavingAndOrderBy +SELECT TRUNCATE(ASCII(LEFT(first_name, 1))), COUNT(*) count FROM test_emp GROUP BY ASCII(LEFT(first_name, 1)) HAVING COUNT(*) > 5 ORDER BY TRUNCATE(ASCII(LEFT(first_name, 1))) DESC; + +TRUNCATE(ASCII(LEFT(first_name,1)),0):i| count:l +---------------------------------------+--------------- +65 |9 +66 |8 +72 |7 +75 |8 +77 |9 +80 |6 +83 |11 +; + +roundWithGroupByAndOrderBy +SELECT ROUND(salary, 2) ROUNDED, salary FROM test_emp GROUP BY ROUNDED, salary ORDER BY ROUNDED LIMIT 10; + + ROUNDED | salary +-------------+--------------- +25324 |25324 +25945 |25945 +25976 |25976 +26436 |26436 +27215 |27215 +28035 |28035 +28336 |28336 +28941 |28941 +29175 |29175 +30404 |30404 +; + +truncateWithGroupByAndOrderBy +SELECT TRUNCATE(salary, 2) TRUNCATED, salary FROM test_emp GROUP BY TRUNCATED, salary ORDER BY TRUNCATED LIMIT 10; + + TRUNCATED | salary +-------------+--------------- +25324 |25324 +25945 |25945 +25976 |25976 +26436 |26436 +27215 |27215 +28035 |28035 +28336 |28336 +28941 |28941 +29175 |29175 +30404 |30404 +; + +truncateWithAsciiAndOrderBy +SELECT TRUNCATE(ASCII(LEFT(first_name,1)), -1) AS initial, first_name, ASCII(LEFT(first_name, 1)) FROM test_emp ORDER BY ASCII(LEFT(first_name, 1)) DESC LIMIT 15; + + initial | first_name |ASCII(LEFT(first_name,1)) +---------------+---------------+------------------------- +90 |Zvonko |90 +90 |Zhongwei |90 +80 |Yongqiao |89 +80 |Yishay |89 +80 |Yinghua |89 +80 |Xinglin |88 +80 |Weiyi |87 +80 |Vishv |86 +80 |Valdiodio |86 +80 |Valter |86 +80 |Uri |85 +80 |Udi |85 +80 |Tzvetan |84 +80 |Tse |84 +80 |Tuval |84 +; + +truncateWithHavingAndGroupBy +SELECT MIN(salary) mi, MAX(salary) ma, COUNT(*) c, TRUNCATE(AVG(salary)) tr FROM test_emp GROUP BY languages HAVING TRUNCATE(AVG(salary)) > 40000 ORDER BY languages; + + mi:i | ma:I | c:l | tr:i +---------------+---------------+-----------------+----------------- +25976 |73717 |16 |49875 +29175 |73578 |20 |48164 +26436 |74999 |22 |52154 +27215 |74572 |18 |47733 +25324 |73851 |24 |44040 +; + +// https://github.com/elastic/elasticsearch/issues/33773 +minMaxTruncateAndRoundOfAverageWithHavingRoundAndTruncate-Ignore +SELECT MIN(salary) mi, MAX(salary) ma, YEAR(hire_date) year, ROUND(AVG(languages), 1), TRUNCATE(AVG(languages), 1), COUNT(*) FROM test_emp GROUP BY YEAR(hire_date) HAVING ROUND(AVG(languages), 1) > 2.5 AND TRUNCATE(AVG(languages), 1) <= 3.0 ORDER BY YEAR(hire_date); + + mi | ma | year |ROUND(AVG(languages),1)|TRUNCATE(AVG(languages),1)| COUNT(1) +-------------+-------------+---------------+-----------------------+--------------------------+-------------- +26436 |74999 |1985 |3.1 |3.0 |11 +25976 |74970 |1989 |3.1 |3.0 |13 +31120 |71165 |1990 |3.1 |3.0 |12 +32568 |65030 |1991 |2.8 |2.8 |6 +30404 |58715 |1993 |3.0 |3.0 |3 +35742 |67492 |1994 |2.8 |2.7 |4 +28035 |65367 |1995 |2.6 |2.6 |5 +45656 |45656 |1996 |3.0 |3.0 |1 +64675 |64675 |1997 |3.0 |3.0 |1 +; + +// https://github.com/elastic/elasticsearch/issues/33773 +minMaxRoundWithHavingRound-Ignore +SELECT MIN(salary) mi, MAX(salary) ma, YEAR(hire_date) year, ROUND(AVG(languages), 1), COUNT(*) FROM test_emp GROUP BY YEAR(hire_date) HAVING ROUND(AVG(languages), 1) > 2.5 ORDER BY YEAR(hire_date); + + mi | ma | year |ROUND(AVG(languages),1)| COUNT(1) +-------------+-------------+---------------+-----------------------+-------------- +26436 |74999 |1985 |3.1 |11 +31897 |61805 |1986 |3.5 |11 +25324 |70011 |1987 |3.1 |15 +25945 |73578 |1988 |3.1 |9 +25976 |74970 |1989 |3.1 |13 +31120 |71165 |1990 |3.1 |12 +32568 |65030 |1991 |2.8 |6 +27215 |60781 |1992 |4.1 |8 +30404 |58715 |1993 |3.0 |3 +35742 |67492 |1994 |2.8 |4 +28035 |65367 |1995 |2.6 |5 +45656 |45656 |1996 |3.0 |1 +64675 |64675 |1997 |3.0 |1 +; + +groupByAndOrderByTruncateWithPositiveParameter +SELECT TRUNCATE(AVG(salary), 2), AVG(salary), COUNT(*) FROM test_emp GROUP BY TRUNCATE(salary, 2) ORDER BY TRUNCATE(salary, 2) DESC LIMIT 10; + +TRUNCATE(AVG(salary),2):i|AVG(salary):i | COUNT(1):l +-------------------------+---------------+--------------- +74999 |74999 |1 +74970 |74970 |1 +74572 |74572 |1 +73851 |73851 |1 +73717 |73717 |1 +73578 |73578 |1 +71165 |71165 |1 +70011 |70011 |1 +69904 |69904 |1 +68547 |68547 |1 +; + +groupByAndOrderByRoundWithPositiveParameter +SELECT ROUND(AVG(salary), 2), AVG(salary), COUNT(*) FROM test_emp GROUP BY ROUND(salary, 2) ORDER BY ROUND(salary, 2) DESC LIMIT 10; + +ROUND(AVG(salary),2):i| AVG(salary):i | COUNT(1):l +----------------------+-----------------+--------------- +74999 |74999 |1 +74970 |74970 |1 +74572 |74572 |1 +73851 |73851 |1 +73717 |73717 |1 +73578 |73578 |1 +71165 |71165 |1 +70011 |70011 |1 +69904 |69904 |1 +68547 |68547 |1 +; + +groupByAndOrderByRoundWithNoSecondParameter +SELECT ROUND(AVG(salary)), ROUND(salary) rounded, AVG(salary), COUNT(*) FROM test_emp GROUP BY rounded ORDER BY rounded DESC LIMIT 10; + +ROUND(AVG(salary),0):i| rounded:i | AVG(salary):i | COUNT(1):l +----------------------+-----------------+-----------------+--------------- +74999 |74999 |74999 |1 +74970 |74970 |74970 |1 +74572 |74572 |74572 |1 +73851 |73851 |73851 |1 +73717 |73717 |73717 |1 +73578 |73578 |73578 |1 +71165 |71165 |71165 |1 +70011 |70011 |70011 |1 +69904 |69904 |69904 |1 +68547 |68547 |68547 |1 +; diff --git a/x-pack/qa/sql/src/main/resources/math.sql-spec b/x-pack/qa/sql/src/main/resources/math.sql-spec index 6452d2a3ac0..74855b00b11 100644 --- a/x-pack/qa/sql/src/main/resources/math.sql-spec +++ b/x-pack/qa/sql/src/main/resources/math.sql-spec @@ -62,7 +62,7 @@ mathRadians SELECT RADIANS(emp_no) m, first_name FROM "test_emp" WHERE emp_no < 10010 ORDER BY emp_no; // end::radians mathRound -SELECT CAST(ROUND(emp_no) AS INT) m, first_name FROM "test_emp" WHERE emp_no < 10010 ORDER BY emp_no; +SELECT CAST(ROUND(emp_no, 0) AS INT) m, first_name FROM "test_emp" WHERE emp_no < 10010 ORDER BY emp_no; mathSign // tag::sign SELECT SIGN(emp_no) m, first_name FROM "test_emp" WHERE emp_no < 10010 ORDER BY emp_no; @@ -134,3 +134,48 @@ SELECT POWER(emp_no, 2) m, first_name FROM "test_emp" WHERE emp_no < 10010 ORDER mathPowerNegative SELECT POWER(salary, -1) m, first_name FROM "test_emp" WHERE emp_no < 10010 ORDER BY emp_no; // end::power + +roundInline1 +SELECT ROUND(-345.123, -2) AS rounded; + +roundInline2 +SELECT ROUND(-345.123, 2) AS rounded; + +roundInline3 +SELECT ROUND(-345.123, 0) AS rounded; + +roundInline4 +SELECT ROUND(-345.123,-51231231) AS rounded; + +roundInline5 +SELECT ROUND(134.51, 1) AS rounded; + +roundInline6 +SELECT ROUND(134.56, 1) AS rounded; + +roundInline7 +SELECT ROUND(-345.123) AS rounded; + +truncateInline1 +SELECT TRUNCATE(-345.123, -2) AS trimmed; + +truncateInline2 +SELECT TRUNCATE(-345.123, 2) AS trimmed; + +truncateInline3 +SELECT TRUNCATE(-345.123, 0) AS trimmed; + +truncateInline4 +SELECT TRUNCATE(-345.123,-51231231) AS trimmed; + +truncateInline5 +SELECT TRUNCATE(134.51, 1) AS trimmed; + +truncateInline6 +SELECT TRUNCATE(134.56, 1) AS trimmed; + +truncateInline7 +SELECT TRUNCATE(-345.123) AS trimmed; + +truncateAndRoundInline +SELECT ROUND(134.56,1) AS rounded, TRUNCATE(134.56,1) AS trimmed;