diff --git a/docs/reference/query-languages/esql/_snippets/functions/description/scalb.md b/docs/reference/query-languages/esql/_snippets/functions/description/scalb.md new file mode 100644 index 0000000000000..59c1621c689d8 --- /dev/null +++ b/docs/reference/query-languages/esql/_snippets/functions/description/scalb.md @@ -0,0 +1,6 @@ +% This is generated by ESQL's AbstractFunctionTestCase. Do no edit it. See ../README.md for how to regenerate it. + +**Description** + +Returns the result of `d * 2 ^ scaleFactor`, Similar to Java's `scalb` function. Result is rounded as if performed by a single correctly rounded floating-point multiply to a member of the double value set. + diff --git a/docs/reference/query-languages/esql/_snippets/functions/examples/scalb.md b/docs/reference/query-languages/esql/_snippets/functions/examples/scalb.md new file mode 100644 index 0000000000000..1b4b76dd72e4d --- /dev/null +++ b/docs/reference/query-languages/esql/_snippets/functions/examples/scalb.md @@ -0,0 +1,13 @@ +% This is generated by ESQL's AbstractFunctionTestCase. Do no edit it. See ../README.md for how to regenerate it. + +**Example** + +```esql +row x = 3.0, y = 10 | eval z = scalb(x, y) +``` + +| x:double | y:integer | z:double | +| --- | --- | --- | +| 3.0 | 10 | 3072.0 | + + diff --git a/docs/reference/query-languages/esql/_snippets/functions/layout/scalb.md b/docs/reference/query-languages/esql/_snippets/functions/layout/scalb.md new file mode 100644 index 0000000000000..15022863d204b --- /dev/null +++ b/docs/reference/query-languages/esql/_snippets/functions/layout/scalb.md @@ -0,0 +1,23 @@ +% This is generated by ESQL's AbstractFunctionTestCase. Do no edit it. See ../README.md for how to regenerate it. + +## `SCALB` [esql-scalb] + +**Syntax** + +:::{image} ../../../images/functions/scalb.svg +:alt: Embedded +:class: text-center +::: + + +:::{include} ../parameters/scalb.md +::: + +:::{include} ../description/scalb.md +::: + +:::{include} ../types/scalb.md +::: + +:::{include} ../examples/scalb.md +::: diff --git a/docs/reference/query-languages/esql/_snippets/functions/parameters/scalb.md b/docs/reference/query-languages/esql/_snippets/functions/parameters/scalb.md new file mode 100644 index 0000000000000..4e4e726c372dc --- /dev/null +++ b/docs/reference/query-languages/esql/_snippets/functions/parameters/scalb.md @@ -0,0 +1,10 @@ +% This is generated by ESQL's AbstractFunctionTestCase. Do no edit it. See ../README.md for how to regenerate it. + +**Parameters** + +`d` +: Numeric expression for the multiplier. If `null`, the function returns `null`. + +`scaleFactor` +: Numeric expression for the scale factor. If `null`, the function returns `null`. + diff --git a/docs/reference/query-languages/esql/_snippets/functions/types/scalb.md b/docs/reference/query-languages/esql/_snippets/functions/types/scalb.md new file mode 100644 index 0000000000000..4fab8db35a531 --- /dev/null +++ b/docs/reference/query-languages/esql/_snippets/functions/types/scalb.md @@ -0,0 +1,15 @@ +% This is generated by ESQL's AbstractFunctionTestCase. Do no edit it. See ../README.md for how to regenerate it. + +**Supported types** + +| d | scaleFactor | result | +| --- | --- | --- | +| double | integer | double | +| double | long | double | +| integer | integer | double | +| integer | long | double | +| long | integer | double | +| long | long | double | +| unsigned_long | integer | double | +| unsigned_long | long | double | + diff --git a/docs/reference/query-languages/esql/_snippets/lists/math-functions.md b/docs/reference/query-languages/esql/_snippets/lists/math-functions.md index 0c3d0373ad243..5c858d3a7147a 100644 --- a/docs/reference/query-languages/esql/_snippets/lists/math-functions.md +++ b/docs/reference/query-languages/esql/_snippets/lists/math-functions.md @@ -16,6 +16,7 @@ * [`PI`](../../functions-operators/math-functions.md#esql-pi) * [`POW`](../../functions-operators/math-functions.md#esql-pow) * [`ROUND`](../../functions-operators/math-functions.md#esql-round) +* [`SCALB`](../../functions-operators/math-functions.md#esql-scalb) * [`SIGNUM`](../../functions-operators/math-functions.md#esql-signum) * [`SIN`](../../functions-operators/math-functions.md#esql-sin) * [`SINH`](../../functions-operators/math-functions.md#esql-sinh) diff --git a/docs/reference/query-languages/esql/functions-operators/math-functions.md b/docs/reference/query-languages/esql/functions-operators/math-functions.md index eda587ac26540..08d7da548d4aa 100644 --- a/docs/reference/query-languages/esql/functions-operators/math-functions.md +++ b/docs/reference/query-languages/esql/functions-operators/math-functions.md @@ -66,6 +66,9 @@ mapped_pages: :::{include} ../_snippets/functions/layout/round.md ::: +:::{include} ../_snippets/functions/layout/scalb.md +::: + :::{include} ../_snippets/functions/layout/signum.md ::: diff --git a/docs/reference/query-languages/esql/images/functions/scalb.svg b/docs/reference/query-languages/esql/images/functions/scalb.svg new file mode 100644 index 0000000000000..161e05c163c91 --- /dev/null +++ b/docs/reference/query-languages/esql/images/functions/scalb.svg @@ -0,0 +1 @@ +SCALB(d,scaleFactor) \ No newline at end of file diff --git a/docs/reference/query-languages/esql/kibana/definition/functions/scalb.json b/docs/reference/query-languages/esql/kibana/definition/functions/scalb.json new file mode 100644 index 0000000000000..96e5003eee95c --- /dev/null +++ b/docs/reference/query-languages/esql/kibana/definition/functions/scalb.json @@ -0,0 +1,157 @@ +{ + "comment" : "This is generated by ESQL’s AbstractFunctionTestCase. Do no edit it. See ../README.md for how to regenerate it.", + "type" : "scalar", + "name" : "scalb", + "description" : "Returns the result of `d * 2 ^ scaleFactor`,\nSimilar to Java's `scalb` function. Result is rounded as if\nperformed by a single correctly rounded floating-point multiply\nto a member of the double value set.", + "signatures" : [ + { + "params" : [ + { + "name" : "d", + "type" : "double", + "optional" : false, + "description" : "Numeric expression for the multiplier. If `null`, the function returns `null`." + }, + { + "name" : "scaleFactor", + "type" : "integer", + "optional" : false, + "description" : "Numeric expression for the scale factor. If `null`, the function returns `null`." + } + ], + "variadic" : false, + "returnType" : "double" + }, + { + "params" : [ + { + "name" : "d", + "type" : "double", + "optional" : false, + "description" : "Numeric expression for the multiplier. If `null`, the function returns `null`." + }, + { + "name" : "scaleFactor", + "type" : "long", + "optional" : false, + "description" : "Numeric expression for the scale factor. If `null`, the function returns `null`." + } + ], + "variadic" : false, + "returnType" : "double" + }, + { + "params" : [ + { + "name" : "d", + "type" : "integer", + "optional" : false, + "description" : "Numeric expression for the multiplier. If `null`, the function returns `null`." + }, + { + "name" : "scaleFactor", + "type" : "integer", + "optional" : false, + "description" : "Numeric expression for the scale factor. If `null`, the function returns `null`." + } + ], + "variadic" : false, + "returnType" : "double" + }, + { + "params" : [ + { + "name" : "d", + "type" : "integer", + "optional" : false, + "description" : "Numeric expression for the multiplier. If `null`, the function returns `null`." + }, + { + "name" : "scaleFactor", + "type" : "long", + "optional" : false, + "description" : "Numeric expression for the scale factor. If `null`, the function returns `null`." + } + ], + "variadic" : false, + "returnType" : "double" + }, + { + "params" : [ + { + "name" : "d", + "type" : "long", + "optional" : false, + "description" : "Numeric expression for the multiplier. If `null`, the function returns `null`." + }, + { + "name" : "scaleFactor", + "type" : "integer", + "optional" : false, + "description" : "Numeric expression for the scale factor. If `null`, the function returns `null`." + } + ], + "variadic" : false, + "returnType" : "double" + }, + { + "params" : [ + { + "name" : "d", + "type" : "long", + "optional" : false, + "description" : "Numeric expression for the multiplier. If `null`, the function returns `null`." + }, + { + "name" : "scaleFactor", + "type" : "long", + "optional" : false, + "description" : "Numeric expression for the scale factor. If `null`, the function returns `null`." + } + ], + "variadic" : false, + "returnType" : "double" + }, + { + "params" : [ + { + "name" : "d", + "type" : "unsigned_long", + "optional" : false, + "description" : "Numeric expression for the multiplier. If `null`, the function returns `null`." + }, + { + "name" : "scaleFactor", + "type" : "integer", + "optional" : false, + "description" : "Numeric expression for the scale factor. If `null`, the function returns `null`." + } + ], + "variadic" : false, + "returnType" : "double" + }, + { + "params" : [ + { + "name" : "d", + "type" : "unsigned_long", + "optional" : false, + "description" : "Numeric expression for the multiplier. If `null`, the function returns `null`." + }, + { + "name" : "scaleFactor", + "type" : "long", + "optional" : false, + "description" : "Numeric expression for the scale factor. If `null`, the function returns `null`." + } + ], + "variadic" : false, + "returnType" : "double" + } + ], + "examples" : [ + "row x = 3.0, y = 10 | eval z = scalb(x, y)" + ], + "preview" : false, + "snapshot_only" : false +} diff --git a/docs/reference/query-languages/esql/kibana/docs/functions/scalb.md b/docs/reference/query-languages/esql/kibana/docs/functions/scalb.md new file mode 100644 index 0000000000000..1f3426cc5b703 --- /dev/null +++ b/docs/reference/query-languages/esql/kibana/docs/functions/scalb.md @@ -0,0 +1,11 @@ +% This is generated by ESQL's AbstractFunctionTestCase. Do no edit it. See ../README.md for how to regenerate it. + +### SCALB +Returns the result of `d * 2 ^ scaleFactor`, +Similar to Java's `scalb` function. Result is rounded as if +performed by a single correctly rounded floating-point multiply +to a member of the double value set. + +```esql +row x = 3.0, y = 10 | eval z = scalb(x, y) +``` 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 0e5b28b6966af..1128cbc6f4106 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 @@ -592,3 +592,103 @@ s:double | emp_no:integer | salary:integer | salary_change:double -1.0 | 10004 | 36174 | [-0.35, 1.13, 3.65, 13.48] -1.0 | 10005 | 63528 | [-2.14, 13.07] ; + +docsScalb +required_capability: fn_scalb +// tag::scalb[] +row x = 3.0, y = 10 | eval z = scalb(x, y) +// end::scalb[] +; + +// tag::scalb-result[] +x:double | y:integer | z:double +3.0 | 10 | 3072.0 +// end::scalb-result[] +; + +scalbConstant +required_capability: fn_scalb +row y = "foo" | eval x = scalb(3, 10) | keep x; + +x:double +3072.0 +; + +scalbWithNonFoldableArgsAndWhere +required_capability: fn_scalb + +from employees +| eval s = scalb(mv_min(salary_change), emp_no / 1000) +| where scalb(mv_min(salary_change), 3) > 100 +| sort s +| keep s; + +s:double +13127.68 +13936.64 +; + +scalbWithEvalAndSort +required_capability: fn_scalb +from employees +| eval s = scalb(mv_min(salary_change), 5) +| where scalb(mv_max(salary_change), 5) > 100 +| keep s, emp_no, salary, salary_change +| sort s, emp_no +| limit 3; + +s:double | emp_no:integer | salary:integer | salary_change:double +-313.92 | 10065 | 50249 | [-9.81, -1.47, 14.44] +-296.96 | 10042 | 30404 | [-9.28, 9.42] +-295.36 | 10091 | 38645 | [-9.23, 5.19, 5.85, 7.5] +; + +scalbWithHugeScaleFactor +required_capability: fn_scalb + +from employees +| eval s = scalb(abs(mv_min(salary_change)), 2147483648 + emp_no) +| keep s +| limit 3 +; +warning: Line 2:12: evaluation of [scalb(abs(mv_min(salary_change)), 2147483648 + emp_no)] failed, treating result as null. Only first 20 failures recorded. +warning: Line 2:12: java.lang.ArithmeticException: integer overflow + +s:double +null +null +null +; +scalbWithHugeConstantScaleFactor +required_capability: fn_scalb + +from employees +| eval s = scalb(abs(mv_min(salary_change)), 2147483648) +| keep s +| limit 3 +; +warning: Line 2:12: evaluation of [scalb(abs(mv_min(salary_change)), 2147483648)] failed, treating result as null. Only first 20 failures recorded. +warning: Line 2:12: java.lang.ArithmeticException: integer overflow + +s:double +null +null +null +; + +scalbWithHugeconstantFirstArgument +required_capability: fn_scalb + +from employees +| eval s = scalb(2147483648, emp_no) +| keep s +| limit 3 +; +warning: Line 2:12: evaluation of [scalb(2147483648, emp_no)] failed, treating result as null. Only first 20 failures recorded. +warning: Line 2:12: java.lang.ArithmeticException: not a finite double number: Infinity + +s:double +null +null +null +; diff --git a/x-pack/plugin/esql/src/main/generated/org/elasticsearch/xpack/esql/expression/function/scalar/math/ScalbConstantIntEvaluator.java b/x-pack/plugin/esql/src/main/generated/org/elasticsearch/xpack/esql/expression/function/scalar/math/ScalbConstantIntEvaluator.java new file mode 100644 index 0000000000000..1b68a10a8a976 --- /dev/null +++ b/x-pack/plugin/esql/src/main/generated/org/elasticsearch/xpack/esql/expression/function/scalar/math/ScalbConstantIntEvaluator.java @@ -0,0 +1,139 @@ +// 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.ArithmeticException; +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.compute.operator.Warnings; +import org.elasticsearch.core.Releasables; +import org.elasticsearch.xpack.esql.core.tree.Source; + +/** + * {@link EvalOperator.ExpressionEvaluator} implementation for {@link Scalb}. + * This class is generated. Edit {@code EvaluatorImplementer} instead. + */ +public final class ScalbConstantIntEvaluator implements EvalOperator.ExpressionEvaluator { + private final Source source; + + private final EvalOperator.ExpressionEvaluator d; + + private final int scaleFactor; + + private final DriverContext driverContext; + + private Warnings warnings; + + public ScalbConstantIntEvaluator(Source source, EvalOperator.ExpressionEvaluator d, + int scaleFactor, DriverContext driverContext) { + this.source = source; + this.d = d; + this.scaleFactor = scaleFactor; + this.driverContext = driverContext; + } + + @Override + public Block eval(Page page) { + try (DoubleBlock dBlock = (DoubleBlock) d.eval(page)) { + DoubleVector dVector = dBlock.asVector(); + if (dVector == null) { + return eval(page.getPositionCount(), dBlock); + } + return eval(page.getPositionCount(), dVector); + } + } + + public DoubleBlock eval(int positionCount, DoubleBlock dBlock) { + try(DoubleBlock.Builder result = driverContext.blockFactory().newDoubleBlockBuilder(positionCount)) { + position: for (int p = 0; p < positionCount; p++) { + if (dBlock.isNull(p)) { + result.appendNull(); + continue position; + } + if (dBlock.getValueCount(p) != 1) { + if (dBlock.getValueCount(p) > 1) { + warnings().registerException(new IllegalArgumentException("single-value function encountered multi-value")); + } + result.appendNull(); + continue position; + } + try { + result.appendDouble(Scalb.processConstantInt(dBlock.getDouble(dBlock.getFirstValueIndex(p)), this.scaleFactor)); + } catch (ArithmeticException e) { + warnings().registerException(e); + result.appendNull(); + } + } + return result.build(); + } + } + + public DoubleBlock eval(int positionCount, DoubleVector dVector) { + try(DoubleBlock.Builder result = driverContext.blockFactory().newDoubleBlockBuilder(positionCount)) { + position: for (int p = 0; p < positionCount; p++) { + try { + result.appendDouble(Scalb.processConstantInt(dVector.getDouble(p), this.scaleFactor)); + } catch (ArithmeticException e) { + warnings().registerException(e); + result.appendNull(); + } + } + return result.build(); + } + } + + @Override + public String toString() { + return "ScalbConstantIntEvaluator[" + "d=" + d + ", scaleFactor=" + scaleFactor + "]"; + } + + @Override + public void close() { + Releasables.closeExpectNoException(d); + } + + private Warnings warnings() { + if (warnings == null) { + this.warnings = Warnings.createWarnings( + driverContext.warningsMode(), + source.source().getLineNumber(), + source.source().getColumnNumber(), + source.text() + ); + } + return warnings; + } + + static class Factory implements EvalOperator.ExpressionEvaluator.Factory { + private final Source source; + + private final EvalOperator.ExpressionEvaluator.Factory d; + + private final int scaleFactor; + + public Factory(Source source, EvalOperator.ExpressionEvaluator.Factory d, int scaleFactor) { + this.source = source; + this.d = d; + this.scaleFactor = scaleFactor; + } + + @Override + public ScalbConstantIntEvaluator get(DriverContext context) { + return new ScalbConstantIntEvaluator(source, d.get(context), scaleFactor, context); + } + + @Override + public String toString() { + return "ScalbConstantIntEvaluator[" + "d=" + d + ", scaleFactor=" + scaleFactor + "]"; + } + } +} diff --git a/x-pack/plugin/esql/src/main/generated/org/elasticsearch/xpack/esql/expression/function/scalar/math/ScalbConstantLongEvaluator.java b/x-pack/plugin/esql/src/main/generated/org/elasticsearch/xpack/esql/expression/function/scalar/math/ScalbConstantLongEvaluator.java new file mode 100644 index 0000000000000..0e970db07a280 --- /dev/null +++ b/x-pack/plugin/esql/src/main/generated/org/elasticsearch/xpack/esql/expression/function/scalar/math/ScalbConstantLongEvaluator.java @@ -0,0 +1,139 @@ +// 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.ArithmeticException; +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.compute.operator.Warnings; +import org.elasticsearch.core.Releasables; +import org.elasticsearch.xpack.esql.core.tree.Source; + +/** + * {@link EvalOperator.ExpressionEvaluator} implementation for {@link Scalb}. + * This class is generated. Edit {@code EvaluatorImplementer} instead. + */ +public final class ScalbConstantLongEvaluator implements EvalOperator.ExpressionEvaluator { + private final Source source; + + private final EvalOperator.ExpressionEvaluator d; + + private final long scaleFactor; + + private final DriverContext driverContext; + + private Warnings warnings; + + public ScalbConstantLongEvaluator(Source source, EvalOperator.ExpressionEvaluator d, + long scaleFactor, DriverContext driverContext) { + this.source = source; + this.d = d; + this.scaleFactor = scaleFactor; + this.driverContext = driverContext; + } + + @Override + public Block eval(Page page) { + try (DoubleBlock dBlock = (DoubleBlock) d.eval(page)) { + DoubleVector dVector = dBlock.asVector(); + if (dVector == null) { + return eval(page.getPositionCount(), dBlock); + } + return eval(page.getPositionCount(), dVector); + } + } + + public DoubleBlock eval(int positionCount, DoubleBlock dBlock) { + try(DoubleBlock.Builder result = driverContext.blockFactory().newDoubleBlockBuilder(positionCount)) { + position: for (int p = 0; p < positionCount; p++) { + if (dBlock.isNull(p)) { + result.appendNull(); + continue position; + } + if (dBlock.getValueCount(p) != 1) { + if (dBlock.getValueCount(p) > 1) { + warnings().registerException(new IllegalArgumentException("single-value function encountered multi-value")); + } + result.appendNull(); + continue position; + } + try { + result.appendDouble(Scalb.processConstantLong(dBlock.getDouble(dBlock.getFirstValueIndex(p)), this.scaleFactor)); + } catch (ArithmeticException e) { + warnings().registerException(e); + result.appendNull(); + } + } + return result.build(); + } + } + + public DoubleBlock eval(int positionCount, DoubleVector dVector) { + try(DoubleBlock.Builder result = driverContext.blockFactory().newDoubleBlockBuilder(positionCount)) { + position: for (int p = 0; p < positionCount; p++) { + try { + result.appendDouble(Scalb.processConstantLong(dVector.getDouble(p), this.scaleFactor)); + } catch (ArithmeticException e) { + warnings().registerException(e); + result.appendNull(); + } + } + return result.build(); + } + } + + @Override + public String toString() { + return "ScalbConstantLongEvaluator[" + "d=" + d + ", scaleFactor=" + scaleFactor + "]"; + } + + @Override + public void close() { + Releasables.closeExpectNoException(d); + } + + private Warnings warnings() { + if (warnings == null) { + this.warnings = Warnings.createWarnings( + driverContext.warningsMode(), + source.source().getLineNumber(), + source.source().getColumnNumber(), + source.text() + ); + } + return warnings; + } + + static class Factory implements EvalOperator.ExpressionEvaluator.Factory { + private final Source source; + + private final EvalOperator.ExpressionEvaluator.Factory d; + + private final long scaleFactor; + + public Factory(Source source, EvalOperator.ExpressionEvaluator.Factory d, long scaleFactor) { + this.source = source; + this.d = d; + this.scaleFactor = scaleFactor; + } + + @Override + public ScalbConstantLongEvaluator get(DriverContext context) { + return new ScalbConstantLongEvaluator(source, d.get(context), scaleFactor, context); + } + + @Override + public String toString() { + return "ScalbConstantLongEvaluator[" + "d=" + d + ", scaleFactor=" + scaleFactor + "]"; + } + } +} diff --git a/x-pack/plugin/esql/src/main/generated/org/elasticsearch/xpack/esql/expression/function/scalar/math/ScalbIntEvaluator.java b/x-pack/plugin/esql/src/main/generated/org/elasticsearch/xpack/esql/expression/function/scalar/math/ScalbIntEvaluator.java new file mode 100644 index 0000000000000..510f736f55a32 --- /dev/null +++ b/x-pack/plugin/esql/src/main/generated/org/elasticsearch/xpack/esql/expression/function/scalar/math/ScalbIntEvaluator.java @@ -0,0 +1,159 @@ +// 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.ArithmeticException; +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.compute.operator.Warnings; +import org.elasticsearch.core.Releasables; +import org.elasticsearch.xpack.esql.core.tree.Source; + +/** + * {@link EvalOperator.ExpressionEvaluator} implementation for {@link Scalb}. + * This class is generated. Edit {@code EvaluatorImplementer} instead. + */ +public final class ScalbIntEvaluator implements EvalOperator.ExpressionEvaluator { + private final Source source; + + private final EvalOperator.ExpressionEvaluator d; + + private final EvalOperator.ExpressionEvaluator scaleFactor; + + private final DriverContext driverContext; + + private Warnings warnings; + + public ScalbIntEvaluator(Source source, EvalOperator.ExpressionEvaluator d, + EvalOperator.ExpressionEvaluator scaleFactor, DriverContext driverContext) { + this.source = source; + this.d = d; + this.scaleFactor = scaleFactor; + this.driverContext = driverContext; + } + + @Override + public Block eval(Page page) { + try (DoubleBlock dBlock = (DoubleBlock) d.eval(page)) { + try (IntBlock scaleFactorBlock = (IntBlock) scaleFactor.eval(page)) { + DoubleVector dVector = dBlock.asVector(); + if (dVector == null) { + return eval(page.getPositionCount(), dBlock, scaleFactorBlock); + } + IntVector scaleFactorVector = scaleFactorBlock.asVector(); + if (scaleFactorVector == null) { + return eval(page.getPositionCount(), dBlock, scaleFactorBlock); + } + return eval(page.getPositionCount(), dVector, scaleFactorVector); + } + } + } + + public DoubleBlock eval(int positionCount, DoubleBlock dBlock, IntBlock scaleFactorBlock) { + try(DoubleBlock.Builder result = driverContext.blockFactory().newDoubleBlockBuilder(positionCount)) { + position: for (int p = 0; p < positionCount; p++) { + if (dBlock.isNull(p)) { + result.appendNull(); + continue position; + } + if (dBlock.getValueCount(p) != 1) { + if (dBlock.getValueCount(p) > 1) { + warnings().registerException(new IllegalArgumentException("single-value function encountered multi-value")); + } + result.appendNull(); + continue position; + } + if (scaleFactorBlock.isNull(p)) { + result.appendNull(); + continue position; + } + if (scaleFactorBlock.getValueCount(p) != 1) { + if (scaleFactorBlock.getValueCount(p) > 1) { + warnings().registerException(new IllegalArgumentException("single-value function encountered multi-value")); + } + result.appendNull(); + continue position; + } + try { + result.appendDouble(Scalb.process(dBlock.getDouble(dBlock.getFirstValueIndex(p)), scaleFactorBlock.getInt(scaleFactorBlock.getFirstValueIndex(p)))); + } catch (ArithmeticException e) { + warnings().registerException(e); + result.appendNull(); + } + } + return result.build(); + } + } + + public DoubleBlock eval(int positionCount, DoubleVector dVector, IntVector scaleFactorVector) { + try(DoubleBlock.Builder result = driverContext.blockFactory().newDoubleBlockBuilder(positionCount)) { + position: for (int p = 0; p < positionCount; p++) { + try { + result.appendDouble(Scalb.process(dVector.getDouble(p), scaleFactorVector.getInt(p))); + } catch (ArithmeticException e) { + warnings().registerException(e); + result.appendNull(); + } + } + return result.build(); + } + } + + @Override + public String toString() { + return "ScalbIntEvaluator[" + "d=" + d + ", scaleFactor=" + scaleFactor + "]"; + } + + @Override + public void close() { + Releasables.closeExpectNoException(d, scaleFactor); + } + + private Warnings warnings() { + if (warnings == null) { + this.warnings = Warnings.createWarnings( + driverContext.warningsMode(), + source.source().getLineNumber(), + source.source().getColumnNumber(), + source.text() + ); + } + return warnings; + } + + static class Factory implements EvalOperator.ExpressionEvaluator.Factory { + private final Source source; + + private final EvalOperator.ExpressionEvaluator.Factory d; + + private final EvalOperator.ExpressionEvaluator.Factory scaleFactor; + + public Factory(Source source, EvalOperator.ExpressionEvaluator.Factory d, + EvalOperator.ExpressionEvaluator.Factory scaleFactor) { + this.source = source; + this.d = d; + this.scaleFactor = scaleFactor; + } + + @Override + public ScalbIntEvaluator get(DriverContext context) { + return new ScalbIntEvaluator(source, d.get(context), scaleFactor.get(context), context); + } + + @Override + public String toString() { + return "ScalbIntEvaluator[" + "d=" + d + ", scaleFactor=" + scaleFactor + "]"; + } + } +} diff --git a/x-pack/plugin/esql/src/main/generated/org/elasticsearch/xpack/esql/expression/function/scalar/math/ScalbLongEvaluator.java b/x-pack/plugin/esql/src/main/generated/org/elasticsearch/xpack/esql/expression/function/scalar/math/ScalbLongEvaluator.java new file mode 100644 index 0000000000000..7e24b28332dc3 --- /dev/null +++ b/x-pack/plugin/esql/src/main/generated/org/elasticsearch/xpack/esql/expression/function/scalar/math/ScalbLongEvaluator.java @@ -0,0 +1,159 @@ +// 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.ArithmeticException; +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.compute.operator.Warnings; +import org.elasticsearch.core.Releasables; +import org.elasticsearch.xpack.esql.core.tree.Source; + +/** + * {@link EvalOperator.ExpressionEvaluator} implementation for {@link Scalb}. + * This class is generated. Edit {@code EvaluatorImplementer} instead. + */ +public final class ScalbLongEvaluator implements EvalOperator.ExpressionEvaluator { + private final Source source; + + private final EvalOperator.ExpressionEvaluator d; + + private final EvalOperator.ExpressionEvaluator scaleFactor; + + private final DriverContext driverContext; + + private Warnings warnings; + + public ScalbLongEvaluator(Source source, EvalOperator.ExpressionEvaluator d, + EvalOperator.ExpressionEvaluator scaleFactor, DriverContext driverContext) { + this.source = source; + this.d = d; + this.scaleFactor = scaleFactor; + this.driverContext = driverContext; + } + + @Override + public Block eval(Page page) { + try (DoubleBlock dBlock = (DoubleBlock) d.eval(page)) { + try (LongBlock scaleFactorBlock = (LongBlock) scaleFactor.eval(page)) { + DoubleVector dVector = dBlock.asVector(); + if (dVector == null) { + return eval(page.getPositionCount(), dBlock, scaleFactorBlock); + } + LongVector scaleFactorVector = scaleFactorBlock.asVector(); + if (scaleFactorVector == null) { + return eval(page.getPositionCount(), dBlock, scaleFactorBlock); + } + return eval(page.getPositionCount(), dVector, scaleFactorVector); + } + } + } + + public DoubleBlock eval(int positionCount, DoubleBlock dBlock, LongBlock scaleFactorBlock) { + try(DoubleBlock.Builder result = driverContext.blockFactory().newDoubleBlockBuilder(positionCount)) { + position: for (int p = 0; p < positionCount; p++) { + if (dBlock.isNull(p)) { + result.appendNull(); + continue position; + } + if (dBlock.getValueCount(p) != 1) { + if (dBlock.getValueCount(p) > 1) { + warnings().registerException(new IllegalArgumentException("single-value function encountered multi-value")); + } + result.appendNull(); + continue position; + } + if (scaleFactorBlock.isNull(p)) { + result.appendNull(); + continue position; + } + if (scaleFactorBlock.getValueCount(p) != 1) { + if (scaleFactorBlock.getValueCount(p) > 1) { + warnings().registerException(new IllegalArgumentException("single-value function encountered multi-value")); + } + result.appendNull(); + continue position; + } + try { + result.appendDouble(Scalb.process(dBlock.getDouble(dBlock.getFirstValueIndex(p)), scaleFactorBlock.getLong(scaleFactorBlock.getFirstValueIndex(p)))); + } catch (ArithmeticException e) { + warnings().registerException(e); + result.appendNull(); + } + } + return result.build(); + } + } + + public DoubleBlock eval(int positionCount, DoubleVector dVector, LongVector scaleFactorVector) { + try(DoubleBlock.Builder result = driverContext.blockFactory().newDoubleBlockBuilder(positionCount)) { + position: for (int p = 0; p < positionCount; p++) { + try { + result.appendDouble(Scalb.process(dVector.getDouble(p), scaleFactorVector.getLong(p))); + } catch (ArithmeticException e) { + warnings().registerException(e); + result.appendNull(); + } + } + return result.build(); + } + } + + @Override + public String toString() { + return "ScalbLongEvaluator[" + "d=" + d + ", scaleFactor=" + scaleFactor + "]"; + } + + @Override + public void close() { + Releasables.closeExpectNoException(d, scaleFactor); + } + + private Warnings warnings() { + if (warnings == null) { + this.warnings = Warnings.createWarnings( + driverContext.warningsMode(), + source.source().getLineNumber(), + source.source().getColumnNumber(), + source.text() + ); + } + return warnings; + } + + static class Factory implements EvalOperator.ExpressionEvaluator.Factory { + private final Source source; + + private final EvalOperator.ExpressionEvaluator.Factory d; + + private final EvalOperator.ExpressionEvaluator.Factory scaleFactor; + + public Factory(Source source, EvalOperator.ExpressionEvaluator.Factory d, + EvalOperator.ExpressionEvaluator.Factory scaleFactor) { + this.source = source; + this.d = d; + this.scaleFactor = scaleFactor; + } + + @Override + public ScalbLongEvaluator get(DriverContext context) { + return new ScalbLongEvaluator(source, d.get(context), scaleFactor.get(context), context); + } + + @Override + public String toString() { + return "ScalbLongEvaluator[" + "d=" + d + ", scaleFactor=" + scaleFactor + "]"; + } + } +} diff --git a/x-pack/plugin/esql/src/main/java/org/elasticsearch/xpack/esql/action/EsqlCapabilities.java b/x-pack/plugin/esql/src/main/java/org/elasticsearch/xpack/esql/action/EsqlCapabilities.java index 07f910c849c98..f350d322e616a 100644 --- a/x-pack/plugin/esql/src/main/java/org/elasticsearch/xpack/esql/action/EsqlCapabilities.java +++ b/x-pack/plugin/esql/src/main/java/org/elasticsearch/xpack/esql/action/EsqlCapabilities.java @@ -213,6 +213,11 @@ public enum Cap { */ FN_ROUND_UL_FIXES, + /** + * Support for function {@code SCALB}. + */ + FN_SCALB, + /** * Fixes for multiple functions not serializing their source, and emitting warnings with wrong line number and text. */ diff --git a/x-pack/plugin/esql/src/main/java/org/elasticsearch/xpack/esql/expression/ExpressionWritables.java b/x-pack/plugin/esql/src/main/java/org/elasticsearch/xpack/esql/expression/ExpressionWritables.java index 4ffa778b8287a..7e17d25f67e72 100644 --- a/x-pack/plugin/esql/src/main/java/org/elasticsearch/xpack/esql/expression/ExpressionWritables.java +++ b/x-pack/plugin/esql/src/main/java/org/elasticsearch/xpack/esql/expression/ExpressionWritables.java @@ -45,6 +45,7 @@ import org.elasticsearch.xpack.esql.expression.function.scalar.math.Exp; import org.elasticsearch.xpack.esql.expression.function.scalar.math.Floor; import org.elasticsearch.xpack.esql.expression.function.scalar.math.Log10; +import org.elasticsearch.xpack.esql.expression.function.scalar.math.Scalb; 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; @@ -169,6 +170,7 @@ public static List unaryScalars() { entries.add(Not.ENTRY); entries.add(RLike.ENTRY); entries.add(RTrim.ENTRY); + entries.add(Scalb.ENTRY); entries.add(Signum.ENTRY); entries.add(Sin.ENTRY); entries.add(Sinh.ENTRY); 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 b2d85d809c058..67d11197efe5e 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 @@ -100,6 +100,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.Scalb; 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; @@ -325,6 +326,7 @@ private static FunctionDefinition[][] functions() { def(Pi.class, Pi::new, "pi"), def(Pow.class, Pow::new, "pow"), def(Round.class, Round::new, "round"), + def(Scalb.class, Scalb::new, "scalb"), def(Signum.class, Signum::new, "signum"), def(Sin.class, Sin::new, "sin"), def(Sinh.class, Sinh::new, "sinh"), diff --git a/x-pack/plugin/esql/src/main/java/org/elasticsearch/xpack/esql/expression/function/scalar/math/Round.java b/x-pack/plugin/esql/src/main/java/org/elasticsearch/xpack/esql/expression/function/scalar/math/Round.java index 7c977fd1ce5a6..70882573b518d 100644 --- a/x-pack/plugin/esql/src/main/java/org/elasticsearch/xpack/esql/expression/function/scalar/math/Round.java +++ b/x-pack/plugin/esql/src/main/java/org/elasticsearch/xpack/esql/expression/function/scalar/math/Round.java @@ -47,7 +47,6 @@ public class Round extends EsqlScalarFunction implements OptionalArgument { private final Expression field, decimals; - // @TODO: add support for "integer", "long", "unsigned_long" once tests are fixed @FunctionInfo(returnType = { "double", "integer", "long", "unsigned_long" }, description = """ Rounds a number to the specified number of decimal places. Defaults to 0, which returns the nearest integer. If the diff --git a/x-pack/plugin/esql/src/main/java/org/elasticsearch/xpack/esql/expression/function/scalar/math/Scalb.java b/x-pack/plugin/esql/src/main/java/org/elasticsearch/xpack/esql/expression/function/scalar/math/Scalb.java new file mode 100644 index 0000000000000..b23b2323d0498 --- /dev/null +++ b/x-pack/plugin/esql/src/main/java/org/elasticsearch/xpack/esql/expression/function/scalar/math/Scalb.java @@ -0,0 +1,180 @@ +/* + * 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.common.Strings; +import org.elasticsearch.common.io.stream.NamedWriteableRegistry; +import org.elasticsearch.common.io.stream.StreamInput; +import org.elasticsearch.common.io.stream.StreamOutput; +import org.elasticsearch.compute.ann.Evaluator; +import org.elasticsearch.compute.ann.Fixed; +import org.elasticsearch.compute.operator.EvalOperator.ExpressionEvaluator; +import org.elasticsearch.xpack.esql.core.expression.Expression; +import org.elasticsearch.xpack.esql.core.tree.NodeInfo; +import org.elasticsearch.xpack.esql.core.tree.Source; +import org.elasticsearch.xpack.esql.core.type.DataType; +import org.elasticsearch.xpack.esql.core.util.NumericUtils; +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.EsqlScalarFunction; +import org.elasticsearch.xpack.esql.io.stream.PlanStreamInput; + +import java.io.IOException; +import java.util.Arrays; +import java.util.List; + +import static org.elasticsearch.xpack.esql.core.expression.TypeResolutions.ParamOrdinal.FIRST; +import static org.elasticsearch.xpack.esql.core.expression.TypeResolutions.ParamOrdinal.SECOND; +import static org.elasticsearch.xpack.esql.core.expression.TypeResolutions.isNumeric; +import static org.elasticsearch.xpack.esql.core.expression.TypeResolutions.isType; + +public class Scalb extends EsqlScalarFunction { + public static final NamedWriteableRegistry.Entry ENTRY = new NamedWriteableRegistry.Entry(Expression.class, "Scalb", Scalb::new); + + private final Expression d; + private final Expression scaleFactor; + + @FunctionInfo(returnType = "double", description = """ + Returns the result of `d * 2 ^ scaleFactor`, + Similar to Java's `scalb` function. Result is rounded as if + performed by a single correctly rounded floating-point multiply + to a member of the double value set.""", examples = @Example(file = "floats", tag = "scalb")) + + public Scalb( + Source source, + @Param( + name = "d", + type = { "double", "integer", "long", "unsigned_long" }, + description = "Numeric expression for the multiplier. If `null`, the function returns `null`." + ) Expression d, + @Param( + name = "scaleFactor", + type = { "integer", "long" }, + description = "Numeric expression for the scale factor. If `null`, the function returns `null`." + ) Expression scaleFactor + ) { + super(source, Arrays.asList(d, scaleFactor)); + this.d = d; + this.scaleFactor = scaleFactor; + } + + private Scalb(StreamInput in) throws IOException { + this(Source.readFrom((PlanStreamInput) in), in.readNamedWriteable(Expression.class), in.readNamedWriteable(Expression.class)); + } + + @Override + public void writeTo(StreamOutput out) throws IOException { + source().writeTo(out); + out.writeNamedWriteable(d); + out.writeNamedWriteable(scaleFactor); + } + + @Override + public String getWriteableName() { + return ENTRY.name; + } + + @Override + protected TypeResolution resolveType() { + if (childrenResolved() == false) { + return new TypeResolution("Unresolved children"); + } + + TypeResolution resolution = isNumeric(d, sourceText(), FIRST); + if (resolution.unresolved()) { + return resolution; + } + + return scaleFactor == null + ? TypeResolution.TYPE_RESOLVED + : isType( + scaleFactor, + dt -> dt.isWholeNumber() && dt != DataType.UNSIGNED_LONG, + sourceText(), + SECOND, + "whole number except unsigned_long or counter types" + ); + } + + @Override + public boolean foldable() { + return d.foldable() && scaleFactor.foldable(); + } + + @Evaluator(extraName = "Int", warnExceptions = { ArithmeticException.class }) + static double process(double d, int scaleFactor) { + return NumericUtils.asFiniteNumber(Math.scalb(d, scaleFactor)); + } + + @Evaluator(extraName = "Long", warnExceptions = { ArithmeticException.class }) + static double process(double d, long scaleFactor) { + return NumericUtils.asFiniteNumber(Math.scalb(d, Math.toIntExact(scaleFactor))); + } + + @Evaluator(extraName = "ConstantInt", warnExceptions = { ArithmeticException.class }) + static double processConstantInt(double d, @Fixed int scaleFactor) { + return NumericUtils.asFiniteNumber(Math.scalb(d, scaleFactor)); + } + + @Evaluator(extraName = "ConstantLong", warnExceptions = { ArithmeticException.class }) + static double processConstantLong(double d, @Fixed long scaleFactor) { + return NumericUtils.asFiniteNumber(Math.scalb(d, Math.toIntExact(scaleFactor))); + } + + @Override + public final Expression replaceChildren(List newChildren) { + return new Scalb(source(), newChildren.get(0), newChildren.get(1)); + } + + @Override + protected NodeInfo info() { + return NodeInfo.create(this, Scalb::new, d(), scaleFactor()); + } + + public Expression d() { + return d; + } + + public Expression scaleFactor() { + return scaleFactor; + } + + @Override + public DataType dataType() { + return DataType.DOUBLE; + } + + @Override + public ExpressionEvaluator.Factory toEvaluator(ToEvaluator toEvaluator) { + var dEval = Cast.cast(source(), d.dataType(), DataType.DOUBLE, toEvaluator.apply(d)); + if (scaleFactor.foldable()) { + return switch (scaleFactor.dataType()) { + case DataType.INTEGER -> new ScalbConstantIntEvaluator.Factory( + source(), + dEval, + (Integer) (scaleFactor.fold(toEvaluator.foldCtx())) + ); + case DataType.LONG -> new ScalbConstantLongEvaluator.Factory( + source(), + dEval, + (Long) (scaleFactor.fold(toEvaluator.foldCtx())) + ); + default -> throw new IllegalStateException("Invalid type for scaleFactor, should be int or long."); + }; + } + var scaleFactorEval = toEvaluator.apply(scaleFactor); + return switch (scaleFactor.dataType()) { + case DataType.INTEGER -> new ScalbIntEvaluator.Factory(source(), dEval, scaleFactorEval); + case DataType.LONG -> new ScalbLongEvaluator.Factory(source(), dEval, scaleFactorEval); + case DataType type -> throw new IllegalStateException( + Strings.format("Invalid type for scaleFactor: %s, should be int or long.", type) + ); + }; + } +} diff --git a/x-pack/plugin/esql/src/test/java/org/elasticsearch/xpack/esql/expression/function/scalar/math/ScalbTests.java b/x-pack/plugin/esql/src/test/java/org/elasticsearch/xpack/esql/expression/function/scalar/math/ScalbTests.java new file mode 100644 index 0000000000000..96c14c3c07d3e --- /dev/null +++ b/x-pack/plugin/esql/src/test/java/org/elasticsearch/xpack/esql/expression/function/scalar/math/ScalbTests.java @@ -0,0 +1,137 @@ +/* + * 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.common.Strings; +import org.elasticsearch.xpack.esql.core.expression.Expression; +import org.elasticsearch.xpack.esql.core.tree.Source; +import org.elasticsearch.xpack.esql.core.type.DataType; +import org.elasticsearch.xpack.esql.expression.function.AbstractScalarFunctionTestCase; +import org.elasticsearch.xpack.esql.expression.function.TestCaseSupplier; + +import java.util.List; +import java.util.function.Supplier; + +import static org.hamcrest.Matchers.equalTo; + +public class ScalbTests extends AbstractScalarFunctionTestCase { + public ScalbTests(@Name("TestCase") Supplier testCaseSupplier) { + this.testCase = testCaseSupplier.get(); + } + + @ParametersFactory + public static Iterable parameters() { + return parameterSuppliersFromTypedDataWithDefaultChecksNoErrors( + true, + List.of( + // Int scale factor + test(DataType.DOUBLE, DataType.INTEGER, 3.14d, 6, 200.96d), + test(DataType.INTEGER, DataType.INTEGER, 666, 6, 42624.0), + test(DataType.LONG, DataType.INTEGER, 23L, 5, 736.d), + test(DataType.UNSIGNED_LONG, DataType.INTEGER, 24325342L, 34, 1.584563250289466E29), + + // Long scale factor + test(DataType.DOUBLE, DataType.LONG, 3.14d, 6L, 200.96d), + test(DataType.INTEGER, DataType.LONG, 666, 6L, 42624.0), + test(DataType.LONG, DataType.LONG, 23L, 5L, 736.d), + test(DataType.UNSIGNED_LONG, DataType.LONG, 24325342L, 34L, 1.584563250289466E29), + + // null values + test(DataType.LONG, DataType.LONG, null, 34L, null), + test(DataType.LONG, DataType.LONG, 23L, null, null), + + // Overflow - Result + test( + DataType.LONG, + DataType.LONG, + 24325342L, + 344444L, + null, + List.of( + "Line 1:1: evaluation of [source] failed, treating result as null. Only first 20 failures recorded.", + "Line 1:1: java.lang.ArithmeticException: not a finite double number: Infinity" + ) + ), + + // Overflow in a constant scale factor + + new TestCaseSupplier( + ", ", + List.of(DataType.LONG, DataType.LONG), + () -> new TestCaseSupplier.TestCase( + List.of( + new TestCaseSupplier.TypedData(18446744L, DataType.LONG, "number"), + new TestCaseSupplier.TypedData(2147483648L, DataType.LONG, "decimals") + ), + "ScalbLongEvaluator[d=CastLongToDoubleEvaluator[v=Attribute[channel=0]], scaleFactor=Attribute[channel=1]]", + DataType.DOUBLE, + equalTo(null) + ).withWarning("Line 1:1: evaluation of [source] failed, treating result as null. Only first 20 failures recorded.") + .withWarning("Line 1:1: java.lang.ArithmeticException: integer overflow") + + ) + ) + ); + } + + private static TestCaseSupplier test(DataType dType, DataType scaleType, Number d, Number scaleFactor, Number res) { + return test(dType, scaleType, d, scaleFactor, res, List.of()); + } + + private static TestCaseSupplier test( + DataType dType, + DataType scaleType, + Number d, + Number scaleFactor, + Number res, + Iterable warns + ) { + var supplier = new TestCaseSupplier.TestCase( + List.of(new TestCaseSupplier.TypedData(d, dType, "d"), new TestCaseSupplier.TypedData(scaleFactor, scaleType, "scaleFactor")), + Strings.format( + "Scalb%sEvaluator[d=%s, scaleFactor=Attribute[channel=1]]", + pascalize(scaleType), + cast(dType) + + ), + DataType.DOUBLE, + equalTo(res) + ); + for (var warn : warns) { + supplier = supplier.withWarning(warn); + } + var finalSupplier = supplier; + return new TestCaseSupplier( + Strings.format("<%s>, <%s>", dType.typeName(), scaleType.typeName()), + List.of(dType, scaleType), + () -> finalSupplier + ); + } + + private static String cast(DataType from) { + return from == DataType.DOUBLE + ? "Attribute[channel=0]" + : Strings.format("Cast%sToDoubleEvaluator[v=Attribute[channel=0]]", pascalize(from)); + } + + private static String pascalize(DataType type) { + return switch (type) { + case DataType.UNSIGNED_LONG -> "UnsignedLong"; + case DataType.INTEGER -> "Int"; + default -> Strings.capitalize(type.typeName()); + }; + } + + @Override + protected Expression build(Source source, List args) { + return new Scalb(source, args.get(0), args.get(1)); + } +} diff --git a/x-pack/plugin/src/yamlRestTest/resources/rest-api-spec/test/esql/60_usage.yml b/x-pack/plugin/src/yamlRestTest/resources/rest-api-spec/test/esql/60_usage.yml index 18ea2903d1d86..affbab8701f82 100644 --- a/x-pack/plugin/src/yamlRestTest/resources/rest-api-spec/test/esql/60_usage.yml +++ b/x-pack/plugin/src/yamlRestTest/resources/rest-api-spec/test/esql/60_usage.yml @@ -123,7 +123,7 @@ setup: - match: {esql.functions.coalesce: $functions_coalesce} - gt: {esql.functions.categorize: $functions_categorize} # Testing for the entire function set isn't feasible, so we just check that we return the correct count as an approximation. - - length: {esql.functions: 141} # check the "sister" test below for a likely update to the same esql.functions length check + - length: {esql.functions: 142} # check the "sister" test below for a likely update to the same esql.functions length check --- "Basic ESQL usage output (telemetry) non-snapshot version": @@ -221,7 +221,7 @@ setup: - gt: {esql.functions.to_long: $functions_to_long} - match: {esql.functions.coalesce: $functions_coalesce} - gt: {esql.functions.categorize: $functions_categorize} - - length: {esql.functions: 132} # check the "sister" test above for a likely update to the same esql.functions length check + - length: {esql.functions: 133} # check the "sister" test above for a likely update to the same esql.functions length check --- took: