Ver código fonte

ESQL: CBRT function (#108574)

- Added the cube root function to ESQL (`CBRT(x)`). Nearly identical to SQRT, but without the negative numbers exception
- Added docs generation support for Windows end lines (CRLF), as within the examples, it was writing the "\r" without the "\n" (Which was being converted to "\\n"), and some other inconsistencies
- Some updates to `package-info.java` documentation over how to create functions
- Fixes https://github.com/elastic/elasticsearch/issues/108675

Functions issue: https://github.com/elastic/elasticsearch/issues/98545
Iván Cea Fontenla 1 ano atrás
pai
commit
62b372b4dc
28 arquivos alterados com 891 adições e 35 exclusões
  1. 5 0
      docs/changelog/108574.yaml
  2. 5 0
      docs/reference/esql/functions/description/cbrt.asciidoc
  3. 1 1
      docs/reference/esql/functions/description/sqrt.asciidoc
  4. 13 0
      docs/reference/esql/functions/examples/cbrt.asciidoc
  5. 59 0
      docs/reference/esql/functions/kibana/definition/cbrt.json
  6. 1 1
      docs/reference/esql/functions/kibana/definition/sqrt.json
  7. 12 0
      docs/reference/esql/functions/kibana/docs/cbrt.md
  8. 1 1
      docs/reference/esql/functions/kibana/docs/sqrt.md
  9. 15 0
      docs/reference/esql/functions/layout/cbrt.asciidoc
  10. 2 0
      docs/reference/esql/functions/math-functions.asciidoc
  11. 6 0
      docs/reference/esql/functions/parameters/cbrt.asciidoc
  12. 1 0
      docs/reference/esql/functions/signature/cbrt.svg
  13. 12 0
      docs/reference/esql/functions/types/cbrt.asciidoc
  14. 1 0
      x-pack/plugin/esql/build.gradle
  15. 13 1
      x-pack/plugin/esql/qa/server/src/main/java/org/elasticsearch/xpack/esql/qa/rest/EsqlSpecTestCase.java
  16. 62 0
      x-pack/plugin/esql/qa/testFixtures/src/main/resources/math.csv-spec
  17. 6 2
      x-pack/plugin/esql/qa/testFixtures/src/main/resources/meta.csv-spec
  18. 119 0
      x-pack/plugin/esql/src/main/generated/org/elasticsearch/xpack/esql/expression/function/scalar/math/CbrtDoubleEvaluator.java
  19. 120 0
      x-pack/plugin/esql/src/main/generated/org/elasticsearch/xpack/esql/expression/function/scalar/math/CbrtIntEvaluator.java
  20. 120 0
      x-pack/plugin/esql/src/main/generated/org/elasticsearch/xpack/esql/expression/function/scalar/math/CbrtLongEvaluator.java
  21. 110 0
      x-pack/plugin/esql/src/main/generated/org/elasticsearch/xpack/esql/expression/function/scalar/math/CbrtUnsignedLongEvaluator.java
  22. 7 1
      x-pack/plugin/esql/src/main/java/org/elasticsearch/xpack/esql/action/EsqlCapabilities.java
  23. 2 0
      x-pack/plugin/esql/src/main/java/org/elasticsearch/xpack/esql/expression/function/EsqlFunctionRegistry.java
  24. 109 0
      x-pack/plugin/esql/src/main/java/org/elasticsearch/xpack/esql/expression/function/scalar/math/Cbrt.java
  25. 1 1
      x-pack/plugin/esql/src/main/java/org/elasticsearch/xpack/esql/expression/function/scalar/math/Sqrt.java
  26. 3 27
      x-pack/plugin/esql/src/main/java/org/elasticsearch/xpack/esql/expression/function/scalar/package-info.java
  27. 3 0
      x-pack/plugin/esql/src/main/java/org/elasticsearch/xpack/esql/io/stream/PlanNamedTypes.java
  28. 82 0
      x-pack/plugin/esql/src/test/java/org/elasticsearch/xpack/esql/expression/function/scalar/math/CbrtTests.java

+ 5 - 0
docs/changelog/108574.yaml

@@ -0,0 +1,5 @@
+pr: 108574
+summary: "[ESQL] CBRT function"
+area: ES|QL
+type: enhancement
+issues: []

+ 5 - 0
docs/reference/esql/functions/description/cbrt.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 cube root of a number. The input can be any numeric value, the return value is always a double. Cube roots of infinities are null.

+ 1 - 1
docs/reference/esql/functions/description/sqrt.asciidoc

@@ -2,4 +2,4 @@
 
 *Description*
 
-Returns the square root of a number. The input can be any numeric value, the return value is always a double. Square roots of negative numbers and infinites are null.
+Returns the square root of a number. The input can be any numeric value, the return value is always a double. Square roots of negative numbers and infinities are null.

+ 13 - 0
docs/reference/esql/functions/examples/cbrt.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=cbrt]
+----
+[%header.monospaced.styled,format=dsv,separator=|]
+|===
+include::{esql-specs}/math.csv-spec[tag=cbrt-result]
+|===
+

+ 59 - 0
docs/reference/esql/functions/kibana/definition/cbrt.json

@@ -0,0 +1,59 @@
+{
+  "comment" : "This is generated by ESQL's AbstractFunctionTestCase. Do no edit it. See ../README.md for how to regenerate it.",
+  "type" : "eval",
+  "name" : "cbrt",
+  "description" : "Returns the cube root of a number. The input can be any numeric value, the return value is always a double.\nCube roots of infinities are null.",
+  "signatures" : [
+    {
+      "params" : [
+        {
+          "name" : "number",
+          "type" : "double",
+          "optional" : false,
+          "description" : "Numeric expression. If `null`, the function returns `null`."
+        }
+      ],
+      "variadic" : false,
+      "returnType" : "double"
+    },
+    {
+      "params" : [
+        {
+          "name" : "number",
+          "type" : "integer",
+          "optional" : false,
+          "description" : "Numeric expression. If `null`, the function returns `null`."
+        }
+      ],
+      "variadic" : false,
+      "returnType" : "double"
+    },
+    {
+      "params" : [
+        {
+          "name" : "number",
+          "type" : "long",
+          "optional" : false,
+          "description" : "Numeric expression. If `null`, the function returns `null`."
+        }
+      ],
+      "variadic" : false,
+      "returnType" : "double"
+    },
+    {
+      "params" : [
+        {
+          "name" : "number",
+          "type" : "unsigned_long",
+          "optional" : false,
+          "description" : "Numeric expression. If `null`, the function returns `null`."
+        }
+      ],
+      "variadic" : false,
+      "returnType" : "double"
+    }
+  ],
+  "examples" : [
+    "ROW d = 1000.0\n| EVAL c = cbrt(d)"
+  ]
+}

+ 1 - 1
docs/reference/esql/functions/kibana/definition/sqrt.json

@@ -2,7 +2,7 @@
   "comment" : "This is generated by ESQL's AbstractFunctionTestCase. Do no edit it. See ../README.md for how to regenerate it.",
   "type" : "eval",
   "name" : "sqrt",
-  "description" : "Returns the square root of a number. The input can be any numeric value, the return value is always a double.\nSquare roots of negative numbers and infinites are null.",
+  "description" : "Returns the square root of a number. The input can be any numeric value, the return value is always a double.\nSquare roots of negative numbers and infinities are null.",
   "signatures" : [
     {
       "params" : [

+ 12 - 0
docs/reference/esql/functions/kibana/docs/cbrt.md

@@ -0,0 +1,12 @@
+<!--
+This is generated by ESQL's AbstractFunctionTestCase. Do no edit it. See ../README.md for how to regenerate it.
+-->
+
+### CBRT
+Returns the cube root of a number. The input can be any numeric value, the return value is always a double.
+Cube roots of infinities are null.
+
+```
+ROW d = 1000.0
+| EVAL c = cbrt(d)
+```

+ 1 - 1
docs/reference/esql/functions/kibana/docs/sqrt.md

@@ -4,7 +4,7 @@ This is generated by ESQL's AbstractFunctionTestCase. Do no edit it. See ../READ
 
 ### SQRT
 Returns the square root of a number. The input can be any numeric value, the return value is always a double.
-Square roots of negative numbers and infinites are null.
+Square roots of negative numbers and infinities are null.
 
 ```
 ROW d = 100.0

+ 15 - 0
docs/reference/esql/functions/layout/cbrt.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-cbrt]]
+=== `CBRT`
+
+*Syntax*
+
+[.text-center]
+image::esql/functions/signature/cbrt.svg[Embedded,opts=inline]
+
+include::../parameters/cbrt.asciidoc[]
+include::../description/cbrt.asciidoc[]
+include::../types/cbrt.asciidoc[]
+include::../examples/cbrt.asciidoc[]

+ 2 - 0
docs/reference/esql/functions/math-functions.asciidoc

@@ -13,6 +13,7 @@
 * <<esql-asin>>
 * <<esql-atan>>
 * <<esql-atan2>>
+* <<esql-cbrt>>
 * <<esql-ceil>>
 * <<esql-cos>>
 * <<esql-cosh>>
@@ -37,6 +38,7 @@ include::layout/acos.asciidoc[]
 include::layout/asin.asciidoc[]
 include::layout/atan.asciidoc[]
 include::layout/atan2.asciidoc[]
+include::layout/cbrt.asciidoc[]
 include::layout/ceil.asciidoc[]
 include::layout/cos.asciidoc[]
 include::layout/cosh.asciidoc[]

+ 6 - 0
docs/reference/esql/functions/parameters/cbrt.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`.

+ 1 - 0
docs/reference/esql/functions/signature/cbrt.svg

@@ -0,0 +1 @@
+<svg version="1.1" xmlns:xlink="http://www.w3.org/1999/xlink" xmlns="http://www.w3.org/2000/svg" width="264" height="46" viewbox="0 0 264 46"><defs><style type="text/css">#guide .c{fill:none;stroke:#222222;}#guide .k{fill:#000000;font-family:Roboto Mono,Sans-serif;font-size:20px;}#guide .s{fill:#e4f4ff;stroke:#222222;}#guide .syn{fill:#8D8D8D;font-family:Roboto Mono,Sans-serif;font-size:20px;}</style></defs><path class="c" d="M0 31h5m68 0h10m32 0h10m92 0h10m32 0h5"/><rect class="s" x="5" y="5" width="68" height="36"/><text class="k" x="15" y="31">CBRT</text><rect class="s" x="83" y="5" width="32" height="36" rx="7"/><text class="syn" x="93" y="31">(</text><rect class="s" x="125" y="5" width="92" height="36" rx="7"/><text class="k" x="135" y="31">number</text><rect class="s" x="227" y="5" width="32" height="36" rx="7"/><text class="syn" x="237" y="31">)</text></svg>

+ 12 - 0
docs/reference/esql/functions/types/cbrt.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
+|===

+ 1 - 0
x-pack/plugin/esql/build.gradle

@@ -65,6 +65,7 @@ tasks.named("test").configure {
           String tag = it[2]
           boolean isJson = it[3]
           String allExamples = new File("${projectDir}/qa/testFixtures/src/main/resources/${file}").text
+            .replaceAll(System.lineSeparator(), "\n")
           int start = allExamples.indexOf("tag::${tag}[]")
           int end = allExamples.indexOf("end::${tag}[]", start)
           if (start < 0 || end < 0) {

+ 13 - 1
x-pack/plugin/esql/qa/server/src/main/java/org/elasticsearch/xpack/esql/qa/rest/EsqlSpecTestCase.java

@@ -17,6 +17,7 @@ import org.elasticsearch.client.Request;
 import org.elasticsearch.client.ResponseException;
 import org.elasticsearch.client.RestClient;
 import org.elasticsearch.common.xcontent.XContentHelper;
+import org.elasticsearch.features.NodeFeature;
 import org.elasticsearch.geometry.Geometry;
 import org.elasticsearch.geometry.Point;
 import org.elasticsearch.geometry.utils.GeometryValidator;
@@ -27,6 +28,7 @@ import org.elasticsearch.test.rest.ESRestTestCase;
 import org.elasticsearch.test.rest.TestFeatureService;
 import org.elasticsearch.xcontent.XContentType;
 import org.elasticsearch.xpack.esql.CsvTestUtils;
+import org.elasticsearch.xpack.esql.plugin.EsqlFeatures;
 import org.elasticsearch.xpack.esql.qa.rest.RestEsqlTestCase.RequestObjectBuilder;
 import org.elasticsearch.xpack.esql.version.EsqlVersion;
 import org.elasticsearch.xpack.ql.CsvSpecReader.CsvTestCase;
@@ -43,6 +45,8 @@ import java.util.Locale;
 import java.util.Map;
 import java.util.Set;
 import java.util.regex.Pattern;
+import java.util.stream.Collectors;
+import java.util.stream.Stream;
 
 import static org.apache.lucene.geo.GeoEncodingUtils.decodeLatitude;
 import static org.apache.lucene.geo.GeoEncodingUtils.decodeLongitude;
@@ -182,8 +186,16 @@ public abstract class EsqlSpecTestCase extends ESRestTestCase {
                 throw e;
             }
         }
+
+        var features = Stream.concat(
+            new EsqlFeatures().getFeatures().stream(),
+            new EsqlFeatures().getHistoricalFeatures().keySet().stream()
+        ).map(NodeFeature::id).collect(Collectors.toSet());
+
         for (String feature : testCase.requiredCapabilities) {
-            assumeTrue("Test " + testName + " requires " + feature, testFeatureService.clusterHasFeature("esql." + feature));
+            var esqlFeature = "esql." + feature;
+            assumeTrue("Requested capability " + feature + " is an ESQL cluster feature", features.contains(esqlFeature));
+            assumeTrue("Test " + testName + " requires " + feature, testFeatureService.clusterHasFeature(esqlFeature));
         }
     }
 

+ 62 - 0
x-pack/plugin/esql/qa/testFixtures/src/main/resources/math.csv-spec

@@ -1304,6 +1304,68 @@ d:double | s:double
 -0.0     | -0.0
 ;
 
+cbrt
+required_capability: fn_cbrt
+// tag::cbrt[]
+ROW d = 1000.0
+| EVAL c = cbrt(d)
+// end::cbrt[]
+;
+
+// tag::cbrt-result[]
+d: double | c:double
+1000.0    | 10.0
+// end::cbrt-result[]
+;
+
+cbrtOfInteger
+required_capability: fn_cbrt
+row i = 27 | eval c = cbrt(i);
+
+i:integer | c:double
+27        | 3
+;
+
+cbrtOfLong
+required_capability: fn_cbrt
+row l = to_long(1000000000000) | eval c = cbrt(l);
+
+l:long            | c:double
+1000000000000     | 10000
+;
+
+cbrtOfUnsignedLong
+required_capability: fn_cbrt
+row l = to_ul(1000000000000000000) | eval c = cbrt(l);
+
+l:ul                  | c:double
+1000000000000000000   | 1000000
+;
+
+cbrtOfNegative
+required_capability: fn_cbrt
+row d = -1.0 | eval c = cbrt(d);
+
+d:double | c:double
+-1.0     | -1.0
+;
+
+cbrtOfZero
+required_capability: fn_cbrt
+row d = 0.0 | eval c = cbrt(d);
+
+d:double | c:double
+0.0      | 0.0
+;
+
+cbrtOfNegativeZero
+required_capability: fn_cbrt
+row d = -0.0 | eval c = cbrt(d);
+
+d:double | c:double
+-0.0     | -0.0
+;
+
 least
 // tag::least[]
 ROW a = 10, b = 20

+ 6 - 2
x-pack/plugin/esql/qa/testFixtures/src/main/resources/meta.csv-spec

@@ -11,6 +11,7 @@ synopsis:keyword
 "double|date bin(field:integer|long|double|date, buckets:integer|double|date_period|time_duration, ?from:integer|long|double|date, ?to:integer|long|double|date)"
 "double|date bucket(field:integer|long|double|date, buckets:integer|double|date_period|time_duration, ?from:integer|long|double|date, ?to:integer|long|double|date)"
 "boolean|cartesian_point|date|double|geo_point|integer|ip|keyword|long|text|unsigned_long|version case(condition:boolean, trueValue...:boolean|cartesian_point|date|double|geo_point|integer|ip|keyword|long|text|unsigned_long|version)"
+"double cbrt(number:double|integer|long|unsigned_long)"
 "double|integer|long|unsigned_long ceil(number:double|integer|long|unsigned_long)"
 "boolean cidr_match(ip:ip, blockX...:keyword|text)"
 "boolean|text|integer|keyword|long coalesce(first:boolean|text|integer|keyword|long, ?rest...:boolean|text|integer|keyword|long)"
@@ -124,6 +125,7 @@ avg           |number                              |"double|integer|long"
 bin           |[field, buckets, from, to]          |["integer|long|double|date", "integer|double|date_period|time_duration", "integer|long|double|date", "integer|long|double|date"]  |[Numeric or date expression from which to derive buckets., Target number of buckets., Start of the range. Can be a number or a date expressed as a string., End of the range. Can be a number or a date expressed as a string.]
 bucket        |[field, buckets, from, to]          |["integer|long|double|date", "integer|double|date_period|time_duration", "integer|long|double|date", "integer|long|double|date"]  |[Numeric or date expression from which to derive buckets., Target number of buckets., Start of the range. Can be a number or a date expressed as a string., End of the range. Can be a number or a date expressed as a string.]
 case          |[condition, trueValue]              |[boolean, "boolean|cartesian_point|date|double|geo_point|integer|ip|keyword|long|text|unsigned_long|version"]                     |[A condition., The value that's returned when the corresponding condition is the first to evaluate to `true`. The default value is returned when no condition matches.]
+cbrt          |number                              |"double|integer|long|unsigned_long"                                                                                               |"Numeric expression. If `null`, the function returns `null`."
 ceil          |number                              |"double|integer|long|unsigned_long"                                                                                               |Numeric expression. If `null`, the function returns `null`.
 cidr_match    |[ip, blockX]                        |[ip, "keyword|text"]                                                                                                              |[IP address of type `ip` (both IPv4 and IPv6 are supported)., CIDR block to test the IP against.]
 coalesce      |first                               |"boolean|text|integer|keyword|long"                                                                                               |Expression to evaluate.
@@ -238,6 +240,7 @@ avg           |The average of a numeric field.
 bin           |Creates groups of values - buckets - out of a datetime or numeric input. The size of the buckets can either be provided directly, or chosen based on a recommended count and values range.
 bucket        |Creates groups of values - buckets - out of a datetime or numeric input. The size of the buckets can either be provided directly, or chosen based on a recommended count and values range.
 case          |Accepts pairs of conditions and values. The function returns the value that belongs to the first condition that evaluates to `true`.  If the number of arguments is odd, the last argument is the default value which is returned when no condition matches. If the number of arguments is even, and no condition matches, the function returns `null`.
+cbrt          |Returns the cube root of a number. The input can be any numeric value, the return value is always a double. Cube roots of infinities are null.
 ceil          |Round a number up to the nearest integer.
 cidr_match    |Returns true if the provided IP is contained in one of the provided CIDR blocks.
 coalesce      |Returns the first of its arguments that is not null. If all arguments are null, it returns `null`.
@@ -292,7 +295,7 @@ signum        |Returns the sign of the given number. It returns `-1` for negativ
 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.
-sqrt          |Returns the square root of a number. The input can be any numeric value, the return value is always a double. Square roots of negative numbers and infinites are null.
+sqrt          |Returns the square root of a number. The input can be any numeric value, the return value is always a double. Square roots of negative numbers and infinities are null.
 st_centroid_ag|The centroid of a spatial field.
 st_contains   |Returns whether the first geometry contains the second geometry. This is the inverse of the <<esql-st_within,ST_WITHIN>> function.
 st_disjoint   |Returns whether the two geometries or geometry columns are disjoint. This is the inverse of the <<esql-st_intersects,ST_INTERSECTS>> function. In mathematical terms: ST_Disjoint(A, B) ⇔ A ⋂ B = ∅
@@ -353,6 +356,7 @@ avg           |double
 bin           |"double|date"                                                                                                               |[false, false, true, true]  |false           |false
 bucket        |"double|date"                                                                                                               |[false, false, true, true]  |false           |false
 case          |"boolean|cartesian_point|date|double|geo_point|integer|ip|keyword|long|text|unsigned_long|version"                          |[false, false]              |true            |false
+cbrt          |double                                                                                                                      |false                       |false           |false
 ceil          |"double|integer|long|unsigned_long"                                                                                         |false                       |false           |false
 cidr_match    |boolean                                                                                                                     |[false, false]              |true            |false
 coalesce      |"boolean|text|integer|keyword|long"                                                                                         |false                       |true            |false
@@ -467,5 +471,5 @@ countFunctions#[skip:-8.14.99, reason:BIN added]
 meta functions |  stats  a = count(*), b = count(*), c = count(*) |  mv_expand c;
 
 a:long | b:long | c:long
-105    | 105    | 105
+106    | 106    | 106
 ;

+ 119 - 0
x-pack/plugin/esql/src/main/generated/org/elasticsearch/xpack/esql/expression/function/scalar/math/CbrtDoubleEvaluator.java

@@ -0,0 +1,119 @@
+// 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.core.Releasables;
+import org.elasticsearch.xpack.esql.expression.function.Warnings;
+import org.elasticsearch.xpack.ql.tree.Source;
+
+/**
+ * {@link EvalOperator.ExpressionEvaluator} implementation for {@link Cbrt}.
+ * This class is generated. Do not edit it.
+ */
+public final class CbrtDoubleEvaluator implements EvalOperator.ExpressionEvaluator {
+  private final Warnings warnings;
+
+  private final EvalOperator.ExpressionEvaluator val;
+
+  private final DriverContext driverContext;
+
+  public CbrtDoubleEvaluator(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);
+    }
+  }
+
+  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;
+        }
+        try {
+          result.appendDouble(Cbrt.process(valBlock.getDouble(valBlock.getFirstValueIndex(p))));
+        } catch (ArithmeticException e) {
+          warnings.registerException(e);
+          result.appendNull();
+        }
+      }
+      return result.build();
+    }
+  }
+
+  public DoubleBlock eval(int positionCount, DoubleVector valVector) {
+    try(DoubleBlock.Builder result = driverContext.blockFactory().newDoubleBlockBuilder(positionCount)) {
+      position: for (int p = 0; p < positionCount; p++) {
+        try {
+          result.appendDouble(Cbrt.process(valVector.getDouble(p)));
+        } catch (ArithmeticException e) {
+          warnings.registerException(e);
+          result.appendNull();
+        }
+      }
+      return result.build();
+    }
+  }
+
+  @Override
+  public String toString() {
+    return "CbrtDoubleEvaluator[" + "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 CbrtDoubleEvaluator get(DriverContext context) {
+      return new CbrtDoubleEvaluator(source, val.get(context), context);
+    }
+
+    @Override
+    public String toString() {
+      return "CbrtDoubleEvaluator[" + "val=" + val + "]";
+    }
+  }
+}

