Bladeren bron

ESQL: enhance SHOW FUNCTIONS command (#99736)

Fixes https://github.com/elastic/elasticsearch/issues/99507

Enhance SHOW FUNCTIONS command to return as _structured_ information as
possible about the function signature, ie. - function name - return type
- param names - param types - param descriptions

For now, as an example, the annotations are used only on `sin()` and
`date_parse()` functions; **if we agree on this approach**, I'll proceed
to - enhance all the currently implemented functions with the needed
information - improve the function tests to verify that every new
implemented function provides meaningful information

---

This feature can be useful for the end user, but the main goal is to
give Kibana an easy way to produce in-line documentation (contextual
messages, autocomplete) for functions

Similar to current implementation, that has a `@Named("paramName")`
annotation for function parameters, this PR introduces two more
annotations `@Param(name, type, description, optional)` and
`@FunctionInfo()` to provide information about single parameters and
functions.

The result of `SHOW FUNCTIONS` query will have the following columns: -
name (keyword): the function name - synopsis (keyword): the full
signature of the funciton, eg. `double sin(n:integer|long|double)` -
argNames (keyword MV): the function argument names - argTypes (keyword
MV): the function argument types - argDescriptions (keyword MD): a
textual description of each function argument - returnType (keyword):
the return type of the function - description (keyword): a textual
description of the function

---

Open questions: - ~~how structured shoud *types* be? Eg. should we have
a strict `@Typed("keyword")`/`@Typed({"keyword", "text"})` or should we
have a more generic type description, eg. `@Typed("numeric")`,
`@Typed("any")`? The first one is more useful for API consumption but
it's hard with our complex type system (type classes, custom types,
unsupported and so on); the second one is less structured, but probably
more useful for documentation, that is the most immediate use case of
this feature.~~ All the types are listed explicitly

- ~~we have alternatives for the synopsis, eg.~~
  - ~~`functionName(<paramName>:<paramType>, ...): <returnType>`~
  - ~~`<returnType> functionName(<paramName>:<paramType>, ...)`~~
  - ~~`<returnType> functionName(<paramType> <paramName>, ...)`~~
  Using `<returnType> functionName(<paramName>:<paramType>, ...)` for now. If multiple types are supported, then they will be separated by pipes, eg. `double sin(n:integer|long|double)`.
Luigi Dell'Aquila 2 jaren geleden
bovenliggende
commit
6e79013088
42 gewijzigde bestanden met toevoegingen van 607 en 185 verwijderingen
  1. 6 0
      docs/changelog/99736.yaml
  2. 1 1
      docs/reference/esql/functions/types/case.asciidoc
  3. 1 1
      docs/reference/esql/functions/types/coalesce.asciidoc
  4. 1 1
      docs/reference/esql/functions/types/concat.asciidoc
  5. 2 1
      docs/reference/esql/functions/types/date_parse.asciidoc
  6. 4 1
      docs/reference/esql/functions/types/greatest.asciidoc
  7. 4 1
      docs/reference/esql/functions/types/least.asciidoc
  8. 3 1
      x-pack/plugin/esql/compute/gen/src/main/java/org/elasticsearch/compute/gen/ConsumeProcessor.java
  9. 177 87
      x-pack/plugin/esql/qa/testFixtures/src/main/resources/show.csv-spec
  10. 16 1
      x-pack/plugin/esql/src/internalClusterTest/java/org/elasticsearch/xpack/esql/action/EsqlActionIT.java
  11. 69 0
      x-pack/plugin/esql/src/main/java/org/elasticsearch/xpack/esql/expression/function/EsqlFunctionRegistry.java
  12. 6 4
      x-pack/plugin/esql/src/main/java/org/elasticsearch/xpack/esql/expression/function/FunctionInfo.java
  13. 28 0
      x-pack/plugin/esql/src/main/java/org/elasticsearch/xpack/esql/expression/function/Param.java
  14. 6 2
      x-pack/plugin/esql/src/main/java/org/elasticsearch/xpack/esql/expression/function/scalar/conditional/Greatest.java
  15. 6 2
      x-pack/plugin/esql/src/main/java/org/elasticsearch/xpack/esql/expression/function/scalar/conditional/Least.java
  16. 8 2
      x-pack/plugin/esql/src/main/java/org/elasticsearch/xpack/esql/expression/function/scalar/convert/ToString.java
  17. 2 2
      x-pack/plugin/esql/src/main/java/org/elasticsearch/xpack/esql/expression/function/scalar/convert/ToVersion.java
  18. 8 1
      x-pack/plugin/esql/src/main/java/org/elasticsearch/xpack/esql/expression/function/scalar/date/DateParse.java
  19. 2 2
      x-pack/plugin/esql/src/main/java/org/elasticsearch/xpack/esql/expression/function/scalar/math/Abs.java
  20. 2 2
      x-pack/plugin/esql/src/main/java/org/elasticsearch/xpack/esql/expression/function/scalar/math/Acos.java
  21. 2 2
      x-pack/plugin/esql/src/main/java/org/elasticsearch/xpack/esql/expression/function/scalar/math/Asin.java
  22. 2 2
      x-pack/plugin/esql/src/main/java/org/elasticsearch/xpack/esql/expression/function/scalar/math/Atan.java
  23. 6 2
      x-pack/plugin/esql/src/main/java/org/elasticsearch/xpack/esql/expression/function/scalar/math/Atan2.java
  24. 2 2
      x-pack/plugin/esql/src/main/java/org/elasticsearch/xpack/esql/expression/function/scalar/math/Ceil.java
  25. 2 2
      x-pack/plugin/esql/src/main/java/org/elasticsearch/xpack/esql/expression/function/scalar/math/Cos.java
  26. 2 2
      x-pack/plugin/esql/src/main/java/org/elasticsearch/xpack/esql/expression/function/scalar/math/Cosh.java
  27. 2 2
      x-pack/plugin/esql/src/main/java/org/elasticsearch/xpack/esql/expression/function/scalar/math/Floor.java
  28. 2 2
      x-pack/plugin/esql/src/main/java/org/elasticsearch/xpack/esql/expression/function/scalar/math/Log10.java
  29. 6 2
      x-pack/plugin/esql/src/main/java/org/elasticsearch/xpack/esql/expression/function/scalar/math/Pow.java
  30. 8 2
      x-pack/plugin/esql/src/main/java/org/elasticsearch/xpack/esql/expression/function/scalar/math/Sin.java
  31. 2 2
      x-pack/plugin/esql/src/main/java/org/elasticsearch/xpack/esql/expression/function/scalar/math/Sinh.java
  32. 2 2
      x-pack/plugin/esql/src/main/java/org/elasticsearch/xpack/esql/expression/function/scalar/math/Sqrt.java
  33. 2 2
      x-pack/plugin/esql/src/main/java/org/elasticsearch/xpack/esql/expression/function/scalar/math/Tan.java
  34. 2 2
      x-pack/plugin/esql/src/main/java/org/elasticsearch/xpack/esql/expression/function/scalar/math/Tanh.java
  35. 6 2
      x-pack/plugin/esql/src/main/java/org/elasticsearch/xpack/esql/expression/function/scalar/string/Left.java
  36. 6 2
      x-pack/plugin/esql/src/main/java/org/elasticsearch/xpack/esql/expression/function/scalar/string/Right.java
  37. 40 25
      x-pack/plugin/esql/src/main/java/org/elasticsearch/xpack/esql/plan/logical/show/ShowFunctions.java
  38. 43 4
      x-pack/plugin/esql/src/test/java/org/elasticsearch/xpack/esql/expression/function/AbstractFunctionTestCase.java
  39. 1 2
      x-pack/plugin/esql/src/test/java/org/elasticsearch/xpack/esql/expression/function/RailRoadDiagram.java
  40. 45 0
      x-pack/plugin/esql/src/test/java/org/elasticsearch/xpack/esql/expression/function/scalar/conditional/GreatestTests.java
  41. 45 0
      x-pack/plugin/esql/src/test/java/org/elasticsearch/xpack/esql/expression/function/scalar/conditional/LeastTests.java
  42. 27 10
      x-pack/plugin/esql/src/test/java/org/elasticsearch/xpack/esql/expression/function/scalar/date/DateParseTests.java

+ 6 - 0
docs/changelog/99736.yaml

@@ -0,0 +1,6 @@
+pr: 99736
+summary: "ESQL: enhance SHOW FUNCTIONS command"
+area: ES|QL
+type: enhancement
+issues:
+ - 99507

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

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

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

@@ -1,6 +1,6 @@
 [%header.monospaced.styled,format=dsv,separator=|]
 |===
-arg1 | arg2... | result
+arg1 | arg2 | result
 boolean | boolean | boolean
 integer | integer | integer
 keyword | keyword | keyword

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

@@ -1,6 +1,6 @@
 [%header.monospaced.styled,format=dsv,separator=|]
 |===
-arg1 | arg2... | result
+arg1 | arg2 | result
 keyword | keyword | keyword
 text | text | keyword
 |===

+ 2 - 1
docs/reference/esql/functions/types/date_parse.asciidoc

@@ -1,5 +1,6 @@
 [%header.monospaced.styled,format=dsv,separator=|]
 |===
-arg1 | arg2 | result
+datePattern | dateString | result
 keyword | keyword | datetime
+keyword | text | datetime
 |===

+ 4 - 1
docs/reference/esql/functions/types/greatest.asciidoc

@@ -1,9 +1,12 @@
 [%header.monospaced.styled,format=dsv,separator=|]
 |===
-first | rest... | result
+first | rest | result
 boolean | boolean | boolean
+double | double | double
 integer | integer | integer
+ip | ip | ip
 keyword | keyword | keyword
 long | long | long
 text | text | text
+version | version | version
 |===

+ 4 - 1
docs/reference/esql/functions/types/least.asciidoc

@@ -1,9 +1,12 @@
 [%header.monospaced.styled,format=dsv,separator=|]
 |===
-first | rest... | result
+first | rest | result
 boolean | boolean | boolean
+double | double | double
 integer | integer | integer
+ip | ip | ip
 keyword | keyword | keyword
 long | long | long
 text | text | text
+version | version | version
 |===

+ 3 - 1
x-pack/plugin/esql/compute/gen/src/main/java/org/elasticsearch/compute/gen/ConsumeProcessor.java

