diff --git a/docs/changelog/106866.yaml b/docs/changelog/106866.yaml new file mode 100644 index 0000000000000..ffc34e5962850 --- /dev/null +++ b/docs/changelog/106866.yaml @@ -0,0 +1,5 @@ +pr: 106866 +summary: Add ES|QL signum function +area: ES|QL +type: enhancement +issues: [] diff --git a/docs/reference/esql/functions/description/signum.asciidoc b/docs/reference/esql/functions/description/signum.asciidoc new file mode 100644 index 0000000000000..db44c019e247e --- /dev/null +++ b/docs/reference/esql/functions/description/signum.asciidoc @@ -0,0 +1,5 @@ +// This is generated by ESQL's AbstractFunctionTestCase. Do no edit it. See ../README.md for how to regenerate it. + +*Description* + +Returns the sign of the given number. It returns `-1` for negative numbers, `0` for `0` and `1` for positive numbers. diff --git a/docs/reference/esql/functions/examples/signum.asciidoc b/docs/reference/esql/functions/examples/signum.asciidoc new file mode 100644 index 0000000000000..190c1d0f71136 --- /dev/null +++ b/docs/reference/esql/functions/examples/signum.asciidoc @@ -0,0 +1,13 @@ +// This is generated by ESQL's AbstractFunctionTestCase. Do no edit it. See ../README.md for how to regenerate it. + +*Example* + +[source.merge.styled,esql] +---- +include::{esql-specs}/math.csv-spec[tag=signum] +---- +[%header.monospaced.styled,format=dsv,separator=|] +|=== +include::{esql-specs}/math.csv-spec[tag=signum-result] +|=== + diff --git a/docs/reference/esql/functions/layout/signum.asciidoc b/docs/reference/esql/functions/layout/signum.asciidoc new file mode 100644 index 0000000000000..f5b565993f392 --- /dev/null +++ b/docs/reference/esql/functions/layout/signum.asciidoc @@ -0,0 +1,15 @@ +// This is generated by ESQL's AbstractFunctionTestCase. Do no edit it. See ../README.md for how to regenerate it. + +[discrete] +[[esql-signum]] +=== `SIGNUM` + +*Syntax* + +[.text-center] +image::esql/functions/signature/signum.svg[Embedded,opts=inline] + +include::../parameters/signum.asciidoc[] +include::../description/signum.asciidoc[] +include::../types/signum.asciidoc[] +include::../examples/signum.asciidoc[] diff --git a/docs/reference/esql/functions/math-functions.asciidoc b/docs/reference/esql/functions/math-functions.asciidoc index 8748b35443e8e..dd5b8a0a3d4e0 100644 --- a/docs/reference/esql/functions/math-functions.asciidoc +++ b/docs/reference/esql/functions/math-functions.asciidoc @@ -23,6 +23,7 @@ * <> * <> * <> +* <> * <> * <> * <> @@ -46,6 +47,7 @@ include::layout/log10.asciidoc[] include::pi.asciidoc[] include::pow.asciidoc[] include::round.asciidoc[] +include::layout/signum.asciidoc[] include::layout/sin.asciidoc[] include::layout/sinh.asciidoc[] include::sqrt.asciidoc[] diff --git a/docs/reference/esql/functions/parameters/signum.asciidoc b/docs/reference/esql/functions/parameters/signum.asciidoc new file mode 100644 index 0000000000000..65013f4c21265 --- /dev/null +++ b/docs/reference/esql/functions/parameters/signum.asciidoc @@ -0,0 +1,6 @@ +// This is generated by ESQL's AbstractFunctionTestCase. Do no edit it. See ../README.md for how to regenerate it. + +*Parameters* + +`number`:: +Numeric expression. If `null`, the function returns `null`. diff --git a/docs/reference/esql/functions/signature/signum.svg b/docs/reference/esql/functions/signature/signum.svg new file mode 100644 index 0000000000000..76d2972f18f42 --- /dev/null +++ b/docs/reference/esql/functions/signature/signum.svg @@ -0,0 +1 @@ +SIGNUM(number) \ No newline at end of file diff --git a/docs/reference/esql/functions/types/signum.asciidoc b/docs/reference/esql/functions/types/signum.asciidoc new file mode 100644 index 0000000000000..7cda278abdb56 --- /dev/null +++ b/docs/reference/esql/functions/types/signum.asciidoc @@ -0,0 +1,12 @@ +// This is generated by ESQL's AbstractFunctionTestCase. Do no edit it. See ../README.md for how to regenerate it. + +*Supported types* + +[%header.monospaced.styled,format=dsv,separator=|] +|=== +number | result +double | double +integer | double +long | double +unsigned_long | double +|=== diff --git a/x-pack/plugin/esql/qa/testFixtures/src/main/resources/floats.csv-spec b/x-pack/plugin/esql/qa/testFixtures/src/main/resources/floats.csv-spec index 9c343083275cd..0882fec5ec0bf 100644 --- a/x-pack/plugin/esql/qa/testFixtures/src/main/resources/floats.csv-spec +++ b/x-pack/plugin/esql/qa/testFixtures/src/main/resources/floats.csv-spec @@ -544,3 +544,50 @@ required_feature: esql.agg_values [1.56, 1.78] | Tech Lead [1.7, 1.83, 2.05] | null ; + +signumOfPositiveDouble#[skip:-8.13.99,reason:new scalar function added in 8.14] +row d = to_double(100) | eval s = signum(d); + +d:double | s:double +100 | 1.0 +; + +signumOfNegativeDouble#[skip:-8.13.99,reason:new scalar function added in 8.14] +row d = to_double(-100) | eval s = signum(d); + +d:double | s:double +-100 | -1.0 +; + +signumOfZeroDouble#[skip:-8.13.99,reason:new scalar function added in 8.14] +row d = to_double(0) | eval s = signum(d); + +d:double | s:double +0 | 0.0 +; + +signumWithEvalWhereAndStats#[skip:-8.13.99,reason:new scalar function added in 8.14] + +from employees +| where emp_no <= 10009 +| eval s = signum(mv_min(salary_change)) +| where signum(mv_max(salary_change)) >= 0 +| STATS x = AVG(signum(60000 - salary)); + +x:double +0.14285714285714285 +; + +signumWithEvalAndSort#[skip:-8.13.99,reason:new scalar function added in 8.14] +from employees +| eval s = signum(mv_min(salary_change)) +| where signum(mv_max(salary_change)) >= 0 +| keep s, emp_no, salary, salary_change +| sort s, emp_no +| limit 3; + +s:double | emp_no:integer | salary:integer | salary_change:double +-1.0 | 10002 | 56371 | [-7.23, 11.17] +-1.0 | 10004 | 36174 | [-0.35, 1.13, 3.65, 13.48] +-1.0 | 10005 | 63528 | [-2.14, 13.07] +; diff --git a/x-pack/plugin/esql/qa/testFixtures/src/main/resources/ints.csv-spec b/x-pack/plugin/esql/qa/testFixtures/src/main/resources/ints.csv-spec index 8657602e7b16f..3e1d1b19a7f67 100644 --- a/x-pack/plugin/esql/qa/testFixtures/src/main/resources/ints.csv-spec +++ b/x-pack/plugin/esql/qa/testFixtures/src/main/resources/ints.csv-spec @@ -988,3 +988,70 @@ required_feature: esql.agg_values [3, 5] | Tech Lead [1, 4] | null ; + +signumOfPositiveInteger#[skip:-8.13.99,reason:new scalar function added in 8.14] +row i = 100 | eval s = signum(i); + +i:integer | s:double +100 | 1.0 +; + +signumOfNegativeInteger#[skip:-8.13.99,reason:new scalar function added in 8.14] +row i = -100 | eval s = signum(i); + +i:integer | s:double +-100 | -1.0 +; + +signumOfZeroInteger#[skip:-8.13.99,reason:new scalar function added in 8.14] +row i = 0 | eval s = signum(i); + +i:integer | s:double +0 | 0.0 +; + +signumOfPositiveLong#[skip:-8.13.99,reason:new scalar function added in 8.14] +row l = to_long(100) | eval s = signum(l); + +l:long | s:double +100 | 1.0 +; + +signumOfNegativeLong#[skip:-8.13.99,reason:new scalar function added in 8.14] +row l = to_long(-100) | eval s = signum(l); + +l:long | s:double +-100 | -1.0 +; + +signumOfZeroLong#[skip:-8.13.99,reason:new scalar function added in 8.14] +row l = to_long(0) | eval s = signum(l); + +l:long | s:double +0 | 0.0 +; + +signumWithEvalWhereAndStats#[skip:-8.13.99,reason:new scalar function added in 8.14] + +from employees +| eval s = signum(mv_min(salary_change.int)) +| where signum(mv_max(salary_change.int)) >= 0 +| STATS x=AVG(signum(60000 - salary)); + +x:double +0.5409836065573771 +; + +signumWithEvalAndSort#[skip:-8.13.99,reason:new scalar function added in 8.14] +from employees +| eval s = signum(60000 - salary) +| where signum(salary - 55000) >= 0 +| keep s, emp_no, salary +| sort s DESC, salary ASC +| limit 3; + +s:double | emp_no:integer | salary:integer +1.0 | 10052 | 55360 +1.0 | 10002 | 56371 +1.0 | 10041 | 56415 +; diff --git a/x-pack/plugin/esql/qa/testFixtures/src/main/resources/math.csv-spec b/x-pack/plugin/esql/qa/testFixtures/src/main/resources/math.csv-spec index 8491919b3ee93..6caeade1af58c 100644 --- a/x-pack/plugin/esql/qa/testFixtures/src/main/resources/math.csv-spec +++ b/x-pack/plugin/esql/qa/testFixtures/src/main/resources/math.csv-spec @@ -1249,6 +1249,19 @@ i:ul | c:ul | f:ul 1000000000000000000 | 1000000000000000000 | 1000000000000000000 ; +signum#[skip:-8.13.99,reason:new scalar function added in 8.14] +// tag::signum[] +ROW d = 100.0 +| EVAL s = SIGNUM(d) +// end::signum[] +; + +// tag::signum-result[] +d: double | s:double +100 | 1.0 +// end::signum-result[] +; + sqrt // tag::sqrt[] ROW d = 100.0 diff --git a/x-pack/plugin/esql/qa/testFixtures/src/main/resources/meta.csv-spec b/x-pack/plugin/esql/qa/testFixtures/src/main/resources/meta.csv-spec index 524de7c2c3b67..746684aca3e38 100644 --- a/x-pack/plugin/esql/qa/testFixtures/src/main/resources/meta.csv-spec +++ b/x-pack/plugin/esql/qa/testFixtures/src/main/resources/meta.csv-spec @@ -58,6 +58,7 @@ double pi() "keyword right(string:keyword|text, length:integer)" "double round(number:double, ?decimals:integer)" "keyword|text rtrim(string:keyword|text)" +"double signum(number:double|integer|long|unsigned_long)" "double sin(angle:double|integer|long|unsigned_long)" "double sinh(angle:double|integer|long|unsigned_long)" "keyword split(string:keyword|text, delim:keyword|text)" @@ -165,6 +166,7 @@ replace |[string, regex, newString] |["keyword|text", "keyword|te right |[string, length] |["keyword|text", integer] |[, ] round |[number, decimals] |[double, integer] |[The numeric value to round, The number of decimal places to round to. Defaults to 0.] rtrim |string |"keyword|text" |[""] +signum |number |"double|integer|long|unsigned_long" |"Numeric expression. If `null`, the function returns `null`." sin |angle |"double|integer|long|unsigned_long" |An angle, in radians. If `null`, the function returns `null`. sinh |angle |"double|integer|long|unsigned_long" |An angle, in radians. If `null`, the function returns `null`. split |[string, delim] |["keyword|text", "keyword|text"] |[, ] @@ -273,6 +275,7 @@ replace |The function substitutes in the string any match of the regular e right |Return the substring that extracts length chars from the string starting from the right. round |Rounds a number to the closest number with the specified number of digits. rtrim |Removes trailing whitespaces from a string. +signum |Returns the sign of the given number. It returns `-1` for negative numbers, `0` for `0` and `1` for positive numbers. sin |Returns ths {wikipedia}/Sine_and_cosine[Sine] trigonometric function of an angle. sinh |Returns the {wikipedia}/Hyperbolic_functions[hyperbolic sine] of an angle. split |Split a single valued string into multiple strings. @@ -382,6 +385,7 @@ replace |keyword right |keyword |[false, false] |false |false round |double |[false, true] |false |false rtrim |"keyword|text" |false |false |false +signum |double |false |false |false sin |double |false |false |false sinh |double |false |false |false split |keyword |[false, false] |false |false @@ -443,5 +447,5 @@ countFunctions#[skip:-8.13.99] meta functions | stats a = count(*), b = count(*), c = count(*) | mv_expand c; a:long | b:long | c:long -99 | 99 | 99 +100 | 100 | 100 ; diff --git a/x-pack/plugin/esql/qa/testFixtures/src/main/resources/unsigned_long.csv-spec b/x-pack/plugin/esql/qa/testFixtures/src/main/resources/unsigned_long.csv-spec index c6f24d876240f..f1a15f41af7b3 100644 --- a/x-pack/plugin/esql/qa/testFixtures/src/main/resources/unsigned_long.csv-spec +++ b/x-pack/plugin/esql/qa/testFixtures/src/main/resources/unsigned_long.csv-spec @@ -187,3 +187,34 @@ warning:Line 1:22: java.lang.IllegalArgumentException: single-value function enc bytes_in:ul | rad:double 16002960716282089759 | 2.79304354566432608E17 ; + +signumOfPositiveUnsignedLong#[skip:-8.13.99,reason:new scalar function added in 8.14] +row l = to_ul(100) | eval s = signum(l); + +l:ul | s:double +100 | 1.0 +; + +signumOfZeroUnsignedLong#[skip:-8.13.99,reason:new scalar function added in 8.14] +row l = to_ul(0) | eval s = signum(l); + +l:ul | s:double +0 | 0.0 +; + +signumWithEvalAndWhere#[skip:-8.13.99,reason:new scalar function added in 8.14] + +from ul_logs | +where signum(bytes_in) >= 0.0 | +eval s = signum(bytes_out) | +keep s, bytes_in, bytes_out | +sort bytes_out, s | +limit 2; + +warning:Line 2:7: evaluation of [signum(bytes_in)] failed, treating result as null. Only first 20 failures recorded. +warning:Line 2:7: java.lang.IllegalArgumentException: single-value function encountered multi-value + +s:double | bytes_in:ul | bytes_out:ul +1.0 | 1957665857956635540 | 352442273299370793 +1.0 | 2408213296071189837 | 419872666232023984 +; diff --git a/x-pack/plugin/esql/src/main/java/generated/org/elasticsearch/xpack/esql/expression/function/scalar/math/SignumDoubleEvaluator.java b/x-pack/plugin/esql/src/main/java/generated/org/elasticsearch/xpack/esql/expression/function/scalar/math/SignumDoubleEvaluator.java new file mode 100644 index 0000000000000..c7d21a7b9c5a0 --- /dev/null +++ b/x-pack/plugin/esql/src/main/java/generated/org/elasticsearch/xpack/esql/expression/function/scalar/math/SignumDoubleEvaluator.java @@ -0,0 +1,108 @@ +// Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one +// or more contributor license agreements. Licensed under the Elastic License +// 2.0; you may not use this file except in compliance with the Elastic License +// 2.0. +package org.elasticsearch.xpack.esql.expression.function.scalar.math; + +import java.lang.IllegalArgumentException; +import java.lang.Override; +import java.lang.String; +import org.elasticsearch.compute.data.Block; +import org.elasticsearch.compute.data.DoubleBlock; +import org.elasticsearch.compute.data.DoubleVector; +import org.elasticsearch.compute.data.Page; +import org.elasticsearch.compute.operator.DriverContext; +import org.elasticsearch.compute.operator.EvalOperator; +import org.elasticsearch.core.Releasables; +import org.elasticsearch.xpack.esql.expression.function.Warnings; +import org.elasticsearch.xpack.ql.tree.Source; + +/** + * {@link EvalOperator.ExpressionEvaluator} implementation for {@link Signum}. + * This class is generated. Do not edit it. + */ +public final class SignumDoubleEvaluator implements EvalOperator.ExpressionEvaluator { + private final Warnings warnings; + + private final EvalOperator.ExpressionEvaluator val; + + private final DriverContext driverContext; + + public SignumDoubleEvaluator(Source source, EvalOperator.ExpressionEvaluator val, + DriverContext driverContext) { + this.warnings = new Warnings(source); + this.val = val; + this.driverContext = driverContext; + } + + @Override + public Block eval(Page page) { + try (DoubleBlock valBlock = (DoubleBlock) val.eval(page)) { + DoubleVector valVector = valBlock.asVector(); + if (valVector == null) { + return eval(page.getPositionCount(), valBlock); + } + return eval(page.getPositionCount(), valVector).asBlock(); + } + } + + public DoubleBlock eval(int positionCount, DoubleBlock valBlock) { + try(DoubleBlock.Builder result = driverContext.blockFactory().newDoubleBlockBuilder(positionCount)) { + position: for (int p = 0; p < positionCount; p++) { + if (valBlock.isNull(p)) { + result.appendNull(); + continue position; + } + if (valBlock.getValueCount(p) != 1) { + if (valBlock.getValueCount(p) > 1) { + warnings.registerException(new IllegalArgumentException("single-value function encountered multi-value")); + } + result.appendNull(); + continue position; + } + result.appendDouble(Signum.process(valBlock.getDouble(valBlock.getFirstValueIndex(p)))); + } + return result.build(); + } + } + + public DoubleVector eval(int positionCount, DoubleVector valVector) { + try(DoubleVector.Builder result = driverContext.blockFactory().newDoubleVectorBuilder(positionCount)) { + position: for (int p = 0; p < positionCount; p++) { + result.appendDouble(Signum.process(valVector.getDouble(p))); + } + return result.build(); + } + } + + @Override + public String toString() { + return "SignumDoubleEvaluator[" + "val=" + val + "]"; + } + + @Override + public void close() { + Releasables.closeExpectNoException(val); + } + + static class Factory implements EvalOperator.ExpressionEvaluator.Factory { + private final Source source; + + private final EvalOperator.ExpressionEvaluator.Factory val; + + public Factory(Source source, EvalOperator.ExpressionEvaluator.Factory val) { + this.source = source; + this.val = val; + } + + @Override + public SignumDoubleEvaluator get(DriverContext context) { + return new SignumDoubleEvaluator(source, val.get(context), context); + } + + @Override + public String toString() { + return "SignumDoubleEvaluator[" + "val=" + val + "]"; + } + } +} diff --git a/x-pack/plugin/esql/src/main/java/generated/org/elasticsearch/xpack/esql/expression/function/scalar/math/SignumIntEvaluator.java b/x-pack/plugin/esql/src/main/java/generated/org/elasticsearch/xpack/esql/expression/function/scalar/math/SignumIntEvaluator.java new file mode 100644 index 0000000000000..939807d8deffa --- /dev/null +++ b/x-pack/plugin/esql/src/main/java/generated/org/elasticsearch/xpack/esql/expression/function/scalar/math/SignumIntEvaluator.java @@ -0,0 +1,110 @@ +// Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one +// or more contributor license agreements. Licensed under the Elastic License +// 2.0; you may not use this file except in compliance with the Elastic License +// 2.0. +package org.elasticsearch.xpack.esql.expression.function.scalar.math; + +import java.lang.IllegalArgumentException; +import java.lang.Override; +import java.lang.String; +import org.elasticsearch.compute.data.Block; +import org.elasticsearch.compute.data.DoubleBlock; +import org.elasticsearch.compute.data.DoubleVector; +import org.elasticsearch.compute.data.IntBlock; +import org.elasticsearch.compute.data.IntVector; +import org.elasticsearch.compute.data.Page; +import org.elasticsearch.compute.operator.DriverContext; +import org.elasticsearch.compute.operator.EvalOperator; +import org.elasticsearch.core.Releasables; +import org.elasticsearch.xpack.esql.expression.function.Warnings; +import org.elasticsearch.xpack.ql.tree.Source; + +/** + * {@link EvalOperator.ExpressionEvaluator} implementation for {@link Signum}. + * This class is generated. Do not edit it. + */ +public final class SignumIntEvaluator implements EvalOperator.ExpressionEvaluator { + private final Warnings warnings; + + private final EvalOperator.ExpressionEvaluator val; + + private final DriverContext driverContext; + + public SignumIntEvaluator(Source source, EvalOperator.ExpressionEvaluator val, + DriverContext driverContext) { + this.warnings = new Warnings(source); + this.val = val; + this.driverContext = driverContext; + } + + @Override + public Block eval(Page page) { + try (IntBlock valBlock = (IntBlock) val.eval(page)) { + IntVector valVector = valBlock.asVector(); + if (valVector == null) { + return eval(page.getPositionCount(), valBlock); + } + return eval(page.getPositionCount(), valVector).asBlock(); + } + } + + public DoubleBlock eval(int positionCount, IntBlock valBlock) { + try(DoubleBlock.Builder result = driverContext.blockFactory().newDoubleBlockBuilder(positionCount)) { + position: for (int p = 0; p < positionCount; p++) { + if (valBlock.isNull(p)) { + result.appendNull(); + continue position; + } + if (valBlock.getValueCount(p) != 1) { + if (valBlock.getValueCount(p) > 1) { + warnings.registerException(new IllegalArgumentException("single-value function encountered multi-value")); + } + result.appendNull(); + continue position; + } + result.appendDouble(Signum.process(valBlock.getInt(valBlock.getFirstValueIndex(p)))); + } + return result.build(); + } + } + + public DoubleVector eval(int positionCount, IntVector valVector) { + try(DoubleVector.Builder result = driverContext.blockFactory().newDoubleVectorBuilder(positionCount)) { + position: for (int p = 0; p < positionCount; p++) { + result.appendDouble(Signum.process(valVector.getInt(p))); + } + return result.build(); + } + } + + @Override + public String toString() { + return "SignumIntEvaluator[" + "val=" + val + "]"; + } + + @Override + public void close() { + Releasables.closeExpectNoException(val); + } + + static class Factory implements EvalOperator.ExpressionEvaluator.Factory { + private final Source source; + + private final EvalOperator.ExpressionEvaluator.Factory val; + + public Factory(Source source, EvalOperator.ExpressionEvaluator.Factory val) { + this.source = source; + this.val = val; + } + + @Override + public SignumIntEvaluator get(DriverContext context) { + return new SignumIntEvaluator(source, val.get(context), context); + } + + @Override + public String toString() { + return "SignumIntEvaluator[" + "val=" + val + "]"; + } + } +} diff --git a/x-pack/plugin/esql/src/main/java/generated/org/elasticsearch/xpack/esql/expression/function/scalar/math/SignumLongEvaluator.java b/x-pack/plugin/esql/src/main/java/generated/org/elasticsearch/xpack/esql/expression/function/scalar/math/SignumLongEvaluator.java new file mode 100644 index 0000000000000..0c4af4671672a --- /dev/null +++ b/x-pack/plugin/esql/src/main/java/generated/org/elasticsearch/xpack/esql/expression/function/scalar/math/SignumLongEvaluator.java @@ -0,0 +1,110 @@ +// Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one +// or more contributor license agreements. Licensed under the Elastic License +// 2.0; you may not use this file except in compliance with the Elastic License +// 2.0. +package org.elasticsearch.xpack.esql.expression.function.scalar.math; + +import java.lang.IllegalArgumentException; +import java.lang.Override; +import java.lang.String; +import org.elasticsearch.compute.data.Block; +import org.elasticsearch.compute.data.DoubleBlock; +import org.elasticsearch.compute.data.DoubleVector; +import org.elasticsearch.compute.data.LongBlock; +import org.elasticsearch.compute.data.LongVector; +import org.elasticsearch.compute.data.Page; +import org.elasticsearch.compute.operator.DriverContext; +import org.elasticsearch.compute.operator.EvalOperator; +import org.elasticsearch.core.Releasables; +import org.elasticsearch.xpack.esql.expression.function.Warnings; +import org.elasticsearch.xpack.ql.tree.Source; + +/** + * {@link EvalOperator.ExpressionEvaluator} implementation for {@link Signum}. + * This class is generated. Do not edit it. + */ +public final class SignumLongEvaluator implements EvalOperator.ExpressionEvaluator { + private final Warnings warnings; + + private final EvalOperator.ExpressionEvaluator val; + + private final DriverContext driverContext; + + public SignumLongEvaluator(Source source, EvalOperator.ExpressionEvaluator val, + DriverContext driverContext) { + this.warnings = new Warnings(source); + this.val = val; + this.driverContext = driverContext; + } + + @Override + public Block eval(Page page) { + try (LongBlock valBlock = (LongBlock) val.eval(page)) { + LongVector valVector = valBlock.asVector(); + if (valVector == null) { + return eval(page.getPositionCount(), valBlock); + } + return eval(page.getPositionCount(), valVector).asBlock(); + } + } + + public DoubleBlock eval(int positionCount, LongBlock valBlock) { + try(DoubleBlock.Builder result = driverContext.blockFactory().newDoubleBlockBuilder(positionCount)) { + position: for (int p = 0; p < positionCount; p++) { + if (valBlock.isNull(p)) { + result.appendNull(); + continue position; + } + if (valBlock.getValueCount(p) != 1) { + if (valBlock.getValueCount(p) > 1) { + warnings.registerException(new IllegalArgumentException("single-value function encountered multi-value")); + } + result.appendNull(); + continue position; + } + result.appendDouble(Signum.process(valBlock.getLong(valBlock.getFirstValueIndex(p)))); + } + return result.build(); + } + } + + public DoubleVector eval(int positionCount, LongVector valVector) { + try(DoubleVector.Builder result = driverContext.blockFactory().newDoubleVectorBuilder(positionCount)) { + position: for (int p = 0; p < positionCount; p++) { + result.appendDouble(Signum.process(valVector.getLong(p))); + } + return result.build(); + } + } + + @Override + public String toString() { + return "SignumLongEvaluator[" + "val=" + val + "]"; + } + + @Override + public void close() { + Releasables.closeExpectNoException(val); + } + + static class Factory implements EvalOperator.ExpressionEvaluator.Factory { + private final Source source; + + private final EvalOperator.ExpressionEvaluator.Factory val; + + public Factory(Source source, EvalOperator.ExpressionEvaluator.Factory val) { + this.source = source; + this.val = val; + } + + @Override + public SignumLongEvaluator get(DriverContext context) { + return new SignumLongEvaluator(source, val.get(context), context); + } + + @Override + public String toString() { + return "SignumLongEvaluator[" + "val=" + val + "]"; + } + } +} diff --git a/x-pack/plugin/esql/src/main/java/generated/org/elasticsearch/xpack/esql/expression/function/scalar/math/SignumUnsignedLongEvaluator.java b/x-pack/plugin/esql/src/main/java/generated/org/elasticsearch/xpack/esql/expression/function/scalar/math/SignumUnsignedLongEvaluator.java new file mode 100644 index 0000000000000..d3b20c98139c4 --- /dev/null +++ b/x-pack/plugin/esql/src/main/java/generated/org/elasticsearch/xpack/esql/expression/function/scalar/math/SignumUnsignedLongEvaluator.java @@ -0,0 +1,110 @@ +// Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one +// or more contributor license agreements. Licensed under the Elastic License +// 2.0; you may not use this file except in compliance with the Elastic License +// 2.0. +package org.elasticsearch.xpack.esql.expression.function.scalar.math; + +import java.lang.IllegalArgumentException; +import java.lang.Override; +import java.lang.String; +import org.elasticsearch.compute.data.Block; +import org.elasticsearch.compute.data.DoubleBlock; +import org.elasticsearch.compute.data.DoubleVector; +import org.elasticsearch.compute.data.LongBlock; +import org.elasticsearch.compute.data.LongVector; +import org.elasticsearch.compute.data.Page; +import org.elasticsearch.compute.operator.DriverContext; +import org.elasticsearch.compute.operator.EvalOperator; +import org.elasticsearch.core.Releasables; +import org.elasticsearch.xpack.esql.expression.function.Warnings; +import org.elasticsearch.xpack.ql.tree.Source; + +/** + * {@link EvalOperator.ExpressionEvaluator} implementation for {@link Signum}. + * This class is generated. Do not edit it. + */ +public final class SignumUnsignedLongEvaluator implements EvalOperator.ExpressionEvaluator { + private final Warnings warnings; + + private final EvalOperator.ExpressionEvaluator val; + + private final DriverContext driverContext; + + public SignumUnsignedLongEvaluator(Source source, EvalOperator.ExpressionEvaluator val, + DriverContext driverContext) { + this.warnings = new Warnings(source); + this.val = val; + this.driverContext = driverContext; + } + + @Override + public Block eval(Page page) { + try (LongBlock valBlock = (LongBlock) val.eval(page)) { + LongVector valVector = valBlock.asVector(); + if (valVector == null) { + return eval(page.getPositionCount(), valBlock); + } + return eval(page.getPositionCount(), valVector).asBlock(); + } + } + + public DoubleBlock eval(int positionCount, LongBlock valBlock) { + try(DoubleBlock.Builder result = driverContext.blockFactory().newDoubleBlockBuilder(positionCount)) { + position: for (int p = 0; p < positionCount; p++) { + if (valBlock.isNull(p)) { + result.appendNull(); + continue position; + } + if (valBlock.getValueCount(p) != 1) { + if (valBlock.getValueCount(p) > 1) { + warnings.registerException(new IllegalArgumentException("single-value function encountered multi-value")); + } + result.appendNull(); + continue position; + } + result.appendDouble(Signum.processUnsignedLong(valBlock.getLong(valBlock.getFirstValueIndex(p)))); + } + return result.build(); + } + } + + public DoubleVector eval(int positionCount, LongVector valVector) { + try(DoubleVector.Builder result = driverContext.blockFactory().newDoubleVectorBuilder(positionCount)) { + position: for (int p = 0; p < positionCount; p++) { + result.appendDouble(Signum.processUnsignedLong(valVector.getLong(p))); + } + return result.build(); + } + } + + @Override + public String toString() { + return "SignumUnsignedLongEvaluator[" + "val=" + val + "]"; + } + + @Override + public void close() { + Releasables.closeExpectNoException(val); + } + + static class Factory implements EvalOperator.ExpressionEvaluator.Factory { + private final Source source; + + private final EvalOperator.ExpressionEvaluator.Factory val; + + public Factory(Source source, EvalOperator.ExpressionEvaluator.Factory val) { + this.source = source; + this.val = val; + } + + @Override + public SignumUnsignedLongEvaluator get(DriverContext context) { + return new SignumUnsignedLongEvaluator(source, val.get(context), context); + } + + @Override + public String toString() { + return "SignumUnsignedLongEvaluator[" + "val=" + val + "]"; + } + } +} diff --git a/x-pack/plugin/esql/src/main/java/org/elasticsearch/xpack/esql/expression/function/EsqlFunctionRegistry.java b/x-pack/plugin/esql/src/main/java/org/elasticsearch/xpack/esql/expression/function/EsqlFunctionRegistry.java index 9f0976e0045d3..1a27c7b69c1e6 100644 --- a/x-pack/plugin/esql/src/main/java/org/elasticsearch/xpack/esql/expression/function/EsqlFunctionRegistry.java +++ b/x-pack/plugin/esql/src/main/java/org/elasticsearch/xpack/esql/expression/function/EsqlFunctionRegistry.java @@ -59,6 +59,7 @@ import org.elasticsearch.xpack.esql.expression.function.scalar.math.Pi; import org.elasticsearch.xpack.esql.expression.function.scalar.math.Pow; import org.elasticsearch.xpack.esql.expression.function.scalar.math.Round; +import org.elasticsearch.xpack.esql.expression.function.scalar.math.Signum; import org.elasticsearch.xpack.esql.expression.function.scalar.math.Sin; import org.elasticsearch.xpack.esql.expression.function.scalar.math.Sinh; import org.elasticsearch.xpack.esql.expression.function.scalar.math.Sqrt; @@ -152,6 +153,7 @@ private FunctionDefinition[][] functions() { def(Pi.class, Pi::new, "pi"), def(Pow.class, Pow::new, "pow"), def(Round.class, Round::new, "round"), + def(Signum.class, Signum::new, "signum"), def(Sin.class, Sin::new, "sin"), def(Sinh.class, Sinh::new, "sinh"), def(Sqrt.class, Sqrt::new, "sqrt"), diff --git a/x-pack/plugin/esql/src/main/java/org/elasticsearch/xpack/esql/expression/function/scalar/math/Signum.java b/x-pack/plugin/esql/src/main/java/org/elasticsearch/xpack/esql/expression/function/scalar/math/Signum.java new file mode 100644 index 0000000000000..ede41c10f3ac2 --- /dev/null +++ b/x-pack/plugin/esql/src/main/java/org/elasticsearch/xpack/esql/expression/function/scalar/math/Signum.java @@ -0,0 +1,102 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +package org.elasticsearch.xpack.esql.expression.function.scalar.math; + +import org.elasticsearch.compute.ann.Evaluator; +import org.elasticsearch.compute.operator.EvalOperator; +import org.elasticsearch.xpack.esql.EsqlIllegalArgumentException; +import org.elasticsearch.xpack.esql.expression.function.Example; +import org.elasticsearch.xpack.esql.expression.function.FunctionInfo; +import org.elasticsearch.xpack.esql.expression.function.Param; +import org.elasticsearch.xpack.esql.expression.function.scalar.UnaryScalarFunction; +import org.elasticsearch.xpack.ql.expression.Expression; +import org.elasticsearch.xpack.ql.tree.NodeInfo; +import org.elasticsearch.xpack.ql.tree.Source; +import org.elasticsearch.xpack.ql.type.DataType; +import org.elasticsearch.xpack.ql.type.DataTypes; +import org.elasticsearch.xpack.ql.util.NumericUtils; + +import java.util.List; +import java.util.function.Function; + +public class Signum extends UnaryScalarFunction { + @FunctionInfo( + returnType = { "double" }, + description = "Returns the sign of the given number.\n" + + "It returns `-1` for negative numbers, `0` for `0` and `1` for positive numbers.", + examples = @Example(file = "math", tag = "signum") + ) + public Signum( + Source source, + @Param( + name = "number", + type = { "double", "integer", "long", "unsigned_long" }, + description = "Numeric expression. If `null`, the function returns `null`." + ) Expression n + ) { + super(source, n); + } + + @Override + public EvalOperator.ExpressionEvaluator.Factory toEvaluator( + Function toEvaluator + ) { + var field = toEvaluator.apply(field()); + var fieldType = field().dataType(); + + if (fieldType == DataTypes.DOUBLE) { + return new SignumDoubleEvaluator.Factory(source(), field); + } + if (fieldType == DataTypes.INTEGER) { + return new SignumIntEvaluator.Factory(source(), field); + } + if (fieldType == DataTypes.LONG) { + return new SignumLongEvaluator.Factory(source(), field); + } + if (fieldType == DataTypes.UNSIGNED_LONG) { + return new SignumUnsignedLongEvaluator.Factory(source(), field); + } + + throw EsqlIllegalArgumentException.illegalDataType(fieldType); + } + + @Override + public Expression replaceChildren(List newChildren) { + return new Signum(source(), newChildren.get(0)); + } + + @Override + protected NodeInfo info() { + return NodeInfo.create(this, Signum::new, field()); + } + + @Override + public DataType dataType() { + return DataTypes.DOUBLE; + } + + @Evaluator(extraName = "Double") + static double process(double val) { + return Math.signum(val); + } + + @Evaluator(extraName = "Int") + static double process(int val) { + return Math.signum(val); + } + + @Evaluator(extraName = "Long") + static double process(long val) { + return Math.signum(val); + } + + @Evaluator(extraName = "UnsignedLong") + static double processUnsignedLong(long val) { + return Math.signum(NumericUtils.unsignedLongToDouble(val)); + } +} diff --git a/x-pack/plugin/esql/src/main/java/org/elasticsearch/xpack/esql/expression/function/scalar/package-info.java b/x-pack/plugin/esql/src/main/java/org/elasticsearch/xpack/esql/expression/function/scalar/package-info.java index f30425158b1b3..9469889285fd3 100644 --- a/x-pack/plugin/esql/src/main/java/org/elasticsearch/xpack/esql/expression/function/scalar/package-info.java +++ b/x-pack/plugin/esql/src/main/java/org/elasticsearch/xpack/esql/expression/function/scalar/package-info.java @@ -127,12 +127,21 @@ *
  • * Generate a syntax diagram and a table with supported types by running the tests via * gradle: {@code ./gradlew x-pack:plugin:esql:test} - * The generated files can be found here - * {@code docs/reference/esql/functions/signature/myfunction.svg } - * and here - * {@code docs/reference/esql/functions/types/myfunction.asciidoc} - * Make sure to commit them and reference them in your doc file. There are plenty of examples on how - * to reference those files e.g. {@code docs/reference/esql/functions/sin.asciidoc}. + * The generated files are + *
      + *
    1. {@code docs/reference/esql/functions/description/myfunction.asciidoc}
    2. + *
    3. {@code docs/reference/esql/functions/examples/myfunction.asciidoc}
    4. + *
    5. {@code docs/reference/esql/functions/layout/myfunction.asciidoc}
    6. + *
    7. {@code docs/reference/esql/functions/parameters/myfunction.asciidoc}
    8. + *
    9. {@code docs/reference/esql/functions/signature/myfunction.svg}
    10. + *
    11. {@code docs/reference/esql/functions/types/myfunction.asciidoc}
    12. + *
    + * + * Make sure to commit them. Add a reference to the + * {@code docs/reference/esql/functions/layout/myfunction.asciidoc} in the function list + * docs. There are plenty of examples on how + * to reference those files e.g. if you are writing a Math function, you will want to + * list it in {@code docs/reference/esql/functions/math-functions.asciidoc}. *
  • *
  • * Build the docs by cloning the docs repo diff --git a/x-pack/plugin/esql/src/main/java/org/elasticsearch/xpack/esql/io/stream/PlanNamedTypes.java b/x-pack/plugin/esql/src/main/java/org/elasticsearch/xpack/esql/io/stream/PlanNamedTypes.java index 21c17110ad4fe..a85ddac532241 100644 --- a/x-pack/plugin/esql/src/main/java/org/elasticsearch/xpack/esql/io/stream/PlanNamedTypes.java +++ b/x-pack/plugin/esql/src/main/java/org/elasticsearch/xpack/esql/io/stream/PlanNamedTypes.java @@ -80,6 +80,7 @@ import org.elasticsearch.xpack.esql.expression.function.scalar.math.Pi; import org.elasticsearch.xpack.esql.expression.function.scalar.math.Pow; import org.elasticsearch.xpack.esql.expression.function.scalar.math.Round; +import org.elasticsearch.xpack.esql.expression.function.scalar.math.Signum; import org.elasticsearch.xpack.esql.expression.function.scalar.math.Sin; import org.elasticsearch.xpack.esql.expression.function.scalar.math.Sinh; import org.elasticsearch.xpack.esql.expression.function.scalar.math.Sqrt; @@ -349,6 +350,7 @@ public static List namedTypeEntries() { of(ESQL_UNARY_SCLR_CLS, Log10.class, PlanNamedTypes::writeESQLUnaryScalar, PlanNamedTypes::readESQLUnaryScalar), of(ESQL_UNARY_SCLR_CLS, LTrim.class, PlanNamedTypes::writeESQLUnaryScalar, PlanNamedTypes::readESQLUnaryScalar), of(ESQL_UNARY_SCLR_CLS, RTrim.class, PlanNamedTypes::writeESQLUnaryScalar, PlanNamedTypes::readESQLUnaryScalar), + of(ESQL_UNARY_SCLR_CLS, Signum.class, PlanNamedTypes::writeESQLUnaryScalar, PlanNamedTypes::readESQLUnaryScalar), of(ESQL_UNARY_SCLR_CLS, Sin.class, PlanNamedTypes::writeESQLUnaryScalar, PlanNamedTypes::readESQLUnaryScalar), of(ESQL_UNARY_SCLR_CLS, Sinh.class, PlanNamedTypes::writeESQLUnaryScalar, PlanNamedTypes::readESQLUnaryScalar), of(ESQL_UNARY_SCLR_CLS, Sqrt.class, PlanNamedTypes::writeESQLUnaryScalar, PlanNamedTypes::readESQLUnaryScalar), @@ -1296,6 +1298,7 @@ static void writeBinaryLogic(PlanStreamOutput out, BinaryLogic binaryLogic) thro entry(name(LTrim.class), LTrim::new), entry(name(RTrim.class), RTrim::new), entry(name(Neg.class), Neg::new), + entry(name(Signum.class), Signum::new), entry(name(Sin.class), Sin::new), entry(name(Sinh.class), Sinh::new), entry(name(Sqrt.class), Sqrt::new), diff --git a/x-pack/plugin/esql/src/test/java/org/elasticsearch/xpack/esql/expression/function/scalar/math/SignumTests.java b/x-pack/plugin/esql/src/test/java/org/elasticsearch/xpack/esql/expression/function/scalar/math/SignumTests.java new file mode 100644 index 0000000000000..4167029010950 --- /dev/null +++ b/x-pack/plugin/esql/src/test/java/org/elasticsearch/xpack/esql/expression/function/scalar/math/SignumTests.java @@ -0,0 +1,82 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +package org.elasticsearch.xpack.esql.expression.function.scalar.math; + +import com.carrotsearch.randomizedtesting.annotations.Name; +import com.carrotsearch.randomizedtesting.annotations.ParametersFactory; + +import org.elasticsearch.xpack.esql.expression.function.AbstractFunctionTestCase; +import org.elasticsearch.xpack.esql.expression.function.TestCaseSupplier; +import org.elasticsearch.xpack.ql.expression.Expression; +import org.elasticsearch.xpack.ql.tree.Source; +import org.elasticsearch.xpack.ql.type.DataTypes; +import org.elasticsearch.xpack.ql.util.NumericUtils; + +import java.math.BigInteger; +import java.util.ArrayList; +import java.util.List; +import java.util.function.Supplier; + +public class SignumTests extends AbstractFunctionTestCase { + public SignumTests(@Name("TestCase") Supplier testCaseSupplier) { + this.testCase = testCaseSupplier.get(); + } + + @ParametersFactory + public static Iterable parameters() { + String read = "Attribute[channel=0]"; + List suppliers = new ArrayList<>(); + TestCaseSupplier.forUnaryInt( + suppliers, + "SignumIntEvaluator[val=" + read + "]", + DataTypes.DOUBLE, + i -> (double) Math.signum(i), + Integer.MIN_VALUE, + Integer.MAX_VALUE, + List.of() + ); + + TestCaseSupplier.forUnaryLong( + suppliers, + "SignumLongEvaluator[val=" + read + "]", + DataTypes.DOUBLE, + l -> (double) Math.signum(l), + Long.MIN_VALUE, + Long.MAX_VALUE, + List.of() + ); + + TestCaseSupplier.forUnaryUnsignedLong( + suppliers, + "SignumUnsignedLongEvaluator[val=" + read + "]", + DataTypes.DOUBLE, + ul -> Math.signum(NumericUtils.unsignedLongToDouble(NumericUtils.asLongUnsigned(ul))), + BigInteger.ZERO, + UNSIGNED_LONG_MAX, + List.of() + ); + TestCaseSupplier.forUnaryDouble( + suppliers, + "SignumDoubleEvaluator[val=" + read + "]", + DataTypes.DOUBLE, + Math::signum, + -Double.MAX_VALUE, + Double.MAX_VALUE, + List.of() + ); + + suppliers = anyNullIsNull(true, suppliers); + + return parameterSuppliersFromTypedData(errorsForCasesWithoutExamples(suppliers)); + } + + @Override + protected Expression build(Source source, List args) { + return new Signum(source, args.get(0)); + } +}