Browse Source

ESQL: Improve tests and docs for some functions (#107331)

This improves the tests and docs for a few functions, specifically `E`,
`FLOOR`, `PI`, `POW`, and `ROUND`. The examples and tested signatures
will get copied into the docs and kibana signatures.


Co-authored-by: Liam Thompson <32779855+leemthompo@users.noreply.github.com>
Nik Everett 1 year ago
parent
commit
aac17616a3
46 changed files with 485 additions and 307 deletions
  1. 1 1
      docs/reference/esql/functions/description/e.asciidoc
  2. 4 0
      docs/reference/esql/functions/description/floor.asciidoc
  3. 1 1
      docs/reference/esql/functions/description/pi.asciidoc
  4. 3 1
      docs/reference/esql/functions/description/pow.asciidoc
  5. 1 1
      docs/reference/esql/functions/description/round.asciidoc
  6. 2 12
      docs/reference/esql/functions/examples/e.asciidoc
  7. 13 0
      docs/reference/esql/functions/examples/floor.asciidoc
  8. 13 0
      docs/reference/esql/functions/examples/pi.asciidoc
  9. 2 26
      docs/reference/esql/functions/examples/pow.asciidoc
  10. 13 0
      docs/reference/esql/functions/examples/round.asciidoc
  11. 0 34
      docs/reference/esql/functions/floor.asciidoc
  12. 4 1
      docs/reference/esql/functions/kibana/definition/e.json
  13. 8 4
      docs/reference/esql/functions/kibana/definition/floor.json
  14. 4 1
      docs/reference/esql/functions/kibana/definition/pi.json
  15. 38 33
      docs/reference/esql/functions/kibana/definition/pow.json
  16. 90 3
      docs/reference/esql/functions/kibana/definition/round.json
  17. 4 1
      docs/reference/esql/functions/kibana/docs/e.md
  18. 7 0
      docs/reference/esql/functions/kibana/docs/floor.md
  19. 4 1
      docs/reference/esql/functions/kibana/docs/pi.md
  20. 6 1
      docs/reference/esql/functions/kibana/docs/pow.md
  21. 7 0
      docs/reference/esql/functions/kibana/docs/round.md
  22. 1 0
      docs/reference/esql/functions/layout/e.asciidoc
  23. 1 0
      docs/reference/esql/functions/layout/floor.asciidoc
  24. 1 0
      docs/reference/esql/functions/layout/pi.asciidoc
  25. 1 0
      docs/reference/esql/functions/layout/pow.asciidoc
  26. 1 0
      docs/reference/esql/functions/layout/round.asciidoc
  27. 5 5
      docs/reference/esql/functions/math-functions.asciidoc
  28. 1 1
      docs/reference/esql/functions/parameters/floor.asciidoc
  29. 2 2
      docs/reference/esql/functions/parameters/pow.asciidoc
  30. 2 2
      docs/reference/esql/functions/parameters/round.asciidoc
  31. 0 23
      docs/reference/esql/functions/pi.asciidoc
  32. 1 1
      docs/reference/esql/functions/types/case.asciidoc
  33. 1 1
      docs/reference/esql/functions/types/e.asciidoc
  34. 1 1
      docs/reference/esql/functions/types/pi.asciidoc
  35. 2 0
      docs/reference/esql/functions/types/round.asciidoc
  36. 1 1
      docs/reference/esql/functions/types/tau.asciidoc
  37. 9 9
      x-pack/plugin/esql/qa/testFixtures/src/main/resources/meta.csv-spec
  38. 6 1
      x-pack/plugin/esql/src/main/java/org/elasticsearch/xpack/esql/expression/function/scalar/math/E.java
  39. 15 2
      x-pack/plugin/esql/src/main/java/org/elasticsearch/xpack/esql/expression/function/scalar/math/Floor.java
  40. 6 1
      x-pack/plugin/esql/src/main/java/org/elasticsearch/xpack/esql/expression/function/scalar/math/Pi.java
  41. 19 3
      x-pack/plugin/esql/src/main/java/org/elasticsearch/xpack/esql/expression/function/scalar/math/Pow.java
  42. 13 4
      x-pack/plugin/esql/src/main/java/org/elasticsearch/xpack/esql/expression/function/scalar/math/Round.java
  43. 5 2
      x-pack/plugin/esql/src/test/java/org/elasticsearch/xpack/esql/expression/function/AbstractFunctionTestCase.java
  44. 2 13
      x-pack/plugin/esql/src/test/java/org/elasticsearch/xpack/esql/expression/function/scalar/math/ETests.java
  45. 2 13
      x-pack/plugin/esql/src/test/java/org/elasticsearch/xpack/esql/expression/function/scalar/math/PiTests.java
  46. 162 101
      x-pack/plugin/esql/src/test/java/org/elasticsearch/xpack/esql/expression/function/scalar/math/RoundTests.java

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

@@ -2,4 +2,4 @@
 
 *Description*
 
-Euler’s number.
+Returns {wikipedia}/E_(mathematical_constant)[Euler's number].

+ 4 - 0
docs/reference/esql/functions/description/floor.asciidoc

@@ -3,3 +3,7 @@
 *Description*
 
 Round a number down to the nearest integer.
+
+NOTE: This is a noop for `long` (including unsigned) and `integer`.
+For `double` this picks the closest `double` value to the integer
+similar to {javadoc}/java.base/java/lang/Math.html#floor(double)[Math.floor].

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

@@ -2,4 +2,4 @@
 
 *Description*
 
-The ratio of a circle’s circumference to its diameter.
+Returns the {wikipedia}/Pi[ratio] of a circle's circumference to its diameter.

+ 3 - 1
docs/reference/esql/functions/description/pow.asciidoc

@@ -2,4 +2,6 @@
 
 *Description*
 
-Returns the value of a base raised to the power of an exponent.
+Returns the value of `base` raised to the power of `exponent`.
+
+NOTE: It is still possible to overflow a double result here; in that case, null will be returned.

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

@@ -2,4 +2,4 @@
 
 *Description*
 
-Rounds a number to the closest number with the specified number of digits.
+Rounds a number to the closest number with the specified number of digits. Defaults to 0 digits if no number of digits is provided. If the specified number of digits is negative, rounds to the number of digits left of the decimal point.

+ 2 - 12
docs/reference/esql/functions/e.asciidoc → docs/reference/esql/functions/examples/e.asciidoc

@@ -1,15 +1,4 @@
-[discrete]
-[[esql-e]]
-=== `E`
-
-*Syntax*
-
-[.text-center]
-image::esql/functions/signature/e.svg[Embedded,opts=inline]
-
-*Description*
-
-Returns {wikipedia}/E_(mathematical_constant)[Euler's number].
+// This is generated by ESQL's AbstractFunctionTestCase. Do no edit it. See ../README.md for how to regenerate it.
 
 *Example*
 
@@ -21,3 +10,4 @@ include::{esql-specs}/math.csv-spec[tag=e]
 |===
 include::{esql-specs}/math.csv-spec[tag=e-result]
 |===
+

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

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

+ 2 - 26
docs/reference/esql/functions/pow.asciidoc → docs/reference/esql/functions/examples/pow.asciidoc

@@ -1,27 +1,4 @@
-[discrete]
-[[esql-pow]]
-=== `POW`
-
-*Syntax*
-
-[.text-center]
-image::esql/functions/signature/pow.svg[Embedded,opts=inline]
-
-*Parameters*
-
-`base`::
-Numeric expression. If `null`, the function returns `null`.
-
-`exponent`::
-Numeric expression. If `null`, the function returns `null`.
-
-*Description*
-
-Returns the value of `base` raised to the power of `exponent`. Both arguments
-must be numeric. The output is always a double.  Note that it is still possible
-to overflow a double result here; in that case, null will be returned.
-
-include::types/pow.asciidoc[]
+// This is generated by ESQL's AbstractFunctionTestCase. Do no edit it. See ../README.md for how to regenerate it.
 
 *Examples*
 
@@ -33,10 +10,8 @@ include::{esql-specs}/math.csv-spec[tag=powDI]
 |===
 include::{esql-specs}/math.csv-spec[tag=powDI-result]
 |===
-
 The exponent can be a fraction, which is similar to performing a root.
 For example, the exponent of `0.5` will give the square root of the base:
-
 [source.merge.styled,esql]
 ----
 include::{esql-specs}/math.csv-spec[tag=powID-sqrt]
@@ -45,3 +20,4 @@ include::{esql-specs}/math.csv-spec[tag=powID-sqrt]
 |===
 include::{esql-specs}/math.csv-spec[tag=powID-sqrt-result]
 |===
+

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

+ 0 - 34
docs/reference/esql/functions/floor.asciidoc

@@ -1,34 +0,0 @@
-[discrete]
-[[esql-floor]]
-=== `FLOOR`
-
-*Syntax*
-
-[.text-center]
-image::esql/functions/signature/floor.svg[Embedded,opts=inline]
-
-*Parameters*
-
-`n`::
-Numeric expression. If `null`, the function returns `null`.
-
-*Description*
-
-Rounds a number down to the nearest integer.
-
-NOTE: This is a noop for `long` (including unsigned) and `integer`.
-      For `double` this picks the closest `double` value to the integer
-      similar to {javadoc}/java.base/java/lang/Math.html#floor(double)[Math.floor].
-
-include::types/floor.asciidoc[]
-
-*Example*
-
-[source.merge.styled,esql]
-----
-include::{esql-specs}/math.csv-spec[tag=floor]
-----
-[%header.monospaced.styled,format=dsv,separator=|]
-|===
-include::{esql-specs}/math.csv-spec[tag=floor-result]
-|===

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

@@ -2,11 +2,14 @@
   "comment" : "This is generated by ESQL's AbstractFunctionTestCase. Do no edit it. See ../README.md for how to regenerate it.",
   "type" : "eval",
   "name" : "e",
-  "description" : "Euler’s number.",
+  "description" : "Returns Euler's number.",
   "signatures" : [
     {
       "params" : [ ],
       "returnType" : "double"
     }
+  ],
+  "examples" : [
+    "ROW E()"
   ]
 }

+ 8 - 4
docs/reference/esql/functions/kibana/definition/floor.json

@@ -3,6 +3,7 @@
   "type" : "eval",
   "name" : "floor",
   "description" : "Round a number down to the nearest integer.",
+  "note" : "This is a noop for `long` (including unsigned) and `integer`.\nFor `double` this picks the closest `double` value to the integer\nsimilar to Math.floor.",
   "signatures" : [
     {
       "params" : [
@@ -10,7 +11,7 @@
           "name" : "number",
           "type" : "double",
           "optional" : false,
-          "description" : ""
+          "description" : "Numeric expression. If `null`, the function returns `null`."
         }
       ],
       "variadic" : false,
@@ -22,7 +23,7 @@
           "name" : "number",
           "type" : "integer",
           "optional" : false,
-          "description" : ""
+          "description" : "Numeric expression. If `null`, the function returns `null`."
         }
       ],
       "variadic" : false,
@@ -34,7 +35,7 @@
           "name" : "number",
           "type" : "long",
           "optional" : false,
-          "description" : ""
+          "description" : "Numeric expression. If `null`, the function returns `null`."
         }
       ],
       "variadic" : false,
@@ -46,11 +47,14 @@
           "name" : "number",
           "type" : "unsigned_long",
           "optional" : false,
-          "description" : ""
+          "description" : "Numeric expression. If `null`, the function returns `null`."
         }
       ],
       "variadic" : false,
       "returnType" : "unsigned_long"
     }
+  ],
+  "examples" : [
+    "ROW a=1.8\n| EVAL a=FLOOR(a)"
   ]
 }

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

@@ -2,11 +2,14 @@
   "comment" : "This is generated by ESQL's AbstractFunctionTestCase. Do no edit it. See ../README.md for how to regenerate it.",
   "type" : "eval",
   "name" : "pi",
-  "description" : "The ratio of a circle’s circumference to its diameter.",
+  "description" : "Returns the ratio of a circle's circumference to its diameter.",
   "signatures" : [
     {
       "params" : [ ],
       "returnType" : "double"
     }
+  ],
+  "examples" : [
+    "ROW PI()"
   ]
 }

+ 38 - 33
docs/reference/esql/functions/kibana/definition/pow.json

@@ -2,7 +2,8 @@
   "comment" : "This is generated by ESQL's AbstractFunctionTestCase. Do no edit it. See ../README.md for how to regenerate it.",
   "type" : "eval",
   "name" : "pow",
-  "description" : "Returns the value of a base raised to the power of an exponent.",
+  "description" : "Returns the value of `base` raised to the power of `exponent`.",
+  "note" : "It is still possible to overflow a double result here; in that case, null will be returned.",
   "signatures" : [
     {
       "params" : [
@@ -10,13 +11,13 @@
           "name" : "base",
           "type" : "double",
           "optional" : false,
-          "description" : ""
+          "description" : "Numeric expression for the base. If `null`, the function returns `null`."
         },
         {
           "name" : "exponent",
           "type" : "double",
           "optional" : false,
-          "description" : ""
+          "description" : "Numeric expression for the exponent. If `null`, the function returns `null`."
         }
       ],
       "variadic" : false,
@@ -28,13 +29,13 @@
           "name" : "base",
           "type" : "double",
           "optional" : false,
-          "description" : ""
+          "description" : "Numeric expression for the base. If `null`, the function returns `null`."
         },
         {
           "name" : "exponent",
           "type" : "integer",
           "optional" : false,
-          "description" : ""
+          "description" : "Numeric expression for the exponent. If `null`, the function returns `null`."
         }
       ],
       "variadic" : false,
@@ -46,13 +47,13 @@
           "name" : "base",
           "type" : "double",
           "optional" : false,
-          "description" : ""
+          "description" : "Numeric expression for the base. If `null`, the function returns `null`."
         },
         {
           "name" : "exponent",
           "type" : "long",
           "optional" : false,
-          "description" : ""
+          "description" : "Numeric expression for the exponent. If `null`, the function returns `null`."
         }
       ],
       "variadic" : false,
@@ -64,13 +65,13 @@
           "name" : "base",
           "type" : "double",
           "optional" : false,
-          "description" : ""
+          "description" : "Numeric expression for the base. If `null`, the function returns `null`."
         },
         {
           "name" : "exponent",
           "type" : "unsigned_long",
           "optional" : false,
-          "description" : ""
+          "description" : "Numeric expression for the exponent. If `null`, the function returns `null`."
         }
       ],
       "variadic" : false,
@@ -82,13 +83,13 @@
           "name" : "base",
           "type" : "integer",
           "optional" : false,
-          "description" : ""
+          "description" : "Numeric expression for the base. If `null`, the function returns `null`."
         },
         {
           "name" : "exponent",
           "type" : "double",
           "optional" : false,
-          "description" : ""
+          "description" : "Numeric expression for the exponent. If `null`, the function returns `null`."
         }
       ],
       "variadic" : false,
@@ -100,13 +101,13 @@
           "name" : "base",
           "type" : "integer",
           "optional" : false,
-          "description" : ""
+          "description" : "Numeric expression for the base. If `null`, the function returns `null`."
         },
         {
           "name" : "exponent",
           "type" : "integer",
           "optional" : false,
-          "description" : ""
+          "description" : "Numeric expression for the exponent. If `null`, the function returns `null`."
         }
       ],
       "variadic" : false,
@@ -118,13 +119,13 @@
           "name" : "base",
           "type" : "integer",
           "optional" : false,
-          "description" : ""
+          "description" : "Numeric expression for the base. If `null`, the function returns `null`."
         },
         {
           "name" : "exponent",
           "type" : "long",
           "optional" : false,
-          "description" : ""
+          "description" : "Numeric expression for the exponent. If `null`, the function returns `null`."
         }
       ],
       "variadic" : false,