+ 120 - 0
x-pack/plugin/esql/src/main/generated/org/elasticsearch/xpack/esql/expression/function/scalar/math/CbrtIntEvaluator.java

@@ -0,0 +1,120 @@
+// 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.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 Cbrt}.
+ * This class is generated. Do not edit it.
+ */
+public final class CbrtIntEvaluator implements EvalOperator.ExpressionEvaluator {
+  private final Warnings warnings;
+
+  private final EvalOperator.ExpressionEvaluator val;
+
+  private final DriverContext driverContext;
+
+  public CbrtIntEvaluator(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);
+    }
+  }
+
+  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;
+        }
+        try {
+          result.appendDouble(Cbrt.process(valBlock.getInt(valBlock.getFirstValueIndex(p))));
+        } catch (ArithmeticException e) {
+          warnings.registerException(e);
+          result.appendNull();
+        }
+      }
+      return result.build();
+    }
+  }
+
+  public DoubleBlock eval(int positionCount, IntVector valVector) {
+    try(DoubleBlock.Builder result = driverContext.blockFactory().newDoubleBlockBuilder(positionCount)) {
+      position: for (int p = 0; p < positionCount; p++) {
+        try {
+          result.appendDouble(Cbrt.process(valVector.getInt(p)));
+        } catch (ArithmeticException e) {
+          warnings.registerException(e);
+          result.appendNull();
+        }
+      }
+      return result.build();
+    }
+  }
+
+  @Override
+  public String toString() {
+    return "CbrtIntEvaluator[" + "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 CbrtIntEvaluator get(DriverContext context) {
+      return new CbrtIntEvaluator(source, val.get(context), context);
+    }
+
+    @Override
+    public String toString() {
+      return "CbrtIntEvaluator[" + "val=" + val + "]";
+    }
+  }
+}

