瀏覽代碼

Don't return TEXT type for functions that take TEXT (#114334) (#115625)

Always return `KEYWORD` for functions that previously returned `TEXT`, because any change to the value, no matter how small, is enough to render meaningless the original analyzer associated with the `TEXT` field value. In principle, if the attribute is no longer the original `FieldAttribute`, it can no longer claim to have the type `TEXT`.

This has been done for all functions: conversion functions, aggregating functions, multi-value functions. There were several that already produced `KEYWORD` for `TEXT` input (eg. ToString, FromBase64 and ToBase64, MvZip, ToLower, ToUpper, DateFormat, Concat, Left, Repeat, Replace, Right, Split, Substring), but many others that incorrectly claimed to produce `TEXT`, while this was really a false claim. This PR makes that now strict, and includes changes to the functions' units tests to disallow the tests to expect any functions output to be `TEXT`.

One side effect of this change is that methods that take multiple parameters that require all of them to have the same type, will now treat TEXT and KEYWORD the same. This was already the case for functions like `Concat`, but is now also the case for `Greatest`, `Least`, `Case`, `Coalesce` and `MvAppend`.

An associated change is that the type casting operator `::text` has been entirely removed. It used to map onto the `ToString` function which returned type KEYWORD, and so `::text` really produced a `KEYWORD`, which is a lie, or at least a `bug`, which is now fixed. Should we ever wish to actually produce real `TEXT`, we might love the fact that this operator has been freed up for future use (although it seems likely that function will require parameters to specify the analyzer, so might never be an operator again).

### Backwards compatibility issues:

This is a change that will fail BWC tests, since we have many tests that assert on TEXT output to functions. For this reason we needed to block two scenarios:

* We used the capability `functions_never_emit_text` to prevent 7 csv-spec tests and 2 yaml tests from being run against older versions that still emit text.
* We used `skipTest` to also block those two yaml tests from being run against the latest build, but using older yaml files downloaded (as far back as 8.14).

In all cases the change observed in these tests was simply the results columns no longer having `text` type, and instead being `keyword`.

---------

Co-authored-by: Luigi Dell'Aquila <luigi.dellaquila@gmail.com>
Craig Taverner 11 月之前
父節點
當前提交
3b3e7f7484
共有 85 個文件被更改,包括 253 次插入123 次删除
  1. 7 0
      docs/changelog/114334.yaml
  2. 50 2
      docs/reference/esql/functions/kibana/definition/case.json
  3. 2 2
      docs/reference/esql/functions/kibana/definition/coalesce.json
  4. 2 2
      docs/reference/esql/functions/kibana/definition/greatest.json
  5. 2 2
      docs/reference/esql/functions/kibana/definition/least.json
  6. 1 1
      docs/reference/esql/functions/kibana/definition/ltrim.json
  7. 1 1
      docs/reference/esql/functions/kibana/definition/max.json
  8. 1 1
      docs/reference/esql/functions/kibana/definition/min.json
  9. 1 1
      docs/reference/esql/functions/kibana/definition/mv_append.json
  10. 1 1
      docs/reference/esql/functions/kibana/definition/mv_dedupe.json
  11. 1 1
      docs/reference/esql/functions/kibana/definition/mv_first.json
  12. 1 1
      docs/reference/esql/functions/kibana/definition/mv_last.json
  13. 1 1
      docs/reference/esql/functions/kibana/definition/mv_max.json
  14. 1 1
      docs/reference/esql/functions/kibana/definition/mv_min.json
  15. 1 1
      docs/reference/esql/functions/kibana/definition/mv_slice.json
  16. 1 1
      docs/reference/esql/functions/kibana/definition/mv_sort.json
  17. 1 1
      docs/reference/esql/functions/kibana/definition/reverse.json
  18. 1 1
      docs/reference/esql/functions/kibana/definition/rtrim.json
  19. 1 1
      docs/reference/esql/functions/kibana/definition/to_lower.json
  20. 1 1
      docs/reference/esql/functions/kibana/definition/to_upper.json
  21. 1 1
      docs/reference/esql/functions/kibana/definition/top.json
  22. 1 1
      docs/reference/esql/functions/kibana/definition/trim.json
  23. 1 1
      docs/reference/esql/functions/kibana/definition/values.json
  24. 0 1
      docs/reference/esql/functions/kibana/inline_cast.json
  25. 4 2
      docs/reference/esql/functions/types/case.asciidoc
  26. 2 2
      docs/reference/esql/functions/types/coalesce.asciidoc
  27. 2 2
      docs/reference/esql/functions/types/greatest.asciidoc
  28. 2 2
      docs/reference/esql/functions/types/least.asciidoc
  29. 1 1
      docs/reference/esql/functions/types/ltrim.asciidoc
  30. 1 1
      docs/reference/esql/functions/types/max.asciidoc
  31. 1 1
      docs/reference/esql/functions/types/min.asciidoc
  32. 1 1
      docs/reference/esql/functions/types/mv_append.asciidoc
  33. 1 1
      docs/reference/esql/functions/types/mv_dedupe.asciidoc
  34. 1 1
      docs/reference/esql/functions/types/mv_first.asciidoc
  35. 1 1
      docs/reference/esql/functions/types/mv_last.asciidoc
  36. 1 1
      docs/reference/esql/functions/types/mv_max.asciidoc
  37. 1 1
      docs/reference/esql/functions/types/mv_min.asciidoc
  38. 1 1
      docs/reference/esql/functions/types/mv_slice.asciidoc
  39. 1 1
      docs/reference/esql/functions/types/mv_sort.asciidoc
  40. 1 1
      docs/reference/esql/functions/types/reverse.asciidoc
  41. 1 1
      docs/reference/esql/functions/types/rtrim.asciidoc
  42. 1 1
      docs/reference/esql/functions/types/to_lower.asciidoc
  43. 1 1
      docs/reference/esql/functions/types/to_upper.asciidoc
  44. 1 1
      docs/reference/esql/functions/types/top.asciidoc
  45. 1 1
      docs/reference/esql/functions/types/trim.asciidoc
  46. 1 1
      docs/reference/esql/functions/types/values.asciidoc
  47. 2 0
      x-pack/plugin/build.gradle
  48. 4 0
      x-pack/plugin/esql-core/src/main/java/org/elasticsearch/xpack/esql/core/type/DataType.java
  49. 3 3
      x-pack/plugin/esql/qa/testFixtures/src/main/resources/convert.csv-spec
  50. 9 5
      x-pack/plugin/esql/qa/testFixtures/src/main/resources/stats.csv-spec
  51. 4 2
      x-pack/plugin/esql/qa/testFixtures/src/main/resources/stats_top.csv-spec
  52. 2 1
      x-pack/plugin/esql/qa/testFixtures/src/main/resources/string.csv-spec
  53. 5 0
      x-pack/plugin/esql/src/main/java/org/elasticsearch/xpack/esql/action/EsqlCapabilities.java
  54. 2 2
      x-pack/plugin/esql/src/main/java/org/elasticsearch/xpack/esql/expression/function/aggregate/Max.java
  55. 2 2
      x-pack/plugin/esql/src/main/java/org/elasticsearch/xpack/esql/expression/function/aggregate/Min.java
  56. 2 2
      x-pack/plugin/esql/src/main/java/org/elasticsearch/xpack/esql/expression/function/aggregate/Top.java
  57. 2 2
      x-pack/plugin/esql/src/main/java/org/elasticsearch/xpack/esql/expression/function/aggregate/Values.java
  58. 1 1
      x-pack/plugin/esql/src/main/java/org/elasticsearch/xpack/esql/expression/function/scalar/UnaryScalarFunction.java
  59. 2 3
      x-pack/plugin/esql/src/main/java/org/elasticsearch/xpack/esql/expression/function/scalar/conditional/Case.java
  60. 3 3
      x-pack/plugin/esql/src/main/java/org/elasticsearch/xpack/esql/expression/function/scalar/conditional/Greatest.java
  61. 3 3
      x-pack/plugin/esql/src/main/java/org/elasticsearch/xpack/esql/expression/function/scalar/conditional/Least.java
  62. 3 4
      x-pack/plugin/esql/src/main/java/org/elasticsearch/xpack/esql/expression/function/scalar/multivalue/MvAppend.java
  63. 0 1
      x-pack/plugin/esql/src/main/java/org/elasticsearch/xpack/esql/expression/function/scalar/multivalue/MvDedupe.java
  64. 0 1
      x-pack/plugin/esql/src/main/java/org/elasticsearch/xpack/esql/expression/function/scalar/multivalue/MvFirst.java
  65. 0 1
      x-pack/plugin/esql/src/main/java/org/elasticsearch/xpack/esql/expression/function/scalar/multivalue/MvLast.java
  66. 1 1
      x-pack/plugin/esql/src/main/java/org/elasticsearch/xpack/esql/expression/function/scalar/multivalue/MvMax.java
  67. 1 1
      x-pack/plugin/esql/src/main/java/org/elasticsearch/xpack/esql/expression/function/scalar/multivalue/MvMin.java
  68. 1 2
      x-pack/plugin/esql/src/main/java/org/elasticsearch/xpack/esql/expression/function/scalar/multivalue/MvSlice.java
  69. 2 2
      x-pack/plugin/esql/src/main/java/org/elasticsearch/xpack/esql/expression/function/scalar/multivalue/MvSort.java
  70. 2 3
      x-pack/plugin/esql/src/main/java/org/elasticsearch/xpack/esql/expression/function/scalar/nulls/Coalesce.java
  71. 1 1
      x-pack/plugin/esql/src/main/java/org/elasticsearch/xpack/esql/expression/function/scalar/string/LTrim.java
  72. 1 1
      x-pack/plugin/esql/src/main/java/org/elasticsearch/xpack/esql/expression/function/scalar/string/RTrim.java
  73. 1 1
      x-pack/plugin/esql/src/main/java/org/elasticsearch/xpack/esql/expression/function/scalar/string/Reverse.java
  74. 2 2
      x-pack/plugin/esql/src/main/java/org/elasticsearch/xpack/esql/expression/function/scalar/string/ToLower.java
  75. 2 2
      x-pack/plugin/esql/src/main/java/org/elasticsearch/xpack/esql/expression/function/scalar/string/ToUpper.java
  76. 1 1
      x-pack/plugin/esql/src/main/java/org/elasticsearch/xpack/esql/expression/function/scalar/string/Trim.java
  77. 0 1
      x-pack/plugin/esql/src/main/java/org/elasticsearch/xpack/esql/type/EsqlDataTypeConverter.java
  78. 0 3
      x-pack/plugin/esql/src/test/java/org/elasticsearch/xpack/esql/analysis/ParsingTests.java
  79. 1 1
      x-pack/plugin/esql/src/test/java/org/elasticsearch/xpack/esql/expression/function/TestCaseSupplier.java
  80. 1 1
      x-pack/plugin/esql/src/test/java/org/elasticsearch/xpack/esql/expression/function/aggregate/MaxTests.java
  81. 1 1
      x-pack/plugin/esql/src/test/java/org/elasticsearch/xpack/esql/expression/function/aggregate/MinTests.java
  82. 56 3
      x-pack/plugin/esql/src/test/java/org/elasticsearch/xpack/esql/expression/function/scalar/conditional/CaseTests.java
  83. 1 1
      x-pack/plugin/esql/src/test/java/org/elasticsearch/xpack/esql/expression/function/scalar/string/ToLowerTests.java
  84. 1 1
      x-pack/plugin/esql/src/test/java/org/elasticsearch/xpack/esql/expression/function/scalar/string/ToUpperTests.java
  85. 19 5
      x-pack/plugin/src/yamlRestTest/resources/rest-api-spec/test/esql/80_text.yml

