diff --git a/docs/changelog/132459.yaml b/docs/changelog/132459.yaml
new file mode 100644
index 0000000000000..0364a4d98c1ae
--- /dev/null
+++ b/docs/changelog/132459.yaml
@@ -0,0 +1,5 @@
+pr: 132459
+summary: Small fixes for COPY_SIGN
+area: ES|QL
+type: bug
+issues: []
diff --git a/docs/reference/query-languages/esql/_snippets/functions/description/copy_sign.md b/docs/reference/query-languages/esql/_snippets/functions/description/copy_sign.md
new file mode 100644
index 0000000000000..54ab4a25f48c8
--- /dev/null
+++ b/docs/reference/query-languages/esql/_snippets/functions/description/copy_sign.md
@@ -0,0 +1,6 @@
+% This is generated by ESQL's AbstractFunctionTestCase. Do not edit it. See ../README.md for how to regenerate it.
+
+**Description**
+
+Returns a value with the magnitude of the first argument and the sign of the second argument. This function is similar to Java's Math.copySign(double magnitude, double sign) which is similar to `copysign` from [IEEE 754](https://en.wikipedia.org/wiki/IEEE_754).
+
diff --git a/docs/reference/query-languages/esql/_snippets/functions/layout/copy_sign.md b/docs/reference/query-languages/esql/_snippets/functions/layout/copy_sign.md
new file mode 100644
index 0000000000000..7c423b66344fd
--- /dev/null
+++ b/docs/reference/query-languages/esql/_snippets/functions/layout/copy_sign.md
@@ -0,0 +1,20 @@
+% This is generated by ESQL's AbstractFunctionTestCase. Do not edit it. See ../README.md for how to regenerate it.
+
+## `COPY_SIGN` [esql-copy_sign]
+
+**Syntax**
+
+:::{image} ../../../images/functions/copy_sign.svg
+:alt: Embedded
+:class: text-center
+:::
+
+
+:::{include} ../parameters/copy_sign.md
+:::
+
+:::{include} ../description/copy_sign.md
+:::
+
+:::{include} ../types/copy_sign.md
+:::
diff --git a/docs/reference/query-languages/esql/_snippets/functions/parameters/copy_sign.md b/docs/reference/query-languages/esql/_snippets/functions/parameters/copy_sign.md
new file mode 100644
index 0000000000000..78de6d0388741
--- /dev/null
+++ b/docs/reference/query-languages/esql/_snippets/functions/parameters/copy_sign.md
@@ -0,0 +1,10 @@
+% This is generated by ESQL's AbstractFunctionTestCase. Do not edit it. See ../README.md for how to regenerate it.
+
+**Parameters**
+
+`magnitude`
+: The expression providing the magnitude of the result. Must be a numeric type.
+
+`sign`
+: The expression providing the sign of the result. Must be a numeric type.
+
diff --git a/docs/reference/query-languages/esql/_snippets/functions/types/copy_sign.md b/docs/reference/query-languages/esql/_snippets/functions/types/copy_sign.md
new file mode 100644
index 0000000000000..a0cbc6eed3f8e
--- /dev/null
+++ b/docs/reference/query-languages/esql/_snippets/functions/types/copy_sign.md
@@ -0,0 +1,16 @@
+% This is generated by ESQL's AbstractFunctionTestCase. Do not edit it. See ../README.md for how to regenerate it.
+
+**Supported types**
+
+| magnitude | sign | result |
+| --- | --- | --- |
+| double | double | double |
+| double | integer | double |
+| double | long | double |
+| integer | double | integer |
+| integer | integer | integer |
+| integer | long | integer |
+| long | double | long |
+| long | integer | long |
+| long | long | long |
+
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 54c1a4dd9635a..5ef3b6c499a1b 100644
--- a/docs/reference/query-languages/esql/_snippets/lists/math-functions.md
+++ b/docs/reference/query-languages/esql/_snippets/lists/math-functions.md
@@ -5,6 +5,7 @@
* [`ATAN2`](../../functions-operators/math-functions.md#esql-atan2)
* [`CBRT`](../../functions-operators/math-functions.md#esql-cbrt)
* [`CEIL`](../../functions-operators/math-functions.md#esql-ceil)
+* [`COPY_SIGN`](../../functions-operators/math-functions.md#esql-copy_sign)
* [`COS`](../../functions-operators/math-functions.md#esql-cos)
* [`COSH`](../../functions-operators/math-functions.md#esql-cosh)
* [`E`](../../functions-operators/math-functions.md#esql-e)
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 2a4cb855717d7..99c7c7394191a 100644
--- a/docs/reference/query-languages/esql/functions-operators/math-functions.md
+++ b/docs/reference/query-languages/esql/functions-operators/math-functions.md
@@ -33,6 +33,9 @@ mapped_pages:
:::{include} ../_snippets/functions/layout/ceil.md
:::
+:::{include} ../_snippets/functions/layout/copy_sign.md
+:::
+
:::{include} ../_snippets/functions/layout/cos.md
:::
diff --git a/docs/reference/query-languages/esql/images/functions/copy_sign.svg b/docs/reference/query-languages/esql/images/functions/copy_sign.svg
new file mode 100644
index 0000000000000..82455b59b1eda
--- /dev/null
+++ b/docs/reference/query-languages/esql/images/functions/copy_sign.svg
@@ -0,0 +1 @@
+
\ No newline at end of file
diff --git a/docs/reference/query-languages/esql/kibana/definition/functions/copy_sign.json b/docs/reference/query-languages/esql/kibana/definition/functions/copy_sign.json
new file mode 100644
index 0000000000000..51d3edca937f8
--- /dev/null
+++ b/docs/reference/query-languages/esql/kibana/definition/functions/copy_sign.json
@@ -0,0 +1,172 @@
+{
+ "comment" : "This is generated by ESQL's AbstractFunctionTestCase. Do not edit it. See ../README.md for how to regenerate it.",
+ "type" : "scalar",
+ "name" : "copy_sign",
+ "description" : "Returns a value with the magnitude of the first argument and the sign of the second argument.\nThis function is similar to Java's Math.copySign(double magnitude, double sign) which is\nsimilar to `copysign` from IEEE 754.",
+ "signatures" : [
+ {
+ "params" : [
+ {
+ "name" : "magnitude",
+ "type" : "double",
+ "optional" : false,
+ "description" : "The expression providing the magnitude of the result. Must be a numeric type."
+ },
+ {
+ "name" : "sign",
+ "type" : "double",
+ "optional" : false,
+ "description" : "The expression providing the sign of the result. Must be a numeric type."
+ }
+ ],
+ "variadic" : false,
+ "returnType" : "double"
+ },
+ {
+ "params" : [
+ {
+ "name" : "magnitude",
+ "type" : "double",
+ "optional" : false,
+ "description" : "The expression providing the magnitude of the result. Must be a numeric type."
+ },
+ {
+ "name" : "sign",
+ "type" : "integer",
+ "optional" : false,
+ "description" : "The expression providing the sign of the result. Must be a numeric type."
+ }
+ ],
+ "variadic" : false,
+ "returnType" : "double"
+ },
+ {
+ "params" : [
+ {
+ "name" : "magnitude",
+ "type" : "double",
+ "optional" : false,
+ "description" : "The expression providing the magnitude of the result. Must be a numeric type."
+ },
+ {
+ "name" : "sign",
+ "type" : "long",
+ "optional" : false,
+ "description" : "The expression providing the sign of the result. Must be a numeric type."
+ }
+ ],
+ "variadic" : false,
+ "returnType" : "double"
+ },
+ {
+ "params" : [
+ {
+ "name" : "magnitude",
+ "type" : "integer",
+ "optional" : false,
+ "description" : "The expression providing the magnitude of the result. Must be a numeric type."
+ },
+ {
+ "name" : "sign",
+ "type" : "double",
+ "optional" : false,
+ "description" : "The expression providing the sign of the result. Must be a numeric type."
+ }
+ ],
+ "variadic" : false,
+ "returnType" : "integer"
+ },
+ {
+ "params" : [
+ {
+ "name" : "magnitude",
+ "type" : "integer",
+ "optional" : false,
+ "description" : "The expression providing the magnitude of the result. Must be a numeric type."
+ },
+ {
+ "name" : "sign",
+ "type" : "integer",
+ "optional" : false,
+ "description" : "The expression providing the sign of the result. Must be a numeric type."
+ }
+ ],
+ "variadic" : false,
+ "returnType" : "integer"
+ },
+ {
+ "params" : [
+ {
+ "name" : "magnitude",
+ "type" : "integer",
+ "optional" : false,
+ "description" : "The expression providing the magnitude of the result. Must be a numeric type."
+ },
+ {
+ "name" : "sign",
+ "type" : "long",
+ "optional" : false,
+ "description" : "The expression providing the sign of the result. Must be a numeric type."
+ }
+ ],
+ "variadic" : false,
+ "returnType" : "integer"
+ },
+ {
+ "params" : [
+ {
+ "name" : "magnitude",
+ "type" : "long",
+ "optional" : false,
+ "description" : "The expression providing the magnitude of the result. Must be a numeric type."
+ },
+ {
+ "name" : "sign",
+ "type" : "double",
+ "optional" : false,
+ "description" : "The expression providing the sign of the result. Must be a numeric type."
+ }
+ ],
+ "variadic" : false,
+ "returnType" : "long"
+ },
+ {
+ "params" : [
+ {
+ "name" : "magnitude",
+ "type" : "long",
+ "optional" : false,
+ "description" : "The expression providing the magnitude of the result. Must be a numeric type."
+ },
+ {
+ "name" : "sign",
+ "type" : "integer",
+ "optional" : false,
+ "description" : "The expression providing the sign of the result. Must be a numeric type."
+ }
+ ],
+ "variadic" : false,
+ "returnType" : "long"
+ },
+ {
+ "params" : [
+ {
+ "name" : "magnitude",
+ "type" : "long",
+ "optional" : false,
+ "description" : "The expression providing the magnitude of the result. Must be a numeric type."
+ },
+ {
+ "name" : "sign",
+ "type" : "long",
+ "optional" : false,
+ "description" : "The expression providing the sign of the result. Must be a numeric type."
+ }
+ ],
+ "variadic" : false,
+ "returnType" : "long"
+ }
+ ],
+ "preview" : false,
+ "snapshot_only" : false
+}
diff --git a/docs/reference/query-languages/esql/kibana/docs/functions/copy_sign.md b/docs/reference/query-languages/esql/kibana/docs/functions/copy_sign.md
new file mode 100644
index 0000000000000..e2bd39f147fbd
--- /dev/null
+++ b/docs/reference/query-languages/esql/kibana/docs/functions/copy_sign.md
@@ -0,0 +1,6 @@
+% This is generated by ESQL's AbstractFunctionTestCase. Do not edit it. See ../README.md for how to regenerate it.
+
+### COPY SIGN
+Returns a value with the magnitude of the first argument and the sign of the second argument.
+This function is similar to Java's Math.copySign(double magnitude, double sign) which is
+similar to `copysign` from [IEEE 754](https://en.wikipedia.org/wiki/IEEE_754).
diff --git a/x-pack/plugin/esql/src/main/java/org/elasticsearch/xpack/esql/expression/function/scalar/math/CopySign.java b/x-pack/plugin/esql/src/main/java/org/elasticsearch/xpack/esql/expression/function/scalar/math/CopySign.java
index 884c67c38a3f9..83eb43627aa99 100644
--- a/x-pack/plugin/esql/src/main/java/org/elasticsearch/xpack/esql/expression/function/scalar/math/CopySign.java
+++ b/x-pack/plugin/esql/src/main/java/org/elasticsearch/xpack/esql/expression/function/scalar/math/CopySign.java
@@ -15,6 +15,7 @@
import org.elasticsearch.xpack.esql.EsqlIllegalArgumentException;
import org.elasticsearch.xpack.esql.core.expression.Expression;
import org.elasticsearch.xpack.esql.core.expression.Expressions;
+import org.elasticsearch.xpack.esql.core.expression.TypeResolutions;
import org.elasticsearch.xpack.esql.core.tree.NodeInfo;
import org.elasticsearch.xpack.esql.core.tree.Source;
import org.elasticsearch.xpack.esql.core.type.DataType;
@@ -60,21 +61,20 @@ EvalOperator.ExpressionEvaluator.Factory create(
private DataType dataType;
- @FunctionInfo(
- description = "Returns a value with the magnitude of the first argument and the sign of the second argument. "
- + "This function is similar to Java's Math.copySign(double magnitude, double sign).",
- returnType = { "double", "float" }
- )
+ @FunctionInfo(description = """
+ Returns a value with the magnitude of the first argument and the sign of the second argument.
+ This function is similar to Java's Math.copySign(double magnitude, double sign) which is
+ similar to `copysign` from [IEEE 754](https://en.wikipedia.org/wiki/IEEE_754).""", returnType = { "double", "integer", "long" })
public CopySign(
Source source,
@Param(
name = "magnitude",
- type = { "double", "float", "integer", "long" },
+ type = { "double", "integer", "long" },
description = "The expression providing the magnitude of the result. Must be a numeric type."
) Expression magnitude,
@Param(
name = "sign",
- type = { "double", "float", "integer", "long" },
+ type = { "double", "integer", "long" },
description = "The expression providing the sign of the result. Must be a numeric type."
) Expression sign
) {
@@ -125,11 +125,25 @@ public TypeResolution resolveType() {
}
var magnitude = children().get(0);
var sign = children().get(1);
- if (magnitude.dataType().isNumeric() == false) {
- return new TypeResolution("Magnitude must be a numeric type");
+ TypeResolution resolution = TypeResolutions.isType(
+ magnitude,
+ t -> t.isNumeric() && t != DataType.UNSIGNED_LONG,
+ sourceText(),
+ TypeResolutions.ParamOrdinal.FIRST,
+ "numeric"
+ );
+ if (resolution.unresolved()) {
+ return resolution;
}
- if (sign.dataType().isNumeric() == false) {
- return new TypeResolution("Sign must be a numeric type");
+ resolution = TypeResolutions.isType(
+ sign,
+ t -> t.isNumeric() && t != DataType.UNSIGNED_LONG,
+ sourceText(),
+ TypeResolutions.ParamOrdinal.SECOND,
+ "numeric"
+ );
+ if (resolution.unresolved()) {
+ return resolution;
}
// The return type is the same as the magnitude type, so we can use it directly.
dataType = magnitude.dataType();
diff --git a/x-pack/plugin/esql/src/test/java/org/elasticsearch/xpack/esql/expression/function/TestCaseSupplier.java b/x-pack/plugin/esql/src/test/java/org/elasticsearch/xpack/esql/expression/function/TestCaseSupplier.java
index fe06a0de470e1..242c2bb6c0182 100644
--- a/x-pack/plugin/esql/src/test/java/org/elasticsearch/xpack/esql/expression/function/TestCaseSupplier.java
+++ b/x-pack/plugin/esql/src/test/java/org/elasticsearch/xpack/esql/expression/function/TestCaseSupplier.java
@@ -47,6 +47,7 @@
import java.util.function.UnaryOperator;
import java.util.stream.Collectors;
+import static org.elasticsearch.xpack.esql.core.util.NumericUtils.UNSIGNED_LONG_MAX;
import static org.elasticsearch.xpack.esql.core.util.SpatialCoordinateTypes.CARTESIAN;
import static org.elasticsearch.xpack.esql.core.util.SpatialCoordinateTypes.GEO;
import static org.hamcrest.Matchers.equalTo;
@@ -357,6 +358,26 @@ public static List getSuppliersForNumericType(DataType type,
throw new IllegalArgumentException("bogus numeric type [" + type + "]");
}
+ /**
+ * A {@link List} of the cases for the specified type without any limits.
+ * See {@link #getSuppliersForNumericType} for cases with limits on numbers.
+ */
+ public static List unlimitedSuppliers(DataType type) {
+ if (type == DataType.INTEGER) {
+ return intCases(Integer.MIN_VALUE, Integer.MAX_VALUE, true);
+ }
+ if (type == DataType.LONG) {
+ return longCases(Long.MIN_VALUE, Long.MAX_VALUE, true);
+ }
+ if (type == DataType.UNSIGNED_LONG) {
+ return ulongCases(BigInteger.ZERO, UNSIGNED_LONG_MAX, true);
+ }
+ if (type == DataType.DOUBLE) {
+ return doubleCases(-Double.MAX_VALUE, Double.MAX_VALUE, true);
+ }
+ throw new IllegalArgumentException("bogus numeric type [" + type + "]");
+ }
+
public static List forBinaryComparisonWithWidening(
NumericTypeTestConfigs typeStuff,
String lhsName,
diff --git a/x-pack/plugin/esql/src/test/java/org/elasticsearch/xpack/esql/expression/function/scalar/math/CopySignErrorTests.java b/x-pack/plugin/esql/src/test/java/org/elasticsearch/xpack/esql/expression/function/scalar/math/CopySignErrorTests.java
new file mode 100644
index 0000000000000..d0792951f98e2
--- /dev/null
+++ b/x-pack/plugin/esql/src/test/java/org/elasticsearch/xpack/esql/expression/function/scalar/math/CopySignErrorTests.java
@@ -0,0 +1,37 @@
+/*
+ * 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.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.ErrorsForCasesWithoutExamplesTestCase;
+import org.elasticsearch.xpack.esql.expression.function.TestCaseSupplier;
+import org.hamcrest.Matcher;
+
+import java.util.List;
+import java.util.Set;
+
+import static org.hamcrest.Matchers.equalTo;
+
+public class CopySignErrorTests extends ErrorsForCasesWithoutExamplesTestCase {
+ @Override
+ protected List cases() {
+ return paramsToSuppliers(CopySignTests.parameters());
+ }
+
+ @Override
+ protected Expression build(Source source, List args) {
+ return new CopySign(source, args.get(0), args.get(1));
+ }
+
+ @Override
+ protected Matcher expectedTypeErrorMatcher(List> validPerPosition, List signature) {
+ return equalTo(typeErrorMessage(true, validPerPosition, signature, (v, i) -> "numeric"));
+ }
+}
diff --git a/x-pack/plugin/esql/src/test/java/org/elasticsearch/xpack/esql/expression/function/scalar/math/CopySignTests.java b/x-pack/plugin/esql/src/test/java/org/elasticsearch/xpack/esql/expression/function/scalar/math/CopySignTests.java
new file mode 100644
index 0000000000000..5a7dd395eca99
--- /dev/null
+++ b/x-pack/plugin/esql/src/test/java/org/elasticsearch/xpack/esql/expression/function/scalar/math/CopySignTests.java
@@ -0,0 +1,106 @@
+/*
+ * 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.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 org.hamcrest.Matcher;
+import org.hamcrest.Matchers;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.function.BiFunction;
+import java.util.function.BinaryOperator;
+import java.util.function.Supplier;
+
+import static org.elasticsearch.xpack.esql.expression.function.TestCaseSupplier.casesCrossProduct;
+import static org.elasticsearch.xpack.esql.expression.function.TestCaseSupplier.getCastEvaluator;
+import static org.elasticsearch.xpack.esql.expression.function.TestCaseSupplier.unlimitedSuppliers;
+import static org.hamcrest.Matchers.equalTo;
+
+public class CopySignTests extends AbstractScalarFunctionTestCase {
+ public CopySignTests(@Name("TestCase") Supplier testCaseSupplier) {
+ this.testCase = testCaseSupplier.get();
+ }
+
+ @ParametersFactory
+ public static Iterable