@@ -37,7 +37,9 @@ public class ConsumeProcessor implements Processor {
         return Set.of(
             "org.elasticsearch.core.Nullable",
             "org.elasticsearch.common.inject.Inject",
-            "org.elasticsearch.xpack.esql.expression.function.Named",
+            "org.elasticsearch.xpack.esql.expression.function.FunctionInfo",
+            "org.elasticsearch.xpack.esql.expression.function.Param",
+
             Fixed.class.getName()
         );
     }

+ 177 - 87
x-pack/plugin/esql/qa/testFixtures/src/main/resources/show.csv-spec

@@ -8,96 +8,186 @@ v:long
 showFunctions
 show functions;
 
-  name:keyword           |           synopsis:keyword
-abs                      |abs(n)
-acos                     |acos(n)
-asin                     |asin(n)
-atan                     |atan(n)
-atan2                    |atan2(y, x)
-auto_bucket              |auto_bucket(arg1, arg2, arg3, arg4)
-avg                      |avg(arg1)
-case                     |case(arg1, arg2...)
-ceil                     |ceil(n)
-cidr_match               |cidr_match(arg1, arg2...)
-coalesce                 |coalesce(arg1, arg2...)
-concat                   |concat(arg1, arg2...)
-cos                      |cos(n)
-cosh                     |cosh(n)
-count                    |count(arg1)
-count_distinct           |count_distinct(arg1, arg2)
-date_extract             |date_extract(arg1, arg2)
-date_format              |date_format(arg1, arg2)
-date_parse               |date_parse(arg1, arg2)
-date_trunc               |date_trunc(arg1, arg2)
-e                        |e()
-ends_with                |ends_with(arg1, arg2)
-floor                    |floor(n)
-greatest                 |greatest(first, rest...)
-is_finite                |is_finite(arg1)
-is_infinite              |is_infinite(arg1)
-is_nan                   |is_nan(arg1)
-least                    |least(first, rest...)
-left                     |left(string, length)
-length                   |length(arg1)
-log10                    |log10(n)
-ltrim                    |ltrim(arg1)
-max                      |max(arg1)
-median                   |median(arg1)
-median_absolute_deviation|median_absolute_deviation(arg1)
-min                      |min(arg1)
-mv_avg                   |mv_avg(arg1)
-mv_concat                |mv_concat(arg1, arg2)
-mv_count                 |mv_count(arg1)
-mv_dedupe                |mv_dedupe(arg1)
-mv_max                   |mv_max(arg1)
-mv_median                |mv_median(arg1)
-mv_min                   |mv_min(arg1)
-mv_sum                   |mv_sum(arg1)
-now                      |now()
-percentile               |percentile(arg1, arg2)
-pi                       |pi()
-pow                      |pow(base, exponent)
-replace                  |replace(arg1, arg2, arg3)
-right                    |right(string, length)
-round                    |round(arg1, arg2)
-rtrim                    |rtrim(arg1)
-sin                      |sin(n)
-sinh                     |sinh(n)
-split                    |split(arg1, arg2)
-sqrt                     |sqrt(n)
-starts_with              |starts_with(arg1, arg2)
-substring                |substring(arg1, arg2, arg3)
-sum                      |sum(arg1)
-tan                      |tan(n)
-tanh                     |tanh(n)
-tau                      |tau()
-to_bool                  |to_bool(arg1)
-to_boolean               |to_boolean(arg1)
-to_datetime              |to_datetime(arg1)
-to_dbl                   |to_dbl(arg1)
-to_degrees               |to_degrees(arg1)
-to_double                |to_double(arg1)
-to_dt                    |to_dt(arg1)
-to_int                   |to_int(arg1)
-to_integer               |to_integer(arg1)
-to_ip                    |to_ip(arg1)
-to_long                  |to_long(arg1)
-to_radians               |to_radians(arg1)
-to_str                   |to_str(v)
-to_string                |to_string(v)
-to_ul                    |to_ul(arg1)
-to_ulong                 |to_ulong(arg1)
-to_unsigned_long         |to_unsigned_long(arg1)
-to_ver                   |to_ver(v)
-to_version               |to_version(v)
-trim                     |trim(arg1)
+       name:keyword      |                        synopsis:keyword          |       argNames:keyword  | argTypes:keyword |             argDescriptions:keyword                |returnType:keyword   |    description:keyword  | optionalArgs:boolean | variadic:boolean                
+abs                      |"? abs(n:integer|long|double|unsigned_long)"      |n                        |"integer|long|double|unsigned_long"                 | ""                                                 |?                    | ""                      | false                | false
+acos                     |"? acos(n:integer|long|double|unsigned_long)"     |n                        |"integer|long|double|unsigned_long"                 | ""                                                 |?                    | ""                      | false                | false
+asin                     |"? asin(n:integer|long|double|unsigned_long)"     |n                        |"integer|long|double|unsigned_long"                 | ""                                                 |?                    | ""                      | false                | false
+atan                     |"? atan(n:integer|long|double|unsigned_long)"     |n                        |"integer|long|double|unsigned_long"                 | ""                                                 |?                    | ""                      | false                | false
+atan2                    |"? atan2(y:integer|long|double|unsigned_long, x:integer|long|double|unsigned_long)"                                       |[y, x]                   |["integer|long|double|unsigned_long", "integer|long|double|unsigned_long"]            |["", ""]                                            |?                    | ""                      | [false, false]       | false
+auto_bucket              |? auto_bucket(arg1:?, arg2:?, arg3:?, arg4:?)           |[arg1, arg2, arg3, arg4] |[?, ?, ?, ?]      |["", "", "", ""]                                    |?                    | ""                      | [false, false, false, false]   | false
+avg                      |? avg(arg1:?)                                           |arg1                     |?                 |   ""                                               |?                    | ""                      | false                | false
+case                     |? case(arg1:?, arg2...:?)                               |[arg1, arg2]             |[?, ?]            |["", ""]                                            |?                    | ""                      | [false, false]       | true
+ceil                     |"? ceil(n:integer|long|double|unsigned_long)"     |n                        |"integer|long|double|unsigned_long"                 | ""                                                 |?                    | ""                      | false                | false
+cidr_match               |? cidr_match(arg1:?, arg2...:?)                         |[arg1, arg2]             |[?, ?]            |["", ""]                                            |?                    | ""                      | [false, false]       | true
+coalesce                 |? coalesce(arg1:?, arg2...:?)                           |[arg1, arg2]             |[?, ?]            |["", ""]                                            |?                    | ""                      | [false, false]       | true
+concat                   |? concat(arg1:?, arg2...:?)                             |[arg1, arg2]             |[?, ?]            |["", ""]                                            |?                    | ""                      | [false, false]       | true
+cos                      |"? cos(n:integer|long|double|unsigned_long)"      |n                        |"integer|long|double|unsigned_long"                 | ""                                                 |?                    | ""                      | false                | false
+cosh                     |"? cosh(n:integer|long|double|unsigned_long)"     |n                        |"integer|long|double|unsigned_long"                 | ""                                                 |?                    | ""                      | false                | false
+count                    |? count(arg1:?)                                         |arg1                     |?                 | ""                                                 |?                    | ""                      | false                | false
+count_distinct           |? count_distinct(arg1:?, arg2:?)                        |[arg1, arg2]             |[?, ?]            |["", ""]                                            |?                    | ""                      | [false, false]       | false
+date_extract             |? date_extract(arg1:?, arg2:?)                          |[arg1, arg2]             |[?, ?]            |["", ""]                                            |?                    | ""                      | [false, false]       | false
+date_format              |? date_format(arg1:?, arg2:?)                           |[arg1, arg2]             |[?, ?]            |["", ""]                                            |?                    | ""                      | [false, false]       | false
+date_parse               |"date date_parse(?datePattern:keyword, dateString:keyword|text)"|[datePattern, dateString]|["keyword", "keyword|text"]|[A valid date pattern, A string representing a date]|date                 |Parses a string into a date value | [true, false]       | false         
+date_trunc               |? date_trunc(arg1:?, arg2:?)                            |[arg1, arg2]             |[?, ?]            |["", ""]                                            |?                    | ""                      | [false, false]       | false
+e                        |? e()                                                   | null                    | null             | null                                               |?                    | ""                      | null                 | false
+ends_with                |? ends_with(arg1:?, arg2:?)                             |[arg1, arg2]             |[?, ?]            |["", ""]                                            |?                    | ""                      | [false, false]       | false
+floor                    |"? floor(n:integer|long|double|unsigned_long)"    |n                        |"integer|long|double|unsigned_long"    | ""                                                 |?                    | ""                      | false                | false
+greatest                 |"? greatest(first:integer|long|double|boolean|keyword|text|ip|version, rest...:integer|long|double|boolean|keyword|text|ip|version)"        |[first, rest]            |["integer|long|double|boolean|keyword|text|ip|version", "integer|long|double|boolean|keyword|text|ip|version"]            |["", ""]                                            |?                    | ""                      | [false, false]       | true
+is_finite                |? is_finite(arg1:?)                                     |arg1                     |?                 | ""                                                 |?                    | ""                      | false                | false
+is_infinite              |? is_infinite(arg1:?)                                   |arg1                     |?                 | ""                                                 |?                    | ""                      | false                | false
+is_nan                   |? is_nan(arg1:?)                                        |arg1                     |?                 | ""                                                 |?                    | ""                      | false                | false
+least                 |"? least(first:integer|long|double|boolean|keyword|text|ip|version, rest...:integer|long|double|boolean|keyword|text|ip|version)"        |[first, rest]            |["integer|long|double|boolean|keyword|text|ip|version", "integer|long|double|boolean|keyword|text|ip|version"]            |["", ""]                                            |?                    | ""                      | [false, false]       | true
+left                     |"? left(string:keyword, length:integer)"                              |[string, length]         |["keyword", "integer"]            |["", ""]                                            |?                    | ""                      | [false, false]       | false
+length                   |? length(arg1:?)                                        |arg1                     |?                 | ""                                                 |?                    | ""                      | false                | false
+log10                    |"? log10(n:integer|long|double|unsigned_long)"    |n                        |"integer|long|double|unsigned_long" | ""                                                 |?                    | ""                      | false                | false
+ltrim                    |? ltrim(arg1:?)                                         |arg1                     |?                 | ""                                                 |?                    | ""                      | false                | false
+max                      |? max(arg1:?)                                           |arg1                     |?                 | ""                                                 |?                    | ""                      | false                | false
+median                   |? median(arg1:?)                                        |arg1                     |?                 | ""                                                 |?                    | ""                      | false                | false
+median_absolute_deviation|? median_absolute_deviation(arg1:?)                     |arg1                     |?                 | ""                                                 |?                    | ""                      | false                | false
+min                      |? min(arg1:?)                                           |arg1                     |?                 | ""                                                 |?                    | ""                      | false                | false
+mv_avg                   |? mv_avg(arg1:?)                                        |arg1                     |?                 | ""                                                 |?                    | ""                      | false                | false
+mv_concat                |? mv_concat(arg1:?, arg2:?)                             |[arg1, arg2]             |[?, ?]            |["", ""]                                            |?                    | ""                      | [false, false]       | false
+mv_count                 |? mv_count(arg1:?)                                      |arg1                     |?                 | ""                                                 |?                    | ""                      | false                | false
+mv_dedupe                |? mv_dedupe(arg1:?)                                     |arg1                     |?                 | ""                                                 |?                    | ""                      | false                | false
+mv_max                   |? mv_max(arg1:?)                                        |arg1                     |?                 | ""                                                 |?                    | ""                      | false                | false
+mv_median                |? mv_median(arg1:?)                                     |arg1                     |?                 | ""                                                 |?                    | ""                      | false                | false
+mv_min                   |? mv_min(arg1:?)                                        |arg1                     |?                 | ""                                                 |?                    | ""                      | false                | false
+mv_sum                   |? mv_sum(arg1:?)                                        |arg1                     |?                 | ""                                                 |?                    | ""                      | false                | false
+now                      |? now()                                                 | null                    |null              | null                                               |?                    | ""                      | null                 | false
+percentile               |? percentile(arg1:?, arg2:?)                            |[arg1, arg2]             |[?, ?]            |["", ""]                                            |?                    | ""                      | [false, false]       | false
+pi                       |? pi()                                                  | null                    |  null            | null                                               |?                    | ""                      | null                 | false
+pow                      |"? pow(base:integer|long|double, exponent:integer|double)" |[base, exponent]         |["integer|long|double", "integer|double"]            |["", ""]                                            |?                    | ""                      | [false, false]       | false
+replace                  |"? replace(arg1:?, arg2:?, arg3:?)"                       | [arg1, arg2, arg3]      | [?, ?, ?]        |["", "", ""]                                        |?                    | ""                      | [false, false, false]| false 
+right                    |"? right(string:keyword, length:integer)"     |[string, length]         |["keyword", "integer"]            |["", ""]                                            |?                    | ""                      | [false, false]       | false
+round                    |? round(arg1:?, arg2:?)                                 |[arg1, arg2]             |[?, ?]            |["", ""]                                            |?                    | ""                      | [false, false]       | false
+rtrim                    |? rtrim(arg1:?)                                         |arg1                     |?                 | ""                                                 |?                    | ""                      | false                | false
+sin                      |"double sin(n:integer|long|double|unsigned_long)" |n                        |"integer|long|double|unsigned_long"           |An angle, in radians                                |double               |Returns the trigonometric sine of an angle  | false      | false
+sinh                     |"? sinh(n:integer|long|double|unsigned_long)"|n                        |"integer|long|double|unsigned_long"           | ""                                                 |?                    | ""                      | false                | false
+split                    |? split(arg1:?, arg2:?)                                 |[arg1, arg2]             |[?, ?]            |["", ""]                                            |?                    | ""                      | [false, false]       | false
+sqrt                     |"? sqrt(n:integer|long|double|unsigned_long)"     |n                        |"integer|long|double|unsigned_long"                 | ""                                                 |?                    | ""                      | false                | false
+starts_with              |? starts_with(arg1:?, arg2:?)                           |[arg1, arg2]             |[?, ?]            |["", ""]                                            |?                    | ""                      | [false, false]       | false
+substring                |? substring(arg1:?, arg2:?, arg3:?)                     |[arg1, arg2, arg3]       |[?, ?, ?]         |["", "", ""]                                        |?                    | ""                      | [false, false, false]| false
+sum                      |? sum(arg1:?)                                           |arg1                     |?                 | ""                                                 |?                    | ""                      | false                | false
+tan                      |"? tan(n:integer|long|double|unsigned_long)"      |n                        |"integer|long|double|unsigned_long"           | ""                                                 |?                    | ""                      | false                | false
+tanh                     |"? tanh(n:integer|long|double|unsigned_long)"     |n                        |"integer|long|double|unsigned_long"           | ""                                                 |?                    | ""                      | false                | false
+tau                      |? tau()                                                 | null                    | null             | null                                               |?                    | ""                      | null                 | false
+to_bool                  |? to_bool(arg1:?)                                       |arg1                     |?                 | ""                                                 |?                    | ""                      | false                | false
+to_boolean               |? to_boolean(arg1:?)                                    |arg1                     |?                 | ""                                                 |?                    | ""                      | false                | false
+to_datetime              |? to_datetime(arg1:?)                                   |arg1                     |?                 | ""                                                 |?                    | ""                      | false                | false
+to_dbl                   |? to_dbl(arg1:?)                                        |arg1                     |?                 | ""                                                 |?                    | ""                      | false                | false
+to_degrees               |? to_degrees(arg1:?)                                    |arg1                     |?                 | ""                                                 |?                    | ""                      | false                | false
+to_double                |? to_double(arg1:?)                                     |arg1                     |?                 | ""                                                 |?                    | ""                      | false                | false
+to_dt                    |? to_dt(arg1:?)                                         |arg1                     |?                 | ""                                                 |?                    | ""                      | false                | false
+to_int                   |? to_int(arg1:?)                                        |arg1                     |?                 | ""                                                 |?                    | ""                      | false                | false
+to_integer               |? to_integer(arg1:?)                                    |arg1                     |?                 | ""                                                 |?                    | ""                      | false                | false
+to_ip                    |? to_ip(arg1:?)                                         |arg1                     |?                 | ""                                                 |?                    | ""                      | false                | false
+to_long                  |? to_long(arg1:?)                                       |arg1                     |?                 | ""                                                 |?                    | ""                      | false                | false
+to_radians               |? to_radians(arg1:?)                                    |arg1                     |?                 | ""                                                 |?                    | ""                      | false                | false
+to_str                   |"? to_str(v:unsigned_long|date|boolean|double|ip|text|integer|keyword|version|long)"|v                        |"unsigned_long|date|boolean|double|ip|text|integer|keyword|version|long"                 | ""                                                 |?                    | ""                      | false                | false
+to_string                |"? to_string(v:unsigned_long|date|boolean|double|ip|text|integer|keyword|version|long)"|v                        |"unsigned_long|date|boolean|double|ip|text|integer|keyword|version|long"                 | ""                                                 |?                    | ""                      | false                | false
+to_ul                    |? to_ul(arg1:?)                                         |arg1                     |?                 | ""                                                 |?                    | ""                      | false                | false
+to_ulong                 |? to_ulong(arg1:?)                                      |arg1                     |?                 | ""                                                 |?                    | ""                      | false                | false
+to_unsigned_long         |? to_unsigned_long(arg1:?)                              |arg1                     |?                 | ""                                                 |?                    | ""                      | false                | false
+to_ver                   |"? to_ver(v:keyword|text|version)"                      |v                        |"keyword|text|version"| ""                                                 |?                    | ""                      | false                | false
+to_version               |"? to_version(v:keyword|text|version)"                  |v                        |"keyword|text|version"| ""                                                 |?                    | ""                      | false                | false
+trim                     |? trim(arg1:?)                                          |arg1                     |?                 | ""                                                 |?                    | ""                      | false                | false
 ;
 
