diff --git a/plugin/sql/src/main/java/org/elasticsearch/xpack/sql/expression/function/FunctionRegistry.java b/plugin/sql/src/main/java/org/elasticsearch/xpack/sql/expression/function/FunctionRegistry.java index e54027a3313..25915c1a0a8 100644 --- a/plugin/sql/src/main/java/org/elasticsearch/xpack/sql/expression/function/FunctionRegistry.java +++ b/plugin/sql/src/main/java/org/elasticsearch/xpack/sql/expression/function/FunctionRegistry.java @@ -40,6 +40,7 @@ import org.elasticsearch.xpack.sql.expression.function.scalar.math.Cbrt; import org.elasticsearch.xpack.sql.expression.function.scalar.math.Ceil; import org.elasticsearch.xpack.sql.expression.function.scalar.math.Cos; import org.elasticsearch.xpack.sql.expression.function.scalar.math.Cosh; +import org.elasticsearch.xpack.sql.expression.function.scalar.math.Cot; import org.elasticsearch.xpack.sql.expression.function.scalar.math.Degrees; import org.elasticsearch.xpack.sql.expression.function.scalar.math.E; import org.elasticsearch.xpack.sql.expression.function.scalar.math.Exp; @@ -50,7 +51,9 @@ import org.elasticsearch.xpack.sql.expression.function.scalar.math.Log10; import org.elasticsearch.xpack.sql.expression.function.scalar.math.Pi; import org.elasticsearch.xpack.sql.expression.function.scalar.math.Power; import org.elasticsearch.xpack.sql.expression.function.scalar.math.Radians; +import org.elasticsearch.xpack.sql.expression.function.scalar.math.Random; import org.elasticsearch.xpack.sql.expression.function.scalar.math.Round; +import org.elasticsearch.xpack.sql.expression.function.scalar.math.Sign; 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; @@ -109,9 +112,10 @@ public class FunctionRegistry { def(ATan.class, ATan::new), def(ATan2.class, ATan2::new), def(Cbrt.class, Cbrt::new), - def(Ceil.class, Ceil::new), + def(Ceil.class, Ceil::new, "CEILING"), def(Cos.class, Cos::new), def(Cosh.class, Cosh::new), + def(Cot.class, Cot::new), def(Degrees.class, Degrees::new), def(E.class, E::new), def(Exp.class, Exp::new), @@ -124,7 +128,9 @@ public class FunctionRegistry { def(Pi.class, Pi::new), def(Power.class, Power::new), def(Radians.class, Radians::new), + def(Random.class, Random::new, "RAND"), def(Round.class, Round::new), + def(Sign.class, Sign::new, "SIGNUM"), def(Sin.class, Sin::new), def(Sinh.class, Sinh::new), def(Sqrt.class, Sqrt::new), diff --git a/plugin/sql/src/main/java/org/elasticsearch/xpack/sql/expression/function/scalar/math/Cot.java b/plugin/sql/src/main/java/org/elasticsearch/xpack/sql/expression/function/scalar/math/Cot.java new file mode 100644 index 00000000000..5bb4e0630bb --- /dev/null +++ b/plugin/sql/src/main/java/org/elasticsearch/xpack/sql/expression/function/scalar/math/Cot.java @@ -0,0 +1,45 @@ +/* + * 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.function.scalar.math.MathProcessor.MathOperation; +import org.elasticsearch.xpack.sql.tree.Location; +import org.elasticsearch.xpack.sql.tree.NodeInfo; + +import java.util.Locale; + +import static java.lang.String.format; + +/** + * Cotangent + * function. + */ +public class Cot extends MathFunction { + public Cot(Location location, Expression field) { + super(location, field); + } + + @Override + protected NodeInfo info() { + return NodeInfo.create(this, Cot::new, field()); + } + + @Override + protected Cot replaceChild(Expression newChild) { + return new Cot(location(), newChild); + } + + @Override + protected String formatScript(String template) { + return super.formatScript(format(Locale.ROOT, "1.0 / Math.tan(%s)", template)); + } + + @Override + protected MathOperation operation() { + return MathOperation.COT; + } +} diff --git a/plugin/sql/src/main/java/org/elasticsearch/xpack/sql/expression/function/scalar/math/MathProcessor.java b/plugin/sql/src/main/java/org/elasticsearch/xpack/sql/expression/function/scalar/math/MathProcessor.java index 38889ecb43b..b9bf56f33a4 100644 --- a/plugin/sql/src/main/java/org/elasticsearch/xpack/sql/expression/function/scalar/math/MathProcessor.java +++ b/plugin/sql/src/main/java/org/elasticsearch/xpack/sql/expression/function/scalar/math/MathProcessor.java @@ -5,12 +5,14 @@ */ package org.elasticsearch.xpack.sql.expression.function.scalar.math; +import org.elasticsearch.common.Randomness; 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.processor.runtime.Processor; import java.io.IOException; +import java.util.Random; import java.util.function.DoubleFunction; import java.util.function.Function; import java.util.function.Supplier; @@ -26,6 +28,7 @@ public class MathProcessor implements Processor { return Math.abs(((Double) l).doubleValue()); } long lo = ((Number) l).longValue(); + //handles the corner-case of Long.MIN_VALUE return lo >= 0 ? lo : lo == Long.MIN_VALUE ? Long.MAX_VALUE : -lo; }), @@ -36,6 +39,7 @@ public class MathProcessor implements Processor { CEIL(Math::ceil), COS(Math::cos), COSH(Math::cosh), + COT((Object l) -> 1.0d / Math.tan(((Number) l).doubleValue())), DEGREES(Math::toDegrees), E(() -> Math.E), EXP(Math::exp), @@ -45,7 +49,11 @@ public class MathProcessor implements Processor { LOG10(Math::log10), PI(() -> Math.PI), RADIANS(Math::toRadians), + 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), SQRT(Math::sqrt), @@ -54,7 +62,20 @@ public class MathProcessor implements Processor { private final Function apply; MathOperation(Function apply) { - this.apply = l -> l == null ? null : apply.apply(l); + this(apply, false); + } + + /** + * Wrapper for nulls around the given function. + * If true, nulls are passed through, otherwise the function is short-circuited + * and null returned. + */ + MathOperation(Function apply, boolean nullAware) { + if (nullAware) { + this.apply = apply; + } else { + this.apply = l -> l == null ? null : apply.apply(l); + } } MathOperation(DoubleFunction apply) { diff --git a/plugin/sql/src/main/java/org/elasticsearch/xpack/sql/expression/function/scalar/math/Random.java b/plugin/sql/src/main/java/org/elasticsearch/xpack/sql/expression/function/scalar/math/Random.java new file mode 100644 index 00000000000..4e078ed2126 --- /dev/null +++ b/plugin/sql/src/main/java/org/elasticsearch/xpack/sql/expression/function/scalar/math/Random.java @@ -0,0 +1,47 @@ +/* + * 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.function.scalar.math.MathProcessor.MathOperation; +import org.elasticsearch.xpack.sql.tree.Location; +import org.elasticsearch.xpack.sql.tree.NodeInfo; + +import java.util.Locale; + +import static java.lang.String.format; + +/** + * Returns a random double (using the given seed). + */ +public class Random extends MathFunction { + + public Random(Location location, Expression field) { + super(location, field); + } + + @Override + protected NodeInfo info() { + return NodeInfo.create(this, Random::new, field()); + } + + @Override + protected Random replaceChild(Expression newChild) { + return new Random(location(), newChild); + } + + @Override + protected String formatScript(String template) { + //TODO: Painless script uses Random since Randomness is not whitelisted + return super.formatScript( + format(Locale.ROOT, "%s != null ? new Random((long) %s).nextDouble() : Math.random()", template, template)); + } + + @Override + protected MathOperation operation() { + return MathOperation.RANDOM; + } +} diff --git a/plugin/sql/src/main/java/org/elasticsearch/xpack/sql/expression/function/scalar/math/Sign.java b/plugin/sql/src/main/java/org/elasticsearch/xpack/sql/expression/function/scalar/math/Sign.java new file mode 100644 index 00000000000..f162f015bf5 --- /dev/null +++ b/plugin/sql/src/main/java/org/elasticsearch/xpack/sql/expression/function/scalar/math/Sign.java @@ -0,0 +1,51 @@ +/* + * 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.function.scalar.math.MathProcessor.MathOperation; +import org.elasticsearch.xpack.sql.tree.Location; +import org.elasticsearch.xpack.sql.tree.NodeInfo; +import org.elasticsearch.xpack.sql.type.DataType; + +/** + * Returns the sign of the given expression: + *
    + *
  • -1 if it is negative
  • + *
  • 0 if it is zero
  • + *
  • +1 if it is positive
  • + *
+ */ +public class Sign extends MathFunction { + public Sign(Location location, Expression field) { + super(location, field); + } + + @Override + protected NodeInfo info() { + return NodeInfo.create(this, Sign::new, field()); + } + + @Override + protected Sign replaceChild(Expression newChild) { + return new Sign(location(), newChild); + } + + @Override + protected String mathFunction() { + return "signum"; + } + + @Override + protected MathOperation operation() { + return MathOperation.SIGN; + } + + @Override + public DataType dataType() { + return DataType.INTEGER; + } +} diff --git a/plugin/sql/src/test/java/org/elasticsearch/xpack/sql/analysis/analyzer/VerifierErrorMessagesTests.java b/plugin/sql/src/test/java/org/elasticsearch/xpack/sql/analysis/analyzer/VerifierErrorMessagesTests.java index 433cece90d6..355c4d2f7b7 100644 --- a/plugin/sql/src/test/java/org/elasticsearch/xpack/sql/analysis/analyzer/VerifierErrorMessagesTests.java +++ b/plugin/sql/src/test/java/org/elasticsearch/xpack/sql/analysis/analyzer/VerifierErrorMessagesTests.java @@ -55,7 +55,7 @@ public class VerifierErrorMessagesTests extends ESTestCase { } public void testMisspelledFunction() { - assertEquals("1:8: Unknown function [COONT], did you mean [COUNT]?", verify("SELECT COONT(bool) FROM test")); + assertEquals("1:8: Unknown function [COONT], did you mean any of [COUNT, COT]?", verify("SELECT COONT(bool) FROM test")); } public void testMissingColumnInGroupBy() { diff --git a/plugin/sql/src/test/java/org/elasticsearch/xpack/sql/expression/function/scalar/math/MathFunctionProcessorTests.java b/plugin/sql/src/test/java/org/elasticsearch/xpack/sql/expression/function/scalar/math/MathFunctionProcessorTests.java index d64bd057a26..9ff32c5a057 100644 --- a/plugin/sql/src/test/java/org/elasticsearch/xpack/sql/expression/function/scalar/math/MathFunctionProcessorTests.java +++ b/plugin/sql/src/test/java/org/elasticsearch/xpack/sql/expression/function/scalar/math/MathFunctionProcessorTests.java @@ -49,4 +49,10 @@ public class MathFunctionProcessorTests extends AbstractWireSerializingTestCase< assertEquals("A number is required; received [string]", siae.getMessage()); } + + public void testRandom() { + MathProcessor proc = new MathProcessor(MathOperation.RANDOM); + assertNotNull(proc.process(null)); + assertNotNull(proc.process(randomLong())); + } } diff --git a/qa/sql/src/main/resources/command.csv-spec b/qa/sql/src/main/resources/command.csv-spec index b9e505e60f8..d54fb6bf155 100644 --- a/qa/sql/src/main/resources/command.csv-spec +++ b/qa/sql/src/main/resources/command.csv-spec @@ -45,8 +45,10 @@ ATAN |SCALAR ATAN2 |SCALAR CBRT |SCALAR CEIL |SCALAR +CEILING |SCALAR COS |SCALAR COSH |SCALAR +COT |SCALAR DEGREES |SCALAR E |SCALAR EXP |SCALAR @@ -58,7 +60,11 @@ MOD |SCALAR PI |SCALAR POWER |SCALAR RADIANS |SCALAR +RANDOM |SCALAR +RAND |SCALAR ROUND |SCALAR +SIGN |SCALAR +SIGNUM |SCALAR SIN |SCALAR SINH |SCALAR SQRT |SCALAR diff --git a/qa/sql/src/main/resources/math.sql-spec b/qa/sql/src/main/resources/math.sql-spec index cdfa1ee35eb..e38de2aa6bc 100644 --- a/qa/sql/src/main/resources/math.sql-spec +++ b/qa/sql/src/main/resources/math.sql-spec @@ -31,6 +31,10 @@ mathCosh // tag::cosh SELECT COSH(emp_no) m, first_name FROM "test_emp" WHERE emp_no < 10010 ORDER BY emp_no; // end::cosh +mathCot +// tag::cot +SELECT COT(emp_no) m, first_name FROM "test_emp" WHERE emp_no < 10010 ORDER BY emp_no; +// end::cot mathDegrees // tag::degrees SELECT DEGREES(emp_no) m, first_name FROM "test_emp" WHERE emp_no < 10010 ORDER BY emp_no; @@ -59,6 +63,10 @@ SELECT RADIANS(emp_no) m, first_name FROM "test_emp" WHERE emp_no < 10010 ORDER // end::radians mathRound SELECT CAST(ROUND(emp_no) 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; +// end::sign mathSin // tag::sin SELECT SIN(emp_no) m, first_name FROM "test_emp" WHERE emp_no < 10010 ORDER BY emp_no;