+ 7 - 0
docs/changelog/114334.yaml

@@ -0,0 +1,7 @@
+pr: 114334
+summary: Don't return TEXT type for functions that take TEXT
+area: ES|QL
+type: bug
+issues:
+ - 111537
+ - 114333

+ 50 - 2
docs/reference/esql/functions/kibana/definition/case.json

@@ -424,6 +424,30 @@
       "variadic" : true,
       "returnType" : "keyword"
     },
+    {
+      "params" : [
+        {
+          "name" : "condition",
+          "type" : "boolean",
+          "optional" : false,
+          "description" : "A condition."
+        },
+        {
+          "name" : "trueValue",
+          "type" : "keyword",
+          "optional" : false,
+          "description" : "The value that's returned when the corresponding condition is the first to evaluate to `true`. The default value is returned when no condition matches."
+        },
+        {
+          "name" : "elseValue",
+          "type" : "text",
+          "optional" : true,
+          "description" : "The value that's returned when no condition evaluates to `true`."
+        }
+      ],
+      "variadic" : true,
+      "returnType" : "keyword"
+    },
     {
       "params" : [
         {
@@ -482,7 +506,31 @@
         }
       ],
       "variadic" : true,
-      "returnType" : "text"
+      "returnType" : "keyword"
+    },
+    {
+      "params" : [
+        {
+          "name" : "condition",
+          "type" : "boolean",
+          "optional" : false,
+          "description" : "A condition."
+        },
+        {
+          "name" : "trueValue",
+          "type" : "text",
+          "optional" : false,
+          "description" : "The value that's returned when the corresponding condition is the first to evaluate to `true`. The default value is returned when no condition matches."
+        },
+        {
+          "name" : "elseValue",
+          "type" : "keyword",
+          "optional" : true,
+          "description" : "The value that's returned when no condition evaluates to `true`."
+        }
+      ],
+      "variadic" : true,
+      "returnType" : "keyword"
     },
     {
       "params" : [
@@ -506,7 +554,7 @@
         }
       ],
       "variadic" : true,
-      "returnType" : "text"
+      "returnType" : "keyword"
     },
     {
       "params" : [

+ 2 - 2
docs/reference/esql/functions/kibana/definition/coalesce.json

@@ -242,7 +242,7 @@
         }
       ],
       "variadic" : true,
-      "returnType" : "text"
+      "returnType" : "keyword"
     },
     {
       "params" : [
@@ -260,7 +260,7 @@
         }
       ],
       "variadic" : true,
-      "returnType" : "text"
+      "returnType" : "keyword"
     },
     {
       "params" : [

+ 2 - 2
docs/reference/esql/functions/kibana/definition/greatest.json

@@ -189,7 +189,7 @@
         }
       ],
       "variadic" : true,