+ 120 - 0
x-pack/plugin/esql/src/main/generated/org/elasticsearch/xpack/esql/expression/function/scalar/math/CbrtLongEvaluator.java

@@ -0,0 +1,120 @@
+// 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.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 Cbrt}.
+ * This class is generated. Do not edit it.
+ */
+public final class CbrtLongEvaluator implements EvalOperator.ExpressionEvaluator {
+  private final Warnings warnings;
+
+  private final EvalOperator.ExpressionEvaluator val;
+
+  private final DriverContext driverContext;
+
+  public CbrtLongEvaluator(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);
+    }
+  }
+
+  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;
+        }
+        try {
+          result.appendDouble(Cbrt.process(valBlock.getLong(valBlock.getFirstValueIndex(p))));
+        } catch (ArithmeticException e) {
+          warnings.registerException(e);
+          result.appendNull();
+        }
+      }
+      return result.build();
+    }
+  }
+
+  public DoubleBlock eval(int positionCount, LongVector valVector) {
+    try(DoubleBlock.Builder result = driverContext.blockFactory().newDoubleBlockBuilder(positionCount)) {
+      position: for (int p = 0; p < positionCount; p++) {
+        try {
+          result.appendDouble(Cbrt.process(valVector.getLong(p)));
+        } catch (ArithmeticException e) {
+          warnings.registerException(e);
+          result.appendNull();
+        }
+      }
+      return result.build();
+    }
+  }
+
+  @Override
+  public String toString() {
+    return "CbrtLongEvaluator[" + "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 CbrtLongEvaluator get(DriverContext context) {
+      return new CbrtLongEvaluator(source, val.get(context), context);
+    }
+
+    @Override
+    public String toString() {
+      return "CbrtLongEvaluator[" + "val=" + val + "]";
+    }
+  }
+}