@@ -136,13 +137,13 @@
           "name" : "base",
           "type" : "integer",
           "optional" : false,
-          "description" : ""
+          "description" : "Numeric expression for the base. If `null`, the function returns `null`."
         },
         {
           "name" : "exponent",
           "type" : "unsigned_long",
           "optional" : false,
-          "description" : ""
+          "description" : "Numeric expression for the exponent. If `null`, the function returns `null`."
         }
       ],
       "variadic" : false,
@@ -154,13 +155,13 @@
           "name" : "base",
           "type" : "long",
           "optional" : false,
-          "description" : ""
+          "description" : "Numeric expression for the base. If `null`, the function returns `null`."
         },
         {
           "name" : "exponent",
           "type" : "double",
           "optional" : false,
-          "description" : ""
+          "description" : "Numeric expression for the exponent. If `null`, the function returns `null`."
         }
       ],
       "variadic" : false,
@@ -172,13 +173,13 @@
           "name" : "base",
           "type" : "long",
           "optional" : false,
-          "description" : ""
+          "description" : "Numeric expression for the base. If `null`, the function returns `null`."
         },
         {
           "name" : "exponent",
           "type" : "integer",
           "optional" : false,
-          "description" : ""
+          "description" : "Numeric expression for the exponent. If `null`, the function returns `null`."
         }
       ],
       "variadic" : false,
@@ -190,13 +191,13 @@
           "name" : "base",
           "type" : "long",
           "optional" : false,
-          "description" : ""
+          "description" : "Numeric expression for the base. If `null`, the function returns `null`."
         },
         {
           "name" : "exponent",
           "type" : "long",
           "optional" : false,
-          "description" : ""
+          "description" : "Numeric expression for the exponent. If `null`, the function returns `null`."
         }
       ],
       "variadic" : false,
@@ -208,13 +209,13 @@
           "name" : "base",
           "type" : "long",
           "optional" : false,
-          "description" : ""
+          "description" : "Numeric expression for the base. If `null`, the function returns `null`."
         },
         {
           "name" : "exponent",
           "type" : "unsigned_long",
           "optional" : false,
-          "description" : ""
+          "description" : "Numeric expression for the exponent. If `null`, the function returns `null`."
         }
       ],
       "variadic" : false,
@@ -226,13 +227,13 @@
           "name" : "base",
           "type" : "unsigned_long",
           "optional" : false,
-          "description" : ""
+          "description" : "Numeric expression for the base. If `null`, the function returns `null`."
         },
         {
           "name" : "exponent",
           "type" : "double",
           "optional" : false,
-          "description" : ""
+          "description" : "Numeric expression for the exponent. If `null`, the function returns `null`."
         }
       ],
       "variadic" : false,
@@ -244,13 +245,13 @@
           "name" : "base",
           "type" : "unsigned_long",
           "optional" : false,
-          "description" : ""
+          "description" : "Numeric expression for the base. If `null`, the function returns `null`."
         },
         {
           "name" : "exponent",
           "type" : "integer",
           "optional" : false,
-          "description" : ""
+          "description" : "Numeric expression for the exponent. If `null`, the function returns `null`."
         }
       ],
       "variadic" : false,
@@ -262,13 +263,13 @@
           "name" : "base",
           "type" : "unsigned_long",
           "optional" : false,
-          "description" : ""
+          "description" : "Numeric expression for the base. If `null`, the function returns `null`."
         },
         {
           "name" : "exponent",
           "type" : "long",
           "optional" : false,
-          "description" : ""
+          "description" : "Numeric expression for the exponent. If `null`, the function returns `null`."
         }
       ],
       "variadic" : false,
@@ -280,17 +281,21 @@
           "name" : "base",
           "type" : "unsigned_long",
           "optional" : false,
-          "description" : ""
+          "description" : "Numeric expression for the base. If `null`, the function returns `null`."
         },
         {
           "name" : "exponent",
           "type" : "unsigned_long",
           "optional" : false,
-          "description" : ""
+          "description" : "Numeric expression for the exponent. If `null`, the function returns `null`."
         }
       ],
       "variadic" : false,
       "returnType" : "double"
     }
+  ],
+  "examples" : [
+    "ROW base = 2.0, exponent = 2\n| EVAL result = POW(base, exponent)",
+    "ROW base = 4, exponent = 0.5\n| EVAL s = POW(base, exponent)"
   ]
 }

+ 90 - 3
docs/reference/esql/functions/kibana/definition/round.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" : "round",
-  "description" : "Rounds a number to the closest number with the specified number of digits.",
+  "description" : "Rounds a number to the closest number with the specified number of digits.\nDefaults to 0 digits if no number of digits is provided. If the specified number\nof digits is negative, rounds to the number of digits left of the decimal point.",
   "signatures" : [
     {
       "params" : [
@@ -10,17 +10,104 @@
           "name" : "number",
           "type" : "double",
           "optional" : false,
-          "description" : "The numeric value to round"
+          "description" : "The numeric value to round. If `null`, the function returns `null`."
+        }
+      ],
+      "variadic" : false,
+      "returnType" : "double"
+    },
+    {
+      "params" : [
+        {
+          "name" : "number",
+          "type" : "double",
+          "optional" : false,
+          "description" : "The numeric value to round. If `null`, the function returns `null`."
         },
         {
           "name" : "decimals",
           "type" : "integer",
           "optional" : true,
-          "description" : "The number of decimal places to round to. Defaults to 0."
+          "description" : "The number of decimal places to round to. Defaults to 0. If `null`, the function returns `null`."
         }
       ],
       "variadic" : false,
       "returnType" : "double"
+    },
+    {
+      "params" : [
+        {
+          "name" : "number",
+          "type" : "integer",
+          "optional" : false,
+          "description" : "The numeric value to round. If `null`, the function returns `null`."
+        }
+      ],
+      "variadic" : false,
+      "returnType" : "integer"
+    },
+    {
+      "params" : [
+        {
+          "name" : "number",
+          "type" : "integer",
+          "optional" : false,
+          "description" : "The numeric value to round. If `null`, the function returns `null`."
+        },
+        {
+          "name" : "decimals",
+          "type" : "integer",
+          "optional" : true,
+          "description" : "The number of decimal places to round to. Defaults to 0. If `null`, the function returns `null`."
+        }
+      ],
+      "variadic" : false,
+      "returnType" : "integer"
+    },
+    {
+      "params" : [
+        {
+          "name" : "number",
+          "type" : "long",
+          "optional" : false,
+          "description" : "The numeric value to round. If `null`, the function returns `null`."
+        }
+      ],
+      "variadic" : false,
+      "returnType" : "long"
+    },
+    {
+      "params" : [
+        {
+          "name" : "number",
+          "type" : "long",
+          "optional" : false,
+          "description" : "The numeric value to round. If `null`, the function returns `null`."
+        },
+        {
+          "name" : "decimals",
+          "type" : "integer",
+          "optional" : true,
+          "description" : "The number of decimal places to round to. Defaults to 0. If `null`, the function returns `null`."
+        }
+      ],
+      "variadic" : false,
+      "returnType" : "long"
+    },
+    {
+      "params" : [
+        {
+          "name" : "number",
+          "type" : "unsigned_long",
+          "optional" : false,
+          "description" : "The numeric value to round. If `null`, the function returns `null`."
+        }
+      ],
+      "variadic" : false,
+      "returnType" : "unsigned_long"
     }
+  ],
+  "examples" : [
+    "FROM employees\n| KEEP first_name, last_name, height\n| EVAL height_ft = ROUND(height * 3.281, 1)"
   ]
 }

+ 4 - 1
docs/reference/esql/functions/kibana/docs/e.md

@@ -3,5 +3,8 @@ This is generated by ESQL's AbstractFunctionTestCase. Do no edit it. See ../READ
 -->
 
 ### E
-Euler’s number.
+Returns Euler's number.
 
+```
+ROW E()
+```