+
+showFunctionsSynopsis
+show functions | keep synopsis;
+
+synopsis:keyword          
+"? abs(n:integer|long|double|unsigned_long)"      
+"? acos(n:integer|long|double|unsigned_long)"     
+"? asin(n:integer|long|double|unsigned_long)"     
+"? atan(n:integer|long|double|unsigned_long)"     
+"? atan2(y:integer|long|double|unsigned_long, x:integer|long|double|unsigned_long)"                                       
+? auto_bucket(arg1:?, arg2:?, arg3:?, arg4:?)           
+? avg(arg1:?)                                           
+? case(arg1:?, arg2...:?)                               
+"? ceil(n:integer|long|double|unsigned_long)"     
+? cidr_match(arg1:?, arg2...:?)                         
+? coalesce(arg1:?, arg2...:?)                           
+? concat(arg1:?, arg2...:?)                             
+"? cos(n:integer|long|double|unsigned_long)"      
+"? cosh(n:integer|long|double|unsigned_long)"     
+? count(arg1:?)                                         
+? count_distinct(arg1:?, arg2:?)                        
+? date_extract(arg1:?, arg2:?)                          
+? date_format(arg1:?, arg2:?)                           
+"date date_parse(?datePattern:keyword, dateString:keyword|text)"
+? date_trunc(arg1:?, arg2:?)                            
+? e()                                                   
+? ends_with(arg1:?, arg2:?)                             
+"? floor(n:integer|long|double|unsigned_long)"    
+"? greatest(first:integer|long|double|boolean|keyword|text|ip|version, rest...:integer|long|double|boolean|keyword|text|ip|version)"        
+? is_finite(arg1:?)                                     
+? is_infinite(arg1:?)                                   
+? is_nan(arg1:?)                                        
+"? least(first:integer|long|double|boolean|keyword|text|ip|version, rest...:integer|long|double|boolean|keyword|text|ip|version)"        
+"? left(string:keyword, length:integer)"                              
+? length(arg1:?)                                        
+"? log10(n:integer|long|double|unsigned_long)"    
+? ltrim(arg1:?)                                         
+? max(arg1:?)                                           
+? median(arg1:?)                                        
+? median_absolute_deviation(arg1:?)                     
+? min(arg1:?)                                           
+? mv_avg(arg1:?)                                        
+? mv_concat(arg1:?, arg2:?)                             
+? mv_count(arg1:?)                                      
+? mv_dedupe(arg1:?)                                     
+? mv_max(arg1:?)                                        
+? mv_median(arg1:?)                                     
+? mv_min(arg1:?)                                        
+? mv_sum(arg1:?)                                        
+? now()                                                 
+? percentile(arg1:?, arg2:?)                            
+? pi()                                                  
+"? pow(base:integer|long|double, exponent:integer|double)" 
+"? replace(arg1:?, arg2:?, arg3:?)"
+"? right(string:keyword, length:integer)"     
+? round(arg1:?, arg2:?)                                 
+? rtrim(arg1:?)                                         
+"double sin(n:integer|long|double|unsigned_long)" 
+"? sinh(n:integer|long|double|unsigned_long)"
+? split(arg1:?, arg2:?)                                 
+"? sqrt(n:integer|long|double|unsigned_long)"     
+? starts_with(arg1:?, arg2:?)                           
+? substring(arg1:?, arg2:?, arg3:?)                     
+? sum(arg1:?)                                           
+"? tan(n:integer|long|double|unsigned_long)"      
+"? tanh(n:integer|long|double|unsigned_long)"     
+? tau()                                                 
+? to_bool(arg1:?)                                       
+? to_boolean(arg1:?)                                    
+? to_datetime(arg1:?)                                   
+? to_dbl(arg1:?)                                        
+? to_degrees(arg1:?)                                    
+? to_double(arg1:?)                                     
+? to_dt(arg1:?)                                         
+? to_int(arg1:?)                                        
+? to_integer(arg1:?)                                    
+? to_ip(arg1:?)                                         
+? to_long(arg1:?)                                       
+? to_radians(arg1:?)                                    
+"? to_str(v:unsigned_long|date|boolean|double|ip|text|integer|keyword|version|long)"                 
+"? to_string(v:unsigned_long|date|boolean|double|ip|text|integer|keyword|version|long)"                 
+? to_ul(arg1:?)                                         
+? to_ulong(arg1:?)                                      
+? to_unsigned_long(arg1:?)                              
+"? to_ver(v:keyword|text|version)"
+"? to_version(v:keyword|text|version)"
+? trim(arg1:?)                                          
+;
+
+
 showFunctionsFiltered
 show functions | where starts_with(name, "is_");
 