+ 110 - 0
x-pack/plugin/esql/src/main/generated/org/elasticsearch/xpack/esql/expression/function/scalar/math/CbrtUnsignedLongEvaluator.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 Cbrt}.
+ * This class is generated. Do not edit it.
+ */
+public final class CbrtUnsignedLongEvaluator implements EvalOperator.ExpressionEvaluator {
+  private final Warnings warnings;
+
+  private final EvalOperator.ExpressionEvaluator val;
+
+  private final DriverContext driverContext;
+
+  public CbrtUnsignedLongEvaluator(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(Cbrt.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(Cbrt.processUnsignedLong(valVector.getLong(p)));
+      }
+      return result.build();
+    }
+  }
+
+  @Override
+  public String toString() {
+    return "CbrtUnsignedLongEvaluator[" + "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 CbrtUnsignedLongEvaluator get(DriverContext context) {
+      return new CbrtUnsignedLongEvaluator(source, val.get(context), context);
+    }
+
+    @Override
+    public String toString() {
+      return "CbrtUnsignedLongEvaluator[" + "val=" + val + "]";
+    }
+  }
+}

+ 7 - 1
x-pack/plugin/esql/src/main/java/org/elasticsearch/xpack/esql/action/EsqlCapabilities.java

@@ -21,13 +21,19 @@ import java.util.Set;
  * {@link RestNodesCapabilitiesAction} and we use them to enable tests.
  */
 public class EsqlCapabilities {
+    /**
+     * Support for function {@code CBRT}. Done in #108574.
+     */
+    private static final String FN_CBRT = "fn_cbrt";
+
     static final Set<String> CAPABILITIES = capabilities();
 
     private static Set<String> capabilities() {
+        List<String> caps = new ArrayList<>(List.of(FN_CBRT));
+
         /*
          * Add all of our cluster features without the leading "esql."
          */
-        List<String> caps = new ArrayList<>();
         for (NodeFeature feature : new EsqlFeatures().getFeatures()) {
             caps.add(cap(feature));
         }

+ 2 - 0
x-pack/plugin/esql/src/main/java/org/elasticsearch/xpack/esql/expression/function/EsqlFunctionRegistry.java

@@ -51,6 +51,7 @@ import org.elasticsearch.xpack.esql.expression.function.scalar.math.Acos;
 import org.elasticsearch.xpack.esql.expression.function.scalar.math.Asin;
 import org.elasticsearch.xpack.esql.expression.function.scalar.math.Atan;
 import org.elasticsearch.xpack.esql.expression.function.scalar.math.Atan2;
+import org.elasticsearch.xpack.esql.expression.function.scalar.math.Cbrt;
 import org.elasticsearch.xpack.esql.expression.function.scalar.math.Ceil;
 import org.elasticsearch.xpack.esql.expression.function.scalar.math.Cos;
 import org.elasticsearch.xpack.esql.expression.function.scalar.math.Cosh;
@@ -197,6 +198,7 @@ public final class EsqlFunctionRegistry extends FunctionRegistry {
                 def(Asin.class, Asin::new, "asin"),
                 def(Atan.class, Atan::new, "atan"),
                 def(Atan2.class, Atan2::new, "atan2"),
+                def(Cbrt.class, Cbrt::new, "cbrt"),
                 def(Ceil.class, Ceil::new, "ceil"),
                 def(Cos.class, Cos::new, "cos"),
                 def(Cosh.class, Cosh::new, "cosh"),

+ 109 - 0
x-pack/plugin/esql/src/main/java/org/elasticsearch/xpack/esql/expression/function/scalar/math/Cbrt.java

@@ -0,0 +1,109 @@
+/*
+ * 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.ExpressionEvaluator;
+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 java.util.List;
+import java.util.function.Function;
+
+import static org.elasticsearch.xpack.esql.type.EsqlDataTypeConverter.unsignedLongToDouble;
+import static org.elasticsearch.xpack.ql.expression.TypeResolutions.ParamOrdinal.DEFAULT;
+import static org.elasticsearch.xpack.ql.expression.TypeResolutions.isNumeric;
+
+public class Cbrt extends UnaryScalarFunction {
+    @FunctionInfo(returnType = "double", description = """
+        Returns the cube root of a number. The input can be any numeric value, the return value is always a double.
+        Cube roots of infinities are null.""", examples = @Example(file = "math", tag = "cbrt"))
+    public Cbrt(
+        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 ExpressionEvaluator.Factory toEvaluator(Function<Expression, ExpressionEvaluator.Factory> toEvaluator) {
+        var field = toEvaluator.apply(field());
+        var fieldType = field().dataType();
+
+        if (fieldType == DataTypes.DOUBLE) {
+            return new CbrtDoubleEvaluator.Factory(source(), field);
+        }
+        if (fieldType == DataTypes.INTEGER) {
+            return new CbrtIntEvaluator.Factory(source(), field);
+        }
+        if (fieldType == DataTypes.LONG) {
+            return new CbrtLongEvaluator.Factory(source(), field);
+        }
+        if (fieldType == DataTypes.UNSIGNED_LONG) {
+            return new CbrtUnsignedLongEvaluator.Factory(source(), field);
+        }
+
+        throw EsqlIllegalArgumentException.illegalDataType(fieldType);
+    }
+
+    @Evaluator(extraName = "Double", warnExceptions = ArithmeticException.class)
+    static double process(double val) {
+        return Math.cbrt(val);
+    }
+
+    @Evaluator(extraName = "Long", warnExceptions = ArithmeticException.class)
+    static double process(long val) {
+        return Math.cbrt(val);
+    }
+
+    @Evaluator(extraName = "UnsignedLong")
+    static double processUnsignedLong(long val) {
+        return Math.cbrt(unsignedLongToDouble(val));
+    }
+
+    @Evaluator(extraName = "Int", warnExceptions = ArithmeticException.class)
+    static double process(int val) {
+        return Math.cbrt(val);
+    }
+
+    @Override
+    public final Expression replaceChildren(List<Expression> newChildren) {
+        return new Cbrt(source(), newChildren.get(0));
+    }
+
+    @Override
+    protected NodeInfo<? extends Expression> info() {
+        return NodeInfo.create(this, Cbrt::new, field());
+    }
+
+    @Override
+    public DataType dataType() {
+        return DataTypes.DOUBLE;
+    }
+
+    @Override
+    protected TypeResolution resolveType() {
+        if (childrenResolved() == false) {
+            return new TypeResolution("Unresolved children");
+        }
+
+        return isNumeric(field, sourceText(), DEFAULT);
+    }
+}

+ 1 - 1
x-pack/plugin/esql/src/main/java/org/elasticsearch/xpack/esql/expression/function/scalar/math/Sqrt.java

@@ -30,7 +30,7 @@ import static org.elasticsearch.xpack.ql.expression.TypeResolutions.isNumeric;
 public class Sqrt extends UnaryScalarFunction {
     @FunctionInfo(returnType = "double", description = """
         Returns the square root of a number. The input can be any numeric value, the return value is always a double.
-        Square roots of negative numbers and infinites are null.""", examples = @Example(file = "math", tag = "sqrt"))
+        Square roots of negative numbers and infinities are null.""", examples = @Example(file = "math", tag = "sqrt"))
     public Sqrt(
         Source source,
         @Param(

+ 3 - 27
x-pack/plugin/esql/src/main/java/org/elasticsearch/xpack/esql/expression/function/scalar/package-info.java

@@ -97,35 +97,11 @@
  *     </li>
  *     <li>
  *         Now you can run all of the ESQL tests like CI:
- *         {@code ./gradlew -p x-pack/plugin/esql/ check}
+ *         {@code ./gradlew -p x-pack/plugin/esql/ test}
  *     </li>
  *     <li>
- *         Now it's time to write some docs! Open {@code docs/reference/esql/esql-functions-operators.asciidoc}
- *         and add your function in alphabetical order to the list at the top and then add it to
- *         the includes below.
- *     </li>
- *     <li>
- *         Now go make a file to include. You can start by copying one of it's neighbors.
- *     </li>
- *     <li>
- *         It's important that any examples you add to the docs be included from the csv-spec file.
- *         That looks like:
- *         <pre>{@code
- * [source.merge.styled,esql]
- * ----
- * include::{esql-specs}/math.csv-spec[tag=mv_min]
- * ----
- * [%header.monospaced.styled,format=dsv,separator=|]
- * |===
- * include::{esql-specs}/math.csv-spec[tag=mv_min-result]
- * |===
- *         }</pre>
- *         This includes the bit of the csv-spec file fenced by {@code // tag::mv_min[]}. You'll
- *         want a fence descriptive for your function. Consider the non-includes lines to be
- *         asciidoc ceremony to make the result look right in the rendered docs.
- *     </li>
- *     <li>
- *         Generate a syntax diagram and a table with supported types by running the tests via
+ *         Now it's time to write some docs!
+ *         Generate the docs, 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 are
  *         <ol>

+ 3 - 0
x-pack/plugin/esql/src/main/java/org/elasticsearch/xpack/esql/io/stream/PlanNamedTypes.java

@@ -67,6 +67,7 @@ import org.elasticsearch.xpack.esql.expression.function.scalar.math.Acos;
 import org.elasticsearch.xpack.esql.expression.function.scalar.math.Asin;
 import org.elasticsearch.xpack.esql.expression.function.scalar.math.Atan;
 import org.elasticsearch.xpack.esql.expression.function.scalar.math.Atan2;
+import org.elasticsearch.xpack.esql.expression.function.scalar.math.Cbrt;
 import org.elasticsearch.xpack.esql.expression.function.scalar.math.Ceil;
 import org.elasticsearch.xpack.esql.expression.function.scalar.math.Cos;
 import org.elasticsearch.xpack.esql.expression.function.scalar.math.Cosh;
@@ -344,6 +345,7 @@ public final class PlanNamedTypes {
             of(ESQL_UNARY_SCLR_CLS, Acos.class, PlanNamedTypes::writeESQLUnaryScalar, PlanNamedTypes::readESQLUnaryScalar),
             of(ESQL_UNARY_SCLR_CLS, Asin.class, PlanNamedTypes::writeESQLUnaryScalar, PlanNamedTypes::readESQLUnaryScalar),
             of(ESQL_UNARY_SCLR_CLS, Atan.class, PlanNamedTypes::writeESQLUnaryScalar, PlanNamedTypes::readESQLUnaryScalar),
+            of(ESQL_UNARY_SCLR_CLS, Cbrt.class, PlanNamedTypes::writeESQLUnaryScalar, PlanNamedTypes::readESQLUnaryScalar),
             of(ESQL_UNARY_SCLR_CLS, Ceil.class, PlanNamedTypes::writeESQLUnaryScalar, PlanNamedTypes::readESQLUnaryScalar),
             of(ESQL_UNARY_SCLR_CLS, Cos.class, PlanNamedTypes::writeESQLUnaryScalar, PlanNamedTypes::readESQLUnaryScalar),
             of(ESQL_UNARY_SCLR_CLS, Cosh.class, PlanNamedTypes::writeESQLUnaryScalar, PlanNamedTypes::readESQLUnaryScalar),
@@ -1289,6 +1291,7 @@ public final class PlanNamedTypes {
         entry(name(Acos.class), Acos::new),
         entry(name(Asin.class), Asin::new),
         entry(name(Atan.class), Atan::new),
+        entry(name(Cbrt.class), Cbrt::new),
         entry(name(Ceil.class), Ceil::new),
         entry(name(Cos.class), Cos::new),
         entry(name(Cosh.class), Cosh::new),

+ 82 - 0
x-pack/plugin/esql/src/test/java/org/elasticsearch/xpack/esql/expression/function/scalar/math/CbrtTests.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;
+
+import static org.elasticsearch.xpack.esql.type.EsqlDataTypeConverter.unsignedLongToDouble;
+
+public class CbrtTests extends AbstractFunctionTestCase {
+    public CbrtTests(@Name("TestCase") Supplier<TestCaseSupplier.TestCase> testCaseSupplier) {
+        this.testCase = testCaseSupplier.get();
+    }
+
+    @ParametersFactory
+    public static Iterable<Object[]> parameters() {
+        String read = "Attribute[channel=0]";
+        List<TestCaseSupplier> suppliers = new ArrayList<>();
+        // Valid values
+        TestCaseSupplier.forUnaryInt(
+            suppliers,
+            "CbrtIntEvaluator[val=" + read + "]",
+            DataTypes.DOUBLE,
+            Math::cbrt,
+            Integer.MIN_VALUE,
+            Integer.MAX_VALUE,
+            List.of()
+        );
+        TestCaseSupplier.forUnaryLong(
+            suppliers,
+            "CbrtLongEvaluator[val=" + read + "]",
+            DataTypes.DOUBLE,
+            Math::cbrt,
+            Long.MIN_VALUE,
+            Long.MAX_VALUE,
+            List.of()
+        );
+        TestCaseSupplier.forUnaryUnsignedLong(
+            suppliers,
+            "CbrtUnsignedLongEvaluator[val=" + read + "]",
+            DataTypes.DOUBLE,
+            ul -> Math.cbrt(unsignedLongToDouble(NumericUtils.asLongUnsigned(ul))),
+            BigInteger.ZERO,
+            UNSIGNED_LONG_MAX,
+            List.of()
+        );
+        TestCaseSupplier.forUnaryDouble(
+            suppliers,
+            "CbrtDoubleEvaluator[val=" + read + "]",
+            DataTypes.DOUBLE,
+            Math::cbrt,
+            Double.MIN_VALUE,
+            Double.MAX_VALUE,
+            List.of()
+        );
+        suppliers = anyNullIsNull(true, suppliers);
+
+        return parameterSuppliersFromTypedData(errorsForCasesWithoutExamples(suppliers));
+    }
+
+    @Override
+    protected Expression build(Source source, List<Expression> args) {
+        return new Cbrt(source, args.get(0));
+    }
+}