-      "returnType" : "text"
+      "returnType" : "keyword"
     },
     {
       "params" : [
@@ -207,7 +207,7 @@
         }
       ],
       "variadic" : true,
-      "returnType" : "text"
+      "returnType" : "keyword"
     },
     {
       "params" : [

+ 2 - 2
docs/reference/esql/functions/kibana/definition/least.json

@@ -188,7 +188,7 @@
         }
       ],
       "variadic" : true,
-      "returnType" : "text"
+      "returnType" : "keyword"
     },
     {
       "params" : [
@@ -206,7 +206,7 @@
         }
       ],
       "variadic" : true,
-      "returnType" : "text"
+      "returnType" : "keyword"
     },
     {
       "params" : [

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

@@ -26,7 +26,7 @@
         }
       ],
       "variadic" : false,
-      "returnType" : "text"
+      "returnType" : "keyword"
     }
   ],
   "examples" : [

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

@@ -98,7 +98,7 @@
         }
       ],
       "variadic" : false,
-      "returnType" : "text"
+      "returnType" : "keyword"
     },
     {
       "params" : [

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

@@ -98,7 +98,7 @@
         }
       ],
       "variadic" : false,
-      "returnType" : "text"
+      "returnType" : "keyword"
     },
     {
       "params" : [

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

@@ -218,7 +218,7 @@
         }
       ],
       "variadic" : false,
-      "returnType" : "text"
+      "returnType" : "keyword"
     },
     {
       "params" : [

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

@@ -147,7 +147,7 @@
         }
       ],
       "variadic" : false,
-      "returnType" : "text"
+      "returnType" : "keyword"
     },
     {
       "params" : [

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

@@ -146,7 +146,7 @@
         }
       ],
       "variadic" : false,
-      "returnType" : "text"
+      "returnType" : "keyword"
     },
     {
       "params" : [

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

@@ -146,7 +146,7 @@
         }
       ],
       "variadic" : false,
-      "returnType" : "text"
+      "returnType" : "keyword"
     },
     {
       "params" : [

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

@@ -98,7 +98,7 @@
         }
       ],
       "variadic" : false,
-      "returnType" : "text"
+      "returnType" : "keyword"
     },
     {
       "params" : [

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

@@ -98,7 +98,7 @@
         }
       ],
       "variadic" : false,
-      "returnType" : "text"
+      "returnType" : "keyword"
     },
     {
       "params" : [

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

@@ -290,7 +290,7 @@
         }
       ],
       "variadic" : false,
-      "returnType" : "text"
+      "returnType" : "keyword"
     },
     {
       "params" : [

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

@@ -146,7 +146,7 @@
         }
       ],
       "variadic" : false,
-      "returnType" : "text"
+      "returnType" : "keyword"
     },
     {
       "params" : [

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

@@ -27,7 +27,7 @@
         }
       ],
       "variadic" : false,
-      "returnType" : "text"
+      "returnType" : "keyword"
     }
   ],
   "examples" : [

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

@@ -26,7 +26,7 @@
         }
       ],
       "variadic" : false,
-      "returnType" : "text"
+      "returnType" : "keyword"
     }
   ],
   "examples" : [

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

@@ -26,7 +26,7 @@
         }
       ],
       "variadic" : false,
-      "returnType" : "text"
+      "returnType" : "keyword"
     }
   ],
   "examples" : [

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

@@ -26,7 +26,7 @@
         }
       ],
       "variadic" : false,
-      "returnType" : "text"
+      "returnType" : "keyword"
     }
   ],
   "examples" : [

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

@@ -194,7 +194,7 @@
         }
       ],
       "variadic" : false,
-      "returnType" : "text"
+      "returnType" : "keyword"
     }
   ],
   "examples" : [

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

@@ -26,7 +26,7 @@
         }
       ],
       "variadic" : false,
-      "returnType" : "text"
+      "returnType" : "keyword"
     }
   ],
   "examples" : [

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

@@ -98,7 +98,7 @@
         }
       ],
       "variadic" : false,