-  name:keyword           |           synopsis:keyword
-is_finite                |is_finite(arg1)
-is_infinite              |is_infinite(arg1)
-is_nan                   |is_nan(arg1)
+       name:keyword      |                        synopsis:keyword                |       argNames:keyword  | argTypes:keyword |             argDescriptions:keyword                |  returnType:keyword   |  description:keyword  |   optionalArgs:boolean |  variadic:boolean
+is_finite                |? is_finite(arg1:?)                                     |arg1                     |?                 |  ""                                                  |?              | ""                      | false                | false
+is_infinite              |? is_infinite(arg1:?)                                   |arg1                     |?                 |  ""                                                  |?              | ""                      | false                | false
+is_nan                   |? is_nan(arg1:?)                                        |arg1                     |?                 |  ""                                                  |?              | ""                      | false                | false
 ;

+ 16 - 1
x-pack/plugin/esql/src/internalClusterTest/java/org/elasticsearch/xpack/esql/action/EsqlActionIT.java

@@ -942,7 +942,22 @@ public class EsqlActionIT extends AbstractEsqlIntegTestCase {
 
     public void testShowFunctions() {
         EsqlQueryResponse results = run("show functions");
-        assertThat(results.columns(), equalTo(List.of(new ColumnInfo("name", "keyword"), new ColumnInfo("synopsis", "keyword"))));
+        assertThat(
+            results.columns(),
+            equalTo(
+                List.of(
+                    new ColumnInfo("name", "keyword"),
+                    new ColumnInfo("synopsis", "keyword"),
+                    new ColumnInfo("argNames", "keyword"),
+                    new ColumnInfo("argTypes", "keyword"),
+                    new ColumnInfo("argDescriptions", "keyword"),
+                    new ColumnInfo("returnType", "keyword"),
+                    new ColumnInfo("description", "keyword"),
+                    new ColumnInfo("optionalArgs", "boolean"),
+                    new ColumnInfo("variadic", "boolean")
+                )
+            )
+        );
         assertThat(getValuesList(results).size(), equalTo(new EsqlFunctionRegistry().listFunctions().size()));
     }
 

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

@@ -81,10 +81,16 @@ import org.elasticsearch.xpack.esql.expression.function.scalar.string.Split;
 import org.elasticsearch.xpack.esql.expression.function.scalar.string.StartsWith;
 import org.elasticsearch.xpack.esql.expression.function.scalar.string.Substring;
 import org.elasticsearch.xpack.esql.expression.function.scalar.string.Trim;
+import org.elasticsearch.xpack.esql.plan.logical.show.ShowFunctions;
 import org.elasticsearch.xpack.ql.expression.function.FunctionDefinition;
 import org.elasticsearch.xpack.ql.expression.function.FunctionRegistry;
+import org.elasticsearch.xpack.ql.session.Configuration;
 
+import java.lang.reflect.Constructor;
+import java.util.ArrayList;
+import java.util.List;
 import java.util.Locale;
+import java.util.stream.Collectors;
 
 public class EsqlFunctionRegistry extends FunctionRegistry {
 
@@ -198,4 +204,67 @@ public class EsqlFunctionRegistry extends FunctionRegistry {
     public static String normalizeName(String name) {
         return name.toLowerCase(Locale.ROOT);
     }
+
+    public record ArgSignature(String name, String[] type, String description, boolean optional) {}
+
+    public record FunctionDescription(String name, List<ArgSignature> args, String[] returnType, String description, boolean variadic) {
+        public String fullSignature() {
+            StringBuilder builder = new StringBuilder();
+            builder.append(ShowFunctions.withPipes(returnType));
+            builder.append(" ");
+            builder.append(name);
+            builder.append("(");
+            for (int i = 0; i < args.size(); i++) {
+                ArgSignature arg = args.get(i);
+                if (i > 0) {
+                    builder.append(", ");
+                }
+                if (arg.optional()) {
+                    builder.append("?");
+                }
+                builder.append(arg.name());
+                if (i == args.size() - 1 && variadic) {
+                    builder.append("...");
+                }
+                builder.append(":");
+                builder.append(ShowFunctions.withPipes(arg.type()));
+            }
+            builder.append(")");
+            return builder.toString();
+        }
+
+        public List<String> argNames() {
+            return args.stream().map(ArgSignature::name).collect(Collectors.toList());
+        }
+
+    }
+
+    public static FunctionDescription description(FunctionDefinition def) {
+        var constructors = def.clazz().getConstructors();
+        if (constructors.length == 0) {
+            return new FunctionDescription(def.name(), List.of(), null, null, false);
+        }
+        Constructor<?> constructor = constructors[0];
+        FunctionInfo functionInfo = constructor.getAnnotation(FunctionInfo.class);
+        String functionDescription = functionInfo == null ? "" : functionInfo.description();
+        String[] returnType = functionInfo == null ? new String[] { "?" } : functionInfo.returnType();
+        var params = constructor.getParameters(); // no multiple c'tors supported
+
+        List<EsqlFunctionRegistry.ArgSignature> args = new ArrayList<>(params.length);
+        boolean variadic = false;
+        for (int i = 1; i < params.length; i++) { // skipping 1st argument, the source
+            if (Configuration.class.isAssignableFrom(params[i].getType()) == false) {
+                Param paramInfo = params[i].getAnnotation(Param.class);
+                String name = paramInfo == null ? params[i].getName() : paramInfo.name();
+                variadic |= List.class.isAssignableFrom(params[i].getType());
+                String[] type = paramInfo == null ? new String[] { "?" } : paramInfo.type();
+                String desc = paramInfo == null ? "" : paramInfo.description();
+                boolean optional = paramInfo == null ? false : paramInfo.optional();
+
+                args.add(new EsqlFunctionRegistry.ArgSignature(name, type, desc, optional));
+            }
+        }
+        return new FunctionDescription(def.name(), args, returnType, functionDescription, variadic);
+    }
+
 }

+ 6 - 4
x-pack/plugin/esql/src/main/java/org/elasticsearch/xpack/esql/expression/function/Named.java → x-pack/plugin/esql/src/main/java/org/elasticsearch/xpack/esql/expression/function/FunctionInfo.java

@@ -13,10 +13,12 @@ import java.lang.annotation.RetentionPolicy;
 import java.lang.annotation.Target;
 
 /**
- * Names function parameters.
+ * Describes functions.
  */
 @Retention(RetentionPolicy.RUNTIME)
-@Target(ElementType.PARAMETER)
-public @interface Named {
-    String value();
+@Target(ElementType.CONSTRUCTOR)
+public @interface FunctionInfo {
+    String[] returnType();
+
+    String description() default "";
 }

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

@@ -0,0 +1,28 @@
+/*
+ * 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;
+
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
+
+/**
+ * Describes function parameters.
+ */
+@Retention(RetentionPolicy.RUNTIME)
+@Target(ElementType.PARAMETER)
+public @interface Param {
+    String name();
+
+    String[] type();
+
+    String description() default "";
+
+    boolean optional() default false;
+}

+ 6 - 2
x-pack/plugin/esql/src/main/java/org/elasticsearch/xpack/esql/expression/function/scalar/conditional/Greatest.java

@@ -13,7 +13,7 @@ import org.elasticsearch.compute.operator.EvalOperator;
 import org.elasticsearch.compute.operator.EvalOperator.ExpressionEvaluator;
 import org.elasticsearch.xpack.esql.EsqlIllegalArgumentException;
 import org.elasticsearch.xpack.esql.evaluator.mapper.EvaluatorMapper;
-import org.elasticsearch.xpack.esql.expression.function.Named;
+import org.elasticsearch.xpack.esql.expression.function.Param;
 import org.elasticsearch.xpack.esql.expression.function.scalar.multivalue.MvMaxBooleanEvaluator;
 import org.elasticsearch.xpack.esql.expression.function.scalar.multivalue.MvMaxBytesRefEvaluator;
 import org.elasticsearch.xpack.esql.expression.function.scalar.multivalue.MvMaxDoubleEvaluator;
@@ -42,7 +42,11 @@ import static org.elasticsearch.xpack.ql.type.DataTypes.NULL;
 public class Greatest extends ScalarFunction implements EvaluatorMapper, OptionalArgument {
     private DataType dataType;
 
-    public Greatest(Source source, @Named("first") Expression first, @Named("rest") List<Expression> rest) {
+    public Greatest(
+        Source source,
+        @Param(name = "first", type = { "integer", "long", "double", "boolean", "keyword", "text", "ip", "version" }) Expression first,
+        @Param(name = "rest", type = { "integer", "long", "double", "boolean", "keyword", "text", "ip", "version" }) List<Expression> rest
+    ) {
         super(source, Stream.concat(Stream.of(first), rest.stream()).toList());
     }
 

+ 6 - 2
x-pack/plugin/esql/src/main/java/org/elasticsearch/xpack/esql/expression/function/scalar/conditional/Least.java

@@ -13,7 +13,7 @@ import org.elasticsearch.compute.operator.EvalOperator;
 import org.elasticsearch.compute.operator.EvalOperator.ExpressionEvaluator;
 import org.elasticsearch.xpack.esql.EsqlIllegalArgumentException;
 import org.elasticsearch.xpack.esql.evaluator.mapper.EvaluatorMapper;
-import org.elasticsearch.xpack.esql.expression.function.Named;
+import org.elasticsearch.xpack.esql.expression.function.Param;
 import org.elasticsearch.xpack.esql.expression.function.scalar.multivalue.MvMinBooleanEvaluator;
 import org.elasticsearch.xpack.esql.expression.function.scalar.multivalue.MvMinBytesRefEvaluator;
 import org.elasticsearch.xpack.esql.expression.function.scalar.multivalue.MvMinDoubleEvaluator;
@@ -42,7 +42,11 @@ import static org.elasticsearch.xpack.ql.type.DataTypes.NULL;
 public class Least extends ScalarFunction implements EvaluatorMapper, OptionalArgument {
     private DataType dataType;
 
-    public Least(Source source, @Named("first") Expression first, @Named("rest") List<Expression> rest) {
+    public Least(
+        Source source,
+        @Param(name = "first", type = { "integer", "long", "double", "boolean", "keyword", "text", "ip", "version" }) Expression first,
+        @Param(name = "rest", type = { "integer", "long", "double", "boolean", "keyword", "text", "ip", "version" }) List<Expression> rest
+    ) {
         super(source, Stream.concat(Stream.of(first), rest.stream()).toList());
     }
 

+ 8 - 2
x-pack/plugin/esql/src/main/java/org/elasticsearch/xpack/esql/expression/function/scalar/convert/ToString.java

@@ -14,7 +14,7 @@ import org.elasticsearch.compute.operator.DriverContext;
 import org.elasticsearch.compute.operator.EvalOperator;
 import org.elasticsearch.search.DocValueFormat;
 import org.elasticsearch.xpack.esql.evaluator.mapper.EvaluatorMapper;
-import org.elasticsearch.xpack.esql.expression.function.Named;
+import org.elasticsearch.xpack.esql.expression.function.Param;
 import org.elasticsearch.xpack.ql.expression.Expression;
 import org.elasticsearch.xpack.ql.tree.NodeInfo;
 import org.elasticsearch.xpack.ql.tree.Source;
@@ -64,7 +64,13 @@ public class ToString extends AbstractConvertFunction implements EvaluatorMapper
             ToStringFromUnsignedLongEvaluator::new
         );
 
-    public ToString(Source source, @Named("v") Expression v) {
+    public ToString(
+        Source source,
+        @Param(
+            name = "v",
+            type = { "unsigned_long", "date", "boolean", "double", "ip", "text", "integer", "keyword", "version", "long" }
+        ) Expression v
+    ) {
         super(source, v);
     }
 

+ 2 - 2
x-pack/plugin/esql/src/main/java/org/elasticsearch/xpack/esql/expression/function/scalar/convert/ToVersion.java

@@ -12,7 +12,7 @@ import org.elasticsearch.common.TriFunction;
 import org.elasticsearch.compute.ann.ConvertEvaluator;
 import org.elasticsearch.compute.operator.DriverContext;
 import org.elasticsearch.compute.operator.EvalOperator;
-import org.elasticsearch.xpack.esql.expression.function.Named;
+import org.elasticsearch.xpack.esql.expression.function.Param;
 import org.elasticsearch.xpack.ql.expression.Expression;
 import org.elasticsearch.xpack.ql.tree.NodeInfo;
 import org.elasticsearch.xpack.ql.tree.Source;
@@ -36,7 +36,7 @@ public class ToVersion extends AbstractConvertFunction {
             Map.entry(TEXT, ToVersionFromStringEvaluator::new)
         );
 
-    public ToVersion(Source source, @Named("v") Expression v) {
+    public ToVersion(Source source, @Param(name = "v", type = { "keyword", "text", "version" }) Expression v) {
         super(source, v);
     }
 

+ 8 - 1
x-pack/plugin/esql/src/main/java/org/elasticsearch/xpack/esql/expression/function/scalar/date/DateParse.java

@@ -14,6 +14,8 @@ import org.elasticsearch.compute.ann.Fixed;
 import org.elasticsearch.compute.operator.EvalOperator.ExpressionEvaluator;
 import org.elasticsearch.xpack.esql.EsqlIllegalArgumentException;
 import org.elasticsearch.xpack.esql.evaluator.mapper.EvaluatorMapper;
+import org.elasticsearch.xpack.esql.expression.function.FunctionInfo;
+import org.elasticsearch.xpack.esql.expression.function.Param;
 import org.elasticsearch.xpack.ql.expression.Expression;
 import org.elasticsearch.xpack.ql.expression.function.OptionalArgument;
 import org.elasticsearch.xpack.ql.expression.function.scalar.ScalarFunction;
@@ -40,7 +42,12 @@ public class DateParse extends ScalarFunction implements OptionalArgument, Evalu
     private final Expression field;
     private final Expression format;
 
-    public DateParse(Source source, Expression first, Expression second) {
+    @FunctionInfo(returnType = "date", description = "Parses a string into a date value")
+    public DateParse(
+        Source source,
+        @Param(name = "datePattern", type = { "keyword" }, description = "A valid date pattern", optional = true) Expression first,
+        @Param(name = "dateString", type = { "keyword", "text" }, description = "A string representing a date") Expression second
+    ) {
         super(source, second != null ? List.of(first, second) : List.of(first));
         this.field = second != null ? second : first;
         this.format = second != null ? first : null;

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

@@ -11,7 +11,7 @@ import org.elasticsearch.compute.ann.Evaluator;
 import org.elasticsearch.compute.operator.EvalOperator.ExpressionEvaluator;
 import org.elasticsearch.xpack.esql.EsqlIllegalArgumentException;
 import org.elasticsearch.xpack.esql.evaluator.mapper.EvaluatorMapper;
-import org.elasticsearch.xpack.esql.expression.function.Named;
+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;
@@ -22,7 +22,7 @@ import java.util.List;
 import java.util.function.Function;
 
 public class Abs extends UnaryScalarFunction implements EvaluatorMapper {
-    public Abs(Source source, @Named("n") Expression n) {
+    public Abs(Source source, @Param(name = "n", type = { "integer", "long", "double", "unsigned_long" }) Expression n) {
         super(source, n);
     }
 

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

@@ -10,7 +10,7 @@ package org.elasticsearch.xpack.esql.expression.function.scalar.math;
 import org.elasticsearch.compute.ann.Evaluator;
 import org.elasticsearch.compute.operator.DriverContext;
 import org.elasticsearch.compute.operator.EvalOperator;
-import org.elasticsearch.xpack.esql.expression.function.Named;
+import org.elasticsearch.xpack.esql.expression.function.Param;
 import org.elasticsearch.xpack.ql.expression.Expression;
 import org.elasticsearch.xpack.ql.tree.NodeInfo;
 import org.elasticsearch.xpack.ql.tree.Source;
@@ -21,7 +21,7 @@ import java.util.List;
  * Inverse cosine trigonometric function.
  */
 public class Acos extends AbstractTrigonometricFunction {
-    public Acos(Source source, @Named("n") Expression n) {
+    public Acos(Source source, @Param(name = "n", type = { "integer", "long", "double", "unsigned_long" }) Expression n) {
         super(source, n);
     }
 

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

@@ -10,7 +10,7 @@ package org.elasticsearch.xpack.esql.expression.function.scalar.math;
 import org.elasticsearch.compute.ann.Evaluator;
 import org.elasticsearch.compute.operator.DriverContext;
 import org.elasticsearch.compute.operator.EvalOperator;
-import org.elasticsearch.xpack.esql.expression.function.Named;
+import org.elasticsearch.xpack.esql.expression.function.Param;
 import org.elasticsearch.xpack.ql.expression.Expression;
 import org.elasticsearch.xpack.ql.tree.NodeInfo;
 import org.elasticsearch.xpack.ql.tree.Source;
@@ -21,7 +21,7 @@ import java.util.List;
  * Inverse cosine trigonometric function.
  */
 public class Asin extends AbstractTrigonometricFunction {
-    public Asin(Source source, @Named("n") Expression n) {
+    public Asin(Source source, @Param(name = "n", type = { "integer", "long", "double", "unsigned_long" }) Expression n) {
         super(source, n);
     }
 

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

@@ -10,7 +10,7 @@ package org.elasticsearch.xpack.esql.expression.function.scalar.math;
 import org.elasticsearch.compute.ann.Evaluator;
 import org.elasticsearch.compute.operator.DriverContext;
 import org.elasticsearch.compute.operator.EvalOperator;
-import org.elasticsearch.xpack.esql.expression.function.Named;
+import org.elasticsearch.xpack.esql.expression.function.Param;
 import org.elasticsearch.xpack.ql.expression.Expression;
 import org.elasticsearch.xpack.ql.tree.NodeInfo;
 import org.elasticsearch.xpack.ql.tree.Source;
@@ -21,7 +21,7 @@ import java.util.List;
  * Inverse cosine trigonometric function.
  */
 public class Atan extends AbstractTrigonometricFunction {
-    public Atan(Source source, @Named("n") Expression n) {
+    public Atan(Source source, @Param(name = "n", type = { "integer", "long", "double", "unsigned_long" }) Expression n) {
         super(source, n);
     }
 

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

@@ -10,7 +10,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.evaluator.mapper.EvaluatorMapper;
-import org.elasticsearch.xpack.esql.expression.function.Named;
+import org.elasticsearch.xpack.esql.expression.function.Param;
 import org.elasticsearch.xpack.ql.expression.Expression;
 import org.elasticsearch.xpack.ql.expression.Expressions;
 import org.elasticsearch.xpack.ql.expression.TypeResolutions;
@@ -33,7 +33,11 @@ public class Atan2 extends ScalarFunction implements EvaluatorMapper {
     private final Expression y;
     private final Expression x;
 
-    public Atan2(Source source, @Named("y") Expression y, @Named("x") Expression x) {
+    public Atan2(
+        Source source,
+        @Param(name = "y", type = { "integer", "long", "double", "unsigned_long" }) Expression y,
+        @Param(name = "x", type = { "integer", "long", "double", "unsigned_long" }) Expression x
+    ) {
         super(source, List.of(y, x));
         this.y = y;
         this.x = x;

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

@@ -10,7 +10,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.evaluator.mapper.EvaluatorMapper;
-import org.elasticsearch.xpack.esql.expression.function.Named;
+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;
@@ -30,7 +30,7 @@ import static org.elasticsearch.xpack.ql.expression.TypeResolutions.isNumeric;
  * </p>
  */
 public class Ceil extends UnaryScalarFunction implements EvaluatorMapper {
-    public Ceil(Source source, @Named("n") Expression n) {
+    public Ceil(Source source, @Param(name = "n", type = { "integer", "long", "double", "unsigned_long" }) Expression n) {
         super(source, n);
     }
 

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

@@ -10,7 +10,7 @@ package org.elasticsearch.xpack.esql.expression.function.scalar.math;
 import org.elasticsearch.compute.ann.Evaluator;
 import org.elasticsearch.compute.operator.DriverContext;
 import org.elasticsearch.compute.operator.EvalOperator;
-import org.elasticsearch.xpack.esql.expression.function.Named;
+import org.elasticsearch.xpack.esql.expression.function.Param;
 import org.elasticsearch.xpack.ql.expression.Expression;
 import org.elasticsearch.xpack.ql.tree.NodeInfo;
 import org.elasticsearch.xpack.ql.tree.Source;
@@ -21,7 +21,7 @@ import java.util.List;
  * Cosine trigonometric function.
  */
 public class Cos extends AbstractTrigonometricFunction {
-    public Cos(Source source, @Named("n") Expression n) {
+    public Cos(Source source, @Param(name = "n", type = { "integer", "long", "double", "unsigned_long" }) Expression n) {
         super(source, n);
     }
 

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

@@ -10,7 +10,7 @@ package org.elasticsearch.xpack.esql.expression.function.scalar.math;
 import org.elasticsearch.compute.ann.Evaluator;
 import org.elasticsearch.compute.operator.DriverContext;
 import org.elasticsearch.compute.operator.EvalOperator;
-import org.elasticsearch.xpack.esql.expression.function.Named;
+import org.elasticsearch.xpack.esql.expression.function.Param;
 import org.elasticsearch.xpack.ql.expression.Expression;
 import org.elasticsearch.xpack.ql.tree.NodeInfo;
 import org.elasticsearch.xpack.ql.tree.Source;
@@ -21,7 +21,7 @@ import java.util.List;
  * Cosine hyperbolic function.
  */
 public class Cosh extends AbstractTrigonometricFunction {
-    public Cosh(Source source, @Named("n") Expression n) {
+    public Cosh(Source source, @Param(name = "n", type = { "integer", "long", "double", "unsigned_long" }) Expression n) {
         super(source, n);
     }
 

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

@@ -10,7 +10,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.evaluator.mapper.EvaluatorMapper;
-import org.elasticsearch.xpack.esql.expression.function.Named;
+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;
@@ -30,7 +30,7 @@ import static org.elasticsearch.xpack.ql.expression.TypeResolutions.isNumeric;
  * </p>
  */
 public class Floor extends UnaryScalarFunction implements EvaluatorMapper {
-    public Floor(Source source, @Named("n") Expression n) {
+    public Floor(Source source, @Param(name = "n", type = { "integer", "long", "double", "unsigned_long" }) Expression n) {
         super(source, n);
     }
 

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

@@ -11,7 +11,7 @@ import org.elasticsearch.compute.ann.Evaluator;
 import org.elasticsearch.compute.operator.EvalOperator.ExpressionEvaluator;
 import org.elasticsearch.xpack.esql.EsqlIllegalArgumentException;
 import org.elasticsearch.xpack.esql.evaluator.mapper.EvaluatorMapper;
-import org.elasticsearch.xpack.esql.expression.function.Named;
+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;
@@ -27,7 +27,7 @@ import static org.elasticsearch.xpack.ql.expression.TypeResolutions.ParamOrdinal
 import static org.elasticsearch.xpack.ql.expression.TypeResolutions.isNumeric;
 
 public class Log10 extends UnaryScalarFunction implements EvaluatorMapper {
-    public Log10(Source source, @Named("n") Expression n) {
+    public Log10(Source source, @Param(name = "n", type = { "integer", "long", "double", "unsigned_long" }) Expression n) {
         super(source, n);
     }
 

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

@@ -10,7 +10,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.evaluator.mapper.EvaluatorMapper;
-import org.elasticsearch.xpack.esql.expression.function.Named;
+import org.elasticsearch.xpack.esql.expression.function.Param;
 import org.elasticsearch.xpack.ql.expression.Expression;
 import org.elasticsearch.xpack.ql.expression.function.OptionalArgument;
 import org.elasticsearch.xpack.ql.expression.function.scalar.ScalarFunction;
@@ -35,7 +35,11 @@ public class Pow extends ScalarFunction implements OptionalArgument, EvaluatorMa
     private final Expression base, exponent;
     private final DataType dataType;
 
-    public Pow(Source source, @Named("base") Expression base, @Named("exponent") Expression exponent) {
+    public Pow(
+        Source source,
+        @Param(name = "base", type = { "integer", "long", "double" }) Expression base,
+        @Param(name = "exponent", type = { "integer", "double" }) Expression exponent
+    ) {
         super(source, Arrays.asList(base, exponent));
         this.base = base;
         this.exponent = exponent;

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

@@ -10,7 +10,8 @@ package org.elasticsearch.xpack.esql.expression.function.scalar.math;
 import org.elasticsearch.compute.ann.Evaluator;
 import org.elasticsearch.compute.operator.DriverContext;
 import org.elasticsearch.compute.operator.EvalOperator;
-import org.elasticsearch.xpack.esql.expression.function.Named;
+import org.elasticsearch.xpack.esql.expression.function.FunctionInfo;
+import org.elasticsearch.xpack.esql.expression.function.Param;
 import org.elasticsearch.xpack.ql.expression.Expression;
 import org.elasticsearch.xpack.ql.tree.NodeInfo;
 import org.elasticsearch.xpack.ql.tree.Source;
@@ -21,7 +22,12 @@ import java.util.List;
  * Sine trigonometric function.
  */
 public class Sin extends AbstractTrigonometricFunction {
-    public Sin(Source source, @Named("n") Expression n) {
+
+    @FunctionInfo(returnType = "double", description = "Returns the trigonometric sine of an angle")
+    public Sin(
+        Source source,
+        @Param(name = "n", type = { "integer", "long", "double", "unsigned_long" }, description = "An angle, in radians") Expression n
+    ) {
         super(source, n);
     }
 

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

@@ -10,7 +10,7 @@ package org.elasticsearch.xpack.esql.expression.function.scalar.math;
 import org.elasticsearch.compute.ann.Evaluator;
 import org.elasticsearch.compute.operator.DriverContext;
 import org.elasticsearch.compute.operator.EvalOperator;
-import org.elasticsearch.xpack.esql.expression.function.Named;
+import org.elasticsearch.xpack.esql.expression.function.Param;
 import org.elasticsearch.xpack.ql.expression.Expression;
 import org.elasticsearch.xpack.ql.tree.NodeInfo;
 import org.elasticsearch.xpack.ql.tree.Source;
@@ -21,7 +21,7 @@ import java.util.List;
  * Sine hyperbolic function.
  */
 public class Sinh extends AbstractTrigonometricFunction {
-    public Sinh(Source source, @Named("n") Expression n) {
+    public Sinh(Source source, @Param(name = "n", type = { "integer", "long", "double", "unsigned_long" }) Expression n) {
         super(source, n);
     }
 

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

@@ -11,7 +11,7 @@ import org.elasticsearch.compute.ann.Evaluator;
 import org.elasticsearch.compute.operator.EvalOperator.ExpressionEvaluator;
 import org.elasticsearch.xpack.esql.EsqlIllegalArgumentException;
 import org.elasticsearch.xpack.esql.evaluator.mapper.EvaluatorMapper;
-import org.elasticsearch.xpack.esql.expression.function.Named;
+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;
@@ -27,7 +27,7 @@ import static org.elasticsearch.xpack.ql.expression.TypeResolutions.ParamOrdinal
 import static org.elasticsearch.xpack.ql.expression.TypeResolutions.isNumeric;
 
 public class Sqrt extends UnaryScalarFunction implements EvaluatorMapper {
-    public Sqrt(Source source, @Named("n") Expression n) {
+    public Sqrt(Source source, @Param(name = "n", type = { "integer", "long", "double", "unsigned_long" }) Expression n) {
         super(source, n);
     }
 

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

@@ -10,7 +10,7 @@ package org.elasticsearch.xpack.esql.expression.function.scalar.math;
 import org.elasticsearch.compute.ann.Evaluator;
 import org.elasticsearch.compute.operator.DriverContext;
 import org.elasticsearch.compute.operator.EvalOperator;
-import org.elasticsearch.xpack.esql.expression.function.Named;
+import org.elasticsearch.xpack.esql.expression.function.Param;
 import org.elasticsearch.xpack.ql.expression.Expression;
 import org.elasticsearch.xpack.ql.tree.NodeInfo;
 import org.elasticsearch.xpack.ql.tree.Source;
@@ -21,7 +21,7 @@ import java.util.List;
  * Tangent trigonometric function.
  */
 public class Tan extends AbstractTrigonometricFunction {
-    public Tan(Source source, @Named("n") Expression n) {
+    public Tan(Source source, @Param(name = "n", type = { "integer", "long", "double", "unsigned_long" }) Expression n) {
         super(source, n);
     }
 

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

@@ -10,7 +10,7 @@ package org.elasticsearch.xpack.esql.expression.function.scalar.math;
 import org.elasticsearch.compute.ann.Evaluator;
 import org.elasticsearch.compute.operator.DriverContext;
 import org.elasticsearch.compute.operator.EvalOperator;
-import org.elasticsearch.xpack.esql.expression.function.Named;
+import org.elasticsearch.xpack.esql.expression.function.Param;
 import org.elasticsearch.xpack.ql.expression.Expression;
 import org.elasticsearch.xpack.ql.tree.NodeInfo;
 import org.elasticsearch.xpack.ql.tree.Source;
@@ -21,7 +21,7 @@ import java.util.List;
  * Tangent hyperbolic function.
  */
 public class Tanh extends AbstractTrigonometricFunction {
-    public Tanh(Source source, @Named("n") Expression n) {
+    public Tanh(Source source, @Param(name = "n", type = { "integer", "long", "double", "unsigned_long" }) Expression n) {
         super(source, n);
     }
 

+ 6 - 2
x-pack/plugin/esql/src/main/java/org/elasticsearch/xpack/esql/expression/function/scalar/string/Left.java

@@ -13,7 +13,7 @@ import org.elasticsearch.compute.ann.Evaluator;
 import org.elasticsearch.compute.ann.Fixed;
 import org.elasticsearch.compute.operator.EvalOperator.ExpressionEvaluator;
 import org.elasticsearch.xpack.esql.evaluator.mapper.EvaluatorMapper;
-import org.elasticsearch.xpack.esql.expression.function.Named;
+import org.elasticsearch.xpack.esql.expression.function.Param;
 import org.elasticsearch.xpack.ql.expression.Expression;
 import org.elasticsearch.xpack.ql.expression.function.scalar.ScalarFunction;
 import org.elasticsearch.xpack.ql.expression.gen.script.ScriptTemplate;
@@ -42,7 +42,11 @@ public class Left extends ScalarFunction implements EvaluatorMapper {
 
     private final Expression length;
 
-    public Left(Source source, @Named("string") Expression str, @Named("length") Expression length) {
+    public Left(
+        Source source,
+        @Param(name = "string", type = { "keyword" }) Expression str,
+        @Param(name = "length", type = { "integer" }) Expression length
+    ) {
         super(source, Arrays.asList(str, length));
         this.source = source;
         this.str = str;

+ 6 - 2
x-pack/plugin/esql/src/main/java/org/elasticsearch/xpack/esql/expression/function/scalar/string/Right.java

@@ -13,7 +13,7 @@ import org.elasticsearch.compute.ann.Evaluator;
 import org.elasticsearch.compute.ann.Fixed;
 import org.elasticsearch.compute.operator.EvalOperator.ExpressionEvaluator;
 import org.elasticsearch.xpack.esql.evaluator.mapper.EvaluatorMapper;
-import org.elasticsearch.xpack.esql.expression.function.Named;
+import org.elasticsearch.xpack.esql.expression.function.Param;
 import org.elasticsearch.xpack.ql.expression.Expression;
 import org.elasticsearch.xpack.ql.expression.function.scalar.ScalarFunction;
 import org.elasticsearch.xpack.ql.expression.gen.script.ScriptTemplate;
@@ -42,7 +42,11 @@ public class Right extends ScalarFunction implements EvaluatorMapper {
 
     private final Expression length;
 
-    public Right(Source source, @Named("string") Expression str, @Named("length") Expression length) {
+    public Right(
+        Source source,
+        @Param(name = "string", type = { "keyword" }) Expression str,
+        @Param(name = "length", type = { "integer" }) Expression length
+    ) {
         super(source, Arrays.asList(str, length));
         this.source = source;
         this.str = str;

+ 40 - 25
x-pack/plugin/esql/src/main/java/org/elasticsearch/xpack/esql/plan/logical/show/ShowFunctions.java

@@ -9,22 +9,23 @@ package org.elasticsearch.xpack.esql.plan.logical.show;
 
 import org.apache.lucene.util.BytesRef;
 import org.elasticsearch.common.Strings;
-import org.elasticsearch.xpack.esql.expression.function.Named;
+import org.elasticsearch.xpack.esql.expression.function.EsqlFunctionRegistry;
 import org.elasticsearch.xpack.ql.expression.Attribute;
 import org.elasticsearch.xpack.ql.expression.ReferenceAttribute;
-import org.elasticsearch.xpack.ql.expression.function.FunctionDefinition;
 import org.elasticsearch.xpack.ql.expression.function.FunctionRegistry;
 import org.elasticsearch.xpack.ql.plan.logical.LeafPlan;
 import org.elasticsearch.xpack.ql.plan.logical.LogicalPlan;
-import org.elasticsearch.xpack.ql.session.Configuration;
 import org.elasticsearch.xpack.ql.tree.NodeInfo;
 import org.elasticsearch.xpack.ql.tree.Source;
 
 import java.util.ArrayList;
+import java.util.Arrays;
 import java.util.Comparator;
 import java.util.List;
+import java.util.function.Function;
 import java.util.stream.Collectors;
 
+import static org.elasticsearch.xpack.ql.type.DataTypes.BOOLEAN;
 import static org.elasticsearch.xpack.ql.type.DataTypes.KEYWORD;
 
 public class ShowFunctions extends LeafPlan {
@@ -35,9 +36,12 @@ public class ShowFunctions extends LeafPlan {
         super(source);
 
         attributes = new ArrayList<>();
-        for (var name : List.of("name", "synopsis")) {
+        for (var name : List.of("name", "synopsis", "argNames", "argTypes", "argDescriptions", "returnType", "description")) {
             attributes.add(new ReferenceAttribute(Source.EMPTY, name, KEYWORD));
         }
+        for (var name : List.of("optionalArgs", "variadic")) {
+            attributes.add(new ReferenceAttribute(Source.EMPTY, name, BOOLEAN));
+        }
     }
 
     @Override
@@ -48,37 +52,48 @@ public class ShowFunctions extends LeafPlan {
     public List<List<Object>> values(FunctionRegistry functionRegistry) {
         List<List<Object>> rows = new ArrayList<>();
         for (var def : functionRegistry.listFunctions(null)) {
+            EsqlFunctionRegistry.FunctionDescription signature = EsqlFunctionRegistry.description(def);
             List<Object> row = new ArrayList<>();
-            row.add(asBytesRefOrNull(def.name()));
-            row.add(new BytesRef(def.name() + "(" + signature(def).stream().collect(Collectors.joining(", ")) + ")"));
+            row.add(asBytesRefOrNull(signature.name()));
+            row.add(new BytesRef(signature.fullSignature()));
+            row.add(collect(signature, EsqlFunctionRegistry.ArgSignature::name));
+            row.add(collect(signature, EsqlFunctionRegistry.ArgSignature::type));
+            row.add(collect(signature, EsqlFunctionRegistry.ArgSignature::description));
+            row.add(withPipes(signature.returnType()));
+            row.add(signature.description());
+            row.add(collect(signature, EsqlFunctionRegistry.ArgSignature::optional));
+            row.add(signature.variadic());
             rows.add(row);
         }
         rows.sort(Comparator.comparing(x -> ((BytesRef) x.get(0))));
         return rows;
     }
 
-    /**
-     * Render the function's signature to a string.
-     */
-    public static List<String> signature(FunctionDefinition def) {
-        var constructors = def.clazz().getConstructors();
-        if (constructors.length == 0) {
-            return List.of();
+    private Object collect(EsqlFunctionRegistry.FunctionDescription signature, Function<EsqlFunctionRegistry.ArgSignature, ?> x) {
+        if (signature.args().size() == 0) {
+            return null;
+        }
+        if (signature.args().size() == 1) {
+            Object result = x.apply(signature.args().get(0));
+            if (result instanceof String[] r) {
+                return withPipes(r);
+            }
+            return result;
         }
-        var params = constructors[0].getParameters(); // no multiple c'tors supported
-        List<String> args = new ArrayList<>(params.length);
-        for (int i = 1; i < params.length; i++) { // skipping 1st argument, the source
-            if (Configuration.class.isAssignableFrom(params[i].getType()) == false) {
-                Named namedAnn = params[i].getAnnotation(Named.class);
-                String name = namedAnn == null ? params[i].getName() : namedAnn.value();
-
-                if (List.class.isAssignableFrom(params[i].getType())) {
-                    name = name + "...";
-                }
-                args.add(name);
+
+        List<?> result = signature.args().stream().map(x).collect(Collectors.toList());
+        if (result.isEmpty() == false && result.get(0) instanceof String[]) {
+            List<String> newResult = new ArrayList<>();
+            for (Object item : result) {
+                newResult.add(withPipes((String[]) item));
             }
+            return newResult;
         }
-        return args;
+        return result;
+    }
+
+    public static String withPipes(String[] items) {
+        return Arrays.stream(items).collect(Collectors.joining("|"));
     }
 
     private static BytesRef asBytesRefOrNull(String string) {

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

@@ -28,7 +28,6 @@ import org.elasticsearch.xpack.esql.evaluator.EvalMapper;
 import org.elasticsearch.xpack.esql.expression.function.scalar.conditional.Greatest;
 import org.elasticsearch.xpack.esql.expression.function.scalar.nulls.Coalesce;
 import org.elasticsearch.xpack.esql.optimizer.FoldNull;
-import org.elasticsearch.xpack.esql.plan.logical.show.ShowFunctions;
 import org.elasticsearch.xpack.esql.planner.Layout;
 import org.elasticsearch.xpack.esql.type.EsqlDataTypes;
 import org.elasticsearch.xpack.ql.expression.Expression;
@@ -52,6 +51,7 @@ import java.nio.file.Path;
 import java.time.Duration;
 import java.time.Period;
 import java.util.ArrayList;
+import java.util.Arrays;
 import java.util.Collections;
 import java.util.HashMap;
 import java.util.HashSet;
@@ -292,6 +292,45 @@ public abstract class AbstractFunctionTestCase extends ESTestCase {
         assertSerialization(buildFieldExpression(testCase));
     }
 
+    @AfterClass
+    public static void testFunctionInfo() {
+        FunctionDefinition definition = definition();
+        if (definition == null) {
+            LogManager.getLogger(getTestClass()).info("Skipping function info checks because the function isn't registered");
+            return;
+        }
+        EsqlFunctionRegistry.FunctionDescription description = EsqlFunctionRegistry.description(definition);
+        List<EsqlFunctionRegistry.ArgSignature> args = description.args();
+
+        List<Set<String>> typesFromSignature = new ArrayList<>();
+        Set<String> returnFromSignature = new HashSet<>();
+        for (int i = 0; i < args.size(); i++) {
+            typesFromSignature.add(new HashSet<>());
+        }
+        for (Map.Entry<List<DataType>, DataType> entry : signatures.entrySet()) {
+            List<DataType> types = entry.getKey();
+            for (int i = 0; i < args.size() && i < types.size(); i++) {
+                typesFromSignature.get(i).add(types.get(i).esType());
+            }
+            returnFromSignature.add(entry.getValue().esType());
+        }
+
+        for (int i = 0; i < args.size(); i++) {
+            Set<String> annotationTypes = Arrays.stream(args.get(i).type()).collect(Collectors.toSet());
+            if (annotationTypes.equals(Set.of("?"))) {
+                continue; // TODO remove this eventually, so that all the functions will have to provide singature info
+            }
+            Set<String> signatureTypes = typesFromSignature.get(i);
+            assertEquals(annotationTypes, signatureTypes);
+        }
+
+        Set<String> returnTypes = Arrays.stream(description.returnType()).collect(Collectors.toSet());
+        if (returnTypes.equals(Set.of("?")) == false) { // TODO remove this eventually, so that all the functions will have to provide
+                                                        // singature info
+            assertEquals(returnTypes, returnFromSignature);
+        }
+    }
+
     /**
      * Adds cases with {@code null} and asserts that the result is {@code null}.
      * <p>
@@ -573,16 +612,16 @@ public abstract class AbstractFunctionTestCase extends ESTestCase {
             return;
         }
 
-        List<String> definedSignature = ShowFunctions.signature(definition);
+        List<String> args = EsqlFunctionRegistry.description(definition).argNames();
         StringBuilder header = new StringBuilder();
-        for (String arg : definedSignature) {
+        for (String arg : args) {
             header.append(arg).append(" | ");
         }
         header.append("result");
 
         List<String> table = new ArrayList<>();
         for (Map.Entry<List<DataType>, DataType> sig : signatures.entrySet()) {
-            if (sig.getKey().size() != definedSignature.size()) {
+            if (sig.getKey().size() != args.size()) {
                 continue;
             }
             StringBuilder b = new StringBuilder();

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

@@ -20,7 +20,6 @@ import net.nextencia.rrdiagram.grammar.rrdiagram.RRElement;
 import net.nextencia.rrdiagram.grammar.rrdiagram.RRText;
 
 import org.elasticsearch.common.util.LazyInitializable;
-import org.elasticsearch.xpack.esql.plan.logical.show.ShowFunctions;
 import org.elasticsearch.xpack.ql.expression.function.FunctionDefinition;
 
 import java.awt.Font;
@@ -48,7 +47,7 @@ public class RailRoadDiagram {
         expressions.add(new SpecialSequence(definition.name().toUpperCase(Locale.ROOT)));
         expressions.add(new Syntax("("));
         boolean first = true;
-        List<String> args = ShowFunctions.signature(definition);
+        List<String> args = EsqlFunctionRegistry.description(definition).argNames();
         for (String arg : args) {
             if (arg.endsWith("...")) {
                 expressions.add(new Repetition(new Sequence(new Syntax(","), new Literal(arg.substring(0, arg.length() - 3))), 0, null));

+ 45 - 0
x-pack/plugin/esql/src/test/java/org/elasticsearch/xpack/esql/expression/function/scalar/conditional/GreatestTests.java

@@ -55,6 +55,51 @@ public class GreatestTests extends AbstractFunctionTestCase {
                 )
             )
         );
+        suppliers.add(
+            new TestCaseSupplier(
+                "(a, b)",
+                List.of(DataTypes.VERSION, DataTypes.VERSION),
+                () -> new TestCaseSupplier.TestCase(
+                    List.of(
+                        new TestCaseSupplier.TypedData(new BytesRef("1"), DataTypes.VERSION, "a"),
+                        new TestCaseSupplier.TypedData(new BytesRef("2"), DataTypes.VERSION, "b")
+                    ),
+                    "GreatestBytesRefEvaluator[values=[MvMax[field=Attribute[channel=0]], MvMax[field=Attribute[channel=1]]]]",
+                    DataTypes.VERSION,
+                    equalTo(new BytesRef("2"))
+                )
+            )
+        );
+        suppliers.add(
+            new TestCaseSupplier(
+                "(a, b)",
+                List.of(DataTypes.IP, DataTypes.IP),
+                () -> new TestCaseSupplier.TestCase(
+                    List.of(
+                        new TestCaseSupplier.TypedData(new BytesRef("127.0.0.1"), DataTypes.IP, "a"),
+                        new TestCaseSupplier.TypedData(new BytesRef("127.0.0.2"), DataTypes.IP, "b")
+                    ),
+                    "GreatestBytesRefEvaluator[values=[MvMax[field=Attribute[channel=0]], MvMax[field=Attribute[channel=1]]]]",
+                    DataTypes.IP,
+                    equalTo(new BytesRef("127.0.0.2"))
+                )
+            )
+        );
+        suppliers.add(
+            new TestCaseSupplier(
+                "(a, b)",
+                List.of(DataTypes.DOUBLE, DataTypes.DOUBLE),
+                () -> new TestCaseSupplier.TestCase(
+                    List.of(
+                        new TestCaseSupplier.TypedData(1d, DataTypes.DOUBLE, "a"),
+                        new TestCaseSupplier.TypedData(2d, DataTypes.DOUBLE, "b")
+                    ),
+                    "GreatestDoubleEvaluator[values=[MvMax[field=Attribute[channel=0]], MvMax[field=Attribute[channel=1]]]]",
+                    DataTypes.DOUBLE,
+                    equalTo(2d)
+                )
+            )
+        );
         return parameterSuppliersFromTypedData(anyNullIsNull(false, suppliers));
     }
 

+ 45 - 0
x-pack/plugin/esql/src/test/java/org/elasticsearch/xpack/esql/expression/function/scalar/conditional/LeastTests.java

@@ -54,6 +54,51 @@ public class LeastTests extends AbstractFunctionTestCase {
                 )
             )
         );
+        suppliers.add(
+            new TestCaseSupplier(
+                "(a, b)",
+                List.of(DataTypes.VERSION, DataTypes.VERSION),
+                () -> new TestCaseSupplier.TestCase(
+                    List.of(
+                        new TestCaseSupplier.TypedData(new BytesRef("1"), DataTypes.VERSION, "a"),
+                        new TestCaseSupplier.TypedData(new BytesRef("2"), DataTypes.VERSION, "b")
+                    ),
+                    "LeastBytesRefEvaluator[values=[MvMin[field=Attribute[channel=0]], MvMin[field=Attribute[channel=1]]]]",
+                    DataTypes.VERSION,
+                    equalTo(new BytesRef("1"))
+                )
+            )
+        );
+        suppliers.add(
+            new TestCaseSupplier(
+                "(a, b)",
+                List.of(DataTypes.IP, DataTypes.IP),
+                () -> new TestCaseSupplier.TestCase(
+                    List.of(
+                        new TestCaseSupplier.TypedData(new BytesRef("127.0.0.1"), DataTypes.IP, "a"),
+                        new TestCaseSupplier.TypedData(new BytesRef("127.0.0.2"), DataTypes.IP, "b")
+                    ),
+                    "LeastBytesRefEvaluator[values=[MvMin[field=Attribute[channel=0]], MvMin[field=Attribute[channel=1]]]]",
+                    DataTypes.IP,
+                    equalTo(new BytesRef("127.0.0.1"))
+                )
+            )
+        );
+        suppliers.add(
+            new TestCaseSupplier(
+                "(a, b)",
+                List.of(DataTypes.DOUBLE, DataTypes.DOUBLE),
+                () -> new TestCaseSupplier.TestCase(
+                    List.of(
+                        new TestCaseSupplier.TypedData(1d, DataTypes.DOUBLE, "a"),
+                        new TestCaseSupplier.TypedData(2d, DataTypes.DOUBLE, "b")
+                    ),
+                    "LeastDoubleEvaluator[values=[MvMin[field=Attribute[channel=0]], MvMin[field=Attribute[channel=1]]]]",
+                    DataTypes.DOUBLE,
+                    equalTo(1d)
+                )
+            )
+        );
         return parameterSuppliersFromTypedData(anyNullIsNull(false, suppliers));
     }
 

+ 27 - 10
x-pack/plugin/esql/src/test/java/org/elasticsearch/xpack/esql/expression/function/scalar/date/DateParseTests.java

@@ -30,17 +30,34 @@ public class DateParseTests extends AbstractScalarFunctionTestCase {
 
     @ParametersFactory
     public static Iterable<Object[]> parameters() {
-        return parameterSuppliersFromTypedData(List.of(new TestCaseSupplier("Basic Case", () -> {
-            return new TestCaseSupplier.TestCase(
-                List.of(
-                    new TestCaseSupplier.TypedData(new BytesRef("yyyy-MM-dd"), DataTypes.KEYWORD, "second"),
-                    new TestCaseSupplier.TypedData(new BytesRef("2023-05-05"), DataTypes.KEYWORD, "first")
+        return parameterSuppliersFromTypedData(
+            List.of(
+                new TestCaseSupplier(
+                    "Basic Case",
+                    () -> new TestCaseSupplier.TestCase(
+                        List.of(
+                            new TestCaseSupplier.TypedData(new BytesRef("yyyy-MM-dd"), DataTypes.KEYWORD, "second"),
+                            new TestCaseSupplier.TypedData(new BytesRef("2023-05-05"), DataTypes.KEYWORD, "first")
+                        ),
+                        "DateParseEvaluator[val=Attribute[channel=1], formatter=Attribute[channel=0], zoneId=Z]",
+                        DataTypes.DATETIME,
+                        equalTo(1683244800000L)
+                    )
                 ),
-                "DateParseEvaluator[val=Attribute[channel=1], formatter=Attribute[channel=0], zoneId=Z]",
-                DataTypes.DATETIME,
-                equalTo(1683244800000L)
-            );
-        })));
+                new TestCaseSupplier(
+                    "With Text",
+                    () -> new TestCaseSupplier.TestCase(
+                        List.of(
+                            new TestCaseSupplier.TypedData(new BytesRef("yyyy-MM-dd"), DataTypes.KEYWORD, "second"),
+                            new TestCaseSupplier.TypedData(new BytesRef("2023-05-05"), DataTypes.TEXT, "first")
+                        ),
+                        "DateParseEvaluator[val=Attribute[channel=1], formatter=Attribute[channel=0], zoneId=Z]",
+                        DataTypes.DATETIME,
+                        equalTo(1683244800000L)
+                    )
+                )
+            )
+        );
     }
 
     @Override