+ 7 - 0
docs/reference/esql/functions/kibana/docs/floor.md

@@ -5,3 +5,10 @@ This is generated by ESQL's AbstractFunctionTestCase. Do no edit it. See ../READ
 ### FLOOR
 Round a number down to the nearest integer.
 
+```
+ROW a=1.8
+| EVAL a=FLOOR(a)
+```
+Note: This is a noop for `long` (including unsigned) and `integer`.
+For `double` this picks the closest `double` value to the integer
+similar to {javadoc}/java.base/java/lang/Math.html#floor(double)[Math.floor].

+ 4 - 1
docs/reference/esql/functions/kibana/docs/pi.md

@@ -3,5 +3,8 @@ This is generated by ESQL's AbstractFunctionTestCase. Do no edit it. See ../READ
 -->
 
 ### PI
-The ratio of a circle’s circumference to its diameter.
+Returns the {wikipedia}/Pi[ratio] of a circle's circumference to its diameter.
 
+```
+ROW PI()
+```

+ 6 - 1
docs/reference/esql/functions/kibana/docs/pow.md

@@ -3,5 +3,10 @@ This is generated by ESQL's AbstractFunctionTestCase. Do no edit it. See ../READ
 -->
 
 ### POW
-Returns the value of a base raised to the power of an exponent.
+Returns the value of `base` raised to the power of `exponent`.
 
+```
+ROW base = 2.0, exponent = 2
+| EVAL result = POW(base, exponent)
+```
+Note: It is still possible to overflow a double result here; in that case, null will be returned.

+ 7 - 0
docs/reference/esql/functions/kibana/docs/round.md

@@ -4,4 +4,11 @@ This is generated by ESQL's AbstractFunctionTestCase. Do no edit it. See ../READ
 
 ### ROUND
 Rounds a number to the closest number with the specified number of digits.
+Defaults to 0 digits if no number of digits is provided. If the specified number
+of digits is negative, rounds to the number of digits left of the decimal point.
 
+```
+FROM employees
+| KEEP first_name, last_name, height
+| EVAL height_ft = ROUND(height * 3.281, 1)
+```

+ 1 - 0
docs/reference/esql/functions/layout/e.asciidoc

@@ -12,3 +12,4 @@ image::esql/functions/signature/e.svg[Embedded,opts=inline]
 include::../parameters/e.asciidoc[]
 include::../description/e.asciidoc[]
 include::../types/e.asciidoc[]
+include::../examples/e.asciidoc[]

+ 1 - 0
docs/reference/esql/functions/layout/floor.asciidoc

@@ -12,3 +12,4 @@ image::esql/functions/signature/floor.svg[Embedded,opts=inline]
 include::../parameters/floor.asciidoc[]
 include::../description/floor.asciidoc[]
 include::../types/floor.asciidoc[]
+include::../examples/floor.asciidoc[]

+ 1 - 0
docs/reference/esql/functions/layout/pi.asciidoc

@@ -12,3 +12,4 @@ image::esql/functions/signature/pi.svg[Embedded,opts=inline]
 include::../parameters/pi.asciidoc[]
 include::../description/pi.asciidoc[]
 include::../types/pi.asciidoc[]
+include::../examples/pi.asciidoc[]

+ 1 - 0
docs/reference/esql/functions/layout/pow.asciidoc

@@ -12,3 +12,4 @@ image::esql/functions/signature/pow.svg[Embedded,opts=inline]
 include::../parameters/pow.asciidoc[]
 include::../description/pow.asciidoc[]
 include::../types/pow.asciidoc[]
+include::../examples/pow.asciidoc[]

+ 1 - 0
docs/reference/esql/functions/layout/round.asciidoc

@@ -12,3 +12,4 @@ image::esql/functions/signature/round.svg[Embedded,opts=inline]
 include::../parameters/round.asciidoc[]
 include::../description/round.asciidoc[]
 include::../types/round.asciidoc[]
+include::../examples/round.asciidoc[]

+ 5 - 5
docs/reference/esql/functions/math-functions.asciidoc

@@ -40,13 +40,13 @@ include::layout/atan2.asciidoc[]
 include::layout/ceil.asciidoc[]
 include::layout/cos.asciidoc[]
 include::layout/cosh.asciidoc[]
-include::e.asciidoc[]
-include::floor.asciidoc[]
+include::layout/e.asciidoc[]
+include::layout/floor.asciidoc[]
 include::layout/log.asciidoc[]
 include::layout/log10.asciidoc[]
-include::pi.asciidoc[]
-include::pow.asciidoc[]
-include::round.asciidoc[]
+include::layout/pi.asciidoc[]
+include::layout/pow.asciidoc[]
+include::layout/round.asciidoc[]
 include::layout/signum.asciidoc[]
 include::layout/sin.asciidoc[]
 include::layout/sinh.asciidoc[]

+ 1 - 1
docs/reference/esql/functions/parameters/floor.asciidoc

@@ -3,4 +3,4 @@
 *Parameters*
 
 `number`::
-
+Numeric expression. If `null`, the function returns `null`.

+ 2 - 2
docs/reference/esql/functions/parameters/pow.asciidoc

@@ -3,7 +3,7 @@
 *Parameters*
 
 `base`::
-
+Numeric expression for the base. If `null`, the function returns `null`.
 
 `exponent`::
-
+Numeric expression for the exponent. If `null`, the function returns `null`.

+ 2 - 2
docs/reference/esql/functions/parameters/round.asciidoc

@@ -3,7 +3,7 @@
 *Parameters*
 
 `number`::
-The numeric value to round
+The numeric value to round. If `null`, the function returns `null`.
 
 `decimals`::
-The number of decimal places to round to. Defaults to 0.
+The number of decimal places to round to. Defaults to 0. If `null`, the function returns `null`.

+ 0 - 23
docs/reference/esql/functions/pi.asciidoc

@@ -1,23 +0,0 @@
-[discrete]
-[[esql-pi]]
-=== `PI`
-
-*Syntax*
-
-[.text-center]
-image::esql/functions/signature/pi.svg[Embedded,opts=inline]
-
-*Description*
-
-Returns the {wikipedia}/Pi[ratio] of a circle's circumference to its diameter.
-
-*Example*
-
-[source.merge.styled,esql]
-----
-include::{esql-specs}/math.csv-spec[tag=pi]
-----
-[%header.monospaced.styled,format=dsv,separator=|]
-|===
-include::{esql-specs}/math.csv-spec[tag=pi-result]
-|===

+ 1 - 1
docs/reference/esql/functions/types/case.asciidoc

@@ -5,5 +5,5 @@
 [%header.monospaced.styled,format=dsv,separator=|]
 |===
 condition | trueValue | result
-
+keyword
 |===

+ 1 - 1
docs/reference/esql/functions/types/e.asciidoc

@@ -5,5 +5,5 @@
 [%header.monospaced.styled,format=dsv,separator=|]
 |===
 result
-
+double
 |===

+ 1 - 1
docs/reference/esql/functions/types/pi.asciidoc

@@ -5,5 +5,5 @@
 [%header.monospaced.styled,format=dsv,separator=|]
 |===
 result
-
+double
 |===

+ 2 - 0
docs/reference/esql/functions/types/round.asciidoc

@@ -6,4 +6,6 @@
 |===
 number | decimals | result
 double | integer | double
+integer | integer | integer
+long | integer | long
 |===

+ 1 - 1
docs/reference/esql/functions/types/tau.asciidoc

@@ -5,5 +5,5 @@
 [%header.monospaced.styled,format=dsv,separator=|]
 |===
 result
-
+double
 |===

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

@@ -57,7 +57,7 @@ double pi()
 "double pow(base:double|integer|long|unsigned_long, exponent:double|integer|long|unsigned_long)"
 "keyword replace(string:keyword|text, regex:keyword|text, newString:keyword|text)"
 "keyword right(string:keyword|text, length:integer)"