-      "returnType" : "text"
+      "returnType" : "keyword"
     },
     {
       "params" : [

+ 0 - 1
docs/reference/esql/functions/kibana/inline_cast.json

@@ -15,7 +15,6 @@
   "keyword" : "to_string",
   "long" : "to_long",
   "string" : "to_string",
-  "text" : "to_string",
   "time_duration" : "to_timeduration",
   "unsigned_long" : "to_unsigned_long",
   "version" : "to_version"

+ 4 - 2
docs/reference/esql/functions/types/case.asciidoc

@@ -24,11 +24,13 @@ boolean | integer | | integer
 boolean | ip | ip | ip
 boolean | ip | | ip
 boolean | keyword | keyword | keyword
+boolean | keyword | text | keyword
 boolean | keyword | | keyword
 boolean | long | long | long
 boolean | long | | long
-boolean | text | text | text
-boolean | text | | text
+boolean | text | keyword | keyword
+boolean | text | text | keyword
+boolean | text | | keyword
 boolean | unsigned_long | unsigned_long | unsigned_long
 boolean | unsigned_long | | unsigned_long
 boolean | version | version | version

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

@@ -19,7 +19,7 @@ keyword | keyword | keyword
 keyword | | keyword
 long | long | long
 long | | long
-text | text | text
-text | | text
+text | text | keyword
+text | | keyword
 version | version | version
 |===

+ 2 - 2
docs/reference/esql/functions/types/greatest.asciidoc

@@ -16,7 +16,7 @@ keyword | keyword | keyword
 keyword | | keyword
 long | long | long
 long | | long
-text | text | text
-text | | text
+text | text | keyword
+text | | keyword
 version | version | version
 |===

+ 2 - 2
docs/reference/esql/functions/types/least.asciidoc

@@ -16,7 +16,7 @@ keyword | keyword | keyword
 keyword | | keyword
 long | long | long
 long | | long
-text | text | text
-text | | text
+text | text | keyword
+text | | keyword
 version | version | version
 |===

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

@@ -6,5 +6,5 @@
 |===
 string | result
 keyword | keyword
-text | text
+text | keyword
 |===

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

@@ -12,6 +12,6 @@ integer | integer
 ip | ip
 keyword | keyword
 long | long
-text | text
+text | keyword
 version | version
 |===

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

@@ -12,6 +12,6 @@ integer | integer
 ip | ip
 keyword | keyword
 long | long
-text | text
+text | keyword
 version | version
 |===

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

@@ -16,6 +16,6 @@ integer | integer | integer
 ip | ip | ip
 keyword | keyword | keyword
 long | long | long
-text | text | text
+text | text | keyword
 version | version | version
 |===

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

@@ -16,6 +16,6 @@ integer | integer
 ip | ip
 keyword | keyword
 long | long
-text | text
+text | keyword
 version | version
 |===

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

@@ -16,7 +16,7 @@ integer | integer
 ip | ip
 keyword | keyword
 long | long
-text | text
+text | keyword
 unsigned_long | unsigned_long
 version | version
 |===

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

@@ -16,7 +16,7 @@ integer | integer
 ip | ip
 keyword | keyword
 long | long
-text | text
+text | keyword
 unsigned_long | unsigned_long
 version | version
 |===

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

@@ -12,7 +12,7 @@ integer | integer
 ip | ip
 keyword | keyword
 long | long
-text | text
+text | keyword
 unsigned_long | unsigned_long
 version | version
 |===

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

@@ -12,7 +12,7 @@ integer | integer
 ip | ip
 keyword | keyword
 long | long
-text | text
+text | keyword
 unsigned_long | unsigned_long
 version | version
 |===

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

@@ -16,6 +16,6 @@ integer | integer | integer | integer
 ip | integer | integer | ip
 keyword | integer | integer | keyword
 long | integer | integer | long
-text | integer | integer | text
+text | integer | integer | keyword
 version | integer | integer | version
 |===

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

@@ -12,6 +12,6 @@ integer | keyword | integer
 ip | keyword | ip
 keyword | keyword | keyword
 long | keyword | long
-text | keyword | text
+text | keyword | keyword
 version | keyword | version
 |===

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

@@ -6,5 +6,5 @@
 |===
 str | result
 keyword | keyword
-text | text
+text | keyword
 |===

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

@@ -6,5 +6,5 @@
 |===
 string | result
 keyword | keyword
-text | text
+text | keyword
 |===

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

@@ -6,5 +6,5 @@
 |===
 str | result
 keyword | keyword
-text | text
+text | keyword
 |===

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

@@ -6,5 +6,5 @@
 |===
 str | result
 keyword | keyword
-text | text
+text | keyword
 |===

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

@@ -12,5 +12,5 @@ integer | integer | keyword | integer
 ip | integer | keyword | ip
 keyword | integer | keyword | keyword
 long | integer | keyword | long
-text | integer | keyword | text
+text | integer | keyword | keyword
 |===

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

@@ -6,5 +6,5 @@
 |===
 string | result
 keyword | keyword
-text | text
+text | keyword
 |===

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

@@ -12,6 +12,6 @@ integer | integer
 ip | ip
 keyword | keyword
 long | long
-text | text
+text | keyword
 version | version
 |===

+ 2 - 0
x-pack/plugin/build.gradle

@@ -203,5 +203,7 @@ tasks.named("yamlRestTestV7CompatTransform").configure({ task ->
   task.skipTest("security/10_forbidden/Test bulk response with invalid credentials", "warning does not exist for compatibility")
   task.skipTest("inference/inference_crud/Test get all", "Assertions on number of inference models break due to default configs")
   task.skipTest("esql/60_usage/Basic ESQL usage output (telemetry)", "The telemetry output changed. We dropped a column. That's safe.")
+  task.skipTest("esql/80_text/reverse text", "The output type changed from TEXT to KEYWORD.")
+  task.skipTest("esql/80_text/values function", "The output type changed from TEXT to KEYWORD.")
 })
 

+ 4 - 0
x-pack/plugin/esql-core/src/main/java/org/elasticsearch/xpack/esql/core/type/DataType.java

@@ -584,6 +584,10 @@ public enum DataType {
         return new Builder();
     }
 
+    public DataType noText() {
+        return this == TEXT ? KEYWORD : this;
+    }
+
     /**
      * Named parameters with default values. It's just easier to do this with
      * a builder in java....

+ 3 - 3
x-pack/plugin/esql/qa/testFixtures/src/main/resources/convert.csv-spec

@@ -58,11 +58,11 @@ ROW zero="0"::double
 
 convertToString
 required_capability: casting_operator
-ROW one=1::keyword, two=2::text, three=3::string
+ROW one=1::keyword, two=2::double, three=3::string
 ;
 
- one:keyword   | two:keyword   | three:keyword
-1              |2              |3
+one:keyword | two:double | three:keyword
+1           | 2.0        | 3
 ;
 
 convertToDatetime

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

@@ -131,17 +131,19 @@ OPQS                | OPQS      | OPQS      | ___       | small
 
 maxOfText
 required_capability: agg_max_min_string_support
+required_capability: functions_never_emit_text
 from airports
 | eval x = name
 | where scalerank >= 9
 | stats max(name), a = max(name), b = max(x);
 
-max(name):text   | a:text           | b:text
-Zaporozhye Int'l | Zaporozhye Int'l | Zaporozhye Int'l
+max(name):keyword | a:keyword        | b:keyword
+Zaporozhye Int'l  | Zaporozhye Int'l | Zaporozhye Int'l
 ;
 
 maxOfTextGrouping
 required_capability: agg_max_min_string_support
+required_capability: functions_never_emit_text
 from airports
 | eval x = name
 | where scalerank >= 9
@@ -149,7 +151,7 @@ from airports
 | sort type asc
 | limit 4;
 
-max(name):text   | a:text           | b:text           | type:keyword
+max(name):keyword| a:keyword        | b:keyword        | type:keyword
 Cheongju Int'l   | Cheongju Int'l   | Cheongju Int'l   | major
 Zaporozhye Int'l | Zaporozhye Int'l | Zaporozhye Int'l | mid
 Zaporozhye Int'l | Zaporozhye Int'l | Zaporozhye Int'l | military
@@ -211,17 +213,19 @@ LUH                 | LUH       | LUH       | ___       | small
 
 minOfText
 required_capability: agg_max_min_string_support
+required_capability: functions_never_emit_text
 from airports
 | eval x = name
 | where scalerank >= 9
 | stats min(name), a = min(name), b = min(x);
 
-min(name):text      | a:text              | b:text
+min(name):keyword   | a:keyword           | b:keyword
 Abdul Rachman Saleh | Abdul Rachman Saleh | Abdul Rachman Saleh
 ;
 
 minOfTextGrouping
 required_capability: agg_max_min_string_support
+required_capability: functions_never_emit_text
 from airports
 | eval x = name
 | where scalerank >= 9
@@ -229,7 +233,7 @@ from airports
 | sort type asc
 | limit 4;
 
-min(name):text      | a:text              | b:text              | type:keyword
+min(name):keyword   | a:keyword           | b:keyword           | type:keyword
 Chandigarh Int'l    | Chandigarh Int'l    | Chandigarh Int'l    | major
 Abdul Rachman Saleh | Abdul Rachman Saleh | Abdul Rachman Saleh | mid
 Abdul Rachman Saleh | Abdul Rachman Saleh | Abdul Rachman Saleh | military

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

@@ -263,6 +263,7 @@ FROM employees
 topText
 required_capability: agg_top
 required_capability: agg_top_string_support
+required_capability: functions_never_emit_text
 # we don't need MATCH, but the loader for books.csv is busted in CsvTests
 required_capability: match_operator
 
@@ -273,13 +274,14 @@ FROM books
     calc = TOP(calc, 3, "asc"),
     evil = TOP(CASE(year < 1980, title, author), 3, "desc");
 
-title:text | calc:keyword | evil:text
+title:keyword | calc:keyword | evil:keyword
 [Worlds of Exile and Illusion: Three Complete Novels of the Hainish Series in One Volume--Rocannon's World, Planet of Exile, City of Illusions, Woman-The Full Story: A Dynamic Celebration of Freedoms, Winter notes on summer impressions] | ["'Bria", "Gent", "HE UN"] | [William Faulkner, William Faulkner, William Faulkner]
 ;
 
 topTextGrouping
 required_capability: agg_top
 required_capability: agg_top_string_support
+required_capability: functions_never_emit_text
 # we don't need MATCH, but the loader for books.csv is busted in CsvTests
 required_capability: match_operator
 
@@ -293,7 +295,7 @@ FROM books
 | SORT author
 | LIMIT 3;
 
-                                                                                                             title:text | calc:keyword | evil:text | author:text
+                                                                                                             title:keyword | calc:keyword | evil:keyword | author:text
                                    A Tolkien Compass: Including J. R. R. Tolkien's Guide to the Names in The Lord of the Rings |  Tolk | A Tolkien Compass: Including J. R. R. Tolkien's Guide to the Names in The Lord of the Rings | Agnes Perkins
                                                     The Lord of the Rings Poster Collection: Six Paintings by Alan Lee (No. 1) | he Lo |                                                                [J. R. R. Tolkien, Alan Lee] | Alan Lee
 A Gentle Creature and Other Stories: White Nights, A Gentle Creature, and The Dream of a Ridiculous Man (The World's Classics) |  Gent |                                        [W. J. Leatherbarrow, Fyodor Dostoevsky, Alan Myers] | Alan Myers

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

@@ -1289,6 +1289,7 @@ x:integer   | y:string
 
 reverseWithTextFields
 required_capability: fn_reverse
+required_capability: functions_never_emit_text
 FROM books 
 | EVAL title_reversed = REVERSE(title), author_reversed_twice = REVERSE(REVERSE(author)), eq = author_reversed_twice == author 
 | KEEP title, title_reversed, author, author_reversed_twice, eq, book_no
@@ -1296,7 +1297,7 @@ FROM books
 | WHERE book_no IN ("1211", "1463")
 | LIMIT 2;
 
-title:text                                      | title_reversed:text                           | author:text               | author_reversed_twice:text    | eq:boolean    | book_no:keyword
+title:text                                      | title_reversed:keyword                        | author:text               | author_reversed_twice:keyword | eq:boolean    | book_no:keyword
 The brothers Karamazov                          | vozamaraK srehtorb ehT                        | Fyodor Dostoevsky         | Fyodor Dostoevsky             | true          | 1211
 Realms of Tolkien: Images of Middle-earth       | htrae-elddiM fo segamI :neikloT fo smlaeR     | J. R. R. Tolkien          | J. R. R. Tolkien              | true          | 1463
 ;

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

@@ -73,6 +73,11 @@ public class EsqlCapabilities {
          */
         FN_SUBSTRING_EMPTY_NULL,
 
+        /**
+         * All functions that take TEXT should never emit TEXT, only KEYWORD. #114334
+         */
+        FUNCTIONS_NEVER_EMIT_TEXT,
+
         /**
          * Support for the {@code INLINESTATS} syntax.
          */

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

@@ -55,7 +55,7 @@ public class Max extends AggregateFunction implements ToAggregator, SurrogateExp
     );
 
     @FunctionInfo(
-        returnType = { "boolean", "double", "integer", "long", "date", "ip", "keyword", "text", "long", "version" },
+        returnType = { "boolean", "double", "integer", "long", "date", "ip", "keyword", "long", "version" },
         description = "The maximum value of a field.",
         isAggregation = true,
         examples = {
@@ -119,7 +119,7 @@ public class Max extends AggregateFunction implements ToAggregator, SurrogateExp
 
     @Override
     public DataType dataType() {
-        return field().dataType();
+        return field().dataType().noText();
     }
 
     @Override

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

@@ -55,7 +55,7 @@ public class Min extends AggregateFunction implements ToAggregator, SurrogateExp
     );
 
     @FunctionInfo(
-        returnType = { "boolean", "double", "integer", "long", "date", "ip", "keyword", "text", "long", "version" },
+        returnType = { "boolean", "double", "integer", "long", "date", "ip", "keyword", "long", "version" },
         description = "The minimum value of a field.",
         isAggregation = true,
         examples = {
@@ -119,7 +119,7 @@ public class Min extends AggregateFunction implements ToAggregator, SurrogateExp
 
     @Override
     public DataType dataType() {
-        return field().dataType();
+        return field().dataType().noText();
     }
 
     @Override

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

@@ -51,7 +51,7 @@ public class Top extends AggregateFunction implements ToAggregator, SurrogateExp
     private static final String ORDER_DESC = "DESC";
 
     @FunctionInfo(
-        returnType = { "boolean", "double", "integer", "long", "date", "ip", "keyword", "text" },
+        returnType = { "boolean", "double", "integer", "long", "date", "ip", "keyword" },
         description = "Collects the top values for a field. Includes repeated values.",
         isAggregation = true,
         examples = @Example(file = "stats_top", tag = "top")
@@ -175,7 +175,7 @@ public class Top extends AggregateFunction implements ToAggregator, SurrogateExp
 
     @Override
     public DataType dataType() {
-        return field().dataType();
+        return field().dataType().noText();
     }
 
     @Override

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

@@ -52,7 +52,7 @@ public class Values extends AggregateFunction implements ToAggregator {
     );
 
     @FunctionInfo(
-        returnType = { "boolean", "date", "double", "integer", "ip", "keyword", "long", "text", "version" },
+        returnType = { "boolean", "date", "double", "integer", "ip", "keyword", "long", "version" },
         preview = true,
         description = "Returns all values in a group as a multivalued field. The order of the returned values isn't guaranteed. "
             + "If you need the values returned in order use <<esql-mv_sort>>.",
@@ -105,7 +105,7 @@ public class Values extends AggregateFunction implements ToAggregator {
 
     @Override
     public DataType dataType() {
-        return field().dataType();
+        return field().dataType().noText();
     }
 
     @Override

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

@@ -164,6 +164,6 @@ public abstract class UnaryScalarFunction extends EsqlScalarFunction {
 
     @Override
     public DataType dataType() {
-        return field.dataType();
+        return field.dataType().noText();
     }
 }

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

@@ -73,7 +73,6 @@ public final class Case extends EsqlScalarFunction {
             "ip",
             "keyword",
             "long",
-            "text",
             "unsigned_long",
             "version" },
         description = """
@@ -195,12 +194,12 @@ public final class Case extends EsqlScalarFunction {
 
     private TypeResolution resolveValueType(Expression value, int position) {
         if (dataType == null || dataType == NULL) {
-            dataType = value.dataType();
+            dataType = value.dataType().noText();
             return TypeResolution.TYPE_RESOLVED;
         }
         return TypeResolutions.isType(
             value,
-            t -> t == dataType,
+            t -> t.noText() == dataType,
             sourceText(),
             TypeResolutions.ParamOrdinal.fromIndex(position),
             dataType.typeName()

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

@@ -43,7 +43,7 @@ public class Greatest extends EsqlScalarFunction implements OptionalArgument {
     private DataType dataType;
 
     @FunctionInfo(
-        returnType = { "boolean", "date", "date_nanos", "double", "integer", "ip", "keyword", "long", "text", "version" },
+        returnType = { "boolean", "date", "date_nanos", "double", "integer", "ip", "keyword", "long", "version" },
         description = "Returns the maximum value from multiple columns. This is similar to <<esql-mv_max>>\n"
             + "except it is intended to run on multiple columns at once.",
         note = "When run on `keyword` or `text` fields, this returns the last string in alphabetical order. "
@@ -104,12 +104,12 @@ public class Greatest extends EsqlScalarFunction implements OptionalArgument {
         for (int position = 0; position < children().size(); position++) {
             Expression child = children().get(position);
             if (dataType == null || dataType == NULL) {
-                dataType = child.dataType();
+                dataType = child.dataType().noText();
                 continue;
             }
             TypeResolution resolution = TypeResolutions.isType(
                 child,
-                t -> t == dataType,
+                t -> t.noText() == dataType,
                 sourceText(),
                 TypeResolutions.ParamOrdinal.fromIndex(position),
                 dataType.typeName()

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

@@ -43,7 +43,7 @@ public class Least extends EsqlScalarFunction implements OptionalArgument {
     private DataType dataType;
 
     @FunctionInfo(
-        returnType = { "boolean", "date", "date_nanos", "double", "integer", "ip", "keyword", "long", "text", "version" },
+        returnType = { "boolean", "date", "date_nanos", "double", "integer", "ip", "keyword", "long", "version" },
         description = "Returns the minimum value from multiple columns. "
             + "This is similar to <<esql-mv_min>> except it is intended to run on multiple columns at once.",
         examples = @Example(file = "math", tag = "least")
@@ -102,12 +102,12 @@ public class Least extends EsqlScalarFunction implements OptionalArgument {
         for (int position = 0; position < children().size(); position++) {
             Expression child = children().get(position);
             if (dataType == null || dataType == NULL) {
-                dataType = child.dataType();
+                dataType = child.dataType().noText();
                 continue;
             }
             TypeResolution resolution = TypeResolutions.isType(
                 child,
-                t -> t == dataType,
+                t -> t.noText() == dataType,
                 sourceText(),
                 TypeResolutions.ParamOrdinal.fromIndex(position),
                 dataType.typeName()

+ 3 - 4
x-pack/plugin/esql/src/main/java/org/elasticsearch/xpack/esql/expression/function/scalar/multivalue/MvAppend.java

@@ -62,7 +62,6 @@ public class MvAppend extends EsqlScalarFunction implements EvaluatorMapper {
             "ip",
             "keyword",
             "long",
-            "text",
             "version" },
         description = "Concatenates values of two multi-value fields."
     )
@@ -134,12 +133,12 @@ public class MvAppend extends EsqlScalarFunction implements EvaluatorMapper {
         if (resolution.unresolved()) {
             return resolution;
         }
-        dataType = field1.dataType();
+        dataType = field1.dataType().noText();
         if (dataType == DataType.NULL) {
-            dataType = field2.dataType();
+            dataType = field2.dataType().noText();
             return isType(field2, DataType::isRepresentable, sourceText(), SECOND, "representable");
         }
-        return isType(field2, t -> t == dataType, sourceText(), SECOND, dataType.typeName());
+        return isType(field2, t -> t.noText() == dataType, sourceText(), SECOND, dataType.typeName());
     }
 
     @Override

+ 0 - 1
x-pack/plugin/esql/src/main/java/org/elasticsearch/xpack/esql/expression/function/scalar/multivalue/MvDedupe.java

@@ -46,7 +46,6 @@ public class MvDedupe extends AbstractMultivalueFunction {
             "ip",
             "keyword",
             "long",
-            "text",
             "version" },
         description = "Remove duplicate values from a multivalued field.",
         note = "`MV_DEDUPE` may, but won't always, sort the values in the column.",

+ 0 - 1
x-pack/plugin/esql/src/main/java/org/elasticsearch/xpack/esql/expression/function/scalar/multivalue/MvFirst.java

@@ -53,7 +53,6 @@ public class MvFirst extends AbstractMultivalueFunction {
             "ip",
             "keyword",
             "long",
-            "text",
             "unsigned_long",
             "version" },
         description = """

+ 0 - 1
x-pack/plugin/esql/src/main/java/org/elasticsearch/xpack/esql/expression/function/scalar/multivalue/MvLast.java

@@ -53,7 +53,6 @@ public class MvLast extends AbstractMultivalueFunction {
             "ip",
             "keyword",
             "long",
-            "text",
             "unsigned_long",
             "version" },
         description = """

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

@@ -36,7 +36,7 @@ public class MvMax extends AbstractMultivalueFunction {
     public static final NamedWriteableRegistry.Entry ENTRY = new NamedWriteableRegistry.Entry(Expression.class, "MvMax", MvMax::new);
 
     @FunctionInfo(
-        returnType = { "boolean", "date", "date_nanos", "double", "integer", "ip", "keyword", "long", "text", "unsigned_long", "version" },
+        returnType = { "boolean", "date", "date_nanos", "double", "integer", "ip", "keyword", "long", "unsigned_long", "version" },
         description = "Converts a multivalued expression into a single valued column containing the maximum value.",
         examples = {
             @Example(file = "math", tag = "mv_max"),

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

@@ -36,7 +36,7 @@ public class MvMin extends AbstractMultivalueFunction {
     public static final NamedWriteableRegistry.Entry ENTRY = new NamedWriteableRegistry.Entry(Expression.class, "MvMin", MvMin::new);
 
     @FunctionInfo(
-        returnType = { "boolean", "date", "date_nanos", "double", "integer", "ip", "keyword", "long", "text", "unsigned_long", "version" },
+        returnType = { "boolean", "date", "date_nanos", "double", "integer", "ip", "keyword", "long", "unsigned_long", "version" },
         description = "Converts a multivalued expression into a single valued column containing the minimum value.",
         examples = {
             @Example(file = "math", tag = "mv_min"),

+ 1 - 2
x-pack/plugin/esql/src/main/java/org/elasticsearch/xpack/esql/expression/function/scalar/multivalue/MvSlice.java

@@ -67,7 +67,6 @@ public class MvSlice extends EsqlScalarFunction implements OptionalArgument, Eva
             "ip",
             "keyword",
             "long",
-            "text",
             "version" },
         description = """
             Returns a subset of the multivalued field using the start and end index values.
@@ -240,7 +239,7 @@ public class MvSlice extends EsqlScalarFunction implements OptionalArgument, Eva
 
     @Override
     public DataType dataType() {
-        return field.dataType();
+        return field.dataType().noText();
     }
 
     static int adjustIndex(int oldOffset, int fieldValueCount, int first) {

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

@@ -69,7 +69,7 @@ public class MvSort extends EsqlScalarFunction implements OptionalArgument, Vali
     private static final String INVALID_ORDER_ERROR = "Invalid order value in [{}], expected one of [{}, {}] but got [{}]";
 
     @FunctionInfo(
-        returnType = { "boolean", "date", "date_nanos", "double", "integer", "ip", "keyword", "long", "text", "version" },
+        returnType = { "boolean", "date", "date_nanos", "double", "integer", "ip", "keyword", "long", "version" },
         description = "Sorts a multivalued field in lexicographical order.",
         examples = @Example(file = "ints", tag = "mv_sort")
     )
@@ -226,7 +226,7 @@ public class MvSort extends EsqlScalarFunction implements OptionalArgument, Vali
 
     @Override
     public DataType dataType() {
-        return field.dataType();
+        return field.dataType().noText();
     }
 
     @Override

+ 2 - 3
x-pack/plugin/esql/src/main/java/org/elasticsearch/xpack/esql/expression/function/scalar/nulls/Coalesce.java

@@ -61,7 +61,6 @@ public class Coalesce extends EsqlScalarFunction implements OptionalArgument {
             "ip",
             "keyword",
             "long",
-            "text",
             "version" },
         description = "Returns the first of its arguments that is not null. If all arguments are null, it returns `null`.",
         examples = { @Example(file = "null", tag = "coalesce") }
@@ -145,12 +144,12 @@ public class Coalesce extends EsqlScalarFunction implements OptionalArgument {
 
         for (int position = 0; position < children().size(); position++) {
             if (dataType == null || dataType == NULL) {
-                dataType = children().get(position).dataType();
+                dataType = children().get(position).dataType().noText();
                 continue;
             }
             TypeResolution resolution = TypeResolutions.isType(
                 children().get(position),
-                t -> t == dataType,
+                t -> t.noText() == dataType,
                 sourceText(),
                 TypeResolutions.ParamOrdinal.fromIndex(position),
                 dataType.typeName()

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

@@ -34,7 +34,7 @@ public class LTrim extends UnaryScalarFunction {
     public static final NamedWriteableRegistry.Entry ENTRY = new NamedWriteableRegistry.Entry(Expression.class, "LTrim", LTrim::new);
 
     @FunctionInfo(
-        returnType = { "keyword", "text" },
+        returnType = { "keyword" },
         description = "Removes leading whitespaces from a string.",
         examples = @Example(file = "string", tag = "ltrim")
     )

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

@@ -34,7 +34,7 @@ public class RTrim extends UnaryScalarFunction {
     public static final NamedWriteableRegistry.Entry ENTRY = new NamedWriteableRegistry.Entry(Expression.class, "RTrim", RTrim::new);
 
     @FunctionInfo(
-        returnType = { "keyword", "text" },
+        returnType = { "keyword" },
         description = "Removes trailing whitespaces from a string.",
         examples = @Example(file = "string", tag = "rtrim")
     )

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

@@ -37,7 +37,7 @@ public class Reverse extends UnaryScalarFunction {
     public static final NamedWriteableRegistry.Entry ENTRY = new NamedWriteableRegistry.Entry(Expression.class, "Reverse", Reverse::new);
 
     @FunctionInfo(
-        returnType = { "keyword", "text" },
+        returnType = { "keyword" },
         description = "Returns a new string representing the input string in reverse order.",
         examples = {
             @Example(file = "string", tag = "reverse"),

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

@@ -39,7 +39,7 @@ public class ToLower extends EsqlConfigurationFunction {
     private final Expression field;
 
     @FunctionInfo(
-        returnType = { "keyword", "text" },
+        returnType = { "keyword" },
         description = "Returns a new string representing the input string converted to lower case.",
         examples = @Example(file = "string", tag = "to_lower")
     )
@@ -72,7 +72,7 @@ public class ToLower extends EsqlConfigurationFunction {
 
     @Override
     public DataType dataType() {
-        return field.dataType();
+        return DataType.KEYWORD;
     }
 
     @Override

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

@@ -39,7 +39,7 @@ public class ToUpper extends EsqlConfigurationFunction {
     private final Expression field;
 
     @FunctionInfo(
-        returnType = { "keyword", "text" },
+        returnType = { "keyword" },
         description = "Returns a new string representing the input string converted to upper case.",
         examples = @Example(file = "string", tag = "to_upper")
     )
@@ -72,7 +72,7 @@ public class ToUpper extends EsqlConfigurationFunction {
 
     @Override
     public DataType dataType() {
-        return field.dataType();
+        return DataType.KEYWORD;
     }
 
     @Override

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

@@ -34,7 +34,7 @@ public final class Trim extends UnaryScalarFunction {
     public static final NamedWriteableRegistry.Entry ENTRY = new NamedWriteableRegistry.Entry(Expression.class, "Trim", Trim::new);
 
     @FunctionInfo(
-        returnType = { "keyword", "text" },
+        returnType = { "keyword" },
         description = "Removes leading and trailing whitespaces from a string.",
         examples = @Example(file = "string", tag = "trim")
     )

+ 0 - 1
x-pack/plugin/esql/src/main/java/org/elasticsearch/xpack/esql/type/EsqlDataTypeConverter.java

@@ -117,7 +117,6 @@ public class EsqlDataTypeConverter {
         entry(LONG, ToLong::new),
         // ToRadians, typeless
         entry(KEYWORD, ToString::new),
-        entry(TEXT, ToString::new),
         entry(UNSIGNED_LONG, ToUnsignedLong::new),
         entry(VERSION, ToVersion::new),
         entry(DATE_PERIOD, ToDatePeriod::new),

+ 0 - 3
x-pack/plugin/esql/src/test/java/org/elasticsearch/xpack/esql/analysis/ParsingTests.java

@@ -88,9 +88,6 @@ public class ParsingTests extends ESTestCase {
             Collections.sort(namesAndAliases);
             for (String nameOrAlias : namesAndAliases) {
                 DataType expectedType = DataType.fromNameOrAlias(nameOrAlias);
-                if (expectedType == DataType.TEXT) {
-                    expectedType = DataType.KEYWORD;
-                }
                 if (EsqlDataTypeConverter.converterFunctionFactory(expectedType) == null) {
                     continue;
                 }

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

@@ -1435,7 +1435,7 @@ public record TestCaseSupplier(String name, List<DataType> types, Supplier<TestC
             this.source = Source.EMPTY;
             this.data = data;
             this.evaluatorToString = evaluatorToString;
-            this.expectedType = expectedType;
+            this.expectedType = expectedType == null ? null : expectedType.noText();
             @SuppressWarnings("unchecked")
             Matcher<Object> downcast = (Matcher<Object>) matcher;
             this.matcher = downcast;

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

@@ -128,7 +128,7 @@ public class MaxTests extends AbstractAggregationTestCase {
                     return new TestCaseSupplier.TestCase(
                         List.of(TestCaseSupplier.TypedData.multiRow(List.of(value), DataType.TEXT, "field")),
                         "Max[field=Attribute[channel=0]]",
-                        DataType.TEXT,
+                        DataType.KEYWORD,
                         equalTo(value)
                     );
                 }),

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

@@ -128,7 +128,7 @@ public class MinTests extends AbstractAggregationTestCase {
                     return new TestCaseSupplier.TestCase(
                         List.of(TestCaseSupplier.TypedData.multiRow(List.of(value), DataType.TEXT, "field")),
                         "Min[field=Attribute[channel=0]]",
-                        DataType.TEXT,
+                        DataType.KEYWORD,
                         equalTo(value)
                     );
                 }),

+ 56 - 3
x-pack/plugin/esql/src/test/java/org/elasticsearch/xpack/esql/expression/function/scalar/conditional/CaseTests.java

@@ -151,6 +151,33 @@ public class CaseTests extends AbstractScalarFunctionTestCase {
                 return testCase(type, typedData, lhsOrRhs ? lhs : rhs, toStringMatcher(1, false), false, null, addWarnings(warnings));
             })
         );
+        if (type.noText() == DataType.KEYWORD) {
+            DataType otherType = type == DataType.KEYWORD ? DataType.TEXT : DataType.KEYWORD;
+            suppliers.add(
+                new TestCaseSupplier(
+                    TestCaseSupplier.nameFrom(Arrays.asList(cond, type, otherType)),
+                    List.of(DataType.BOOLEAN, type, otherType),
+                    () -> {
+                        Object lhs = randomLiteral(type).value();
+                        Object rhs = randomLiteral(otherType).value();
+                        List<TestCaseSupplier.TypedData> typedData = List.of(
+                            cond(cond, "cond"),
+                            new TestCaseSupplier.TypedData(lhs, type, "lhs"),
+                            new TestCaseSupplier.TypedData(rhs, otherType, "rhs")
+                        );
+                        return testCase(
+                            type,
+                            typedData,
+                            lhsOrRhs ? lhs : rhs,
+                            toStringMatcher(1, false),
+                            false,
+                            null,
+                            addWarnings(warnings)
+                        );
+                    }
+                )
+            );
+        }
         if (lhsOrRhs) {
             suppliers.add(
                 new TestCaseSupplier(
@@ -222,7 +249,6 @@ public class CaseTests extends AbstractScalarFunctionTestCase {
                 )
             );
         }
-
         suppliers.add(
             new TestCaseSupplier(
                 "partial foldable " + TestCaseSupplier.nameFrom(Arrays.asList(cond, type, type)),
@@ -292,6 +318,33 @@ public class CaseTests extends AbstractScalarFunctionTestCase {
                     }
                 )
             );
+            if (type.noText() == DataType.KEYWORD) {
+                DataType otherType = type == DataType.KEYWORD ? DataType.TEXT : DataType.KEYWORD;
+                suppliers.add(
+                    new TestCaseSupplier(
+                        TestCaseSupplier.nameFrom(Arrays.asList(DataType.NULL, type, otherType)),
+                        List.of(DataType.NULL, type, otherType),
+                        () -> {
+                            Object lhs = randomLiteral(type).value();
+                            Object rhs = randomLiteral(otherType).value();
+                            List<TestCaseSupplier.TypedData> typedData = List.of(
+                                new TestCaseSupplier.TypedData(null, DataType.NULL, "cond"),
+                                new TestCaseSupplier.TypedData(lhs, type, "lhs"),
+                                new TestCaseSupplier.TypedData(rhs, otherType, "rhs")
+                            );
+                            return testCase(
+                                type,
+                                typedData,
+                                lhsOrRhs ? lhs : rhs,
+                                startsWith("CaseEagerEvaluator[conditions=[ConditionEvaluator[condition="),
+                                false,
+                                null,
+                                addWarnings(warnings)
+                            );
+                        }
+                    )
+                );
+            }
         }
         suppliers.add(
             new TestCaseSupplier(
@@ -804,7 +857,7 @@ public class CaseTests extends AbstractScalarFunctionTestCase {
         if (types.get(0) != DataType.BOOLEAN && types.get(0) != DataType.NULL) {
             return typeErrorMessage(includeOrdinal, types, 0, "boolean");
         }
-        DataType mainType = types.get(1);
+        DataType mainType = types.get(1).noText();
         for (int i = 2; i < types.size(); i++) {
             if (i % 2 == 0 && i != types.size() - 1) {
                 // condition
@@ -813,7 +866,7 @@ public class CaseTests extends AbstractScalarFunctionTestCase {
                 }
             } else {
                 // value
-                if (types.get(i) != mainType) {
+                if (types.get(i).noText() != mainType) {
                     return typeErrorMessage(includeOrdinal, types, i, mainType.typeName());
                 }
             }

+ 1 - 1
x-pack/plugin/esql/src/test/java/org/elasticsearch/xpack/esql/expression/function/scalar/string/ToLowerTests.java

@@ -47,7 +47,7 @@ public class ToLowerTests extends AbstractConfigurationFunctionTestCase {
         suppliers.add(supplier("text unicode", DataType.TEXT, () -> randomUnicodeOfLengthBetween(1, 10)));
 
         // add null as parameter
-        return parameterSuppliersFromTypedDataWithDefaultChecks(false, suppliers, (v, p) -> "string");
+        return parameterSuppliersFromTypedDataWithDefaultChecks(true, suppliers, (v, p) -> "string");
     }
 
     public void testRandomLocale() {

+ 1 - 1
x-pack/plugin/esql/src/test/java/org/elasticsearch/xpack/esql/expression/function/scalar/string/ToUpperTests.java

@@ -47,7 +47,7 @@ public class ToUpperTests extends AbstractConfigurationFunctionTestCase {
         suppliers.add(supplier("text unicode", DataType.TEXT, () -> randomUnicodeOfLengthBetween(1, 10)));
 
         // add null as parameter
-        return parameterSuppliersFromTypedDataWithDefaultChecks(false, suppliers, (v, p) -> "string");
+        return parameterSuppliersFromTypedDataWithDefaultChecks(true, suppliers, (v, p) -> "string");
     }
 
     public void testRandomLocale() {

+ 19 - 5
x-pack/plugin/src/yamlRestTest/resources/rest-api-spec/test/esql/80_text.yml

@@ -392,7 +392,7 @@ setup:
         - method: POST
           path: /_query
           parameters: [method, path, parameters, capabilities]
-          capabilities: [fn_reverse]
+          capabilities: [fn_reverse, functions_never_emit_text]
       reason: "reverse not yet added"
   - do:
       allowed_warnings_regex:
@@ -402,10 +402,10 @@ setup:
           query: 'FROM test | SORT name | EVAL job_reversed = REVERSE(job), tag_reversed = REVERSE(tag) | KEEP job_reversed, tag_reversed'
 
   - match: { columns.0.name: "job_reversed" }
-  - match: { columns.0.type: "text" }
+  - match: { columns.0.type: "keyword" }
 
   - match: { columns.1.name: "tag_reversed" }
-  - match: { columns.1.type: "text" }
+  - match: { columns.1.type: "keyword" }
 
   - length: { values: 2 }
   - match: { values.0: [ "rotceriD TI", "rab oof" ] }
@@ -573,7 +573,6 @@ setup:
         body:
           query: 'FROM test | STATS job = VALUES(job) | EVAL job = MV_SORT(job) | LIMIT 1'
   - match: { columns.0.name: "job" }
-  - match: { columns.0.type: "text" }
   - length: { values: 1 }
   - match: { values.0: [ [ "IT Director", "Payroll Specialist" ] ] }
 
@@ -592,7 +591,22 @@ setup:
   - match: { columns.0.name: "tag" }
   - match: { columns.0.type: "text" }
   - match: { columns.1.name: "job" }
-  - match: { columns.1.type: "text" }
   - length: { values: 2 }
   - match: { values.0: [ "baz", [ "Other", "Payroll Specialist" ] ] }
   - match: { values.1: [ "foo bar", "IT Director" ] }
+
+---
+"remove text typecast":
+  - requires:
+      capabilities:
+        - method: POST
+          path: /_query
+          parameters: [ method, path, parameters, capabilities ]
+          capabilities: [ functions_never_emit_text ]
+      reason: "Disabling ::text was done in 8.17 as part of removing all possibilities to emit text"
+
+  - do:
+      catch: /Unsupported conversion to type \[TEXT\]/
+      esql.query:
+        body:
+          query: 'FROM test | EVAL tag = name::text | KEEP name'