-"double round(number:double, ?decimals:integer)"
+"double|integer|long|unsigned_long round(number:double|integer|long|unsigned_long, ?decimals:integer)"
 "keyword|text rtrim(string:keyword|text)"
 "double signum(number:double|integer|long|unsigned_long)"
 "double sin(angle:double|integer|long|unsigned_long)"
@@ -135,7 +135,7 @@ date_parse    |[datePattern, dateString]           |["keyword|text", "keyword|te
 date_trunc    |[interval, date]                    |["date_period|time_duration", date]                                                                                               |[Interval; expressed using the timespan literal syntax., Date expression]
 e             |null                                |null                                                                                                                              |null
 ends_with     |[str, suffix]                       |["keyword|text", "keyword|text"]                                                                                                  |[, ]
-floor         |number                              |"double|integer|long|unsigned_long"                                                                                               |[""]
+floor         |number                              |"double|integer|long|unsigned_long"                                                                                               |Numeric expression. If `null`, the function returns `null`.
 greatest      |first                               |"integer|long|double|boolean|keyword|text|ip|version"                                                                             |[""]
 least         |first                               |"integer|long|double|boolean|keyword|text|ip|version"                                                                             |[""]
 left          |[string, length]                    |["keyword|text", integer]                                                                                                         |[The string from which to return a substring., The number of characters to return.]
@@ -164,10 +164,10 @@ mv_zip        |[string1, string2, delim]           |["keyword|text", "keyword|te
 now           |null                                |null                                                                                                                              |null
 percentile    |[number, percentile]                |["double|integer|long", "double|integer|long"]                                                                                    |[, ]
 pi            |null                                |null                                                                                                                              |null
-pow           |[base, exponent]                    |["double|integer|long|unsigned_long", "double|integer|long|unsigned_long"]                                                        |[, ]
+pow           |[base, exponent]                    |["double|integer|long|unsigned_long", "double|integer|long|unsigned_long"]                                                        |["Numeric expression for the base. If `null`\, the function returns `null`.", "Numeric expression for the exponent. If `null`\, the function returns `null`."]
 replace       |[string, regex, newString]          |["keyword|text", "keyword|text", "keyword|text"]                                                                                  |[, , ]
 right         |[string, length]                    |["keyword|text", integer]                                                                                                         |[, ]
-round         |[number, decimals]                  |[double, integer]                                                                                                                 |[The numeric value to round, The number of decimal places to round to. Defaults to 0.]
+round         |[number, decimals]                  |["double|integer|long|unsigned_long", integer]                                                                                    |["The numeric value to round. If `null`\, the function returns `null`.", "The number of decimal places to round to. Defaults to 0. If `null`\, the function returns `null`."]
 rtrim         |string                              |"keyword|text"                                                                                                                    |[""]
 signum        |number                              |"double|integer|long|unsigned_long"                                                                                               |"Numeric expression. If `null`, the function returns `null`."
 sin           |angle                               |"double|integer|long|unsigned_long"                                                                                               |An angle, in radians. If `null`, the function returns `null`.
@@ -244,7 +244,7 @@ date_extract  |Extracts parts of a date, like year, month, day, hour.
 date_format   |Returns a string representation of a date, in the provided format.
 date_parse    |Parses a string into a date value
 date_trunc    |Rounds down a date to the closest interval.
-e             |Euler’s number.
+e             |Returns {wikipedia}/E_(mathematical_constant)[Euler's number].
 ends_with     |Returns a boolean that indicates whether a keyword string ends with another string
 floor         |Round a number down to the nearest integer.
 greatest      |Returns the maximum value from many columns.
@@ -274,11 +274,11 @@ mv_sum        |Converts a multivalued field into a single valued field containin
 mv_zip        |Combines the values from two multivalued fields with a delimiter that joins them together.
 now           |Returns current date and time.
 percentile    |The value at which a certain percentage of observed values occur.
-pi            |The ratio of a circle’s circumference to its diameter.
-pow           |Returns the value of a base raised to the power of an exponent.
+pi            |Returns {wikipedia}/Pi[Pi], the ratio of a circle's circumference to its diameter.
+pow           |Returns the value of `base` raised to the power of `exponent`.
 replace       |The function substitutes in the string any match of the regular expression with the replacement string.
 right         |Return the substring that extracts length chars from the string starting from the right.
-round         |Rounds a number to the closest number with the specified number of digits.
+round         |Rounds a number to the specified number of decimal places. Defaults to 0, which returns the nearest integer. If the precision is a negative number, rounds to the number of digits left of the decimal point.
 rtrim         |Removes trailing whitespaces from a string.
 signum        |Returns the sign of the given number. It returns `-1` for negative numbers, `0` for `0` and `1` for positive numbers.
 sin           |Returns ths {wikipedia}/Sine_and_cosine[Sine] trigonometric function of an angle.
@@ -390,7 +390,7 @@ pi            |double
 pow           |double                                                                                                                      |[false, false]              |false           |false
 replace       |keyword                                                                                                                     |[false, false, false]       |false           |false
 right         |keyword                                                                                                                     |[false, false]              |false           |false
-round         |double                                                                                                                      |[false, true]               |false           |false
+round         |"double|integer|long|unsigned_long"                                                                                         |[false, true]               |false           |false
 rtrim         |"keyword|text"                                                                                                              |false                       |false           |false
 signum        |double                                                                                                                      |false                       |false           |false
 sin           |double                                                                                                                      |false                       |false           |false

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

@@ -7,6 +7,7 @@
 
 package org.elasticsearch.xpack.esql.expression.function.scalar.math;
 
+import org.elasticsearch.xpack.esql.expression.function.Example;
 import org.elasticsearch.xpack.esql.expression.function.FunctionInfo;
 import org.elasticsearch.xpack.ql.expression.Expression;
 import org.elasticsearch.xpack.ql.tree.Source;
@@ -17,7 +18,11 @@ import java.util.List;
  * Function that emits Euler's number.
  */
 public class E extends DoubleConstantFunction {
-    @FunctionInfo(returnType = "double", description = "Euler’s number.")
+    @FunctionInfo(
+        returnType = "double",
+        description = "Returns {wikipedia}/E_(mathematical_constant)[Euler's number].",
+        examples = @Example(file = "math", tag = "e")
+    )
     public E(Source source) {
         super(source);
     }

+ 15 - 2
x-pack/plugin/esql/src/main/java/org/elasticsearch/xpack/esql/expression/function/scalar/math/Floor.java

@@ -9,6 +9,7 @@ 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.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;
@@ -32,9 +33,21 @@ import static org.elasticsearch.xpack.ql.expression.TypeResolutions.isNumeric;
 public class Floor extends UnaryScalarFunction {
     @FunctionInfo(
         returnType = { "double", "integer", "long", "unsigned_long" },
-        description = "Round a number down to the nearest integer."
+        description = "Round a number down to the nearest integer.",
+        note = """
+            This is a noop for `long` (including unsigned) and `integer`.
+            For `double` this picks the closest `double` value to the integer
+            similar to {javadoc}/java.base/java/lang/Math.html#floor(double)[Math.floor].""",
+        examples = @Example(file = "math", tag = "floor")
     )
-    public Floor(Source source, @Param(name = "number", type = { "double", "integer", "long", "unsigned_long" }) Expression n) {
+    public Floor(
+        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);
     }
 

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

@@ -7,6 +7,7 @@
 
 package org.elasticsearch.xpack.esql.expression.function.scalar.math;
 
+import org.elasticsearch.xpack.esql.expression.function.Example;
 import org.elasticsearch.xpack.esql.expression.function.FunctionInfo;
 import org.elasticsearch.xpack.ql.expression.Expression;
 import org.elasticsearch.xpack.ql.tree.Source;
@@ -18,7 +19,11 @@ import java.util.List;
  */
 public class Pi extends DoubleConstantFunction {
 
-    @FunctionInfo(returnType = "double", description = "The ratio of a circle’s circumference to its diameter.")
+    @FunctionInfo(
+        returnType = "double",
+        description = "Returns {wikipedia}/Pi[Pi], the ratio of a circle's circumference to its diameter.",
+        examples = @Example(file = "math", tag = "pi")
+    )
     public Pi(Source source) {
         super(source);
     }

+ 19 - 3
x-pack/plugin/esql/src/main/java/org/elasticsearch/xpack/esql/expression/function/scalar/math/Pow.java

@@ -9,6 +9,7 @@ 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.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;
@@ -34,11 +35,26 @@ public class Pow extends EsqlScalarFunction implements OptionalArgument {
     private final Expression base, exponent;
     private final DataType dataType;
 
-    @FunctionInfo(returnType = "double", description = "Returns the value of a base raised to the power of an exponent.")
+    @FunctionInfo(
+        returnType = "double",
+        description = "Returns the value of `base` raised to the power of `exponent`.",
+        note = "It is still possible to overflow a double result here; in that case, null will be returned.",
+        examples = { @Example(file = "math", tag = "powDI"), @Example(file = "math", tag = "powID-sqrt", description = """
+            The exponent can be a fraction, which is similar to performing a root.
+            For example, the exponent of `0.5` will give the square root of the base:"""), }
+    )
     public Pow(
         Source source,
-        @Param(name = "base", type = { "double", "integer", "long", "unsigned_long" }) Expression base,
-        @Param(name = "exponent", type = { "double", "integer", "long", "unsigned_long" }) Expression exponent
+        @Param(
+            name = "base",
+            type = { "double", "integer", "long", "unsigned_long" },
+            description = "Numeric expression for the base. If `null`, the function returns `null`."
+        ) Expression base,
+        @Param(
+            name = "exponent",
+            type = { "double", "integer", "long", "unsigned_long" },
+            description = "Numeric expression for the exponent. If `null`, the function returns `null`."
+        ) Expression exponent
     ) {
         super(source, Arrays.asList(base, exponent));
         this.base = base;

+ 13 - 4
x-pack/plugin/esql/src/main/java/org/elasticsearch/xpack/esql/expression/function/scalar/math/Round.java

@@ -11,6 +11,7 @@ import org.elasticsearch.common.TriFunction;
 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.EsqlScalarFunction;
@@ -44,15 +45,23 @@ 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", description = "Rounds a number to the closest number with the specified number of digits.")
+    @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
+        precision is a negative number, rounds to the number of digits left
+        of the decimal point.""", examples = @Example(file = "docs", tag = "round"))
     public Round(
         Source source,
-        @Param(name = "number", type = "double", description = "The numeric value to round") Expression field,
+        @Param(
+            name = "number",
+            type = { "double", "integer", "long", "unsigned_long" },
+            description = "The numeric value to round. If `null`, the function returns `null`."
+        ) Expression field,
         @Param(
             optional = true,
             name = "decimals",
-            type = { "integer" },
-            description = "The number of decimal places to round to. Defaults to 0."
+            type = { "integer" },  // TODO long is supported here too
+            description = "The number of decimal places to round to. Defaults to 0. If `null`, the function returns `null`."
         ) Expression decimals
     ) {
         super(source, decimals != null ? Arrays.asList(field, decimals) : Arrays.asList(field));

+ 5 - 2
x-pack/plugin/esql/src/test/java/org/elasticsearch/xpack/esql/expression/function/AbstractFunctionTestCase.java

@@ -1111,6 +1111,9 @@ public abstract class AbstractFunctionTestCase extends ESTestCase {
             table.add(b.toString());
         }
         Collections.sort(table);
+        if (table.isEmpty()) {
+            table.add(signatures.values().iterator().next().typeName());
+        }
 
         String rendered = DOCS_WARNING + """
             *Supported types*
@@ -1211,7 +1214,7 @@ public abstract class AbstractFunctionTestCase extends ESTestCase {
 
             """);
         builder.append("### ").append(name.toUpperCase(Locale.ROOT)).append("\n");
-        builder.append(info.description()).append("\n\n");
+        builder.append(removeAsciidocLinks(info.description())).append("\n\n");
 
         if (info.examples().length > 0) {
             Example example = info.examples()[0];
@@ -1220,7 +1223,7 @@ public abstract class AbstractFunctionTestCase extends ESTestCase {
             builder.append("```\n");
         }
         if (Strings.isNullOrEmpty(info.note()) == false) {
-            builder.append("Note: ").append(info.note()).append("\n");
+            builder.append("Note: ").append(removeAsciidocLinks(info.note())).append("\n");
         }
         String rendered = builder.toString();
         LogManager.getLogger(getTestClass()).info("Writing kibana inline docs for [{}]:\n{}", functionName(), rendered);

+ 2 - 13
x-pack/plugin/esql/src/test/java/org/elasticsearch/xpack/esql/expression/function/scalar/math/ETests.java

@@ -12,11 +12,10 @@ import com.carrotsearch.randomizedtesting.annotations.ParametersFactory;
 
 import org.elasticsearch.compute.data.Block;
 import org.elasticsearch.compute.data.DoubleBlock;
+import org.elasticsearch.xpack.esql.expression.function.AbstractFunctionTestCase;
 import org.elasticsearch.xpack.esql.expression.function.TestCaseSupplier;
-import org.elasticsearch.xpack.esql.expression.function.scalar.AbstractScalarFunctionTestCase;
 import org.elasticsearch.xpack.ql.expression.Expression;
 import org.elasticsearch.xpack.ql.tree.Source;
-import org.elasticsearch.xpack.ql.type.DataType;
 import org.elasticsearch.xpack.ql.type.DataTypes;
 import org.hamcrest.Matcher;
 
@@ -25,7 +24,7 @@ import java.util.function.Supplier;
 
 import static org.hamcrest.Matchers.equalTo;
 
-public class ETests extends AbstractScalarFunctionTestCase {
+public class ETests extends AbstractFunctionTestCase {
     public ETests(@Name("TestCase") Supplier<TestCaseSupplier.TestCase> testCaseSupplier) {
         this.testCase = testCaseSupplier.get();
     }
@@ -47,16 +46,6 @@ public class ETests extends AbstractScalarFunctionTestCase {
         return new E(Source.EMPTY);
     }
 
-    @Override
-    protected List<ArgumentSpec> argSpec() {
-        return List.of();
-    }
-
-    @Override
-    protected DataType expectedType(List<DataType> argTypes) {
-        return DataTypes.DOUBLE;
-    }
-
     @Override
     protected void assertSimpleWithNulls(List<Object> data, Block value, int nullBlock) {
         assertThat(((DoubleBlock) value).asVector().getDouble(0), equalTo(Math.E));

+ 2 - 13
x-pack/plugin/esql/src/test/java/org/elasticsearch/xpack/esql/expression/function/scalar/math/PiTests.java

@@ -12,11 +12,10 @@ import com.carrotsearch.randomizedtesting.annotations.ParametersFactory;
 
 import org.elasticsearch.compute.data.Block;
 import org.elasticsearch.compute.data.DoubleBlock;
+import org.elasticsearch.xpack.esql.expression.function.AbstractFunctionTestCase;
 import org.elasticsearch.xpack.esql.expression.function.TestCaseSupplier;
-import org.elasticsearch.xpack.esql.expression.function.scalar.AbstractScalarFunctionTestCase;
 import org.elasticsearch.xpack.ql.expression.Expression;
 import org.elasticsearch.xpack.ql.tree.Source;
-import org.elasticsearch.xpack.ql.type.DataType;
 import org.elasticsearch.xpack.ql.type.DataTypes;
 import org.hamcrest.Matcher;
 
@@ -25,7 +24,7 @@ import java.util.function.Supplier;
 
 import static org.hamcrest.Matchers.equalTo;
 
-public class PiTests extends AbstractScalarFunctionTestCase {
+public class PiTests extends AbstractFunctionTestCase {
     public PiTests(@Name("TestCase") Supplier<TestCaseSupplier.TestCase> testCaseSupplier) {
         this.testCase = testCaseSupplier.get();
     }
@@ -47,16 +46,6 @@ public class PiTests extends AbstractScalarFunctionTestCase {
         return new Pi(Source.EMPTY);
     }
 
-    @Override
-    protected List<ArgumentSpec> argSpec() {
-        return List.of();
-    }
-
-    @Override
-    protected DataType expectedType(List<DataType> argTypes) {
-        return DataTypes.DOUBLE;
-    }
-
     @Override
     protected void assertSimpleWithNulls(List<Object> data, Block value, int nullBlock) {
         assertThat(((DoubleBlock) value).asVector().getDouble(0), equalTo(Math.PI));

+ 162 - 101
x-pack/plugin/esql/src/test/java/org/elasticsearch/xpack/esql/expression/function/scalar/math/RoundTests.java

@@ -10,154 +10,215 @@ package org.elasticsearch.xpack.esql.expression.function.scalar.math;
 import com.carrotsearch.randomizedtesting.annotations.Name;
 import com.carrotsearch.randomizedtesting.annotations.ParametersFactory;
 
-import org.elasticsearch.compute.data.Block;
+import org.elasticsearch.test.ESTestCase;
+import org.elasticsearch.xpack.esql.expression.function.AbstractFunctionTestCase;
 import org.elasticsearch.xpack.esql.expression.function.TestCaseSupplier;
-import org.elasticsearch.xpack.esql.expression.function.scalar.AbstractScalarFunctionTestCase;
 import org.elasticsearch.xpack.ql.expression.Expression;
 import org.elasticsearch.xpack.ql.expression.predicate.operator.math.Maths;
 import org.elasticsearch.xpack.ql.tree.Source;
 import org.elasticsearch.xpack.ql.type.DataType;
 import org.elasticsearch.xpack.ql.type.DataTypes;
+import org.elasticsearch.xpack.ql.util.NumericUtils;
 
+import java.util.ArrayList;
 import java.util.List;
+import java.util.function.BiFunction;
+import java.util.function.Function;
 import java.util.function.Supplier;
 
-import static org.elasticsearch.compute.data.BlockUtils.toJavaObject;
+import static org.elasticsearch.test.ESTestCase.randomDouble;
 import static org.hamcrest.Matchers.equalTo;
 import static org.hamcrest.Matchers.is;
 import static org.hamcrest.Matchers.nullValue;
 
-public class RoundTests extends AbstractScalarFunctionTestCase {
+public class RoundTests extends AbstractFunctionTestCase {
     public RoundTests(@Name("TestCase") Supplier<TestCaseSupplier.TestCase> testCaseSupplier) {
         this.testCase = testCaseSupplier.get();
     }
 
     @ParametersFactory
     public static Iterable<Object[]> parameters() {
-        return parameterSuppliersFromTypedData(List.of(new TestCaseSupplier("round(<double>, <int>)", () -> {
-            double number = 1 / randomDouble();
-            int precision = between(-30, 30);
-            return new TestCaseSupplier.TestCase(
-                List.of(
-                    new TestCaseSupplier.TypedData(number, DataTypes.DOUBLE, "number"),
-                    new TestCaseSupplier.TypedData(precision, DataTypes.INTEGER, "precision")
-                ),
-                "RoundDoubleEvaluator[val=Attribute[channel=0], decimals=CastIntToLongEvaluator[v=Attribute[channel=1]]]",
+        List<TestCaseSupplier> suppliers = new ArrayList<>();
+        suppliers.add(
+            supplier(
+                "<double>",
                 DataTypes.DOUBLE,
-                equalTo(Maths.round(number, precision))
-            );
-        }), new TestCaseSupplier("round([<double>], <int>)", () -> {
-            double number = 1 / randomDouble();
-            int precision = between(-30, 30);
-            return new TestCaseSupplier.TestCase(
-                List.of(
-                    new TestCaseSupplier.TypedData(List.of(number), DataTypes.DOUBLE, "number"),
-                    new TestCaseSupplier.TypedData(precision, DataTypes.INTEGER, "precision")
-                ),
-                "RoundDoubleEvaluator[val=Attribute[channel=0], decimals=CastIntToLongEvaluator[v=Attribute[channel=1]]]",
+                () -> 1 / randomDouble(),
+                "RoundDoubleNoDecimalsEvaluator[val=Attribute[channel=0]]",
+                d -> Maths.round(d, 0)
+            )
+        );
+        suppliers.add(
+            supplier(
+                "<double>, <integer>",
                 DataTypes.DOUBLE,
-                equalTo(Maths.round(number, precision))
-            );
-        }), new TestCaseSupplier("round([<double>], <int>)", () -> {
+                () -> 1 / randomDouble(),
+                DataTypes.INTEGER,
+                () -> between(-30, 30),
+                "RoundDoubleEvaluator[val=Attribute[channel=0], decimals=CastIntToLongEvaluator[v=Attribute[channel=1]]]",
+                Maths::round
+            )
+        );
+        // TODO randomized cases for more types
+        // TODO errorsForCasesWithoutExamples
+        suppliers = anyNullIsNull(
+            suppliers,
+            (nullPosition, nullValueDataType, original) -> nullPosition == 0 ? nullValueDataType : original.expectedType(),
+            (nullPosition, original) -> original
+        );
+
+        suppliers.add(new TestCaseSupplier("two doubles", List.of(DataTypes.DOUBLE, DataTypes.INTEGER), () -> {
             double number1 = 1 / randomDouble();
             double number2 = 1 / randomDouble();
             int precision = between(-30, 30);
             return new TestCaseSupplier.TestCase(
                 List.of(
                     new TestCaseSupplier.TypedData(List.of(number1, number2), DataTypes.DOUBLE, "number"),
-                    new TestCaseSupplier.TypedData(precision, DataTypes.INTEGER, "precision")
+                    new TestCaseSupplier.TypedData(precision, DataTypes.INTEGER, "decimals")
                 ),
                 "RoundDoubleEvaluator[val=Attribute[channel=0], decimals=CastIntToLongEvaluator[v=Attribute[channel=1]]]",
                 DataTypes.DOUBLE,
                 is(nullValue())
             ).withWarning("Line -1:-1: evaluation of [] failed, treating result as null. Only first 20 failures recorded.")
                 .withWarning("Line -1:-1: java.lang.IllegalArgumentException: single-value function encountered multi-value");
-        })));
-    }
+        }));
 
-    public void testExamples() {
-        assertEquals(123, process(123));
-        assertEquals(123, process(123, randomIntBetween(0, 1024)));
-        assertEquals(120, process(123, -1));
-        assertEquals(123.5, process(123.45, 1));
-        assertEquals(123.0, process(123.45, 0));
-        assertEquals(123.0, process(123.45));
-        assertEquals(123L, process(123L, 0));
-        assertEquals(123L, process(123L, 5));
-        assertEquals(120L, process(123L, -1));
-        assertEquals(100L, process(123L, -2));
-        assertEquals(0L, process(123L, -3));
-        assertEquals(0L, process(123L, -100));
-        assertEquals(1000L, process(999L, -1));
-        assertEquals(1000.0, process(999.0, -1));
-        assertEquals(130L, process(125L, -1));
-        assertEquals(12400L, process(12350L, -2));
-        assertEquals(12400.0, process(12350.0, -2));
-        assertEquals(12300.0, process(12349.0, -2));
-        assertEquals(-12300L, process(-12349L, -2));
-        assertEquals(-12400L, process(-12350L, -2));
-        assertEquals(-12400.0, process(-12350.0, -2));
-        assertEquals(-100L, process(-123L, -2));
-        assertEquals(-120.0, process(-123.45, -1));
-        assertEquals(-123.5, process(-123.45, 1));
-        assertEquals(-124.0, process(-123.5, 0));
-        assertEquals(-123.0, process(-123.45));
-        assertEquals(123.456, process(123.456, Integer.MAX_VALUE));
-        assertEquals(0.0, process(123.456, Integer.MIN_VALUE));
-        assertEquals(0L, process(0L, 0));
-        assertEquals(0, process(0, 0));
-        assertEquals(Long.MAX_VALUE, process(Long.MAX_VALUE));
-        assertEquals(Long.MAX_VALUE, process(Long.MAX_VALUE, 5));
-        assertEquals(Long.MIN_VALUE, process(Long.MIN_VALUE));
-        assertEquals(Long.MIN_VALUE, process(Long.MIN_VALUE, 5));
-    }
+        // Integer or Long without a decimals parameter is a noop
+        suppliers.add(supplier("<integer>", DataTypes.INTEGER, ESTestCase::randomInt, "Attribute[channel=0]", Function.identity()));
+        suppliers.add(supplier("<long>", DataTypes.LONG, ESTestCase::randomLong, "Attribute[channel=0]", Function.identity()));
+        suppliers.add(
+            supplier(
+                "<unsigned_long>",
+                DataTypes.UNSIGNED_LONG,
+                ESTestCase::randomLong,
+                "Attribute[channel=0]",
+                NumericUtils::unsignedLongAsBigInteger
+            )
+        );
+
+        suppliers.add(supplier(0, 0));
+        suppliers.add(supplier(123.45, 123));
+        suppliers.add(supplier(0, 0, 0));
+        suppliers.add(supplier(123.45, 0, 123));
+        suppliers.add(supplier(123.45, 1, 123.5));
+        suppliers.add(supplier(999.0, -1, 1000.0));
+        suppliers.add(supplier(12350.0, -2, 12400.0));
+        suppliers.add(supplier(12349.0, -2, 12300.0));
+        suppliers.add(supplier(-12350.0, -2, -12400.0));
+        suppliers.add(supplier(-123.45, -1, -120.0));
+        suppliers.add(supplier(-123.45, 1, -123.5));
+        suppliers.add(supplier(-123.5, 0, -124.0));
+        suppliers.add(supplier(-123.45, -123.0));
+        suppliers.add(supplier(123.456, Integer.MAX_VALUE, 123.456));
+        suppliers.add(supplier(123.456, Integer.MIN_VALUE, 0.0));
 
-    private Object process(Number val) {
-        try (
-            Block block = evaluator(new Round(Source.EMPTY, field("val", typeOf(val)), null)).get(driverContext()).eval(row(List.of(val)))
-        ) {
-            return toJavaObject(block, 0);
-        }
+        suppliers.add(supplier(123L, 0, 123));
+        suppliers.add(supplier(123L, 5, 123));
+        suppliers.add(supplier(123L, -1, 120));
+        suppliers.add(supplier(123L, -2, 100));
+        suppliers.add(supplier(123L, -3, 0));
+        suppliers.add(supplier(123L, -100, 0));
+        suppliers.add(supplier(999L, -1, 1000));
+        suppliers.add(supplier(-123L, -2, -100));
+        suppliers.add(supplier(125L, -1, 130));
+        suppliers.add(supplier(12350L, -2, 12400));
+        suppliers.add(supplier(-12349L, -2, -12300));
+        suppliers.add(supplier(-12350L, -2, -12400));
+        suppliers.add(supplier(Long.MAX_VALUE, 5, Long.MAX_VALUE));
+        suppliers.add(supplier(Long.MIN_VALUE, 5, Long.MIN_VALUE));
+
+        suppliers.add(supplier(0, 0, 0));
+        suppliers.add(supplier(123, 2, 123));
+        suppliers.add(supplier(123, -1, 120));
+        return parameterSuppliersFromTypedData(suppliers);
     }
 
-    private Object process(Number val, int decimals) {
-        try (
-            Block block = evaluator(new Round(Source.EMPTY, field("val", typeOf(val)), field("decimals", DataTypes.INTEGER))).get(
-                driverContext()
-            ).eval(row(List.of(val, decimals)))
-        ) {
-            return toJavaObject(block, 0);
-        }
+    private static TestCaseSupplier supplier(double v, double expected) {
+        return supplier(
+            "round(" + v + ") -> " + expected,
+            DataTypes.DOUBLE,
+            () -> v,
+            "RoundDoubleNoDecimalsEvaluator[val=Attribute[channel=0]]",
+            value -> expected
+        );
     }
 
-    private DataType typeOf(Number val) {
-        if (val instanceof Integer) {
-            return DataTypes.INTEGER;
-        }
-        if (val instanceof Long) {
-            return DataTypes.LONG;
-        }
-        if (val instanceof Double) {
-            return DataTypes.DOUBLE;
-        }
-        throw new UnsupportedOperationException("unsupported type [" + val.getClass() + "]");
+    private static TestCaseSupplier supplier(double v, int decimals, double expected) {
+        return supplier(
+            "round(" + v + ", " + decimals + ") -> " + expected,
+            DataTypes.DOUBLE,
+            () -> v,
+            DataTypes.INTEGER,
+            () -> decimals,
+            "RoundDoubleEvaluator[val=Attribute[channel=0], decimals=CastIntToLongEvaluator[v=Attribute[channel=1]]]",
+            (value, de) -> expected
+        );
     }
 
-    @Override
-    protected DataType expectedType(List<DataType> argTypes) {
-        return argTypes.get(0);
+    private static TestCaseSupplier supplier(long v, int decimals, long expected) {
+        return supplier(
+            "round(" + v + "L, " + decimals + ") -> " + expected,
+            DataTypes.LONG,
+            () -> v,
+            DataTypes.INTEGER,
+            () -> decimals,
+            "RoundLongEvaluator[val=Attribute[channel=0], decimals=CastIntToLongEvaluator[v=Attribute[channel=1]]]",
+            (value, de) -> expected
+        );
     }
 
-    public void testNoDecimalsToString() {
-        assertThat(
-            evaluator(new Round(Source.EMPTY, field("val", DataTypes.DOUBLE), null)).get(driverContext()).toString(),
-            equalTo("RoundDoubleNoDecimalsEvaluator[val=Attribute[channel=0]]")
+    private static TestCaseSupplier supplier(int v, int decimals, int expected) {
+        return supplier(
+            "round(" + v + ", " + decimals + ") -> " + expected,
+            DataTypes.INTEGER,
+            () -> v,
+            DataTypes.INTEGER,
+            () -> decimals,
+            "RoundIntEvaluator[val=Attribute[channel=0], decimals=CastIntToLongEvaluator[v=Attribute[channel=1]]]",
+            (value, de) -> expected
         );
     }
 
-    @Override
-    protected List<ArgumentSpec> argSpec() {
-        return List.of(required(numerics()), optional(integers()));
+    private static <N> TestCaseSupplier supplier(
+        String name,
+        DataType numberType,
+        Supplier<N> numberSupplier,
+        String expectedEvaluatorName,
+        Function<N, ? extends Number> expected
+    ) {
+        return new TestCaseSupplier(name, List.of(numberType), () -> {
+            N number = numberSupplier.get();
+            return new TestCaseSupplier.TestCase(
+                List.of(new TestCaseSupplier.TypedData(number, numberType, "number")),
+                expectedEvaluatorName,
+                numberType,
+                equalTo(expected.apply(number))
+            );
+        });
+    }
+
+    private static <N, D> TestCaseSupplier supplier(
+        String name,
+        DataType numberType,
+        Supplier<N> numberSupplier,
+        DataType decimalsType,
+        Supplier<D> decimalsSupplier,
+        String expectedEvaluatorName,
+        BiFunction<N, D, ? extends Number> expected
+    ) {
+        return new TestCaseSupplier(name, List.of(numberType, decimalsType), () -> {
+            N number = numberSupplier.get();
+            D decimals = decimalsSupplier.get();
+            return new TestCaseSupplier.TestCase(
+                List.of(
+                    new TestCaseSupplier.TypedData(number, numberType, "number"),
+                    new TestCaseSupplier.TypedData(decimals, decimalsType, "decimals")
+                ),
+                expectedEvaluatorName,
+                numberType,
+                equalTo(expected.apply(number, decimals))
+            );
+        });
     }
 
     @Override