From 4560f7f8a5a4d6beb092592e7c00e85ab565f56c Mon Sep 17 00:00:00 2001 From: Nik Everett Date: Thu, 9 Jan 2025 13:47:25 -0500 Subject: [PATCH 1/7] ESQL: Move more test type error testing This reduces the number of test cases in ESQL a little more ala #119678. It migrates a few random tests and all of the multivalue functions: ``` 92775 -> 43760 3m45 -> 4m04 ``` This adds a few more error test cases that were missing to make sure it all lines up well. And it fixes a few error messages in a few functions. That's *likely* where the extra time goes. --- .../functions/kibana/definition/bucket.json | 2 +- .../functions/kibana/definition/like.json | 2 +- .../kibana/definition/match_operator.json | 2 +- .../kibana/definition/mv_append.json | 72 +++++++ .../kibana/definition/mv_dedupe.json | 12 ++ .../functions/kibana/definition/mv_slice.json | 24 +++ .../functions/kibana/definition/mv_sort.json | 180 ++++++++++++++++++ .../functions/kibana/definition/rlike.json | 2 +- .../functions/kibana/docs/match_operator.md | 9 +- .../esql/functions/types/like.asciidoc | 2 +- .../esql/functions/types/mv_append.asciidoc | 4 + .../esql/functions/types/mv_dedupe.asciidoc | 1 + .../esql/functions/types/mv_slice.asciidoc | 1 + .../esql/functions/types/mv_sort.asciidoc | 10 + .../esql/functions/types/rlike.asciidoc | 2 +- .../function/scalar/multivalue/MvAppend.java | 6 + .../function/scalar/multivalue/MvDedupe.java | 2 + .../function/scalar/multivalue/MvSlice.java | 2 + .../function/scalar/multivalue/MvSort.java | 10 +- .../function/AbstractFunctionTestCase.java | 16 +- .../AbstractScalarFunctionTestCase.java | 16 +- ...ErrorsForCasesWithoutExamplesTestCase.java | 11 +- .../expression/function/TestCaseSupplier.java | 13 ++ .../function/fulltext/KqlErrorTests.java | 44 +++++ .../function/fulltext/MatchErrorTests.java | 75 ++++++++ .../function/fulltext/MatchTests.java | 46 +---- .../NoneFieldFullTextFunctionTestCase.java | 8 +- .../fulltext/QueryStringErrorTests.java | 44 +++++ .../function/fulltext/TermErrorTests.java | 44 +++++ .../function/fulltext/TermTests.java | 7 +- .../scalar/conditional/CaseTests.java | 6 +- .../scalar/multivalue/MvAppendErrorTests.java | 45 +++++ .../scalar/multivalue/MvAppendTests.java | 130 +++++-------- .../scalar/multivalue/MvAvgErrorTests.java | 37 ++++ .../scalar/multivalue/MvAvgTests.java | 2 +- .../scalar/multivalue/MvConcatErrorTests.java | 37 ++++ .../scalar/multivalue/MvConcatTests.java | 2 +- .../scalar/multivalue/MvCountErrorTests.java | 53 ++++++ .../scalar/multivalue/MvCountTests.java | 2 +- .../scalar/multivalue/MvDedupeErrorTests.java | 53 ++++++ .../scalar/multivalue/MvDedupeTests.java | 4 +- .../scalar/multivalue/MvFirstErrorTests.java | 53 ++++++ .../scalar/multivalue/MvFirstTests.java | 7 +- .../scalar/multivalue/MvLastErrorTests.java | 52 +++++ .../scalar/multivalue/MvLastTests.java | 7 +- .../scalar/multivalue/MvMaxErrorTests.java | 37 ++++ .../scalar/multivalue/MvMaxTests.java | 2 +- .../MvMedianAbsoluteDeviationErrorTests.java | 37 ++++ .../MvMedianAbsoluteDeviationTests.java | 2 +- .../scalar/multivalue/MvMedianErrorTests.java | 37 ++++ .../scalar/multivalue/MvMedianTests.java | 2 +- .../scalar/multivalue/MvMinErrorTests.java | 37 ++++ .../scalar/multivalue/MvMinTests.java | 2 +- .../MvPSeriesWeightedSumErrorTests.java | 37 ++++ .../multivalue/MvPSeriesWeightedSumTests.java | 2 - .../multivalue/MvPercentileErrorTests.java | 37 ++++ .../scalar/multivalue/MvPercentileTests.java | 3 +- .../scalar/multivalue/MvSliceErrorTests.java | 40 ++++ .../scalar/multivalue/MvSliceTests.java | 20 ++ .../scalar/multivalue/MvSortErrorTests.java | 44 +++++ .../scalar/multivalue/MvSortTests.java | 101 +++++----- .../scalar/multivalue/MvZipErrorTests.java | 37 ++++ .../scalar/multivalue/MvZipTests.java | 2 +- .../scalar/string/ConcatErrorTests.java | 40 ++++ .../function/scalar/string/ConcatTests.java | 24 --- .../scalar/string/RLikeErrorTests.java | 49 +++++ .../function/scalar/string/RLikeTests.java | 36 +--- .../scalar/string/WildcardLikeErrorTests.java | 49 +++++ .../scalar/string/WildcardLikeTests.java | 8 +- 69 files changed, 1547 insertions(+), 297 deletions(-) create mode 100644 x-pack/plugin/esql/src/test/java/org/elasticsearch/xpack/esql/expression/function/fulltext/KqlErrorTests.java create mode 100644 x-pack/plugin/esql/src/test/java/org/elasticsearch/xpack/esql/expression/function/fulltext/MatchErrorTests.java create mode 100644 x-pack/plugin/esql/src/test/java/org/elasticsearch/xpack/esql/expression/function/fulltext/QueryStringErrorTests.java create mode 100644 x-pack/plugin/esql/src/test/java/org/elasticsearch/xpack/esql/expression/function/fulltext/TermErrorTests.java create mode 100644 x-pack/plugin/esql/src/test/java/org/elasticsearch/xpack/esql/expression/function/scalar/multivalue/MvAppendErrorTests.java create mode 100644 x-pack/plugin/esql/src/test/java/org/elasticsearch/xpack/esql/expression/function/scalar/multivalue/MvAvgErrorTests.java create mode 100644 x-pack/plugin/esql/src/test/java/org/elasticsearch/xpack/esql/expression/function/scalar/multivalue/MvConcatErrorTests.java create mode 100644 x-pack/plugin/esql/src/test/java/org/elasticsearch/xpack/esql/expression/function/scalar/multivalue/MvCountErrorTests.java create mode 100644 x-pack/plugin/esql/src/test/java/org/elasticsearch/xpack/esql/expression/function/scalar/multivalue/MvDedupeErrorTests.java create mode 100644 x-pack/plugin/esql/src/test/java/org/elasticsearch/xpack/esql/expression/function/scalar/multivalue/MvFirstErrorTests.java create mode 100644 x-pack/plugin/esql/src/test/java/org/elasticsearch/xpack/esql/expression/function/scalar/multivalue/MvLastErrorTests.java create mode 100644 x-pack/plugin/esql/src/test/java/org/elasticsearch/xpack/esql/expression/function/scalar/multivalue/MvMaxErrorTests.java create mode 100644 x-pack/plugin/esql/src/test/java/org/elasticsearch/xpack/esql/expression/function/scalar/multivalue/MvMedianAbsoluteDeviationErrorTests.java create mode 100644 x-pack/plugin/esql/src/test/java/org/elasticsearch/xpack/esql/expression/function/scalar/multivalue/MvMedianErrorTests.java create mode 100644 x-pack/plugin/esql/src/test/java/org/elasticsearch/xpack/esql/expression/function/scalar/multivalue/MvMinErrorTests.java create mode 100644 x-pack/plugin/esql/src/test/java/org/elasticsearch/xpack/esql/expression/function/scalar/multivalue/MvPSeriesWeightedSumErrorTests.java create mode 100644 x-pack/plugin/esql/src/test/java/org/elasticsearch/xpack/esql/expression/function/scalar/multivalue/MvPercentileErrorTests.java create mode 100644 x-pack/plugin/esql/src/test/java/org/elasticsearch/xpack/esql/expression/function/scalar/multivalue/MvSliceErrorTests.java create mode 100644 x-pack/plugin/esql/src/test/java/org/elasticsearch/xpack/esql/expression/function/scalar/multivalue/MvSortErrorTests.java create mode 100644 x-pack/plugin/esql/src/test/java/org/elasticsearch/xpack/esql/expression/function/scalar/multivalue/MvZipErrorTests.java create mode 100644 x-pack/plugin/esql/src/test/java/org/elasticsearch/xpack/esql/expression/function/scalar/string/ConcatErrorTests.java create mode 100644 x-pack/plugin/esql/src/test/java/org/elasticsearch/xpack/esql/expression/function/scalar/string/RLikeErrorTests.java create mode 100644 x-pack/plugin/esql/src/test/java/org/elasticsearch/xpack/esql/expression/function/scalar/string/WildcardLikeErrorTests.java diff --git a/docs/reference/esql/functions/kibana/definition/bucket.json b/docs/reference/esql/functions/kibana/definition/bucket.json index 3d96de05c8407..f9c7f2f27d6f9 100644 --- a/docs/reference/esql/functions/kibana/definition/bucket.json +++ b/docs/reference/esql/functions/kibana/definition/bucket.json @@ -1599,7 +1599,7 @@ "FROM sample_data \n| WHERE @timestamp >= NOW() - 1 day and @timestamp < NOW()\n| STATS COUNT(*) BY bucket = BUCKET(@timestamp, 25, NOW() - 1 day, NOW())", "FROM employees\n| WHERE hire_date >= \"1985-01-01T00:00:00Z\" AND hire_date < \"1986-01-01T00:00:00Z\"\n| STATS AVG(salary) BY bucket = BUCKET(hire_date, 20, \"1985-01-01T00:00:00Z\", \"1986-01-01T00:00:00Z\")\n| SORT bucket", "FROM employees\n| STATS s1 = b1 + 1, s2 = BUCKET(salary / 1000 + 999, 50.) + 2 BY b1 = BUCKET(salary / 100 + 99, 50.), b2 = BUCKET(salary / 1000 + 999, 50.)\n| SORT b1, b2\n| KEEP s1, b1, s2, b2", - "FROM employees \n| STATS dates = VALUES(birth_date) BY b = BUCKET(birth_date + 1 HOUR, 1 YEAR) - 1 HOUR\n| EVAL d_count = MV_COUNT(dates)\n| SORT d_count\n| LIMIT 3" + "FROM employees\n| STATS dates = MV_SORT(VALUES(birth_date)) BY b = BUCKET(birth_date + 1 HOUR, 1 YEAR) - 1 HOUR\n| EVAL d_count = MV_COUNT(dates)\n| SORT d_count, b\n| LIMIT 3" ], "preview" : false, "snapshot_only" : false diff --git a/docs/reference/esql/functions/kibana/definition/like.json b/docs/reference/esql/functions/kibana/definition/like.json index f375c697bd60d..fd928f4811fc7 100644 --- a/docs/reference/esql/functions/kibana/definition/like.json +++ b/docs/reference/esql/functions/kibana/definition/like.json @@ -32,7 +32,7 @@ }, { "name" : "pattern", - "type" : "text", + "type" : "keyword", "optional" : false, "description" : "Pattern." } diff --git a/docs/reference/esql/functions/kibana/definition/match_operator.json b/docs/reference/esql/functions/kibana/definition/match_operator.json index 44233bbddb653..c8cbf1cf9d966 100644 --- a/docs/reference/esql/functions/kibana/definition/match_operator.json +++ b/docs/reference/esql/functions/kibana/definition/match_operator.json @@ -2,7 +2,7 @@ "comment" : "This is generated by ESQL's AbstractFunctionTestCase. Do no edit it. See ../README.md for how to regenerate it.", "type" : "operator", "name" : "match_operator", - "description" : "Performs a <> on the specified field. Returns true if the provided query matches the row.", + "description" : "Use `MATCH` to perform a <> on the specified field.\nUsing `MATCH` is equivalent to using the `match` query in the Elasticsearch Query DSL.\n\nMatch can be used on text fields, as well as other field types like boolean, dates, and numeric types.\n\nFor a simplified syntax, you can use the <> `:` operator instead of `MATCH`.\n\n`MATCH` returns true if the provided query matches the row.", "signatures" : [ { "params" : [ diff --git a/docs/reference/esql/functions/kibana/definition/mv_append.json b/docs/reference/esql/functions/kibana/definition/mv_append.json index 81c1b777be498..043625d9ea1e7 100644 --- a/docs/reference/esql/functions/kibana/definition/mv_append.json +++ b/docs/reference/esql/functions/kibana/definition/mv_append.json @@ -76,6 +76,24 @@ "variadic" : false, "returnType" : "date" }, + { + "params" : [ + { + "name" : "field1", + "type" : "date_nanos", + "optional" : false, + "description" : "" + }, + { + "name" : "field2", + "type" : "date_nanos", + "optional" : false, + "description" : "" + } + ], + "variadic" : false, + "returnType" : "date_nanos" + }, { "params" : [ { @@ -184,6 +202,24 @@ "variadic" : false, "returnType" : "keyword" }, + { + "params" : [ + { + "name" : "field1", + "type" : "keyword", + "optional" : false, + "description" : "" + }, + { + "name" : "field2", + "type" : "text", + "optional" : false, + "description" : "" + } + ], + "variadic" : false, + "returnType" : "keyword" + }, { "params" : [ { @@ -202,6 +238,24 @@ "variadic" : false, "returnType" : "long" }, + { + "params" : [ + { + "name" : "field1", + "type" : "text", + "optional" : false, + "description" : "" + }, + { + "name" : "field2", + "type" : "keyword", + "optional" : false, + "description" : "" + } + ], + "variadic" : false, + "returnType" : "keyword" + }, { "params" : [ { @@ -220,6 +274,24 @@ "variadic" : false, "returnType" : "keyword" }, + { + "params" : [ + { + "name" : "field1", + "type" : "unsigned_long", + "optional" : false, + "description" : "" + }, + { + "name" : "field2", + "type" : "unsigned_long", + "optional" : false, + "description" : "" + } + ], + "variadic" : false, + "returnType" : "unsigned_long" + }, { "params" : [ { diff --git a/docs/reference/esql/functions/kibana/definition/mv_dedupe.json b/docs/reference/esql/functions/kibana/definition/mv_dedupe.json index ce2c96dbc1757..2fb5b9c61727f 100644 --- a/docs/reference/esql/functions/kibana/definition/mv_dedupe.json +++ b/docs/reference/esql/functions/kibana/definition/mv_dedupe.json @@ -161,6 +161,18 @@ "variadic" : false, "returnType" : "keyword" }, + { + "params" : [ + { + "name" : "field", + "type" : "unsigned_long", + "optional" : false, + "description" : "Multivalue expression." + } + ], + "variadic" : false, + "returnType" : "unsigned_long" + }, { "params" : [ { diff --git a/docs/reference/esql/functions/kibana/definition/mv_slice.json b/docs/reference/esql/functions/kibana/definition/mv_slice.json index df4d48145fac6..5ad8f588cdc2b 100644 --- a/docs/reference/esql/functions/kibana/definition/mv_slice.json +++ b/docs/reference/esql/functions/kibana/definition/mv_slice.json @@ -316,6 +316,30 @@ "variadic" : false, "returnType" : "keyword" }, + { + "params" : [ + { + "name" : "field", + "type" : "unsigned_long", + "optional" : false, + "description" : "Multivalue expression. If `null`, the function returns `null`." + }, + { + "name" : "start", + "type" : "integer", + "optional" : false, + "description" : "Start position. If `null`, the function returns `null`. The start argument can be negative. An index of -1 is used to specify the last value in the list." + }, + { + "name" : "end", + "type" : "integer", + "optional" : true, + "description" : "End position(included). Optional; if omitted, the position at `start` is returned. The end argument can be negative. An index of -1 is used to specify the last value in the list." + } + ], + "variadic" : false, + "returnType" : "unsigned_long" + }, { "params" : [ { diff --git a/docs/reference/esql/functions/kibana/definition/mv_sort.json b/docs/reference/esql/functions/kibana/definition/mv_sort.json index 072c05743af33..3abedbf752943 100644 --- a/docs/reference/esql/functions/kibana/definition/mv_sort.json +++ b/docs/reference/esql/functions/kibana/definition/mv_sort.json @@ -22,6 +22,24 @@ "variadic" : false, "returnType" : "boolean" }, + { + "params" : [ + { + "name" : "field", + "type" : "boolean", + "optional" : false, + "description" : "Multivalue expression. If `null`, the function returns `null`." + }, + { + "name" : "order", + "type" : "text", + "optional" : true, + "description" : "Sort order. The valid options are ASC and DESC, the default is ASC." + } + ], + "variadic" : false, + "returnType" : "boolean" + }, { "params" : [ { @@ -40,6 +58,24 @@ "variadic" : false, "returnType" : "date" }, + { + "params" : [ + { + "name" : "field", + "type" : "date", + "optional" : false, + "description" : "Multivalue expression. If `null`, the function returns `null`." + }, + { + "name" : "order", + "type" : "text", + "optional" : true, + "description" : "Sort order. The valid options are ASC and DESC, the default is ASC." + } + ], + "variadic" : false, + "returnType" : "date" + }, { "params" : [ { @@ -58,6 +94,24 @@ "variadic" : false, "returnType" : "date_nanos" }, + { + "params" : [ + { + "name" : "field", + "type" : "date_nanos", + "optional" : false, + "description" : "Multivalue expression. If `null`, the function returns `null`." + }, + { + "name" : "order", + "type" : "text", + "optional" : true, + "description" : "Sort order. The valid options are ASC and DESC, the default is ASC." + } + ], + "variadic" : false, + "returnType" : "date_nanos" + }, { "params" : [ { @@ -76,6 +130,24 @@ "variadic" : false, "returnType" : "double" }, + { + "params" : [ + { + "name" : "field", + "type" : "double", + "optional" : false, + "description" : "Multivalue expression. If `null`, the function returns `null`." + }, + { + "name" : "order", + "type" : "text", + "optional" : true, + "description" : "Sort order. The valid options are ASC and DESC, the default is ASC." + } + ], + "variadic" : false, + "returnType" : "double" + }, { "params" : [ { @@ -94,6 +166,24 @@ "variadic" : false, "returnType" : "integer" }, + { + "params" : [ + { + "name" : "field", + "type" : "integer", + "optional" : false, + "description" : "Multivalue expression. If `null`, the function returns `null`." + }, + { + "name" : "order", + "type" : "text", + "optional" : true, + "description" : "Sort order. The valid options are ASC and DESC, the default is ASC." + } + ], + "variadic" : false, + "returnType" : "integer" + }, { "params" : [ { @@ -112,6 +202,24 @@ "variadic" : false, "returnType" : "ip" }, + { + "params" : [ + { + "name" : "field", + "type" : "ip", + "optional" : false, + "description" : "Multivalue expression. If `null`, the function returns `null`." + }, + { + "name" : "order", + "type" : "text", + "optional" : true, + "description" : "Sort order. The valid options are ASC and DESC, the default is ASC." + } + ], + "variadic" : false, + "returnType" : "ip" + }, { "params" : [ { @@ -130,6 +238,24 @@ "variadic" : false, "returnType" : "keyword" }, + { + "params" : [ + { + "name" : "field", + "type" : "keyword", + "optional" : false, + "description" : "Multivalue expression. If `null`, the function returns `null`." + }, + { + "name" : "order", + "type" : "text", + "optional" : true, + "description" : "Sort order. The valid options are ASC and DESC, the default is ASC." + } + ], + "variadic" : false, + "returnType" : "keyword" + }, { "params" : [ { @@ -148,6 +274,24 @@ "variadic" : false, "returnType" : "long" }, + { + "params" : [ + { + "name" : "field", + "type" : "long", + "optional" : false, + "description" : "Multivalue expression. If `null`, the function returns `null`." + }, + { + "name" : "order", + "type" : "text", + "optional" : true, + "description" : "Sort order. The valid options are ASC and DESC, the default is ASC." + } + ], + "variadic" : false, + "returnType" : "long" + }, { "params" : [ { @@ -166,6 +310,24 @@ "variadic" : false, "returnType" : "keyword" }, + { + "params" : [ + { + "name" : "field", + "type" : "text", + "optional" : false, + "description" : "Multivalue expression. If `null`, the function returns `null`." + }, + { + "name" : "order", + "type" : "text", + "optional" : true, + "description" : "Sort order. The valid options are ASC and DESC, the default is ASC." + } + ], + "variadic" : false, + "returnType" : "keyword" + }, { "params" : [ { @@ -183,6 +345,24 @@ ], "variadic" : false, "returnType" : "version" + }, + { + "params" : [ + { + "name" : "field", + "type" : "version", + "optional" : false, + "description" : "Multivalue expression. If `null`, the function returns `null`." + }, + { + "name" : "order", + "type" : "text", + "optional" : true, + "description" : "Sort order. The valid options are ASC and DESC, the default is ASC." + } + ], + "variadic" : false, + "returnType" : "version" } ], "examples" : [ diff --git a/docs/reference/esql/functions/kibana/definition/rlike.json b/docs/reference/esql/functions/kibana/definition/rlike.json index 7a328293383bb..7a8adf23736f9 100644 --- a/docs/reference/esql/functions/kibana/definition/rlike.json +++ b/docs/reference/esql/functions/kibana/definition/rlike.json @@ -32,7 +32,7 @@ }, { "name" : "pattern", - "type" : "text", + "type" : "keyword", "optional" : false, "description" : "A regular expression." } diff --git a/docs/reference/esql/functions/kibana/docs/match_operator.md b/docs/reference/esql/functions/kibana/docs/match_operator.md index b0b6196798087..7681c2d1ce231 100644 --- a/docs/reference/esql/functions/kibana/docs/match_operator.md +++ b/docs/reference/esql/functions/kibana/docs/match_operator.md @@ -3,7 +3,14 @@ This is generated by ESQL's AbstractFunctionTestCase. Do no edit it. See ../READ --> ### MATCH_OPERATOR -Performs a <> on the specified field. Returns true if the provided query matches the row. +Use `MATCH` to perform a <> on the specified field. +Using `MATCH` is equivalent to using the `match` query in the Elasticsearch Query DSL. + +Match can be used on text fields, as well as other field types like boolean, dates, and numeric types. + +For a simplified syntax, you can use the <> `:` operator instead of `MATCH`. + +`MATCH` returns true if the provided query matches the row. ``` FROM books diff --git a/docs/reference/esql/functions/types/like.asciidoc b/docs/reference/esql/functions/types/like.asciidoc index 46532f2af3bf3..fffa6dc0b8371 100644 --- a/docs/reference/esql/functions/types/like.asciidoc +++ b/docs/reference/esql/functions/types/like.asciidoc @@ -6,5 +6,5 @@ |=== str | pattern | result keyword | keyword | boolean -text | text | boolean +text | keyword | boolean |=== diff --git a/docs/reference/esql/functions/types/mv_append.asciidoc b/docs/reference/esql/functions/types/mv_append.asciidoc index 05f9ff6b19f9e..c5e87f78d4051 100644 --- a/docs/reference/esql/functions/types/mv_append.asciidoc +++ b/docs/reference/esql/functions/types/mv_append.asciidoc @@ -9,13 +9,17 @@ boolean | boolean | boolean cartesian_point | cartesian_point | cartesian_point cartesian_shape | cartesian_shape | cartesian_shape date | date | date +date_nanos | date_nanos | date_nanos double | double | double geo_point | geo_point | geo_point geo_shape | geo_shape | geo_shape integer | integer | integer ip | ip | ip keyword | keyword | keyword +keyword | text | keyword long | long | long +text | keyword | keyword text | text | keyword +unsigned_long | unsigned_long | unsigned_long version | version | version |=== diff --git a/docs/reference/esql/functions/types/mv_dedupe.asciidoc b/docs/reference/esql/functions/types/mv_dedupe.asciidoc index 1524ec86cd5ec..e68af2f992b43 100644 --- a/docs/reference/esql/functions/types/mv_dedupe.asciidoc +++ b/docs/reference/esql/functions/types/mv_dedupe.asciidoc @@ -18,5 +18,6 @@ ip | ip keyword | keyword long | long text | keyword +unsigned_long | unsigned_long version | version |=== diff --git a/docs/reference/esql/functions/types/mv_slice.asciidoc b/docs/reference/esql/functions/types/mv_slice.asciidoc index 75f45e333ee0c..ed65d227c8d92 100644 --- a/docs/reference/esql/functions/types/mv_slice.asciidoc +++ b/docs/reference/esql/functions/types/mv_slice.asciidoc @@ -18,5 +18,6 @@ ip | integer | integer | ip keyword | integer | integer | keyword long | integer | integer | long text | integer | integer | keyword +unsigned_long | integer | integer | unsigned_long version | integer | integer | version |=== diff --git a/docs/reference/esql/functions/types/mv_sort.asciidoc b/docs/reference/esql/functions/types/mv_sort.asciidoc index 83d3e45c7be02..97abe60c0ceab 100644 --- a/docs/reference/esql/functions/types/mv_sort.asciidoc +++ b/docs/reference/esql/functions/types/mv_sort.asciidoc @@ -6,13 +6,23 @@ |=== field | order | result boolean | keyword | boolean +boolean | text | boolean date | keyword | date +date | text | date date_nanos | keyword | date_nanos +date_nanos | text | date_nanos double | keyword | double +double | text | double integer | keyword | integer +integer | text | integer ip | keyword | ip +ip | text | ip keyword | keyword | keyword +keyword | text | keyword long | keyword | long +long | text | long text | keyword | keyword +text | text | keyword version | keyword | version +version | text | version |=== diff --git a/docs/reference/esql/functions/types/rlike.asciidoc b/docs/reference/esql/functions/types/rlike.asciidoc index 46532f2af3bf3..fffa6dc0b8371 100644 --- a/docs/reference/esql/functions/types/rlike.asciidoc +++ b/docs/reference/esql/functions/types/rlike.asciidoc @@ -6,5 +6,5 @@ |=== str | pattern | result keyword | keyword | boolean -text | text | boolean +text | keyword | boolean |=== diff --git a/x-pack/plugin/esql/src/main/java/org/elasticsearch/xpack/esql/expression/function/scalar/multivalue/MvAppend.java b/x-pack/plugin/esql/src/main/java/org/elasticsearch/xpack/esql/expression/function/scalar/multivalue/MvAppend.java index bcd6f4c30bf8a..7ec7d1b9b2eca 100644 --- a/x-pack/plugin/esql/src/main/java/org/elasticsearch/xpack/esql/expression/function/scalar/multivalue/MvAppend.java +++ b/x-pack/plugin/esql/src/main/java/org/elasticsearch/xpack/esql/expression/function/scalar/multivalue/MvAppend.java @@ -55,6 +55,7 @@ public class MvAppend extends EsqlScalarFunction implements EvaluatorMapper { "cartesian_point", "cartesian_shape", "date", + "date_nanos", "double", "geo_point", "geo_shape", @@ -62,6 +63,7 @@ public class MvAppend extends EsqlScalarFunction implements EvaluatorMapper { "ip", "keyword", "long", + "unsigned_long", "version" }, description = "Concatenates values of two multi-value fields." ) @@ -74,6 +76,7 @@ public MvAppend( "cartesian_point", "cartesian_shape", "date", + "date_nanos", "double", "geo_point", "geo_shape", @@ -82,6 +85,7 @@ public MvAppend( "keyword", "long", "text", + "unsigned_long", "version" } ) Expression field1, @Param( @@ -91,6 +95,7 @@ public MvAppend( "cartesian_point", "cartesian_shape", "date", + "date_nanos", "double", "geo_point", "geo_shape", @@ -99,6 +104,7 @@ public MvAppend( "keyword", "long", "text", + "unsigned_long", "version" } ) Expression field2 ) { diff --git a/x-pack/plugin/esql/src/main/java/org/elasticsearch/xpack/esql/expression/function/scalar/multivalue/MvDedupe.java b/x-pack/plugin/esql/src/main/java/org/elasticsearch/xpack/esql/expression/function/scalar/multivalue/MvDedupe.java index 9a2b041fafeb6..4d85d3a6a8d2e 100644 --- a/x-pack/plugin/esql/src/main/java/org/elasticsearch/xpack/esql/expression/function/scalar/multivalue/MvDedupe.java +++ b/x-pack/plugin/esql/src/main/java/org/elasticsearch/xpack/esql/expression/function/scalar/multivalue/MvDedupe.java @@ -46,6 +46,7 @@ public class MvDedupe extends AbstractMultivalueFunction { "ip", "keyword", "long", + "unsigned_long", "version" }, description = "Remove duplicate values from a multivalued field.", note = "`MV_DEDUPE` may, but won't always, sort the values in the column.", @@ -69,6 +70,7 @@ public MvDedupe( "keyword", "long", "text", + "unsigned_long", "version" }, description = "Multivalue expression." ) Expression field diff --git a/x-pack/plugin/esql/src/main/java/org/elasticsearch/xpack/esql/expression/function/scalar/multivalue/MvSlice.java b/x-pack/plugin/esql/src/main/java/org/elasticsearch/xpack/esql/expression/function/scalar/multivalue/MvSlice.java index f4f9679dc3704..20f55e660489d 100644 --- a/x-pack/plugin/esql/src/main/java/org/elasticsearch/xpack/esql/expression/function/scalar/multivalue/MvSlice.java +++ b/x-pack/plugin/esql/src/main/java/org/elasticsearch/xpack/esql/expression/function/scalar/multivalue/MvSlice.java @@ -67,6 +67,7 @@ public class MvSlice extends EsqlScalarFunction implements OptionalArgument, Eva "ip", "keyword", "long", + "unsigned_long", "version" }, description = """ Returns a subset of the multivalued field using the start and end index values. @@ -96,6 +97,7 @@ public MvSlice( "keyword", "long", "text", + "unsigned_long", "version" }, description = "Multivalue expression. If `null`, the function returns `null`." ) Expression field, diff --git a/x-pack/plugin/esql/src/main/java/org/elasticsearch/xpack/esql/expression/function/scalar/multivalue/MvSort.java b/x-pack/plugin/esql/src/main/java/org/elasticsearch/xpack/esql/expression/function/scalar/multivalue/MvSort.java index 2286a1357ced8..5ea6d64f482c2 100644 --- a/x-pack/plugin/esql/src/main/java/org/elasticsearch/xpack/esql/expression/function/scalar/multivalue/MvSort.java +++ b/x-pack/plugin/esql/src/main/java/org/elasticsearch/xpack/esql/expression/function/scalar/multivalue/MvSort.java @@ -51,6 +51,7 @@ import static org.elasticsearch.xpack.esql.core.expression.TypeResolutions.ParamOrdinal.FIRST; import static org.elasticsearch.xpack.esql.core.expression.TypeResolutions.ParamOrdinal.SECOND; +import static org.elasticsearch.xpack.esql.core.expression.TypeResolutions.isNotNull; import static org.elasticsearch.xpack.esql.core.expression.TypeResolutions.isString; import static org.elasticsearch.xpack.esql.core.expression.TypeResolutions.isType; import static org.elasticsearch.xpack.esql.expression.Validations.isFoldable; @@ -82,7 +83,7 @@ public MvSort( ) Expression field, @Param( name = "order", - type = { "keyword" }, + type = { "keyword", "text" }, description = "Sort order. The valid options are ASC and DESC, the default is ASC.", optional = true ) Expression order @@ -126,7 +127,10 @@ protected TypeResolution resolveType() { return new TypeResolution("Unresolved children"); } - TypeResolution resolution = isType(field, DataType::isRepresentable, sourceText(), FIRST, "representable"); + TypeResolution resolution = isType(field, dt -> switch (dt) { + case UNSIGNED_LONG, GEO_POINT, GEO_SHAPE, CARTESIAN_POINT, CARTESIAN_SHAPE -> false; + default -> true; + }, sourceText(), FIRST, "not unsigned long or geo"); if (resolution.unresolved()) { return resolution; @@ -136,7 +140,7 @@ protected TypeResolution resolveType() { return resolution; } - return isString(order, sourceText(), SECOND); + return isNotNull(order, sourceText(), SECOND).and(isString(order, sourceText(), SECOND)); } @Override diff --git a/x-pack/plugin/esql/src/test/java/org/elasticsearch/xpack/esql/expression/function/AbstractFunctionTestCase.java b/x-pack/plugin/esql/src/test/java/org/elasticsearch/xpack/esql/expression/function/AbstractFunctionTestCase.java index 03b9dba298951..be8ee2d5fb626 100644 --- a/x-pack/plugin/esql/src/test/java/org/elasticsearch/xpack/esql/expression/function/AbstractFunctionTestCase.java +++ b/x-pack/plugin/esql/src/test/java/org/elasticsearch/xpack/esql/expression/function/AbstractFunctionTestCase.java @@ -381,7 +381,9 @@ protected static TestCaseSupplier typeErrorSupplier( /** * Build a test case that asserts that the combination of parameter types is an error. + * @deprecated use an extension of {@link ErrorsForCasesWithoutExamplesTestCase} */ + @Deprecated protected static TestCaseSupplier typeErrorSupplier( boolean includeOrdinal, List> validPerPosition, @@ -626,11 +628,17 @@ protected static void buildLayout(Layout.Builder builder, Expression e) { protected Object toJavaObjectUnsignedLongAware(Block block, int position) { Object result; result = toJavaObject(block, position); - if (result != null && testCase.expectedType() == DataType.UNSIGNED_LONG) { - assertThat(result, instanceOf(Long.class)); - result = NumericUtils.unsignedLongAsBigInteger((Long) result); + if (result == null || testCase.expectedType() != DataType.UNSIGNED_LONG) { + return result; } - return result; + if (result instanceof List l) { + return l.stream().map(v -> { + assertThat(v, instanceOf(Long.class)); + return NumericUtils.unsignedLongAsBigInteger((Long) v); + }).toList(); + } + assertThat(result, instanceOf(Long.class)); + return NumericUtils.unsignedLongAsBigInteger((Long) result); } /** diff --git a/x-pack/plugin/esql/src/test/java/org/elasticsearch/xpack/esql/expression/function/AbstractScalarFunctionTestCase.java b/x-pack/plugin/esql/src/test/java/org/elasticsearch/xpack/esql/expression/function/AbstractScalarFunctionTestCase.java index 65b9c447170f4..3ec439f579000 100644 --- a/x-pack/plugin/esql/src/test/java/org/elasticsearch/xpack/esql/expression/function/AbstractScalarFunctionTestCase.java +++ b/x-pack/plugin/esql/src/test/java/org/elasticsearch/xpack/esql/expression/function/AbstractScalarFunctionTestCase.java @@ -106,15 +106,9 @@ protected static Iterable parameterSuppliersFromTypedDataWithDefaultCh protected static Iterable parameterSuppliersFromTypedDataWithDefaultChecks( ExpectedType nullsExpectedType, ExpectedEvaluatorToString evaluatorToString, - List suppliers, - PositionalErrorMessageSupplier positionalErrorMessageSupplier + List suppliers ) { - return parameterSuppliersFromTypedData( - errorsForCasesWithoutExamples( - anyNullIsNull(randomizeBytesRefsOffset(suppliers), nullsExpectedType, evaluatorToString), - positionalErrorMessageSupplier - ) - ); + return parameterSuppliersFromTypedData(anyNullIsNull(randomizeBytesRefsOffset(suppliers), nullsExpectedType, evaluatorToString)); } public final void testEvaluate() { @@ -370,7 +364,11 @@ public void testFold() { Object result = nullOptimized.fold(); // Decode unsigned longs into BigIntegers if (testCase.expectedType() == DataType.UNSIGNED_LONG && result != null) { - result = NumericUtils.unsignedLongAsBigInteger((Long) result); + if (result instanceof List l) { + result = l.stream().map(v -> NumericUtils.unsignedLongAsBigInteger((Long) v)).toList(); + } else { + result = NumericUtils.unsignedLongAsBigInteger((Long) result); + } } assertThat(result, testCase.getMatcher()); if (testCase.getExpectedBuildEvaluatorWarnings() != null) { diff --git a/x-pack/plugin/esql/src/test/java/org/elasticsearch/xpack/esql/expression/function/ErrorsForCasesWithoutExamplesTestCase.java b/x-pack/plugin/esql/src/test/java/org/elasticsearch/xpack/esql/expression/function/ErrorsForCasesWithoutExamplesTestCase.java index 7269abad07297..43a3d2c426ece 100644 --- a/x-pack/plugin/esql/src/test/java/org/elasticsearch/xpack/esql/expression/function/ErrorsForCasesWithoutExamplesTestCase.java +++ b/x-pack/plugin/esql/src/test/java/org/elasticsearch/xpack/esql/expression/function/ErrorsForCasesWithoutExamplesTestCase.java @@ -56,8 +56,8 @@ public final void test() { List cases = cases(); Set> valid = cases.stream().map(TestCaseSupplier::types).collect(Collectors.toSet()); List> validPerPosition = AbstractFunctionTestCase.validPerPosition(valid); - Iterable> missingSignatures = missingSignatures(cases, valid)::iterator; - for (List signature : missingSignatures) { + Iterable> testCandidates = testCandidates(cases, valid)::iterator; + for (List signature : testCandidates) { logger.debug("checking {}", signature); List args = new ArrayList<>(signature.size()); for (DataType type : signature) { @@ -80,7 +80,10 @@ protected void assertNumberOfCheckedSignatures(int checked) { assertThat("didn't check any signatures", checked, greaterThan(0)); } - private Stream> missingSignatures(List cases, Set> valid) { + /** + * Build a {@link Stream} of test signatures that we should check are invalid. + */ + protected Stream> testCandidates(List cases, Set> valid) { return cases.stream() .map(s -> s.types().size()) .collect(Collectors.toSet()) @@ -125,7 +128,7 @@ protected static String typeErrorMessage( } if (badArgPosition == -1) { throw new IllegalStateException( - "Can't generate error message for these types, you probably need a custom error message function" + "Can't generate error message for these types, you probably need a custom error message function signature =" + signature ); } String ordinal = includeOrdinal ? TypeResolutions.ParamOrdinal.fromIndex(badArgPosition).name().toLowerCase(Locale.ROOT) + " " : ""; diff --git a/x-pack/plugin/esql/src/test/java/org/elasticsearch/xpack/esql/expression/function/TestCaseSupplier.java b/x-pack/plugin/esql/src/test/java/org/elasticsearch/xpack/esql/expression/function/TestCaseSupplier.java index 72d816a65e632..ddb7f16cb8629 100644 --- a/x-pack/plugin/esql/src/test/java/org/elasticsearch/xpack/esql/expression/function/TestCaseSupplier.java +++ b/x-pack/plugin/esql/src/test/java/org/elasticsearch/xpack/esql/expression/function/TestCaseSupplier.java @@ -1392,6 +1392,10 @@ public static final class TestCase { */ private final String[] expectedBuildEvaluatorWarnings; + /** + * @deprecated use subclasses of {@link ErrorsForCasesWithoutExamplesTestCase} + */ + @Deprecated private final String expectedTypeError; private final boolean canBuildEvaluator; @@ -1412,6 +1416,11 @@ public TestCase(List data, Matcher evaluatorToString, DataTyp this(data, evaluatorToString, expectedType, matcher, null, null, null, null, null, null); } + /** + * Build a test case for type errors. + * @deprecated use a subclass of {@link ErrorsForCasesWithoutExamplesTestCase} instead + */ + @Deprecated public static TestCase typeError(List data, String expectedTypeError) { return new TestCase(data, null, null, null, null, null, expectedTypeError, null, null, null); } @@ -1532,6 +1541,10 @@ public String foldingExceptionMessage() { return foldingExceptionMessage; } + /** + * @deprecated use subclasses of {@link ErrorsForCasesWithoutExamplesTestCase} + */ + @Deprecated public String getExpectedTypeError() { return expectedTypeError; } diff --git a/x-pack/plugin/esql/src/test/java/org/elasticsearch/xpack/esql/expression/function/fulltext/KqlErrorTests.java b/x-pack/plugin/esql/src/test/java/org/elasticsearch/xpack/esql/expression/function/fulltext/KqlErrorTests.java new file mode 100644 index 0000000000000..891c419841e70 --- /dev/null +++ b/x-pack/plugin/esql/src/test/java/org/elasticsearch/xpack/esql/expression/function/fulltext/KqlErrorTests.java @@ -0,0 +1,44 @@ +/* + * 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.fulltext; + +import org.elasticsearch.xpack.esql.core.expression.Expression; +import org.elasticsearch.xpack.esql.core.tree.Source; +import org.elasticsearch.xpack.esql.core.type.DataType; +import org.elasticsearch.xpack.esql.expression.function.ErrorsForCasesWithoutExamplesTestCase; +import org.elasticsearch.xpack.esql.expression.function.TestCaseSupplier; +import org.hamcrest.Matcher; + +import java.util.List; +import java.util.Set; +import java.util.stream.Stream; + +import static org.hamcrest.Matchers.equalTo; + +public class KqlErrorTests extends ErrorsForCasesWithoutExamplesTestCase { + @Override + protected List cases() { + return paramsToSuppliers(KqlTests.parameters()); + } + + @Override + protected Stream> testCandidates(List cases, Set> valid) { + // Don't test null, as it is not allowed but the expected message is not a type error - so we check it separately in VerifierTests + return super.testCandidates(cases, valid).filter(sig -> false == sig.contains(DataType.NULL)); + } + + @Override + protected Expression build(Source source, List args) { + return new Kql(source, args.get(0)); + } + + @Override + protected Matcher expectedTypeErrorMatcher(List> validPerPosition, List signature) { + return equalTo(typeErrorMessage(false, validPerPosition, signature, (v, p) -> "string")); + } +} diff --git a/x-pack/plugin/esql/src/test/java/org/elasticsearch/xpack/esql/expression/function/fulltext/MatchErrorTests.java b/x-pack/plugin/esql/src/test/java/org/elasticsearch/xpack/esql/expression/function/fulltext/MatchErrorTests.java new file mode 100644 index 0000000000000..1f4e8e40a8259 --- /dev/null +++ b/x-pack/plugin/esql/src/test/java/org/elasticsearch/xpack/esql/expression/function/fulltext/MatchErrorTests.java @@ -0,0 +1,75 @@ +/* + * 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.fulltext; + +import org.elasticsearch.xpack.esql.core.expression.Expression; +import org.elasticsearch.xpack.esql.core.expression.TypeResolutions; +import org.elasticsearch.xpack.esql.core.tree.Source; +import org.elasticsearch.xpack.esql.core.type.DataType; +import org.elasticsearch.xpack.esql.expression.function.AbstractFunctionTestCase; +import org.elasticsearch.xpack.esql.expression.function.ErrorsForCasesWithoutExamplesTestCase; +import org.elasticsearch.xpack.esql.expression.function.TestCaseSupplier; +import org.elasticsearch.xpack.esql.expression.predicate.operator.comparison.EsqlBinaryComparison; +import org.hamcrest.Matcher; + +import java.util.List; +import java.util.Locale; +import java.util.Set; + +import static org.hamcrest.Matchers.equalTo; + +public class MatchErrorTests extends ErrorsForCasesWithoutExamplesTestCase { + @Override + protected List cases() { + return paramsToSuppliers(MatchTests.parameters()); + } + + @Override + protected Expression build(Source source, List args) { + return new Match(source, args.get(0), args.get(1)); + } + + @Override + protected Matcher expectedTypeErrorMatcher(List> validPerPosition, List signature) { + return equalTo( + errorMessageStringForMatch(validPerPosition, signature, (l, p) -> p == 0 ? FIELD_TYPE_ERROR_STRING : QUERY_TYPE_ERROR_STRING) + ); + } + + private static String errorMessageStringForMatch( + List> validPerPosition, + List signature, + AbstractFunctionTestCase.PositionalErrorMessageSupplier positionalErrorMessageSupplier + ) { + for (int i = 0; i < signature.size(); i++) { + // Need to check for nulls and bad parameters in order + if (signature.get(i) == DataType.NULL) { + return TypeResolutions.ParamOrdinal.fromIndex(i).name().toLowerCase(Locale.ROOT) + + " argument of [" + + sourceForSignature(signature) + + "] cannot be null, received []"; + } + if (validPerPosition.get(i).contains(signature.get(i)) == false) { + break; + } + } + + try { + return typeErrorMessage(true, validPerPosition, signature, positionalErrorMessageSupplier); + } catch (IllegalStateException e) { + // This means all the positional args were okay, so the expected error is for nulls or from the combination + return EsqlBinaryComparison.formatIncompatibleTypesMessage(signature.get(0), signature.get(1), sourceForSignature(signature)); + } + } + + private static final String FIELD_TYPE_ERROR_STRING = + "keyword, text, boolean, date, date_nanos, double, integer, ip, long, unsigned_long, version"; + + private static final String QUERY_TYPE_ERROR_STRING = + "keyword, boolean, date, date_nanos, double, integer, ip, long, unsigned_long, version"; +} diff --git a/x-pack/plugin/esql/src/test/java/org/elasticsearch/xpack/esql/expression/function/fulltext/MatchTests.java b/x-pack/plugin/esql/src/test/java/org/elasticsearch/xpack/esql/expression/function/fulltext/MatchTests.java index f29add60721da..cb0c9b263b547 100644 --- a/x-pack/plugin/esql/src/test/java/org/elasticsearch/xpack/esql/expression/function/fulltext/MatchTests.java +++ b/x-pack/plugin/esql/src/test/java/org/elasticsearch/xpack/esql/expression/function/fulltext/MatchTests.java @@ -11,20 +11,16 @@ import com.carrotsearch.randomizedtesting.annotations.ParametersFactory; import org.elasticsearch.xpack.esql.core.expression.Expression; -import org.elasticsearch.xpack.esql.core.expression.TypeResolutions; import org.elasticsearch.xpack.esql.core.tree.Source; import org.elasticsearch.xpack.esql.core.type.DataType; import org.elasticsearch.xpack.esql.core.util.NumericUtils; import org.elasticsearch.xpack.esql.expression.function.AbstractFunctionTestCase; import org.elasticsearch.xpack.esql.expression.function.FunctionName; import org.elasticsearch.xpack.esql.expression.function.TestCaseSupplier; -import org.elasticsearch.xpack.esql.expression.predicate.operator.comparison.EsqlBinaryComparison; import java.math.BigInteger; import java.util.ArrayList; import java.util.List; -import java.util.Locale; -import java.util.Set; import java.util.function.Supplier; import static org.elasticsearch.xpack.esql.expression.function.TestCaseSupplier.stringCases; @@ -33,12 +29,6 @@ @FunctionName("match") public class MatchTests extends AbstractFunctionTestCase { - private static final String FIELD_TYPE_ERROR_STRING = - "keyword, text, boolean, date, date_nanos, double, integer, ip, long, unsigned_long, version"; - - private static final String QUERY_TYPE_ERROR_STRING = - "keyword, boolean, date, date_nanos, double, integer, ip, long, unsigned_long, version"; - public MatchTests(@Name("TestCase") Supplier testCaseSupplier) { this.testCase = testCaseSupplier.get(); } @@ -53,37 +43,7 @@ public static Iterable parameters() { addQueryAsStringTestCases(suppliers); addStringTestCases(suppliers); - return parameterSuppliersFromTypedData( - errorsForCasesWithoutExamples( - suppliers, - (o, v, t) -> errorMessageStringForMatch(o, v, t, (l, p) -> p == 0 ? FIELD_TYPE_ERROR_STRING : QUERY_TYPE_ERROR_STRING) - ) - ); - } - - private static String errorMessageStringForMatch( - boolean includeOrdinal, - List> validPerPosition, - List types, - PositionalErrorMessageSupplier positionalErrorMessageSupplier - ) { - for (int i = 0; i < types.size(); i++) { - // Need to check for nulls and bad parameters in order - if (types.get(i) == DataType.NULL) { - return TypeResolutions.ParamOrdinal.fromIndex(i).name().toLowerCase(Locale.ROOT) - + " argument of [] cannot be null, received [null]"; - } - if (validPerPosition.get(i).contains(types.get(i)) == false) { - break; - } - } - - try { - return typeErrorMessage(includeOrdinal, validPerPosition, types, positionalErrorMessageSupplier); - } catch (IllegalStateException e) { - // This means all the positional args were okay, so the expected error is for nulls or from the combination - return EsqlBinaryComparison.formatIncompatibleTypesMessage(types.get(0), types.get(1), ""); - } + return parameterSuppliersFromTypedData(suppliers); } private static void addNonNumericCases(List suppliers) { @@ -410,10 +370,6 @@ private static void addStringTestCases(List suppliers) { public final void testLiteralExpressions() { Expression expression = buildLiteralExpression(testCase); - if (testCase.getExpectedTypeError() != null) { - assertTypeResolutionFailure(expression); - return; - } assertFalse("expected resolved", expression.typeResolved().unresolved()); } diff --git a/x-pack/plugin/esql/src/test/java/org/elasticsearch/xpack/esql/expression/function/fulltext/NoneFieldFullTextFunctionTestCase.java b/x-pack/plugin/esql/src/test/java/org/elasticsearch/xpack/esql/expression/function/fulltext/NoneFieldFullTextFunctionTestCase.java index 383cb8671053d..d528ee0a92de2 100644 --- a/x-pack/plugin/esql/src/test/java/org/elasticsearch/xpack/esql/expression/function/fulltext/NoneFieldFullTextFunctionTestCase.java +++ b/x-pack/plugin/esql/src/test/java/org/elasticsearch/xpack/esql/expression/function/fulltext/NoneFieldFullTextFunctionTestCase.java @@ -28,10 +28,6 @@ public NoneFieldFullTextFunctionTestCase(Supplier tes public final void testFold() { Expression expression = buildLiteralExpression(testCase); - if (testCase.getExpectedTypeError() != null) { - assertTypeResolutionFailure(expression); - return; - } assertFalse("expected resolved", expression.typeResolved().unresolved()); } @@ -46,9 +42,7 @@ protected static Iterable generateParameters() { ) ); } - List errorsSuppliers = errorsForCasesWithoutExamples(suppliers, (v, p) -> "string"); - // Don't test null, as it is not allowed but the expected message is not a type error - so we check it separately in VerifierTests - return parameterSuppliersFromTypedData(errorsSuppliers.stream().filter(s -> s.types().contains(DataType.NULL) == false).toList()); + return parameterSuppliersFromTypedData(suppliers); } private static TestCaseSupplier.TestCase testCase(DataType strType, String str, Matcher matcher) { diff --git a/x-pack/plugin/esql/src/test/java/org/elasticsearch/xpack/esql/expression/function/fulltext/QueryStringErrorTests.java b/x-pack/plugin/esql/src/test/java/org/elasticsearch/xpack/esql/expression/function/fulltext/QueryStringErrorTests.java new file mode 100644 index 0000000000000..b55543a0433c3 --- /dev/null +++ b/x-pack/plugin/esql/src/test/java/org/elasticsearch/xpack/esql/expression/function/fulltext/QueryStringErrorTests.java @@ -0,0 +1,44 @@ +/* + * 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.fulltext; + +import org.elasticsearch.xpack.esql.core.expression.Expression; +import org.elasticsearch.xpack.esql.core.tree.Source; +import org.elasticsearch.xpack.esql.core.type.DataType; +import org.elasticsearch.xpack.esql.expression.function.ErrorsForCasesWithoutExamplesTestCase; +import org.elasticsearch.xpack.esql.expression.function.TestCaseSupplier; +import org.hamcrest.Matcher; + +import java.util.List; +import java.util.Set; +import java.util.stream.Stream; + +import static org.hamcrest.Matchers.equalTo; + +public class QueryStringErrorTests extends ErrorsForCasesWithoutExamplesTestCase { + @Override + protected List cases() { + return paramsToSuppliers(QueryStringTests.parameters()); + } + + @Override + protected Stream> testCandidates(List cases, Set> valid) { + // Don't test null, as it is not allowed but the expected message is not a type error - so we check it separately in VerifierTests + return super.testCandidates(cases, valid).filter(sig -> false == sig.contains(DataType.NULL)); + } + + @Override + protected Expression build(Source source, List args) { + return new QueryString(source, args.get(0)); + } + + @Override + protected Matcher expectedTypeErrorMatcher(List> validPerPosition, List signature) { + return equalTo(typeErrorMessage(false, validPerPosition, signature, (v, p) -> "string")); + } +} diff --git a/x-pack/plugin/esql/src/test/java/org/elasticsearch/xpack/esql/expression/function/fulltext/TermErrorTests.java b/x-pack/plugin/esql/src/test/java/org/elasticsearch/xpack/esql/expression/function/fulltext/TermErrorTests.java new file mode 100644 index 0000000000000..a00858e8e1e43 --- /dev/null +++ b/x-pack/plugin/esql/src/test/java/org/elasticsearch/xpack/esql/expression/function/fulltext/TermErrorTests.java @@ -0,0 +1,44 @@ +/* + * 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.fulltext; + +import org.elasticsearch.xpack.esql.core.expression.Expression; +import org.elasticsearch.xpack.esql.core.tree.Source; +import org.elasticsearch.xpack.esql.core.type.DataType; +import org.elasticsearch.xpack.esql.expression.function.ErrorsForCasesWithoutExamplesTestCase; +import org.elasticsearch.xpack.esql.expression.function.TestCaseSupplier; +import org.hamcrest.Matcher; + +import java.util.List; +import java.util.Set; +import java.util.stream.Stream; + +import static org.hamcrest.Matchers.equalTo; + +public class TermErrorTests extends ErrorsForCasesWithoutExamplesTestCase { + @Override + protected List cases() { + return paramsToSuppliers(TermTests.parameters()); + } + + @Override + protected Stream> testCandidates(List cases, Set> valid) { + // Don't test null, as it is not allowed but the expected message is not a type error - so we check it separately in VerifierTests + return super.testCandidates(cases, valid).filter(sig -> false == sig.contains(DataType.NULL)); + } + + @Override + protected Expression build(Source source, List args) { + return new Term(source, args.get(0), args.get(1)); + } + + @Override + protected Matcher expectedTypeErrorMatcher(List> validPerPosition, List signature) { + return equalTo(typeErrorMessage(true, validPerPosition, signature, (v, p) -> "string")); + } +} diff --git a/x-pack/plugin/esql/src/test/java/org/elasticsearch/xpack/esql/expression/function/fulltext/TermTests.java b/x-pack/plugin/esql/src/test/java/org/elasticsearch/xpack/esql/expression/function/fulltext/TermTests.java index c1c0dc26880ab..d1df0ed09b28e 100644 --- a/x-pack/plugin/esql/src/test/java/org/elasticsearch/xpack/esql/expression/function/fulltext/TermTests.java +++ b/x-pack/plugin/esql/src/test/java/org/elasticsearch/xpack/esql/expression/function/fulltext/TermTests.java @@ -45,12 +45,7 @@ public static Iterable parameters() { } } - List suppliersWithErrors = errorsForCasesWithoutExamples(suppliers, (v, p) -> "string"); - - // Don't test null, as it is not allowed but the expected message is not a type error - so we check it separately in VerifierTests - return parameterSuppliersFromTypedData( - suppliersWithErrors.stream().filter(s -> s.types().contains(DataType.NULL) == false).toList() - ); + return parameterSuppliersFromTypedData(suppliers); } protected static List> supportedParams() { diff --git a/x-pack/plugin/esql/src/test/java/org/elasticsearch/xpack/esql/expression/function/scalar/conditional/CaseTests.java b/x-pack/plugin/esql/src/test/java/org/elasticsearch/xpack/esql/expression/function/scalar/conditional/CaseTests.java index 05923246520fc..eb06ef348f715 100644 --- a/x-pack/plugin/esql/src/test/java/org/elasticsearch/xpack/esql/expression/function/scalar/conditional/CaseTests.java +++ b/x-pack/plugin/esql/src/test/java/org/elasticsearch/xpack/esql/expression/function/scalar/conditional/CaseTests.java @@ -769,10 +769,6 @@ public String toString() { } public void testFancyFolding() { - if (testCase.getExpectedTypeError() != null) { - // Nothing to do - return; - } Expression e = buildFieldExpression(testCase); if (extra().foldable == false) { assertThat(e.foldable(), equalTo(false)); @@ -793,7 +789,7 @@ public void testFancyFolding() { } public void testPartialFold() { - if (testCase.getExpectedTypeError() != null || extra().foldable()) { + if (extra().foldable()) { // Nothing to do return; } diff --git a/x-pack/plugin/esql/src/test/java/org/elasticsearch/xpack/esql/expression/function/scalar/multivalue/MvAppendErrorTests.java b/x-pack/plugin/esql/src/test/java/org/elasticsearch/xpack/esql/expression/function/scalar/multivalue/MvAppendErrorTests.java new file mode 100644 index 0000000000000..df9ab4764c879 --- /dev/null +++ b/x-pack/plugin/esql/src/test/java/org/elasticsearch/xpack/esql/expression/function/scalar/multivalue/MvAppendErrorTests.java @@ -0,0 +1,45 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +package org.elasticsearch.xpack.esql.expression.function.scalar.multivalue; + +import org.elasticsearch.xpack.esql.core.expression.Expression; +import org.elasticsearch.xpack.esql.core.tree.Source; +import org.elasticsearch.xpack.esql.core.type.DataType; +import org.elasticsearch.xpack.esql.expression.function.ErrorsForCasesWithoutExamplesTestCase; +import org.elasticsearch.xpack.esql.expression.function.TestCaseSupplier; +import org.hamcrest.Matcher; + +import java.util.List; +import java.util.Set; + +import static org.hamcrest.Matchers.equalTo; + +public class MvAppendErrorTests extends ErrorsForCasesWithoutExamplesTestCase { + @Override + protected List cases() { + return paramsToSuppliers(MvAppendTests.parameters()); + } + + @Override + protected Expression build(Source source, List args) { + return new MvAppend(source, args.get(0), args.get(1)); + } + + @Override + protected Matcher expectedTypeErrorMatcher(List> validPerPosition, List signature) { + return equalTo( + "second argument of [" + + sourceForSignature(signature) + + "] must be [" + + signature.get(0).noText().typeName() + + "], found value [] type [" + + signature.get(1).typeName() + + "]" + ); + } +} diff --git a/x-pack/plugin/esql/src/test/java/org/elasticsearch/xpack/esql/expression/function/scalar/multivalue/MvAppendTests.java b/x-pack/plugin/esql/src/test/java/org/elasticsearch/xpack/esql/expression/function/scalar/multivalue/MvAppendTests.java index 33733d5e70c61..ca0b997fe0a4f 100644 --- a/x-pack/plugin/esql/src/test/java/org/elasticsearch/xpack/esql/expression/function/scalar/multivalue/MvAppendTests.java +++ b/x-pack/plugin/esql/src/test/java/org/elasticsearch/xpack/esql/expression/function/scalar/multivalue/MvAppendTests.java @@ -13,15 +13,18 @@ import org.apache.lucene.util.BytesRef; import org.elasticsearch.geo.GeometryTestUtils; import org.elasticsearch.geo.ShapeTestUtils; +import org.elasticsearch.test.ESTestCase; import org.elasticsearch.xpack.esql.core.expression.Expression; import org.elasticsearch.xpack.esql.core.tree.Source; import org.elasticsearch.xpack.esql.core.type.DataType; +import org.elasticsearch.xpack.esql.core.util.NumericUtils; import org.elasticsearch.xpack.esql.expression.function.AbstractScalarFunctionTestCase; import org.elasticsearch.xpack.esql.expression.function.TestCaseSupplier; import java.util.ArrayList; import java.util.List; import java.util.function.Supplier; +import java.util.stream.Stream; import static org.elasticsearch.xpack.esql.EsqlTestUtils.randomLiteral; import static org.elasticsearch.xpack.esql.core.util.SpatialCoordinateTypes.CARTESIAN; @@ -41,8 +44,7 @@ public static Iterable parameters() { longs(suppliers); doubles(suppliers); bytesRefs(suppliers); - nulls(suppliers); - return parameterSuppliersFromTypedData(suppliers); + return parameterSuppliersFromTypedData(anyNullIsNull(true, suppliers)); } @Override @@ -102,7 +104,20 @@ private static void longs(List suppliers) { equalTo(result) ); })); - + suppliers.add(new TestCaseSupplier(List.of(DataType.UNSIGNED_LONG, DataType.UNSIGNED_LONG), () -> { + List field1 = randomList(1, 10, ESTestCase::randomLong); + List field2 = randomList(1, 10, ESTestCase::randomLong); + var result = Stream.concat(field1.stream(), field2.stream()).map(NumericUtils::unsignedLongAsBigInteger).toList(); + return new TestCaseSupplier.TestCase( + List.of( + new TestCaseSupplier.TypedData(field1, DataType.UNSIGNED_LONG, "field1"), + new TestCaseSupplier.TypedData(field2, DataType.UNSIGNED_LONG, "field2") + ), + "MvAppendLongEvaluator[field1=Attribute[channel=0], field2=Attribute[channel=1]]", + DataType.UNSIGNED_LONG, + equalTo(result) + ); + })); suppliers.add(new TestCaseSupplier(List.of(DataType.DATETIME, DataType.DATETIME), () -> { List field1 = randomList(1, 10, () -> randomLong()); List field2 = randomList(1, 10, () -> randomLong()); @@ -118,6 +133,21 @@ private static void longs(List suppliers) { equalTo(result) ); })); + suppliers.add(new TestCaseSupplier(List.of(DataType.DATE_NANOS, DataType.DATE_NANOS), () -> { + List field1 = randomList(1, 10, () -> randomNonNegativeLong()); + List field2 = randomList(1, 10, () -> randomNonNegativeLong()); + var result = new ArrayList<>(field1); + result.addAll(field2); + return new TestCaseSupplier.TestCase( + List.of( + new TestCaseSupplier.TypedData(field1, DataType.DATE_NANOS, "field1"), + new TestCaseSupplier.TypedData(field2, DataType.DATE_NANOS, "field2") + ), + "MvAppendLongEvaluator[field1=Attribute[channel=0], field2=Attribute[channel=1]]", + DataType.DATE_NANOS, + equalTo(result) + ); + })); } private static void doubles(List suppliers) { @@ -139,54 +169,25 @@ private static void doubles(List suppliers) { } private static void bytesRefs(List suppliers) { - suppliers.add(new TestCaseSupplier(List.of(DataType.KEYWORD, DataType.KEYWORD), () -> { - List field1 = randomList(1, 10, () -> randomLiteral(DataType.KEYWORD).value()); - List field2 = randomList(1, 10, () -> randomLiteral(DataType.KEYWORD).value()); - var result = new ArrayList<>(field1); - result.addAll(field2); - return new TestCaseSupplier.TestCase( - List.of( - new TestCaseSupplier.TypedData(field1, DataType.KEYWORD, "field1"), - new TestCaseSupplier.TypedData(field2, DataType.KEYWORD, "field2") - ), - "MvAppendBytesRefEvaluator[field1=Attribute[channel=0], field2=Attribute[channel=1]]", - DataType.KEYWORD, - equalTo(result) - ); - })); - - suppliers.add(new TestCaseSupplier(List.of(DataType.TEXT, DataType.TEXT), () -> { - List field1 = randomList(1, 10, () -> randomLiteral(DataType.TEXT).value()); - List field2 = randomList(1, 10, () -> randomLiteral(DataType.TEXT).value()); - var result = new ArrayList<>(field1); - result.addAll(field2); - return new TestCaseSupplier.TestCase( - List.of( - new TestCaseSupplier.TypedData(field1, DataType.TEXT, "field1"), - new TestCaseSupplier.TypedData(field2, DataType.TEXT, "field2") - ), - "MvAppendBytesRefEvaluator[field1=Attribute[channel=0], field2=Attribute[channel=1]]", - DataType.TEXT, - equalTo(result) - ); - })); - - suppliers.add(new TestCaseSupplier(List.of(DataType.SEMANTIC_TEXT, DataType.SEMANTIC_TEXT), () -> { - List field1 = randomList(1, 10, () -> randomLiteral(DataType.SEMANTIC_TEXT).value()); - List field2 = randomList(1, 10, () -> randomLiteral(DataType.SEMANTIC_TEXT).value()); - var result = new ArrayList<>(field1); - result.addAll(field2); - return new TestCaseSupplier.TestCase( - List.of( - new TestCaseSupplier.TypedData(field1, DataType.SEMANTIC_TEXT, "field1"), - new TestCaseSupplier.TypedData(field2, DataType.SEMANTIC_TEXT, "field2") - ), - "MvAppendBytesRefEvaluator[field1=Attribute[channel=0], field2=Attribute[channel=1]]", - DataType.SEMANTIC_TEXT, - equalTo(result) - ); - })); - + for (DataType lhs : new DataType[] { DataType.KEYWORD, DataType.TEXT, DataType.SEMANTIC_TEXT }) { + for (DataType rhs : new DataType[] { DataType.KEYWORD, DataType.TEXT, DataType.SEMANTIC_TEXT }) { + suppliers.add(new TestCaseSupplier(List.of(lhs, rhs), () -> { + List field1 = randomList(1, 10, () -> randomLiteral(lhs).value()); + List field2 = randomList(1, 10, () -> randomLiteral(rhs).value()); + var result = new ArrayList<>(field1); + result.addAll(field2); + return new TestCaseSupplier.TestCase( + List.of( + new TestCaseSupplier.TypedData(field1, lhs, "field1"), + new TestCaseSupplier.TypedData(field2, rhs, "field2") + ), + "MvAppendBytesRefEvaluator[field1=Attribute[channel=0], field2=Attribute[channel=1]]", + DataType.KEYWORD, + equalTo(result) + ); + })); + } + } suppliers.add(new TestCaseSupplier(List.of(DataType.IP, DataType.IP), () -> { List field1 = randomList(1, 10, () -> randomLiteral(DataType.IP).value()); List field2 = randomList(1, 10, () -> randomLiteral(DataType.IP).value()); @@ -283,31 +284,4 @@ private static void bytesRefs(List suppliers) { ); })); } - - private static void nulls(List suppliers) { - suppliers.add(new TestCaseSupplier(List.of(DataType.INTEGER, DataType.INTEGER), () -> { - List field2 = randomList(2, 10, () -> randomInt()); - return new TestCaseSupplier.TestCase( - List.of( - new TestCaseSupplier.TypedData(null, DataType.INTEGER, "field1"), - new TestCaseSupplier.TypedData(field2, DataType.INTEGER, "field2") - ), - "MvAppendIntEvaluator[field1=Attribute[channel=0], field2=Attribute[channel=1]]", - DataType.INTEGER, - equalTo(null) - ); - })); - suppliers.add(new TestCaseSupplier(List.of(DataType.INTEGER, DataType.INTEGER), () -> { - List field1 = randomList(2, 10, () -> randomInt()); - return new TestCaseSupplier.TestCase( - List.of( - new TestCaseSupplier.TypedData(field1, DataType.INTEGER, "field1"), - new TestCaseSupplier.TypedData(null, DataType.INTEGER, "field2") - ), - "MvAppendIntEvaluator[field1=Attribute[channel=0], field2=Attribute[channel=1]]", - DataType.INTEGER, - equalTo(null) - ); - })); - } } diff --git a/x-pack/plugin/esql/src/test/java/org/elasticsearch/xpack/esql/expression/function/scalar/multivalue/MvAvgErrorTests.java b/x-pack/plugin/esql/src/test/java/org/elasticsearch/xpack/esql/expression/function/scalar/multivalue/MvAvgErrorTests.java new file mode 100644 index 0000000000000..9a9a0796aadcf --- /dev/null +++ b/x-pack/plugin/esql/src/test/java/org/elasticsearch/xpack/esql/expression/function/scalar/multivalue/MvAvgErrorTests.java @@ -0,0 +1,37 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +package org.elasticsearch.xpack.esql.expression.function.scalar.multivalue; + +import org.elasticsearch.xpack.esql.core.expression.Expression; +import org.elasticsearch.xpack.esql.core.tree.Source; +import org.elasticsearch.xpack.esql.core.type.DataType; +import org.elasticsearch.xpack.esql.expression.function.ErrorsForCasesWithoutExamplesTestCase; +import org.elasticsearch.xpack.esql.expression.function.TestCaseSupplier; +import org.hamcrest.Matcher; + +import java.util.List; +import java.util.Set; + +import static org.hamcrest.Matchers.equalTo; + +public class MvAvgErrorTests extends ErrorsForCasesWithoutExamplesTestCase { + @Override + protected List cases() { + return paramsToSuppliers(MvAvgTests.parameters()); + } + + @Override + protected Expression build(Source source, List args) { + return new MvAvg(source, args.get(0)); + } + + @Override + protected Matcher expectedTypeErrorMatcher(List> validPerPosition, List signature) { + return equalTo(typeErrorMessage(false, validPerPosition, signature, (v, p) -> "numeric")); + } +} diff --git a/x-pack/plugin/esql/src/test/java/org/elasticsearch/xpack/esql/expression/function/scalar/multivalue/MvAvgTests.java b/x-pack/plugin/esql/src/test/java/org/elasticsearch/xpack/esql/expression/function/scalar/multivalue/MvAvgTests.java index af046a5f39d81..702e48c44fa6a 100644 --- a/x-pack/plugin/esql/src/test/java/org/elasticsearch/xpack/esql/expression/function/scalar/multivalue/MvAvgTests.java +++ b/x-pack/plugin/esql/src/test/java/org/elasticsearch/xpack/esql/expression/function/scalar/multivalue/MvAvgTests.java @@ -55,7 +55,7 @@ public static Iterable parameters() { */ (size, data) -> avg.apply(size, data.mapToDouble(v -> unsignedLongToDouble(NumericUtils.asLongUnsigned(v)))) ); - return parameterSuppliersFromTypedDataWithDefaultChecks(true, cases, (v, p) -> "numeric"); + return parameterSuppliersFromTypedDataWithDefaultChecksNoErrors(true, cases); } @Override diff --git a/x-pack/plugin/esql/src/test/java/org/elasticsearch/xpack/esql/expression/function/scalar/multivalue/MvConcatErrorTests.java b/x-pack/plugin/esql/src/test/java/org/elasticsearch/xpack/esql/expression/function/scalar/multivalue/MvConcatErrorTests.java new file mode 100644 index 0000000000000..38022c2c08be6 --- /dev/null +++ b/x-pack/plugin/esql/src/test/java/org/elasticsearch/xpack/esql/expression/function/scalar/multivalue/MvConcatErrorTests.java @@ -0,0 +1,37 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +package org.elasticsearch.xpack.esql.expression.function.scalar.multivalue; + +import org.elasticsearch.xpack.esql.core.expression.Expression; +import org.elasticsearch.xpack.esql.core.tree.Source; +import org.elasticsearch.xpack.esql.core.type.DataType; +import org.elasticsearch.xpack.esql.expression.function.ErrorsForCasesWithoutExamplesTestCase; +import org.elasticsearch.xpack.esql.expression.function.TestCaseSupplier; +import org.hamcrest.Matcher; + +import java.util.List; +import java.util.Set; + +import static org.hamcrest.Matchers.equalTo; + +public class MvConcatErrorTests extends ErrorsForCasesWithoutExamplesTestCase { + @Override + protected List cases() { + return paramsToSuppliers(MvConcatTests.parameters()); + } + + @Override + protected Expression build(Source source, List args) { + return new MvConcat(source, args.get(0), args.get(1)); + } + + @Override + protected Matcher expectedTypeErrorMatcher(List> validPerPosition, List signature) { + return equalTo(typeErrorMessage(true, validPerPosition, signature, (v, p) -> "string")); + } +} diff --git a/x-pack/plugin/esql/src/test/java/org/elasticsearch/xpack/esql/expression/function/scalar/multivalue/MvConcatTests.java b/x-pack/plugin/esql/src/test/java/org/elasticsearch/xpack/esql/expression/function/scalar/multivalue/MvConcatTests.java index 4467b49cd674a..1fd33c2403ca6 100644 --- a/x-pack/plugin/esql/src/test/java/org/elasticsearch/xpack/esql/expression/function/scalar/multivalue/MvConcatTests.java +++ b/x-pack/plugin/esql/src/test/java/org/elasticsearch/xpack/esql/expression/function/scalar/multivalue/MvConcatTests.java @@ -67,7 +67,7 @@ public static Iterable parameters() { } } } - return parameterSuppliersFromTypedDataWithDefaultChecks(false, suppliers, (v, p) -> "string"); + return parameterSuppliersFromTypedDataWithDefaultChecksNoErrors(false, suppliers); } @Override diff --git a/x-pack/plugin/esql/src/test/java/org/elasticsearch/xpack/esql/expression/function/scalar/multivalue/MvCountErrorTests.java b/x-pack/plugin/esql/src/test/java/org/elasticsearch/xpack/esql/expression/function/scalar/multivalue/MvCountErrorTests.java new file mode 100644 index 0000000000000..d59a1aa2eb098 --- /dev/null +++ b/x-pack/plugin/esql/src/test/java/org/elasticsearch/xpack/esql/expression/function/scalar/multivalue/MvCountErrorTests.java @@ -0,0 +1,53 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +package org.elasticsearch.xpack.esql.expression.function.scalar.multivalue; + +import org.elasticsearch.xpack.esql.core.expression.Expression; +import org.elasticsearch.xpack.esql.core.tree.Source; +import org.elasticsearch.xpack.esql.core.type.DataType; +import org.elasticsearch.xpack.esql.expression.function.ErrorsForCasesWithoutExamplesTestCase; +import org.elasticsearch.xpack.esql.expression.function.TestCaseSupplier; +import org.hamcrest.Matcher; + +import java.util.List; +import java.util.Set; + +import static org.hamcrest.Matchers.equalTo; + +public class MvCountErrorTests extends ErrorsForCasesWithoutExamplesTestCase { + @Override + protected List cases() { + return paramsToSuppliers(MvCountTests.parameters()); + } + + @Override + protected Expression build(Source source, List args) { + return new MvCount(source, args.get(0)); + } + + @Override + protected Matcher expectedTypeErrorMatcher(List> validPerPosition, List signature) { + return equalTo(typeErrorMessage(false, validPerPosition, signature, (v, p) -> { + /* + * In general MvCount should support all signatures. While building a + * new type you may we to temporarily remove this. + */ + throw new UnsupportedOperationException("all signatures should be supported"); + })); + } + + @Override + protected void assertNumberOfCheckedSignatures(int checked) { + /* + * In general MvCount should support all signatures. While building a + * new type you may we to temporarily relax this. + */ + assertThat("all signatures should be supported", checked, equalTo(0)); + } + +} diff --git a/x-pack/plugin/esql/src/test/java/org/elasticsearch/xpack/esql/expression/function/scalar/multivalue/MvCountTests.java b/x-pack/plugin/esql/src/test/java/org/elasticsearch/xpack/esql/expression/function/scalar/multivalue/MvCountTests.java index 51b15ead26c56..6aeab0339c172 100644 --- a/x-pack/plugin/esql/src/test/java/org/elasticsearch/xpack/esql/expression/function/scalar/multivalue/MvCountTests.java +++ b/x-pack/plugin/esql/src/test/java/org/elasticsearch/xpack/esql/expression/function/scalar/multivalue/MvCountTests.java @@ -41,7 +41,7 @@ public static Iterable parameters() { cartesianPoints(cases, "mv_count", "MvCount", DataType.INTEGER, (size, values) -> equalTo(Math.toIntExact(values.count()))); geoShape(cases, "mv_count", "MvCount", DataType.INTEGER, (size, values) -> equalTo(Math.toIntExact(values.count()))); cartesianShape(cases, "mv_count", "MvCount", DataType.INTEGER, (size, values) -> equalTo(Math.toIntExact(values.count()))); - return parameterSuppliersFromTypedDataWithDefaultChecks(true, cases, (v, p) -> ""); + return parameterSuppliersFromTypedDataWithDefaultChecksNoErrors(true, cases); } @Override diff --git a/x-pack/plugin/esql/src/test/java/org/elasticsearch/xpack/esql/expression/function/scalar/multivalue/MvDedupeErrorTests.java b/x-pack/plugin/esql/src/test/java/org/elasticsearch/xpack/esql/expression/function/scalar/multivalue/MvDedupeErrorTests.java new file mode 100644 index 0000000000000..55f34d9a72f41 --- /dev/null +++ b/x-pack/plugin/esql/src/test/java/org/elasticsearch/xpack/esql/expression/function/scalar/multivalue/MvDedupeErrorTests.java @@ -0,0 +1,53 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +package org.elasticsearch.xpack.esql.expression.function.scalar.multivalue; + +import org.elasticsearch.xpack.esql.core.expression.Expression; +import org.elasticsearch.xpack.esql.core.tree.Source; +import org.elasticsearch.xpack.esql.core.type.DataType; +import org.elasticsearch.xpack.esql.expression.function.ErrorsForCasesWithoutExamplesTestCase; +import org.elasticsearch.xpack.esql.expression.function.TestCaseSupplier; +import org.hamcrest.Matcher; + +import java.util.List; +import java.util.Set; + +import static org.hamcrest.Matchers.equalTo; + +public class MvDedupeErrorTests extends ErrorsForCasesWithoutExamplesTestCase { + @Override + protected List cases() { + return paramsToSuppliers(MvDedupeTests.parameters()); + } + + @Override + protected Expression build(Source source, List args) { + return new MvDedupe(source, args.get(0)); + } + + @Override + protected Matcher expectedTypeErrorMatcher(List> validPerPosition, List signature) { + return equalTo(typeErrorMessage(false, validPerPosition, signature, (v, p) -> { + /* + * In general MvDedupe should support all signatures. While building a + * new type you may we to temporarily remove this. + */ + throw new UnsupportedOperationException("all signatures should be supported"); + })); + } + + @Override + protected void assertNumberOfCheckedSignatures(int checked) { + /* + * In general MvDedupe should support all signatures. While building a + * new type you may we to temporarily relax this. + */ + assertThat("all signatures should be supported", checked, equalTo(0)); + } + +} diff --git a/x-pack/plugin/esql/src/test/java/org/elasticsearch/xpack/esql/expression/function/scalar/multivalue/MvDedupeTests.java b/x-pack/plugin/esql/src/test/java/org/elasticsearch/xpack/esql/expression/function/scalar/multivalue/MvDedupeTests.java index f3b44274f3ade..24fd0a349796c 100644 --- a/x-pack/plugin/esql/src/test/java/org/elasticsearch/xpack/esql/expression/function/scalar/multivalue/MvDedupeTests.java +++ b/x-pack/plugin/esql/src/test/java/org/elasticsearch/xpack/esql/expression/function/scalar/multivalue/MvDedupeTests.java @@ -47,9 +47,7 @@ public static Iterable parameters() { cartesianShape(cases, "mv_dedupe", "MvDedupe", DataType.CARTESIAN_SHAPE, (size, values) -> getMatcher(values)); geoPoints(cases, "mv_dedupe", "MvDedupe", (size, values) -> getMatcher(values)); geoShape(cases, "mv_dedupe", "MvDedupe", DataType.GEO_SHAPE, (size, values) -> getMatcher(values)); - - // TODO switch extraction to BigInteger so this just works. - // unsignedLongs(cases, "mv_dedupe", "MvDedupe", (size, values) -> getMatcher(values)); + unsignedLongs(cases, "mv_dedupe", "MvDedupe", (size, values) -> getMatcher(values)); return parameterSuppliersFromTypedData(anyNullIsNull(false, cases)); } diff --git a/x-pack/plugin/esql/src/test/java/org/elasticsearch/xpack/esql/expression/function/scalar/multivalue/MvFirstErrorTests.java b/x-pack/plugin/esql/src/test/java/org/elasticsearch/xpack/esql/expression/function/scalar/multivalue/MvFirstErrorTests.java new file mode 100644 index 0000000000000..7ca829a7629c5 --- /dev/null +++ b/x-pack/plugin/esql/src/test/java/org/elasticsearch/xpack/esql/expression/function/scalar/multivalue/MvFirstErrorTests.java @@ -0,0 +1,53 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +package org.elasticsearch.xpack.esql.expression.function.scalar.multivalue; + +import org.elasticsearch.xpack.esql.core.expression.Expression; +import org.elasticsearch.xpack.esql.core.tree.Source; +import org.elasticsearch.xpack.esql.core.type.DataType; +import org.elasticsearch.xpack.esql.expression.function.ErrorsForCasesWithoutExamplesTestCase; +import org.elasticsearch.xpack.esql.expression.function.TestCaseSupplier; +import org.hamcrest.Matcher; + +import java.util.List; +import java.util.Set; + +import static org.hamcrest.Matchers.equalTo; + +public class MvFirstErrorTests extends ErrorsForCasesWithoutExamplesTestCase { + @Override + protected List cases() { + return paramsToSuppliers(MvFirstTests.parameters()); + } + + @Override + protected Expression build(Source source, List args) { + return new MvFirst(source, args.get(0)); + } + + @Override + protected Matcher expectedTypeErrorMatcher(List> validPerPosition, List signature) { + return equalTo(typeErrorMessage(false, validPerPosition, signature, (v, p) -> { + /* + * In general MvFirst should support all signatures. While building a + * new type you may we to temporarily remove this. + */ + throw new UnsupportedOperationException("all signatures should be supported"); + })); + } + + @Override + protected void assertNumberOfCheckedSignatures(int checked) { + /* + * In general MvFirst should support all signatures. While building a + * new type you may we to temporarily relax this. + */ + assertThat("all signatures should be supported", checked, equalTo(0)); + } + +} diff --git a/x-pack/plugin/esql/src/test/java/org/elasticsearch/xpack/esql/expression/function/scalar/multivalue/MvFirstTests.java b/x-pack/plugin/esql/src/test/java/org/elasticsearch/xpack/esql/expression/function/scalar/multivalue/MvFirstTests.java index f6ef06a84ac2d..3ee98364141c6 100644 --- a/x-pack/plugin/esql/src/test/java/org/elasticsearch/xpack/esql/expression/function/scalar/multivalue/MvFirstTests.java +++ b/x-pack/plugin/esql/src/test/java/org/elasticsearch/xpack/esql/expression/function/scalar/multivalue/MvFirstTests.java @@ -42,16 +42,11 @@ public static Iterable parameters() { cartesianPoints(cases, "mv_first", "MvFirst", DataType.CARTESIAN_POINT, (size, values) -> equalTo(values.findFirst().get())); geoShape(cases, "mv_first", "MvFirst", DataType.GEO_SHAPE, (size, values) -> equalTo(values.findFirst().get())); cartesianShape(cases, "mv_first", "MvFirst", DataType.CARTESIAN_SHAPE, (size, values) -> equalTo(values.findFirst().get())); - return parameterSuppliersFromTypedDataWithDefaultChecks(false, cases, (v, p) -> ""); + return parameterSuppliersFromTypedDataWithDefaultChecksNoErrors(false, cases); } @Override protected Expression build(Source source, Expression field) { return new MvFirst(source, field); } - - @Override - protected DataType expectedType(List argTypes) { - return argTypes.get(0); - } } diff --git a/x-pack/plugin/esql/src/test/java/org/elasticsearch/xpack/esql/expression/function/scalar/multivalue/MvLastErrorTests.java b/x-pack/plugin/esql/src/test/java/org/elasticsearch/xpack/esql/expression/function/scalar/multivalue/MvLastErrorTests.java new file mode 100644 index 0000000000000..3db13f0368a88 --- /dev/null +++ b/x-pack/plugin/esql/src/test/java/org/elasticsearch/xpack/esql/expression/function/scalar/multivalue/MvLastErrorTests.java @@ -0,0 +1,52 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +package org.elasticsearch.xpack.esql.expression.function.scalar.multivalue; + +import org.elasticsearch.xpack.esql.core.expression.Expression; +import org.elasticsearch.xpack.esql.core.tree.Source; +import org.elasticsearch.xpack.esql.core.type.DataType; +import org.elasticsearch.xpack.esql.expression.function.ErrorsForCasesWithoutExamplesTestCase; +import org.elasticsearch.xpack.esql.expression.function.TestCaseSupplier; +import org.hamcrest.Matcher; + +import java.util.List; +import java.util.Set; + +import static org.hamcrest.Matchers.equalTo; + +public class MvLastErrorTests extends ErrorsForCasesWithoutExamplesTestCase { + @Override + protected List cases() { + return paramsToSuppliers(MvLastTests.parameters()); + } + + @Override + protected Expression build(Source source, List args) { + return new MvLast(source, args.get(0)); + } + + @Override + protected Matcher expectedTypeErrorMatcher(List> validPerPosition, List signature) { + return equalTo(typeErrorMessage(false, validPerPosition, signature, (v, p) -> { + /* + * In general MvLast should support all signatures. While building a + * new type you may we to temporarily remove this. + */ + throw new UnsupportedOperationException("all signatures should be supported"); + })); + } + + @Override + protected void assertNumberOfCheckedSignatures(int checked) { + /* + * In general MvLast should support all signatures. While building a + * new type you may we to temporarily relax this. + */ + assertThat("all signatures should be supported", checked, equalTo(0)); + } +} diff --git a/x-pack/plugin/esql/src/test/java/org/elasticsearch/xpack/esql/expression/function/scalar/multivalue/MvLastTests.java b/x-pack/plugin/esql/src/test/java/org/elasticsearch/xpack/esql/expression/function/scalar/multivalue/MvLastTests.java index 09e483c3a43ee..a7a13360ce443 100644 --- a/x-pack/plugin/esql/src/test/java/org/elasticsearch/xpack/esql/expression/function/scalar/multivalue/MvLastTests.java +++ b/x-pack/plugin/esql/src/test/java/org/elasticsearch/xpack/esql/expression/function/scalar/multivalue/MvLastTests.java @@ -42,16 +42,11 @@ public static Iterable parameters() { cartesianPoints(cases, "mv_last", "MvLast", DataType.CARTESIAN_POINT, (size, values) -> equalTo(values.reduce((f, s) -> s).get())); geoShape(cases, "mv_last", "MvLast", DataType.GEO_SHAPE, (size, values) -> equalTo(values.reduce((f, s) -> s).get())); cartesianShape(cases, "mv_last", "MvLast", DataType.CARTESIAN_SHAPE, (size, values) -> equalTo(values.reduce((f, s) -> s).get())); - return parameterSuppliersFromTypedDataWithDefaultChecks(false, cases, (v, p) -> "representable"); + return parameterSuppliersFromTypedDataWithDefaultChecksNoErrors(false, cases); } @Override protected Expression build(Source source, Expression field) { return new MvLast(source, field); } - - @Override - protected DataType expectedType(List argTypes) { - return argTypes.get(0); - } } diff --git a/x-pack/plugin/esql/src/test/java/org/elasticsearch/xpack/esql/expression/function/scalar/multivalue/MvMaxErrorTests.java b/x-pack/plugin/esql/src/test/java/org/elasticsearch/xpack/esql/expression/function/scalar/multivalue/MvMaxErrorTests.java new file mode 100644 index 0000000000000..d406b5157a4b5 --- /dev/null +++ b/x-pack/plugin/esql/src/test/java/org/elasticsearch/xpack/esql/expression/function/scalar/multivalue/MvMaxErrorTests.java @@ -0,0 +1,37 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +package org.elasticsearch.xpack.esql.expression.function.scalar.multivalue; + +import org.elasticsearch.xpack.esql.core.expression.Expression; +import org.elasticsearch.xpack.esql.core.tree.Source; +import org.elasticsearch.xpack.esql.core.type.DataType; +import org.elasticsearch.xpack.esql.expression.function.ErrorsForCasesWithoutExamplesTestCase; +import org.elasticsearch.xpack.esql.expression.function.TestCaseSupplier; +import org.hamcrest.Matcher; + +import java.util.List; +import java.util.Set; + +import static org.hamcrest.Matchers.equalTo; + +public class MvMaxErrorTests extends ErrorsForCasesWithoutExamplesTestCase { + @Override + protected List cases() { + return paramsToSuppliers(MvMaxTests.parameters()); + } + + @Override + protected Expression build(Source source, List args) { + return new MvMax(source, args.get(0)); + } + + @Override + protected Matcher expectedTypeErrorMatcher(List> validPerPosition, List signature) { + return equalTo(typeErrorMessage(false, validPerPosition, signature, (v, p) -> "representableNonSpatial")); + } +} diff --git a/x-pack/plugin/esql/src/test/java/org/elasticsearch/xpack/esql/expression/function/scalar/multivalue/MvMaxTests.java b/x-pack/plugin/esql/src/test/java/org/elasticsearch/xpack/esql/expression/function/scalar/multivalue/MvMaxTests.java index a3ad1f2415e20..4e4a615e9f5a0 100644 --- a/x-pack/plugin/esql/src/test/java/org/elasticsearch/xpack/esql/expression/function/scalar/multivalue/MvMaxTests.java +++ b/x-pack/plugin/esql/src/test/java/org/elasticsearch/xpack/esql/expression/function/scalar/multivalue/MvMaxTests.java @@ -39,7 +39,7 @@ public static Iterable parameters() { unsignedLongs(cases, "mv_max", "MvMax", (size, values) -> equalTo(values.reduce(BigInteger::max).get())); dateTimes(cases, "mv_max", "MvMax", (size, values) -> equalTo(values.max().getAsLong())); dateNanos(cases, "mv_max", "MvMax", DataType.DATE_NANOS, (size, values) -> equalTo(values.max().getAsLong())); - return parameterSuppliersFromTypedDataWithDefaultChecks(false, cases, (v, p) -> "representableNonSpatial"); + return parameterSuppliersFromTypedDataWithDefaultChecksNoErrors(false, cases); } @Override diff --git a/x-pack/plugin/esql/src/test/java/org/elasticsearch/xpack/esql/expression/function/scalar/multivalue/MvMedianAbsoluteDeviationErrorTests.java b/x-pack/plugin/esql/src/test/java/org/elasticsearch/xpack/esql/expression/function/scalar/multivalue/MvMedianAbsoluteDeviationErrorTests.java new file mode 100644 index 0000000000000..a6bced5df46f2 --- /dev/null +++ b/x-pack/plugin/esql/src/test/java/org/elasticsearch/xpack/esql/expression/function/scalar/multivalue/MvMedianAbsoluteDeviationErrorTests.java @@ -0,0 +1,37 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +package org.elasticsearch.xpack.esql.expression.function.scalar.multivalue; + +import org.elasticsearch.xpack.esql.core.expression.Expression; +import org.elasticsearch.xpack.esql.core.tree.Source; +import org.elasticsearch.xpack.esql.core.type.DataType; +import org.elasticsearch.xpack.esql.expression.function.ErrorsForCasesWithoutExamplesTestCase; +import org.elasticsearch.xpack.esql.expression.function.TestCaseSupplier; +import org.hamcrest.Matcher; + +import java.util.List; +import java.util.Set; + +import static org.hamcrest.Matchers.equalTo; + +public class MvMedianAbsoluteDeviationErrorTests extends ErrorsForCasesWithoutExamplesTestCase { + @Override + protected List cases() { + return paramsToSuppliers(MvMedianAbsoluteDeviationTests.parameters()); + } + + @Override + protected Expression build(Source source, List args) { + return new MvMedianAbsoluteDeviation(source, args.get(0)); + } + + @Override + protected Matcher expectedTypeErrorMatcher(List> validPerPosition, List signature) { + return equalTo(typeErrorMessage(false, validPerPosition, signature, (v, p) -> "numeric")); + } +} diff --git a/x-pack/plugin/esql/src/test/java/org/elasticsearch/xpack/esql/expression/function/scalar/multivalue/MvMedianAbsoluteDeviationTests.java b/x-pack/plugin/esql/src/test/java/org/elasticsearch/xpack/esql/expression/function/scalar/multivalue/MvMedianAbsoluteDeviationTests.java index b041faf6510a1..3e4c8296497d5 100644 --- a/x-pack/plugin/esql/src/test/java/org/elasticsearch/xpack/esql/expression/function/scalar/multivalue/MvMedianAbsoluteDeviationTests.java +++ b/x-pack/plugin/esql/src/test/java/org/elasticsearch/xpack/esql/expression/function/scalar/multivalue/MvMedianAbsoluteDeviationTests.java @@ -122,7 +122,7 @@ public static Iterable parameters() { ) ); - return parameterSuppliersFromTypedDataWithDefaultChecks(false, cases, (v, p) -> "numeric"); + return parameterSuppliersFromTypedDataWithDefaultChecksNoErrors(false, cases); } /** diff --git a/x-pack/plugin/esql/src/test/java/org/elasticsearch/xpack/esql/expression/function/scalar/multivalue/MvMedianErrorTests.java b/x-pack/plugin/esql/src/test/java/org/elasticsearch/xpack/esql/expression/function/scalar/multivalue/MvMedianErrorTests.java new file mode 100644 index 0000000000000..734a240ebe6d3 --- /dev/null +++ b/x-pack/plugin/esql/src/test/java/org/elasticsearch/xpack/esql/expression/function/scalar/multivalue/MvMedianErrorTests.java @@ -0,0 +1,37 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +package org.elasticsearch.xpack.esql.expression.function.scalar.multivalue; + +import org.elasticsearch.xpack.esql.core.expression.Expression; +import org.elasticsearch.xpack.esql.core.tree.Source; +import org.elasticsearch.xpack.esql.core.type.DataType; +import org.elasticsearch.xpack.esql.expression.function.ErrorsForCasesWithoutExamplesTestCase; +import org.elasticsearch.xpack.esql.expression.function.TestCaseSupplier; +import org.hamcrest.Matcher; + +import java.util.List; +import java.util.Set; + +import static org.hamcrest.Matchers.equalTo; + +public class MvMedianErrorTests extends ErrorsForCasesWithoutExamplesTestCase { + @Override + protected List cases() { + return paramsToSuppliers(MvMedianTests.parameters()); + } + + @Override + protected Expression build(Source source, List args) { + return new MvMedian(source, args.get(0)); + } + + @Override + protected Matcher expectedTypeErrorMatcher(List> validPerPosition, List signature) { + return equalTo(typeErrorMessage(false, validPerPosition, signature, (v, p) -> "numeric")); + } +} diff --git a/x-pack/plugin/esql/src/test/java/org/elasticsearch/xpack/esql/expression/function/scalar/multivalue/MvMedianTests.java b/x-pack/plugin/esql/src/test/java/org/elasticsearch/xpack/esql/expression/function/scalar/multivalue/MvMedianTests.java index 002aa77946bcf..c1435136eed8b 100644 --- a/x-pack/plugin/esql/src/test/java/org/elasticsearch/xpack/esql/expression/function/scalar/multivalue/MvMedianTests.java +++ b/x-pack/plugin/esql/src/test/java/org/elasticsearch/xpack/esql/expression/function/scalar/multivalue/MvMedianTests.java @@ -92,7 +92,7 @@ public static Iterable parameters() { ) ) ); - return parameterSuppliersFromTypedDataWithDefaultChecks(false, cases, (v, p) -> "numeric"); + return parameterSuppliersFromTypedDataWithDefaultChecksNoErrors(false, cases); } @Override diff --git a/x-pack/plugin/esql/src/test/java/org/elasticsearch/xpack/esql/expression/function/scalar/multivalue/MvMinErrorTests.java b/x-pack/plugin/esql/src/test/java/org/elasticsearch/xpack/esql/expression/function/scalar/multivalue/MvMinErrorTests.java new file mode 100644 index 0000000000000..6155c3f987f06 --- /dev/null +++ b/x-pack/plugin/esql/src/test/java/org/elasticsearch/xpack/esql/expression/function/scalar/multivalue/MvMinErrorTests.java @@ -0,0 +1,37 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +package org.elasticsearch.xpack.esql.expression.function.scalar.multivalue; + +import org.elasticsearch.xpack.esql.core.expression.Expression; +import org.elasticsearch.xpack.esql.core.tree.Source; +import org.elasticsearch.xpack.esql.core.type.DataType; +import org.elasticsearch.xpack.esql.expression.function.ErrorsForCasesWithoutExamplesTestCase; +import org.elasticsearch.xpack.esql.expression.function.TestCaseSupplier; +import org.hamcrest.Matcher; + +import java.util.List; +import java.util.Set; + +import static org.hamcrest.Matchers.equalTo; + +public class MvMinErrorTests extends ErrorsForCasesWithoutExamplesTestCase { + @Override + protected List cases() { + return paramsToSuppliers(MvMinTests.parameters()); + } + + @Override + protected Expression build(Source source, List args) { + return new MvMin(source, args.get(0)); + } + + @Override + protected Matcher expectedTypeErrorMatcher(List> validPerPosition, List signature) { + return equalTo(typeErrorMessage(false, validPerPosition, signature, (v, p) -> "representableNonSpatial")); + } +} diff --git a/x-pack/plugin/esql/src/test/java/org/elasticsearch/xpack/esql/expression/function/scalar/multivalue/MvMinTests.java b/x-pack/plugin/esql/src/test/java/org/elasticsearch/xpack/esql/expression/function/scalar/multivalue/MvMinTests.java index a4d5a4004b840..f958112b93597 100644 --- a/x-pack/plugin/esql/src/test/java/org/elasticsearch/xpack/esql/expression/function/scalar/multivalue/MvMinTests.java +++ b/x-pack/plugin/esql/src/test/java/org/elasticsearch/xpack/esql/expression/function/scalar/multivalue/MvMinTests.java @@ -39,7 +39,7 @@ public static Iterable parameters() { unsignedLongs(cases, "mv_min", "MvMin", (size, values) -> equalTo(values.reduce(BigInteger::min).get())); dateTimes(cases, "mv_min", "MvMin", (size, values) -> equalTo(values.min().getAsLong())); dateNanos(cases, "mv_min", "MvMin", DataType.DATE_NANOS, (size, values) -> equalTo(values.min().getAsLong())); - return parameterSuppliersFromTypedDataWithDefaultChecks(false, cases, (v, p) -> "representableNonSpatial"); + return parameterSuppliersFromTypedDataWithDefaultChecksNoErrors(false, cases); } @Override diff --git a/x-pack/plugin/esql/src/test/java/org/elasticsearch/xpack/esql/expression/function/scalar/multivalue/MvPSeriesWeightedSumErrorTests.java b/x-pack/plugin/esql/src/test/java/org/elasticsearch/xpack/esql/expression/function/scalar/multivalue/MvPSeriesWeightedSumErrorTests.java new file mode 100644 index 0000000000000..4f1f8f911c306 --- /dev/null +++ b/x-pack/plugin/esql/src/test/java/org/elasticsearch/xpack/esql/expression/function/scalar/multivalue/MvPSeriesWeightedSumErrorTests.java @@ -0,0 +1,37 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +package org.elasticsearch.xpack.esql.expression.function.scalar.multivalue; + +import org.elasticsearch.xpack.esql.core.expression.Expression; +import org.elasticsearch.xpack.esql.core.tree.Source; +import org.elasticsearch.xpack.esql.core.type.DataType; +import org.elasticsearch.xpack.esql.expression.function.ErrorsForCasesWithoutExamplesTestCase; +import org.elasticsearch.xpack.esql.expression.function.TestCaseSupplier; +import org.hamcrest.Matcher; + +import java.util.List; +import java.util.Set; + +import static org.hamcrest.Matchers.equalTo; + +public class MvPSeriesWeightedSumErrorTests extends ErrorsForCasesWithoutExamplesTestCase { + @Override + protected List cases() { + return paramsToSuppliers(MvPSeriesWeightedSumTests.parameters()); + } + + @Override + protected Expression build(Source source, List args) { + return new MvPSeriesWeightedSum(source, args.get(0), args.get(1)); + } + + @Override + protected Matcher expectedTypeErrorMatcher(List> validPerPosition, List signature) { + return equalTo(typeErrorMessage(true, validPerPosition, signature, (v, p) -> "double")); + } +} diff --git a/x-pack/plugin/esql/src/test/java/org/elasticsearch/xpack/esql/expression/function/scalar/multivalue/MvPSeriesWeightedSumTests.java b/x-pack/plugin/esql/src/test/java/org/elasticsearch/xpack/esql/expression/function/scalar/multivalue/MvPSeriesWeightedSumTests.java index 156fc4bfe7c36..9b70b1aaa5bb7 100644 --- a/x-pack/plugin/esql/src/test/java/org/elasticsearch/xpack/esql/expression/function/scalar/multivalue/MvPSeriesWeightedSumTests.java +++ b/x-pack/plugin/esql/src/test/java/org/elasticsearch/xpack/esql/expression/function/scalar/multivalue/MvPSeriesWeightedSumTests.java @@ -45,8 +45,6 @@ public static Iterable parameters() { return nullData.type() == DataType.NULL ? equalTo("LiteralsEvaluator[lit=null]") : original; } ); - cases = errorsForCasesWithoutExamples(cases, (valid, position) -> "double"); - return parameterSuppliersFromTypedData(cases); } diff --git a/x-pack/plugin/esql/src/test/java/org/elasticsearch/xpack/esql/expression/function/scalar/multivalue/MvPercentileErrorTests.java b/x-pack/plugin/esql/src/test/java/org/elasticsearch/xpack/esql/expression/function/scalar/multivalue/MvPercentileErrorTests.java new file mode 100644 index 0000000000000..25e7100b7c418 --- /dev/null +++ b/x-pack/plugin/esql/src/test/java/org/elasticsearch/xpack/esql/expression/function/scalar/multivalue/MvPercentileErrorTests.java @@ -0,0 +1,37 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +package org.elasticsearch.xpack.esql.expression.function.scalar.multivalue; + +import org.elasticsearch.xpack.esql.core.expression.Expression; +import org.elasticsearch.xpack.esql.core.tree.Source; +import org.elasticsearch.xpack.esql.core.type.DataType; +import org.elasticsearch.xpack.esql.expression.function.ErrorsForCasesWithoutExamplesTestCase; +import org.elasticsearch.xpack.esql.expression.function.TestCaseSupplier; +import org.hamcrest.Matcher; + +import java.util.List; +import java.util.Set; + +import static org.hamcrest.Matchers.equalTo; + +public class MvPercentileErrorTests extends ErrorsForCasesWithoutExamplesTestCase { + @Override + protected List cases() { + return paramsToSuppliers(MvPercentileTests.parameters()); + } + + @Override + protected Expression build(Source source, List args) { + return new MvPercentile(source, args.get(0), args.get(1)); + } + + @Override + protected Matcher expectedTypeErrorMatcher(List> validPerPosition, List signature) { + return equalTo(typeErrorMessage(true, validPerPosition, signature, (v, p) -> "numeric except unsigned_long")); + } +} diff --git a/x-pack/plugin/esql/src/test/java/org/elasticsearch/xpack/esql/expression/function/scalar/multivalue/MvPercentileTests.java b/x-pack/plugin/esql/src/test/java/org/elasticsearch/xpack/esql/expression/function/scalar/multivalue/MvPercentileTests.java index 0a419d44e3448..31bba744c16ef 100644 --- a/x-pack/plugin/esql/src/test/java/org/elasticsearch/xpack/esql/expression/function/scalar/multivalue/MvPercentileTests.java +++ b/x-pack/plugin/esql/src/test/java/org/elasticsearch/xpack/esql/expression/function/scalar/multivalue/MvPercentileTests.java @@ -357,8 +357,7 @@ public static Iterable parameters() { ? DataType.NULL : original.expectedType(), (nullPosition, nullData, original) -> original, - cases, - (v, p) -> "numeric except unsigned_long" + cases ); } diff --git a/x-pack/plugin/esql/src/test/java/org/elasticsearch/xpack/esql/expression/function/scalar/multivalue/MvSliceErrorTests.java b/x-pack/plugin/esql/src/test/java/org/elasticsearch/xpack/esql/expression/function/scalar/multivalue/MvSliceErrorTests.java new file mode 100644 index 0000000000000..83d0e4fcf3d75 --- /dev/null +++ b/x-pack/plugin/esql/src/test/java/org/elasticsearch/xpack/esql/expression/function/scalar/multivalue/MvSliceErrorTests.java @@ -0,0 +1,40 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +package org.elasticsearch.xpack.esql.expression.function.scalar.multivalue; + +import org.elasticsearch.xpack.esql.core.expression.Expression; +import org.elasticsearch.xpack.esql.core.tree.Source; +import org.elasticsearch.xpack.esql.core.type.DataType; +import org.elasticsearch.xpack.esql.expression.function.ErrorsForCasesWithoutExamplesTestCase; +import org.elasticsearch.xpack.esql.expression.function.TestCaseSupplier; +import org.hamcrest.Matcher; + +import java.util.List; +import java.util.Set; + +import static org.hamcrest.Matchers.equalTo; + +public class MvSliceErrorTests extends ErrorsForCasesWithoutExamplesTestCase { + @Override + protected List cases() { + return paramsToSuppliers(MvSliceTests.parameters()); + } + + @Override + protected Expression build(Source source, List args) { + return new MvSlice(source, args.get(0), args.get(1), args.size() > 2 ? args.get(2) : null); + } + + @Override + protected Matcher expectedTypeErrorMatcher(List> validPerPosition, List signature) { + return equalTo(typeErrorMessage(true, validPerPosition, signature, (v, p) -> switch (p) { + case 1, 2 -> "integer"; + default -> throw new UnsupportedOperationException(); + })); + } +} diff --git a/x-pack/plugin/esql/src/test/java/org/elasticsearch/xpack/esql/expression/function/scalar/multivalue/MvSliceTests.java b/x-pack/plugin/esql/src/test/java/org/elasticsearch/xpack/esql/expression/function/scalar/multivalue/MvSliceTests.java index d5284602bf40c..24da717630733 100644 --- a/x-pack/plugin/esql/src/test/java/org/elasticsearch/xpack/esql/expression/function/scalar/multivalue/MvSliceTests.java +++ b/x-pack/plugin/esql/src/test/java/org/elasticsearch/xpack/esql/expression/function/scalar/multivalue/MvSliceTests.java @@ -16,9 +16,11 @@ import org.elasticsearch.xpack.esql.core.expression.Expression; import org.elasticsearch.xpack.esql.core.tree.Source; import org.elasticsearch.xpack.esql.core.type.DataType; +import org.elasticsearch.xpack.esql.core.util.NumericUtils; import org.elasticsearch.xpack.esql.expression.function.AbstractScalarFunctionTestCase; import org.elasticsearch.xpack.esql.expression.function.TestCaseSupplier; +import java.math.BigInteger; import java.util.ArrayList; import java.util.List; import java.util.function.Supplier; @@ -199,6 +201,24 @@ private static void longs(List suppliers) { equalTo(start == end ? field.get(start) : field.subList(start, end + 1)) ); })); + + suppliers.add(new TestCaseSupplier(List.of(DataType.UNSIGNED_LONG, DataType.INTEGER, DataType.INTEGER), () -> { + List field = randomList(1, 10, () -> randomNonNegativeLong()); + List result = field.stream().map(NumericUtils::unsignedLongAsBigInteger).toList(); + int length = field.size(); + int start = randomIntBetween(0, length - 1); + int end = randomIntBetween(start, length - 1); + return new TestCaseSupplier.TestCase( + List.of( + new TestCaseSupplier.TypedData(field, DataType.UNSIGNED_LONG, "field"), + new TestCaseSupplier.TypedData(start, DataType.INTEGER, "start"), + new TestCaseSupplier.TypedData(end, DataType.INTEGER, "end") + ), + "MvSliceLongEvaluator[field=Attribute[channel=0], start=Attribute[channel=1], end=Attribute[channel=2]]", + DataType.UNSIGNED_LONG, + equalTo(start == end ? result.get(start) : result.subList(start, end + 1)) + ); + })); } private static void doubles(List suppliers) { diff --git a/x-pack/plugin/esql/src/test/java/org/elasticsearch/xpack/esql/expression/function/scalar/multivalue/MvSortErrorTests.java b/x-pack/plugin/esql/src/test/java/org/elasticsearch/xpack/esql/expression/function/scalar/multivalue/MvSortErrorTests.java new file mode 100644 index 0000000000000..15f81c9e9e071 --- /dev/null +++ b/x-pack/plugin/esql/src/test/java/org/elasticsearch/xpack/esql/expression/function/scalar/multivalue/MvSortErrorTests.java @@ -0,0 +1,44 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +package org.elasticsearch.xpack.esql.expression.function.scalar.multivalue; + +import org.elasticsearch.xpack.esql.core.expression.Expression; +import org.elasticsearch.xpack.esql.core.tree.Source; +import org.elasticsearch.xpack.esql.core.type.DataType; +import org.elasticsearch.xpack.esql.expression.function.ErrorsForCasesWithoutExamplesTestCase; +import org.elasticsearch.xpack.esql.expression.function.TestCaseSupplier; +import org.hamcrest.Matcher; + +import java.util.List; +import java.util.Set; + +import static org.hamcrest.Matchers.equalTo; + +public class MvSortErrorTests extends ErrorsForCasesWithoutExamplesTestCase { + @Override + protected List cases() { + return paramsToSuppliers(MvSortTests.parameters()); + } + + @Override + protected Expression build(Source source, List args) { + return new MvSort(source, args.get(0), args.get(1)); + } + + @Override + protected Matcher expectedTypeErrorMatcher(List> validPerPosition, List signature) { + if (validPerPosition.get(0).contains(signature.get(0)) && signature.size() > 1 && signature.get(1) == DataType.NULL) { + return equalTo("second argument of [" + sourceForSignature(signature) + "] cannot be null, received []"); + } + return equalTo(typeErrorMessage(true, validPerPosition, signature, (v, p) -> switch (p) { + case 0 -> "not unsigned long or geo"; + case 1 -> "string"; + default -> throw new UnsupportedOperationException(); + })); + } +} diff --git a/x-pack/plugin/esql/src/test/java/org/elasticsearch/xpack/esql/expression/function/scalar/multivalue/MvSortTests.java b/x-pack/plugin/esql/src/test/java/org/elasticsearch/xpack/esql/expression/function/scalar/multivalue/MvSortTests.java index e5f240c811bd0..03e56f9819cc9 100644 --- a/x-pack/plugin/esql/src/test/java/org/elasticsearch/xpack/esql/expression/function/scalar/multivalue/MvSortTests.java +++ b/x-pack/plugin/esql/src/test/java/org/elasticsearch/xpack/esql/expression/function/scalar/multivalue/MvSortTests.java @@ -36,12 +36,28 @@ public MvSortTests(@Name("TestCase") Supplier testCas @ParametersFactory public static Iterable parameters() { List suppliers = new ArrayList<>(); - booleans(suppliers); - ints(suppliers); - longs(suppliers); - doubles(suppliers); - bytesRefs(suppliers); - nulls(suppliers); + for (DataType orderType : DataType.stringTypes()) { + booleans(suppliers, orderType); + ints(suppliers, orderType); + longs(suppliers, orderType); + doubles(suppliers, orderType); + bytesRefs(suppliers, orderType); + nulls(suppliers, orderType); + } + + List extra = new ArrayList<>(); + for (TestCaseSupplier s : suppliers) { + extra.add(new TestCaseSupplier("null <" + s.types().get(0) + ">, <" + s.types().get(1) + ">", s.types(), () -> { + TestCaseSupplier.TestCase delegate = s.get(); + return new TestCaseSupplier.TestCase( + List.of(new TestCaseSupplier.TypedData(null, s.types().get(0), "field"), delegate.getData().get(1)), + delegate.evaluatorToString(), + delegate.expectedType(), + nullValue() + ); + })); + } + suppliers.addAll(extra); return parameterSuppliersFromTypedData(suppliers); } @@ -50,14 +66,14 @@ protected Expression build(Source source, List args) { return new MvSort(source, args.get(0), args.size() > 1 ? args.get(1) : null); } - private static void booleans(List suppliers) { - suppliers.add(new TestCaseSupplier(List.of(DataType.BOOLEAN, DataType.KEYWORD), () -> { + private static void booleans(List suppliers, DataType orderType) { + suppliers.add(new TestCaseSupplier(List.of(DataType.BOOLEAN, orderType), () -> { List field = randomList(1, 10, () -> randomBoolean()); BytesRef order = new BytesRef("ASC"); return new TestCaseSupplier.TestCase( List.of( new TestCaseSupplier.TypedData(field, DataType.BOOLEAN, "field"), - new TestCaseSupplier.TypedData(order, DataType.KEYWORD, "order").forceLiteral() + new TestCaseSupplier.TypedData(order, orderType, "order").forceLiteral() ), "MvSortBoolean[field=Attribute[channel=0], order=true]", DataType.BOOLEAN, @@ -66,14 +82,14 @@ private static void booleans(List suppliers) { })); } - private static void ints(List suppliers) { - suppliers.add(new TestCaseSupplier(List.of(DataType.INTEGER, DataType.KEYWORD), () -> { + private static void ints(List suppliers, DataType orderType) { + suppliers.add(new TestCaseSupplier(List.of(DataType.INTEGER, orderType), () -> { List field = randomList(1, 10, () -> randomInt()); BytesRef order = new BytesRef("DESC"); return new TestCaseSupplier.TestCase( List.of( new TestCaseSupplier.TypedData(field, DataType.INTEGER, "field"), - new TestCaseSupplier.TypedData(order, DataType.KEYWORD, "order").forceLiteral() + new TestCaseSupplier.TypedData(order, orderType, "order").forceLiteral() ), "MvSortInt[field=Attribute[channel=0], order=false]", DataType.INTEGER, @@ -82,14 +98,14 @@ private static void ints(List suppliers) { })); } - private static void longs(List suppliers) { - suppliers.add(new TestCaseSupplier(List.of(DataType.LONG, DataType.KEYWORD), () -> { + private static void longs(List suppliers, DataType orderType) { + suppliers.add(new TestCaseSupplier(List.of(DataType.LONG, orderType), () -> { List field = randomList(1, 10, () -> randomLong()); BytesRef order = new BytesRef("ASC"); return new TestCaseSupplier.TestCase( List.of( new TestCaseSupplier.TypedData(field, DataType.LONG, "field"), - new TestCaseSupplier.TypedData(order, DataType.KEYWORD, "order").forceLiteral() + new TestCaseSupplier.TypedData(order, orderType, "order").forceLiteral() ), "MvSortLong[field=Attribute[channel=0], order=true]", DataType.LONG, @@ -97,13 +113,13 @@ private static void longs(List suppliers) { ); })); - suppliers.add(new TestCaseSupplier(List.of(DataType.DATETIME, DataType.KEYWORD), () -> { + suppliers.add(new TestCaseSupplier(List.of(DataType.DATETIME, orderType), () -> { List field = randomList(1, 10, () -> randomLong()); BytesRef order = new BytesRef("DESC"); return new TestCaseSupplier.TestCase( List.of( new TestCaseSupplier.TypedData(field, DataType.DATETIME, "field"), - new TestCaseSupplier.TypedData(order, DataType.KEYWORD, "order").forceLiteral() + new TestCaseSupplier.TypedData(order, orderType, "order").forceLiteral() ), "MvSortLong[field=Attribute[channel=0], order=false]", DataType.DATETIME, @@ -111,13 +127,13 @@ private static void longs(List suppliers) { ); })); - suppliers.add(new TestCaseSupplier(List.of(DataType.DATE_NANOS, DataType.KEYWORD), () -> { + suppliers.add(new TestCaseSupplier(List.of(DataType.DATE_NANOS, orderType), () -> { List field = randomList(1, 10, () -> randomLong()); BytesRef order = new BytesRef("DESC"); return new TestCaseSupplier.TestCase( List.of( new TestCaseSupplier.TypedData(field, DataType.DATE_NANOS, "field"), - new TestCaseSupplier.TypedData(order, DataType.KEYWORD, "order").forceLiteral() + new TestCaseSupplier.TypedData(order, orderType, "order").forceLiteral() ), "MvSortLong[field=Attribute[channel=0], order=false]", DataType.DATE_NANOS, @@ -126,14 +142,14 @@ private static void longs(List suppliers) { })); } - private static void doubles(List suppliers) { - suppliers.add(new TestCaseSupplier(List.of(DataType.DOUBLE, DataType.KEYWORD), () -> { + private static void doubles(List suppliers, DataType orderType) { + suppliers.add(new TestCaseSupplier(List.of(DataType.DOUBLE, orderType), () -> { List field = randomList(1, 10, () -> randomDouble()); BytesRef order = new BytesRef("ASC"); return new TestCaseSupplier.TestCase( List.of( new TestCaseSupplier.TypedData(field, DataType.DOUBLE, "field"), - new TestCaseSupplier.TypedData(order, DataType.KEYWORD, "order").forceLiteral() + new TestCaseSupplier.TypedData(order, orderType, "order").forceLiteral() ), "MvSortDouble[field=Attribute[channel=0], order=true]", DataType.DOUBLE, @@ -142,14 +158,14 @@ private static void doubles(List suppliers) { })); } - private static void bytesRefs(List suppliers) { - suppliers.add(new TestCaseSupplier(List.of(DataType.KEYWORD, DataType.KEYWORD), () -> { + private static void bytesRefs(List suppliers, DataType orderType) { + suppliers.add(new TestCaseSupplier(List.of(DataType.KEYWORD, orderType), () -> { List field = randomList(1, 10, () -> randomLiteral(DataType.KEYWORD).value()); BytesRef order = new BytesRef("DESC"); return new TestCaseSupplier.TestCase( List.of( new TestCaseSupplier.TypedData(field, DataType.KEYWORD, "field"), - new TestCaseSupplier.TypedData(order, DataType.KEYWORD, "order").forceLiteral() + new TestCaseSupplier.TypedData(order, orderType, "order").forceLiteral() ), "MvSortBytesRef[field=Attribute[channel=0], order=false]", DataType.KEYWORD, @@ -157,13 +173,13 @@ private static void bytesRefs(List suppliers) { ); })); - suppliers.add(new TestCaseSupplier(List.of(DataType.TEXT, DataType.KEYWORD), () -> { + suppliers.add(new TestCaseSupplier(List.of(DataType.TEXT, orderType), () -> { List field = randomList(1, 10, () -> randomLiteral(DataType.TEXT).value()); BytesRef order = new BytesRef("ASC"); return new TestCaseSupplier.TestCase( List.of( new TestCaseSupplier.TypedData(field, DataType.TEXT, "field"), - new TestCaseSupplier.TypedData(order, DataType.KEYWORD, "order").forceLiteral() + new TestCaseSupplier.TypedData(order, orderType, "order").forceLiteral() ), "MvSortBytesRef[field=Attribute[channel=0], order=true]", DataType.TEXT, @@ -171,13 +187,13 @@ private static void bytesRefs(List suppliers) { ); })); - suppliers.add(new TestCaseSupplier(List.of(DataType.SEMANTIC_TEXT, DataType.KEYWORD), () -> { + suppliers.add(new TestCaseSupplier(List.of(DataType.SEMANTIC_TEXT, orderType), () -> { List field = randomList(1, 10, () -> randomLiteral(DataType.SEMANTIC_TEXT).value()); BytesRef order = new BytesRef("ASC"); return new TestCaseSupplier.TestCase( List.of( new TestCaseSupplier.TypedData(field, DataType.SEMANTIC_TEXT, "field"), - new TestCaseSupplier.TypedData(order, DataType.KEYWORD, "order").forceLiteral() + new TestCaseSupplier.TypedData(order, orderType, "order").forceLiteral() ), "MvSortBytesRef[field=Attribute[channel=0], order=true]", DataType.SEMANTIC_TEXT, @@ -185,13 +201,13 @@ private static void bytesRefs(List suppliers) { ); })); - suppliers.add(new TestCaseSupplier(List.of(DataType.IP, DataType.KEYWORD), () -> { + suppliers.add(new TestCaseSupplier(List.of(DataType.IP, orderType), () -> { List field = randomList(1, 10, () -> randomLiteral(DataType.IP).value()); BytesRef order = new BytesRef("DESC"); return new TestCaseSupplier.TestCase( List.of( new TestCaseSupplier.TypedData(field, DataType.IP, "field"), - new TestCaseSupplier.TypedData(order, DataType.KEYWORD, "order").forceLiteral() + new TestCaseSupplier.TypedData(order, orderType, "order").forceLiteral() ), "MvSortBytesRef[field=Attribute[channel=0], order=false]", DataType.IP, @@ -199,13 +215,13 @@ private static void bytesRefs(List suppliers) { ); })); - suppliers.add(new TestCaseSupplier(List.of(DataType.VERSION, DataType.KEYWORD), () -> { + suppliers.add(new TestCaseSupplier(List.of(DataType.VERSION, orderType), () -> { List field = randomList(1, 10, () -> randomLiteral(DataType.VERSION).value()); BytesRef order = new BytesRef("ASC"); return new TestCaseSupplier.TestCase( List.of( new TestCaseSupplier.TypedData(field, DataType.VERSION, "field"), - new TestCaseSupplier.TypedData(order, DataType.KEYWORD, "order").forceLiteral() + new TestCaseSupplier.TypedData(order, orderType, "order").forceLiteral() ), "MvSortBytesRef[field=Attribute[channel=0], order=true]", DataType.VERSION, @@ -214,26 +230,13 @@ private static void bytesRefs(List suppliers) { })); } - private static void nulls(List suppliers) { - List extra = new ArrayList<>(); - for (TestCaseSupplier s : suppliers) { - extra.add(new TestCaseSupplier("null <" + s.types().get(0) + ">, ", s.types(), () -> { - TestCaseSupplier.TestCase delegate = s.get(); - return new TestCaseSupplier.TestCase( - List.of(new TestCaseSupplier.TypedData(null, s.types().get(0), "field"), delegate.getData().get(1)), - delegate.evaluatorToString(), - delegate.expectedType(), - nullValue() - ); - })); - } - suppliers.addAll(extra); - suppliers.add(new TestCaseSupplier(", ", List.of(DataType.NULL, DataType.KEYWORD), () -> { + private static void nulls(List suppliers, DataType orderType) { + suppliers.add(new TestCaseSupplier(", ", List.of(DataType.NULL, orderType), () -> { BytesRef order = new BytesRef("ASC"); return new TestCaseSupplier.TestCase( List.of( new TestCaseSupplier.TypedData(null, DataType.NULL, "field"), - new TestCaseSupplier.TypedData(order, DataType.KEYWORD, "order").forceLiteral() + new TestCaseSupplier.TypedData(order, orderType, "order").forceLiteral() ), equalTo("LiteralsEvaluator[lit=null]"), DataType.NULL, diff --git a/x-pack/plugin/esql/src/test/java/org/elasticsearch/xpack/esql/expression/function/scalar/multivalue/MvZipErrorTests.java b/x-pack/plugin/esql/src/test/java/org/elasticsearch/xpack/esql/expression/function/scalar/multivalue/MvZipErrorTests.java new file mode 100644 index 0000000000000..1e03be66d579c --- /dev/null +++ b/x-pack/plugin/esql/src/test/java/org/elasticsearch/xpack/esql/expression/function/scalar/multivalue/MvZipErrorTests.java @@ -0,0 +1,37 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +package org.elasticsearch.xpack.esql.expression.function.scalar.multivalue; + +import org.elasticsearch.xpack.esql.core.expression.Expression; +import org.elasticsearch.xpack.esql.core.tree.Source; +import org.elasticsearch.xpack.esql.core.type.DataType; +import org.elasticsearch.xpack.esql.expression.function.ErrorsForCasesWithoutExamplesTestCase; +import org.elasticsearch.xpack.esql.expression.function.TestCaseSupplier; +import org.hamcrest.Matcher; + +import java.util.List; +import java.util.Set; + +import static org.hamcrest.Matchers.equalTo; + +public class MvZipErrorTests extends ErrorsForCasesWithoutExamplesTestCase { + @Override + protected List cases() { + return paramsToSuppliers(MvZipTests.parameters()); + } + + @Override + protected Expression build(Source source, List args) { + return new MvZip(source, args.get(0), args.get(1), args.size() > 2 ? args.get(2) : null); + } + + @Override + protected Matcher expectedTypeErrorMatcher(List> validPerPosition, List signature) { + return equalTo(typeErrorMessage(true, validPerPosition, signature, (v, p) -> "string")); + } +} diff --git a/x-pack/plugin/esql/src/test/java/org/elasticsearch/xpack/esql/expression/function/scalar/multivalue/MvZipTests.java b/x-pack/plugin/esql/src/test/java/org/elasticsearch/xpack/esql/expression/function/scalar/multivalue/MvZipTests.java index d415cb55ea632..ae2afe0e3145e 100644 --- a/x-pack/plugin/esql/src/test/java/org/elasticsearch/xpack/esql/expression/function/scalar/multivalue/MvZipTests.java +++ b/x-pack/plugin/esql/src/test/java/org/elasticsearch/xpack/esql/expression/function/scalar/multivalue/MvZipTests.java @@ -52,7 +52,7 @@ public static Iterable parameters() { } } - return parameterSuppliersFromTypedData(errorsForCasesWithoutExamples(suppliers, (v, p) -> "string")); + return parameterSuppliersFromTypedData(suppliers); } private static TestCaseSupplier supplier(DataType leftType, DataType rightType, DataType delimType) { diff --git a/x-pack/plugin/esql/src/test/java/org/elasticsearch/xpack/esql/expression/function/scalar/string/ConcatErrorTests.java b/x-pack/plugin/esql/src/test/java/org/elasticsearch/xpack/esql/expression/function/scalar/string/ConcatErrorTests.java new file mode 100644 index 0000000000000..e7afced133c95 --- /dev/null +++ b/x-pack/plugin/esql/src/test/java/org/elasticsearch/xpack/esql/expression/function/scalar/string/ConcatErrorTests.java @@ -0,0 +1,40 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +package org.elasticsearch.xpack.esql.expression.function.scalar.string; + +import org.elasticsearch.xpack.esql.core.expression.Expression; +import org.elasticsearch.xpack.esql.core.tree.Source; +import org.elasticsearch.xpack.esql.core.type.DataType; +import org.elasticsearch.xpack.esql.expression.function.ErrorsForCasesWithoutExamplesTestCase; +import org.elasticsearch.xpack.esql.expression.function.TestCaseSupplier; +import org.hamcrest.Matcher; + +import java.util.List; +import java.util.Set; + +import static org.hamcrest.Matchers.equalTo; + +public class ConcatErrorTests extends ErrorsForCasesWithoutExamplesTestCase { + @Override + protected List cases() { + List suppliers = paramsToSuppliers(ConcatTests.parameters()); + // TODO support longer lists. Though this thing has 100s so we probably can't do them all. + suppliers.removeIf(s -> s.types().size() > 3); + return suppliers; + } + + @Override + protected Expression build(Source source, List args) { + return new Concat(source, args.get(0), args.subList(1, args.size())); + } + + @Override + protected Matcher expectedTypeErrorMatcher(List> validPerPosition, List signature) { + return equalTo(typeErrorMessage(false, validPerPosition, signature, (v, p) -> "string")); + } +} diff --git a/x-pack/plugin/esql/src/test/java/org/elasticsearch/xpack/esql/expression/function/scalar/string/ConcatTests.java b/x-pack/plugin/esql/src/test/java/org/elasticsearch/xpack/esql/expression/function/scalar/string/ConcatTests.java index 42c6284a3c25a..c7358ff4fe947 100644 --- a/x-pack/plugin/esql/src/test/java/org/elasticsearch/xpack/esql/expression/function/scalar/string/ConcatTests.java +++ b/x-pack/plugin/esql/src/test/java/org/elasticsearch/xpack/esql/expression/function/scalar/string/ConcatTests.java @@ -26,7 +26,6 @@ import java.util.List; import java.util.Map; import java.util.Objects; -import java.util.Set; import java.util.function.Supplier; import java.util.stream.IntStream; @@ -48,23 +47,6 @@ public static Iterable parameters() { for (int length = 4; length < 100; length++) { suppliers(suppliers, length); } - Set supported = Set.of(DataType.NULL, DataType.KEYWORD, DataType.TEXT, DataType.SEMANTIC_TEXT); - List> supportedPerPosition = List.of(supported, supported); - for (DataType lhs : DataType.types()) { - if (lhs == DataType.NULL || DataType.isRepresentable(lhs) == false) { - continue; - } - for (DataType rhs : DataType.types()) { - if (rhs == DataType.NULL || DataType.isRepresentable(rhs) == false) { - continue; - } - if (DataType.isString(lhs) && DataType.isString(rhs)) { - continue; - } - - suppliers.add(typeErrorSupplier(false, supportedPerPosition, List.of(lhs, rhs), (v, p) -> "string")); - } - } return parameterSuppliersFromTypedData(suppliers); } @@ -133,7 +115,6 @@ private static void add(List suppliers, String name, int lengt return new TestCaseSupplier.TestCase(values, expectedToString, DataType.KEYWORD, equalTo(new BytesRef(expectedValue))); })); } - } @Override @@ -159,11 +140,6 @@ public void testSomeConstant() { fieldValues.add(new BytesRef("dummy")); } Expression expression = build(testCase.getSource(), mix); - if (testCase.getExpectedTypeError() != null) { - assertTrue("expected unresolved", expression.typeResolved().unresolved()); - assertThat(expression.typeResolved().message(), equalTo(testCase.getExpectedTypeError())); - return; - } int totalLength = testDataLength(); if (totalLength >= Concat.MAX_CONCAT_LENGTH || rarely()) { diff --git a/x-pack/plugin/esql/src/test/java/org/elasticsearch/xpack/esql/expression/function/scalar/string/RLikeErrorTests.java b/x-pack/plugin/esql/src/test/java/org/elasticsearch/xpack/esql/expression/function/scalar/string/RLikeErrorTests.java new file mode 100644 index 0000000000000..c73e3da3997f5 --- /dev/null +++ b/x-pack/plugin/esql/src/test/java/org/elasticsearch/xpack/esql/expression/function/scalar/string/RLikeErrorTests.java @@ -0,0 +1,49 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +package org.elasticsearch.xpack.esql.expression.function.scalar.string; + +import org.elasticsearch.xpack.esql.core.expression.Expression; +import org.elasticsearch.xpack.esql.core.tree.Source; +import org.elasticsearch.xpack.esql.core.type.DataType; +import org.elasticsearch.xpack.esql.expression.function.ErrorsForCasesWithoutExamplesTestCase; +import org.elasticsearch.xpack.esql.expression.function.TestCaseSupplier; +import org.hamcrest.Matcher; + +import java.util.List; +import java.util.Set; +import java.util.stream.Stream; + +import static org.hamcrest.Matchers.equalTo; + +public class RLikeErrorTests extends ErrorsForCasesWithoutExamplesTestCase { + @Override + protected List cases() { + return paramsToSuppliers(RLikeTests.parameters()); + } + + @Override + protected Stream> testCandidates(List cases, Set> valid) { + /* + * We can't support certain signatures, and it's safe not to test them because + * you can't even build them.... The building comes directly from the parser + * and can only make certain types. + */ + return super.testCandidates(cases, valid).filter(sig -> sig.get(1) == DataType.KEYWORD) + .filter(sig -> sig.size() > 2 && sig.get(2) == DataType.BOOLEAN); + } + + @Override + protected Expression build(Source source, List args) { + return RLikeTests.buildRLike(logger, source, args); + } + + @Override + protected Matcher expectedTypeErrorMatcher(List> validPerPosition, List signature) { + return equalTo(typeErrorMessage(false, validPerPosition, signature, (v, p) -> "string")); + } +} diff --git a/x-pack/plugin/esql/src/test/java/org/elasticsearch/xpack/esql/expression/function/scalar/string/RLikeTests.java b/x-pack/plugin/esql/src/test/java/org/elasticsearch/xpack/esql/expression/function/scalar/string/RLikeTests.java index 4f8adf3abaae6..9e89401df8179 100644 --- a/x-pack/plugin/esql/src/test/java/org/elasticsearch/xpack/esql/expression/function/scalar/string/RLikeTests.java +++ b/x-pack/plugin/esql/src/test/java/org/elasticsearch/xpack/esql/expression/function/scalar/string/RLikeTests.java @@ -10,6 +10,7 @@ import com.carrotsearch.randomizedtesting.annotations.Name; import com.carrotsearch.randomizedtesting.annotations.ParametersFactory; +import org.apache.logging.log4j.Logger; import org.apache.lucene.util.BytesRef; import org.elasticsearch.xpack.esql.core.expression.Expression; import org.elasticsearch.xpack.esql.core.expression.Literal; @@ -24,7 +25,6 @@ import java.util.function.Function; import java.util.function.Supplier; -import static org.elasticsearch.xpack.esql.EsqlTestUtils.randomLiteral; import static org.hamcrest.Matchers.equalTo; import static org.hamcrest.Matchers.nullValue; import static org.hamcrest.Matchers.startsWith; @@ -68,28 +68,6 @@ static Iterable parameters(Function escapeString, Supp casesForString(cases, "3 bytes, 1 code point", () -> "☕", false, escapeString, optionalPattern); casesForString(cases, "6 bytes, 2 code points", () -> "❗️", false, escapeString, optionalPattern); casesForString(cases, "100 random code points", () -> randomUnicodeOfCodepointLength(100), true, escapeString, optionalPattern); - for (DataType type : DataType.types()) { - if (DataType.isString(type) || type == DataType.NULL) { - continue; - } - if (DataType.isRepresentable(type) == false) { - continue; - } - cases.add( - new TestCaseSupplier( - List.of(type, DataType.KEYWORD, DataType.BOOLEAN), - () -> TestCaseSupplier.TestCase.typeError( - List.of( - new TestCaseSupplier.TypedData(randomLiteral(type).value(), type, "e"), - new TestCaseSupplier.TypedData(new BytesRef(randomAlphaOfLength(10)), DataType.KEYWORD, "pattern") - .forceLiteral(), - new TestCaseSupplier.TypedData(false, DataType.BOOLEAN, "caseInsensitive").forceLiteral() - ), - "argument of [] must be [string], found value [e] type [" + type.typeName() + "]" - ) - ) - ); - } return parameterSuppliersFromTypedData(cases); } @@ -126,12 +104,12 @@ private static void casesForString( private static void cases(List cases, String title, Supplier textAndPattern, boolean expected) { for (DataType type : DataType.stringTypes()) { - cases.add(new TestCaseSupplier(title + " with " + type.esType(), List.of(type, type, DataType.BOOLEAN), () -> { + cases.add(new TestCaseSupplier(title + " with " + type.esType(), List.of(type, DataType.KEYWORD, DataType.BOOLEAN), () -> { TextAndPattern v = textAndPattern.get(); return new TestCaseSupplier.TestCase( List.of( new TestCaseSupplier.TypedData(new BytesRef(v.text), type, "e"), - new TestCaseSupplier.TypedData(new BytesRef(v.pattern), type, "pattern").forceLiteral(), + new TestCaseSupplier.TypedData(new BytesRef(v.pattern), DataType.KEYWORD, "pattern").forceLiteral(), new TestCaseSupplier.TypedData(false, DataType.BOOLEAN, "caseInsensitive").forceLiteral() ), startsWith("AutomataMatchEvaluator[input=Attribute[channel=0], pattern=digraph Automaton {\n"), @@ -139,12 +117,12 @@ private static void cases(List cases, String title, Supplier { + cases.add(new TestCaseSupplier(title + " with " + type.esType(), List.of(type, DataType.KEYWORD), () -> { TextAndPattern v = textAndPattern.get(); return new TestCaseSupplier.TestCase( List.of( new TestCaseSupplier.TypedData(new BytesRef(v.text), type, "e"), - new TestCaseSupplier.TypedData(new BytesRef(v.pattern), type, "pattern").forceLiteral() + new TestCaseSupplier.TypedData(new BytesRef(v.pattern), DataType.KEYWORD, "pattern").forceLiteral() ), startsWith("AutomataMatchEvaluator[input=Attribute[channel=0], pattern=digraph Automaton {\n"), DataType.BOOLEAN, @@ -156,6 +134,10 @@ private static void cases(List cases, String title, Supplier args) { + return buildRLike(logger, source, args); + } + + static Expression buildRLike(Logger logger, Source source, List args) { Expression expression = args.get(0); Literal pattern = (Literal) args.get(1); Literal caseInsensitive = args.size() > 2 ? (Literal) args.get(2) : null; diff --git a/x-pack/plugin/esql/src/test/java/org/elasticsearch/xpack/esql/expression/function/scalar/string/WildcardLikeErrorTests.java b/x-pack/plugin/esql/src/test/java/org/elasticsearch/xpack/esql/expression/function/scalar/string/WildcardLikeErrorTests.java new file mode 100644 index 0000000000000..d6f4fdc699202 --- /dev/null +++ b/x-pack/plugin/esql/src/test/java/org/elasticsearch/xpack/esql/expression/function/scalar/string/WildcardLikeErrorTests.java @@ -0,0 +1,49 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +package org.elasticsearch.xpack.esql.expression.function.scalar.string; + +import org.elasticsearch.xpack.esql.core.expression.Expression; +import org.elasticsearch.xpack.esql.core.tree.Source; +import org.elasticsearch.xpack.esql.core.type.DataType; +import org.elasticsearch.xpack.esql.expression.function.ErrorsForCasesWithoutExamplesTestCase; +import org.elasticsearch.xpack.esql.expression.function.TestCaseSupplier; +import org.hamcrest.Matcher; + +import java.util.List; +import java.util.Set; +import java.util.stream.Stream; + +import static org.hamcrest.Matchers.equalTo; + +public class WildcardLikeErrorTests extends ErrorsForCasesWithoutExamplesTestCase { + @Override + protected List cases() { + return paramsToSuppliers(WildcardLikeTests.parameters()); + } + + @Override + protected Stream> testCandidates(List cases, Set> valid) { + /* + * We can't support certain signatures, and it's safe not to test them because + * you can't even build them.... The building comes directly from the parser + * and can only make certain types. + */ + return super.testCandidates(cases, valid).filter(sig -> sig.get(1) == DataType.KEYWORD) + .filter(sig -> sig.size() > 2 && sig.get(2) == DataType.BOOLEAN); + } + + @Override + protected Expression build(Source source, List args) { + return RLikeTests.buildRLike(logger, source, args); + } + + @Override + protected Matcher expectedTypeErrorMatcher(List> validPerPosition, List signature) { + return equalTo(typeErrorMessage(false, validPerPosition, signature, (v, p) -> "string")); + } +} diff --git a/x-pack/plugin/esql/src/test/java/org/elasticsearch/xpack/esql/expression/function/scalar/string/WildcardLikeTests.java b/x-pack/plugin/esql/src/test/java/org/elasticsearch/xpack/esql/expression/function/scalar/string/WildcardLikeTests.java index eed2c7379e9e1..2eec7897e46ea 100644 --- a/x-pack/plugin/esql/src/test/java/org/elasticsearch/xpack/esql/expression/function/scalar/string/WildcardLikeTests.java +++ b/x-pack/plugin/esql/src/test/java/org/elasticsearch/xpack/esql/expression/function/scalar/string/WildcardLikeTests.java @@ -54,7 +54,7 @@ public static Iterable parameters() { private static void addCases(List suppliers) { for (DataType type : new DataType[] { DataType.KEYWORD, DataType.TEXT, DataType.SEMANTIC_TEXT }) { - suppliers.add(new TestCaseSupplier(" with " + type.esType(), List.of(type, type), () -> { + suppliers.add(new TestCaseSupplier(" with " + type.esType(), List.of(type, DataType.KEYWORD), () -> { BytesRef str = new BytesRef(randomAlphaOfLength(5)); String patternString = randomAlphaOfLength(2); BytesRef pattern = new BytesRef(patternString + "*"); @@ -62,7 +62,7 @@ private static void addCases(List suppliers) { return new TestCaseSupplier.TestCase( List.of( new TestCaseSupplier.TypedData(str, type, "str"), - new TestCaseSupplier.TypedData(pattern, type, "pattern").forceLiteral() + new TestCaseSupplier.TypedData(pattern, DataType.KEYWORD, "pattern").forceLiteral() ), startsWith("AutomataMatchEvaluator[input=Attribute[channel=0], pattern=digraph Automaton {\n"), DataType.BOOLEAN, @@ -74,6 +74,10 @@ private static void addCases(List suppliers) { @Override protected Expression build(Source source, List args) { + return buildWildcardLike(source, args); + } + + static Expression buildWildcardLike(Source source, List args) { Expression expression = args.get(0); Literal pattern = (Literal) args.get(1); if (args.size() > 2) { From 46244b5674233589a98500b3f6bc9f0eab61a8fa Mon Sep 17 00:00:00 2001 From: Nik Everett Date: Wed, 15 Jan 2025 13:22:26 -0500 Subject: [PATCH 2/7] Examples --- .../ErrorsForCasesWithoutExamplesTestCase.java | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/x-pack/plugin/esql/src/test/java/org/elasticsearch/xpack/esql/expression/function/ErrorsForCasesWithoutExamplesTestCase.java b/x-pack/plugin/esql/src/test/java/org/elasticsearch/xpack/esql/expression/function/ErrorsForCasesWithoutExamplesTestCase.java index 43a3d2c426ece..3e31031d46a30 100644 --- a/x-pack/plugin/esql/src/test/java/org/elasticsearch/xpack/esql/expression/function/ErrorsForCasesWithoutExamplesTestCase.java +++ b/x-pack/plugin/esql/src/test/java/org/elasticsearch/xpack/esql/expression/function/ErrorsForCasesWithoutExamplesTestCase.java @@ -12,6 +12,7 @@ import org.elasticsearch.xpack.esql.core.expression.TypeResolutions; import org.elasticsearch.xpack.esql.core.tree.Source; import org.elasticsearch.xpack.esql.core.type.DataType; +import org.elasticsearch.xpack.esql.expression.function.scalar.multivalue.MvCountErrorTests; import org.hamcrest.Matcher; import java.util.ArrayList; @@ -24,6 +25,10 @@ import static org.elasticsearch.xpack.esql.EsqlTestUtils.randomLiteral; import static org.hamcrest.Matchers.greaterThan; +/** + * Extend me to test that all cases not mentioned in a subclass of + * {@link AbstractFunctionTestCase} produce type errors. + */ public abstract class ErrorsForCasesWithoutExamplesTestCase extends ESTestCase { protected abstract List cases(); @@ -37,6 +42,15 @@ public abstract class ErrorsForCasesWithoutExamplesTestCase extends ESTestCase { */ protected abstract Expression build(Source source, List args); + /** + * A matcher for the invalid type error message. + *

+ * If you are implementing this for a function that should process all types + * then have a look how {@link MvCountErrorTests} does it. It's nice to throw + * an error explaining this. But while someone is implementing a new type + * they will want to turn that off temporarily. And we say that in the note too. + *

+ */ protected abstract Matcher expectedTypeErrorMatcher(List> validPerPosition, List signature); protected final List paramsToSuppliers(Iterable cases) { From 1a90c826eae6d6321918d65aeeb354efd290066f Mon Sep 17 00:00:00 2001 From: Nik Everett Date: Thu, 16 Jan 2025 13:03:24 -0500 Subject: [PATCH 3/7] ESQL: Heap attack tests Adds tests for requests that would fill up the heap and crash elasticsearch but for our circuit breakers. In an ideal world we'd stream these results back to and this wouldn't crash anything. But we don't have that at the moment. --- .../xpack/esql/heap_attack/HeapAttackIT.java | 117 +++++++++++++++++- .../esql/enrich/AbstractLookupService.java | 25 +++- 2 files changed, 133 insertions(+), 9 deletions(-) diff --git a/test/external-modules/esql-heap-attack/src/javaRestTest/java/org/elasticsearch/xpack/esql/heap_attack/HeapAttackIT.java b/test/external-modules/esql-heap-attack/src/javaRestTest/java/org/elasticsearch/xpack/esql/heap_attack/HeapAttackIT.java index 958132b3e4076..f50be600949ac 100644 --- a/test/external-modules/esql-heap-attack/src/javaRestTest/java/org/elasticsearch/xpack/esql/heap_attack/HeapAttackIT.java +++ b/test/external-modules/esql-heap-attack/src/javaRestTest/java/org/elasticsearch/xpack/esql/heap_attack/HeapAttackIT.java @@ -23,6 +23,8 @@ import org.elasticsearch.common.unit.ByteSizeValue; import org.elasticsearch.common.util.concurrent.AbstractRunnable; import org.elasticsearch.core.TimeValue; +import org.elasticsearch.index.IndexMode; +import org.elasticsearch.index.IndexSettings; import org.elasticsearch.test.ListMatcher; import org.elasticsearch.test.MapMatcher; import org.elasticsearch.test.cluster.ElasticsearchCluster; @@ -42,6 +44,7 @@ import java.util.List; import java.util.Locale; import java.util.Map; +import java.util.function.IntFunction; import java.util.stream.Collectors; import java.util.stream.IntStream; @@ -57,8 +60,8 @@ import static org.hamcrest.Matchers.matchesRegex; /** - * Tests that run ESQL queries that have, in the past, used so much memory they - * crash Elasticsearch. + * Tests that run ESQL queries that use a ton of memory. We want to make + * sure they don't consume the entire heap and crash Elasticsearch. */ public class HeapAttackIT extends ESRestTestCase { @ClassRule @@ -624,6 +627,53 @@ private Response fetchMvLongs() throws IOException { return query(query.toString(), "columns"); } + public void testLookupExplosion() throws IOException { + int sensorDataCount = 1000; + int lookupEntries = 100; + Map map = responseAsMap(lookupExplosion(sensorDataCount, lookupEntries)); + assertMap(map, matchesMap().extraOk().entry("values", List.of(List.of(sensorDataCount * lookupEntries)))); + } + + public void testLookupExplosionManyMatches() throws IOException { + assertCircuitBreaks( + () -> System.err.println(EntityUtils.toString(lookupExplosion(1000, 10000).getEntity(), StandardCharsets.UTF_8)) + ); + } + + private Response lookupExplosion(int sensorDataCount, int lookupEntries) throws IOException { + initSensorData(sensorDataCount, 1); + initSensorLookup(lookupEntries, 1, i -> "73.9857 40.7484"); + StringBuilder query = startQuery(); + query.append("FROM sensor_data | LOOKUP JOIN sensor_lookup ON id | STATS COUNT(*)\"}"); + return query(query.toString(), null); + } + + public void testEnrichExplosion() throws IOException { + int sensorDataCount = 1000; + int lookupEntries = 100; + Map map = responseAsMap(enrichExplosion(sensorDataCount, lookupEntries)); + assertMap(map, matchesMap().extraOk().entry("values", List.of(List.of(sensorDataCount)))); + } + + public void testEnrichExplosionManyMatches() throws IOException { + assertCircuitBreaks( + () -> System.err.println(EntityUtils.toString(enrichExplosion(1000, 10000).getEntity(), StandardCharsets.UTF_8)) + ); + } + + private Response enrichExplosion(int sensorDataCount, int lookupEntries) throws IOException { + initSensorData(sensorDataCount, 1); + initSensorEnrich(lookupEntries, 1, i -> "73.9857 40.7484"); + try { + StringBuilder query = startQuery(); + query.append("FROM sensor_data | ENRICH sensor ON id | STATS COUNT(*)\"}"); + return query(query.toString(), null); + } finally { + Request delete = new Request("DELETE", "/_enrich/policy/sensor"); + assertMap(responseAsMap(client().performRequest(delete)), matchesMap().entry("acknowledged", true)); + } + } + private void initManyLongs() throws IOException { logger.info("loading many documents with longs"); StringBuilder bulk = new StringBuilder(); @@ -647,7 +697,7 @@ private void initManyLongs() throws IOException { } private void initSingleDocIndex() throws IOException { - logger.info("loading many documents with a single document"); + logger.info("loading a single document"); initIndex("single", """ {"create":{}} {"a":1} @@ -730,6 +780,67 @@ private void initMvLongsIndex(int docs, int fields, int fieldValues) throws IOEx initIndex("mv_longs", bulk.toString()); } + private void initSensorData(int docCount, int sensorCount) throws IOException { + logger.info("loading sensor data"); + int docsPerBulk = 1000; + + StringBuilder data = new StringBuilder(); + for (int i = 0; i < docCount; i++) { + data.append(String.format(Locale.ROOT, """ + {"create":{}} + {"timestamp":"2025-01-01T00:%d:00Z", "id": %d, "value": %f} + """, i, i % sensorCount, i * 1.1)); + if (i % docsPerBulk == docsPerBulk - 1) { + bulk("sensor_data", data.toString()); + data.setLength(0); + } + } + initIndex("sensor_data", data.toString()); + } + + private void initSensorLookup(int lookupEntries, int sensorCount, IntFunction location) throws IOException { + logger.info("loading sensor lookup"); + createIndex("sensor_lookup", Settings.builder().put(IndexSettings.MODE.getKey(), IndexMode.LOOKUP.getName()).build(), """ + { + "properties": { + "id": { "type": "long" }, + "location": { "type": "geo_point" } + } + }"""); + StringBuilder data = new StringBuilder(); + for (int i = 0; i < lookupEntries; i++) { + int sensor = i % sensorCount; + data.append(String.format(Locale.ROOT, """ + {"create":{}} + {"id": %d, "location": "POINT(%s)"} + """, sensor, location.apply(sensor))); + if (i % 1000 == 999) { + bulk("sensor_lookup", data.toString()); + data.setLength(0); + } + } + initIndex("sensor_lookup", data.toString()); + } + + private void initSensorEnrich(int lookupEntries, int sensorCount, IntFunction location) throws IOException { + initSensorLookup(lookupEntries, sensorCount, location); + logger.info("loading sensor enrich"); + + Request create = new Request("PUT", "/_enrich/policy/sensor"); + create.setJsonEntity(""" + { + "match": { + "indices": "sensor_lookup", + "match_field": "id", + "enrich_fields": ["location"] + } + } + """); + assertMap(responseAsMap(client().performRequest(create)), matchesMap().entry("acknowledged", true)); + Request execute = new Request("POST", "/_enrich/policy/sensor/_execute"); + assertMap(responseAsMap(client().performRequest(execute)), matchesMap().entry("status", Map.of("phase", "COMPLETE"))); + } + private void bulk(String name, String bulk) throws IOException { Request request = new Request("POST", "/" + name + "/_bulk"); request.setJsonEntity(bulk); diff --git a/x-pack/plugin/esql/src/main/java/org/elasticsearch/xpack/esql/enrich/AbstractLookupService.java b/x-pack/plugin/esql/src/main/java/org/elasticsearch/xpack/esql/enrich/AbstractLookupService.java index a486d574ddd84..51af06195c9e5 100644 --- a/x-pack/plugin/esql/src/main/java/org/elasticsearch/xpack/esql/enrich/AbstractLookupService.java +++ b/x-pack/plugin/esql/src/main/java/org/elasticsearch/xpack/esql/enrich/AbstractLookupService.java @@ -21,6 +21,7 @@ import org.elasticsearch.cluster.routing.ShardRouting; import org.elasticsearch.cluster.service.ClusterService; import org.elasticsearch.common.CheckedBiFunction; +import org.elasticsearch.common.collect.Iterators; import org.elasticsearch.common.io.stream.StreamInput; import org.elasticsearch.common.settings.Settings; import org.elasticsearch.common.util.BigArrays; @@ -409,13 +410,25 @@ private void doLookup(T request, CancellableTask task, ActionListener driver.cancel(reason); }); var threadContext = transportService.getThreadPool().getThreadContext(); - Driver.start(threadContext, executor, driver, Driver.DEFAULT_MAX_ITERATIONS, listener.map(ignored -> { - List out = collectedPages; - if (mergePages && out.isEmpty()) { - out = List.of(createNullResponse(request.inputPage.getPositionCount(), request.extractFields)); + Driver.start(threadContext, executor, driver, Driver.DEFAULT_MAX_ITERATIONS, new ActionListener() { + @Override + public void onResponse(Void unused) { + List out = collectedPages; + if (mergePages && out.isEmpty()) { + out = List.of(createNullResponse(request.inputPage.getPositionCount(), request.extractFields)); + } + listener.onResponse(out); + } + + @Override + public void onFailure(Exception e) { + Releasables.closeExpectNoException(Releasables.wrap(() -> Iterators.map(collectedPages.iterator(), p -> () -> { + p.allowPassingToDifferentDriver(); + p.releaseBlocks(); + }))); + listener.onFailure(e); } - return out; - })); + }); started = true; } catch (Exception e) { listener.onFailure(e); From 4d8d39abebac65da81b78223b22f3b2791fbc6f2 Mon Sep 17 00:00:00 2001 From: Nik Everett Date: Thu, 16 Jan 2025 13:47:09 -0500 Subject: [PATCH 4/7] REvert more --- .../function/scalar/multivalue/MvSort.java | 12 ++++-------- 1 file changed, 4 insertions(+), 8 deletions(-) diff --git a/x-pack/plugin/esql/src/main/java/org/elasticsearch/xpack/esql/expression/function/scalar/multivalue/MvSort.java b/x-pack/plugin/esql/src/main/java/org/elasticsearch/xpack/esql/expression/function/scalar/multivalue/MvSort.java index 183aa10482e47..b68718acfcd0a 100644 --- a/x-pack/plugin/esql/src/main/java/org/elasticsearch/xpack/esql/expression/function/scalar/multivalue/MvSort.java +++ b/x-pack/plugin/esql/src/main/java/org/elasticsearch/xpack/esql/expression/function/scalar/multivalue/MvSort.java @@ -52,7 +52,6 @@ import static org.elasticsearch.xpack.esql.core.expression.TypeResolutions.ParamOrdinal.FIRST; import static org.elasticsearch.xpack.esql.core.expression.TypeResolutions.ParamOrdinal.SECOND; -import static org.elasticsearch.xpack.esql.core.expression.TypeResolutions.isNotNull; import static org.elasticsearch.xpack.esql.core.expression.TypeResolutions.isString; import static org.elasticsearch.xpack.esql.core.expression.TypeResolutions.isType; import static org.elasticsearch.xpack.esql.expression.Validations.isFoldable; @@ -84,7 +83,7 @@ public MvSort( ) Expression field, @Param( name = "order", - type = { "keyword", "text" }, + type = { "keyword" }, description = "Sort order. The valid options are ASC and DESC, the default is ASC.", optional = true ) Expression order @@ -128,10 +127,7 @@ protected TypeResolution resolveType() { return new TypeResolution("Unresolved children"); } - TypeResolution resolution = isType(field, dt -> switch (dt) { - case UNSIGNED_LONG, GEO_POINT, GEO_SHAPE, CARTESIAN_POINT, CARTESIAN_SHAPE -> false; - default -> true; - }, sourceText(), FIRST, "not unsigned long or geo"); + TypeResolution resolution = isType(field, DataType::isRepresentable, sourceText(), FIRST, "representable"); if (resolution.unresolved()) { return resolution; @@ -141,7 +137,7 @@ protected TypeResolution resolveType() { return resolution; } - return isNotNull(order, sourceText(), SECOND).and(isString(order, sourceText(), SECOND)); + return isString(order, sourceText(), SECOND); } @Override @@ -235,7 +231,7 @@ public DataType dataType() { } @Override - public void postOptimizationVerification(Failures failures) { + public void postLogicalOptimizationVerification(Failures failures) { if (order == null) { return; } From 4eb5758ccf7c0a2d7b6ba494cc5e8f447428de45 Mon Sep 17 00:00:00 2001 From: Nik Everett Date: Thu, 16 Jan 2025 13:48:53 -0500 Subject: [PATCH 5/7] How'd this get in there? --- .../xpack/esql/heap_attack/HeapAttackIT.java | 117 +----------------- .../esql/enrich/AbstractLookupService.java | 25 +--- 2 files changed, 9 insertions(+), 133 deletions(-) diff --git a/test/external-modules/esql-heap-attack/src/javaRestTest/java/org/elasticsearch/xpack/esql/heap_attack/HeapAttackIT.java b/test/external-modules/esql-heap-attack/src/javaRestTest/java/org/elasticsearch/xpack/esql/heap_attack/HeapAttackIT.java index f50be600949ac..958132b3e4076 100644 --- a/test/external-modules/esql-heap-attack/src/javaRestTest/java/org/elasticsearch/xpack/esql/heap_attack/HeapAttackIT.java +++ b/test/external-modules/esql-heap-attack/src/javaRestTest/java/org/elasticsearch/xpack/esql/heap_attack/HeapAttackIT.java @@ -23,8 +23,6 @@ import org.elasticsearch.common.unit.ByteSizeValue; import org.elasticsearch.common.util.concurrent.AbstractRunnable; import org.elasticsearch.core.TimeValue; -import org.elasticsearch.index.IndexMode; -import org.elasticsearch.index.IndexSettings; import org.elasticsearch.test.ListMatcher; import org.elasticsearch.test.MapMatcher; import org.elasticsearch.test.cluster.ElasticsearchCluster; @@ -44,7 +42,6 @@ import java.util.List; import java.util.Locale; import java.util.Map; -import java.util.function.IntFunction; import java.util.stream.Collectors; import java.util.stream.IntStream; @@ -60,8 +57,8 @@ import static org.hamcrest.Matchers.matchesRegex; /** - * Tests that run ESQL queries that use a ton of memory. We want to make - * sure they don't consume the entire heap and crash Elasticsearch. + * Tests that run ESQL queries that have, in the past, used so much memory they + * crash Elasticsearch. */ public class HeapAttackIT extends ESRestTestCase { @ClassRule @@ -627,53 +624,6 @@ private Response fetchMvLongs() throws IOException { return query(query.toString(), "columns"); } - public void testLookupExplosion() throws IOException { - int sensorDataCount = 1000; - int lookupEntries = 100; - Map map = responseAsMap(lookupExplosion(sensorDataCount, lookupEntries)); - assertMap(map, matchesMap().extraOk().entry("values", List.of(List.of(sensorDataCount * lookupEntries)))); - } - - public void testLookupExplosionManyMatches() throws IOException { - assertCircuitBreaks( - () -> System.err.println(EntityUtils.toString(lookupExplosion(1000, 10000).getEntity(), StandardCharsets.UTF_8)) - ); - } - - private Response lookupExplosion(int sensorDataCount, int lookupEntries) throws IOException { - initSensorData(sensorDataCount, 1); - initSensorLookup(lookupEntries, 1, i -> "73.9857 40.7484"); - StringBuilder query = startQuery(); - query.append("FROM sensor_data | LOOKUP JOIN sensor_lookup ON id | STATS COUNT(*)\"}"); - return query(query.toString(), null); - } - - public void testEnrichExplosion() throws IOException { - int sensorDataCount = 1000; - int lookupEntries = 100; - Map map = responseAsMap(enrichExplosion(sensorDataCount, lookupEntries)); - assertMap(map, matchesMap().extraOk().entry("values", List.of(List.of(sensorDataCount)))); - } - - public void testEnrichExplosionManyMatches() throws IOException { - assertCircuitBreaks( - () -> System.err.println(EntityUtils.toString(enrichExplosion(1000, 10000).getEntity(), StandardCharsets.UTF_8)) - ); - } - - private Response enrichExplosion(int sensorDataCount, int lookupEntries) throws IOException { - initSensorData(sensorDataCount, 1); - initSensorEnrich(lookupEntries, 1, i -> "73.9857 40.7484"); - try { - StringBuilder query = startQuery(); - query.append("FROM sensor_data | ENRICH sensor ON id | STATS COUNT(*)\"}"); - return query(query.toString(), null); - } finally { - Request delete = new Request("DELETE", "/_enrich/policy/sensor"); - assertMap(responseAsMap(client().performRequest(delete)), matchesMap().entry("acknowledged", true)); - } - } - private void initManyLongs() throws IOException { logger.info("loading many documents with longs"); StringBuilder bulk = new StringBuilder(); @@ -697,7 +647,7 @@ private void initManyLongs() throws IOException { } private void initSingleDocIndex() throws IOException { - logger.info("loading a single document"); + logger.info("loading many documents with a single document"); initIndex("single", """ {"create":{}} {"a":1} @@ -780,67 +730,6 @@ private void initMvLongsIndex(int docs, int fields, int fieldValues) throws IOEx initIndex("mv_longs", bulk.toString()); } - private void initSensorData(int docCount, int sensorCount) throws IOException { - logger.info("loading sensor data"); - int docsPerBulk = 1000; - - StringBuilder data = new StringBuilder(); - for (int i = 0; i < docCount; i++) { - data.append(String.format(Locale.ROOT, """ - {"create":{}} - {"timestamp":"2025-01-01T00:%d:00Z", "id": %d, "value": %f} - """, i, i % sensorCount, i * 1.1)); - if (i % docsPerBulk == docsPerBulk - 1) { - bulk("sensor_data", data.toString()); - data.setLength(0); - } - } - initIndex("sensor_data", data.toString()); - } - - private void initSensorLookup(int lookupEntries, int sensorCount, IntFunction location) throws IOException { - logger.info("loading sensor lookup"); - createIndex("sensor_lookup", Settings.builder().put(IndexSettings.MODE.getKey(), IndexMode.LOOKUP.getName()).build(), """ - { - "properties": { - "id": { "type": "long" }, - "location": { "type": "geo_point" } - } - }"""); - StringBuilder data = new StringBuilder(); - for (int i = 0; i < lookupEntries; i++) { - int sensor = i % sensorCount; - data.append(String.format(Locale.ROOT, """ - {"create":{}} - {"id": %d, "location": "POINT(%s)"} - """, sensor, location.apply(sensor))); - if (i % 1000 == 999) { - bulk("sensor_lookup", data.toString()); - data.setLength(0); - } - } - initIndex("sensor_lookup", data.toString()); - } - - private void initSensorEnrich(int lookupEntries, int sensorCount, IntFunction location) throws IOException { - initSensorLookup(lookupEntries, sensorCount, location); - logger.info("loading sensor enrich"); - - Request create = new Request("PUT", "/_enrich/policy/sensor"); - create.setJsonEntity(""" - { - "match": { - "indices": "sensor_lookup", - "match_field": "id", - "enrich_fields": ["location"] - } - } - """); - assertMap(responseAsMap(client().performRequest(create)), matchesMap().entry("acknowledged", true)); - Request execute = new Request("POST", "/_enrich/policy/sensor/_execute"); - assertMap(responseAsMap(client().performRequest(execute)), matchesMap().entry("status", Map.of("phase", "COMPLETE"))); - } - private void bulk(String name, String bulk) throws IOException { Request request = new Request("POST", "/" + name + "/_bulk"); request.setJsonEntity(bulk); diff --git a/x-pack/plugin/esql/src/main/java/org/elasticsearch/xpack/esql/enrich/AbstractLookupService.java b/x-pack/plugin/esql/src/main/java/org/elasticsearch/xpack/esql/enrich/AbstractLookupService.java index 51af06195c9e5..a486d574ddd84 100644 --- a/x-pack/plugin/esql/src/main/java/org/elasticsearch/xpack/esql/enrich/AbstractLookupService.java +++ b/x-pack/plugin/esql/src/main/java/org/elasticsearch/xpack/esql/enrich/AbstractLookupService.java @@ -21,7 +21,6 @@ import org.elasticsearch.cluster.routing.ShardRouting; import org.elasticsearch.cluster.service.ClusterService; import org.elasticsearch.common.CheckedBiFunction; -import org.elasticsearch.common.collect.Iterators; import org.elasticsearch.common.io.stream.StreamInput; import org.elasticsearch.common.settings.Settings; import org.elasticsearch.common.util.BigArrays; @@ -410,25 +409,13 @@ private void doLookup(T request, CancellableTask task, ActionListener driver.cancel(reason); }); var threadContext = transportService.getThreadPool().getThreadContext(); - Driver.start(threadContext, executor, driver, Driver.DEFAULT_MAX_ITERATIONS, new ActionListener() { - @Override - public void onResponse(Void unused) { - List out = collectedPages; - if (mergePages && out.isEmpty()) { - out = List.of(createNullResponse(request.inputPage.getPositionCount(), request.extractFields)); - } - listener.onResponse(out); - } - - @Override - public void onFailure(Exception e) { - Releasables.closeExpectNoException(Releasables.wrap(() -> Iterators.map(collectedPages.iterator(), p -> () -> { - p.allowPassingToDifferentDriver(); - p.releaseBlocks(); - }))); - listener.onFailure(e); + Driver.start(threadContext, executor, driver, Driver.DEFAULT_MAX_ITERATIONS, listener.map(ignored -> { + List out = collectedPages; + if (mergePages && out.isEmpty()) { + out = List.of(createNullResponse(request.inputPage.getPositionCount(), request.extractFields)); } - }); + return out; + })); started = true; } catch (Exception e) { listener.onFailure(e); From 06d3f4ce12dc9b71ab89af18394d8da6a4c4fb32 Mon Sep 17 00:00:00 2001 From: Nik Everett Date: Thu, 16 Jan 2025 13:51:29 -0500 Subject: [PATCH 6/7] Revert harder --- .../esql/expression/function/scalar/multivalue/MvSort.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/x-pack/plugin/esql/src/main/java/org/elasticsearch/xpack/esql/expression/function/scalar/multivalue/MvSort.java b/x-pack/plugin/esql/src/main/java/org/elasticsearch/xpack/esql/expression/function/scalar/multivalue/MvSort.java index b68718acfcd0a..8c272ae70d8d7 100644 --- a/x-pack/plugin/esql/src/main/java/org/elasticsearch/xpack/esql/expression/function/scalar/multivalue/MvSort.java +++ b/x-pack/plugin/esql/src/main/java/org/elasticsearch/xpack/esql/expression/function/scalar/multivalue/MvSort.java @@ -231,7 +231,7 @@ public DataType dataType() { } @Override - public void postLogicalOptimizationVerification(Failures failures) { + public void postOptimizationVerification(Failures failures) { if (order == null) { return; } From b68f1f485056a45e8387cb9427b0389e1e40624f Mon Sep 17 00:00:00 2001 From: Nik Everett Date: Thu, 16 Jan 2025 14:22:49 -0500 Subject: [PATCH 7/7] Revert proper --- .../functions/kibana/definition/mv_sort.json | 180 ------------------ .../esql/functions/types/mv_sort.asciidoc | 10 - .../AbstractScalarFunctionTestCase.java | 10 +- .../multivalue/MvPercentileErrorTests.java | 37 ---- .../scalar/multivalue/MvPercentileTests.java | 3 +- 5 files changed, 10 insertions(+), 230 deletions(-) delete mode 100644 x-pack/plugin/esql/src/test/java/org/elasticsearch/xpack/esql/expression/function/scalar/multivalue/MvPercentileErrorTests.java diff --git a/docs/reference/esql/functions/kibana/definition/mv_sort.json b/docs/reference/esql/functions/kibana/definition/mv_sort.json index 3abedbf752943..072c05743af33 100644 --- a/docs/reference/esql/functions/kibana/definition/mv_sort.json +++ b/docs/reference/esql/functions/kibana/definition/mv_sort.json @@ -22,24 +22,6 @@ "variadic" : false, "returnType" : "boolean" }, - { - "params" : [ - { - "name" : "field", - "type" : "boolean", - "optional" : false, - "description" : "Multivalue expression. If `null`, the function returns `null`." - }, - { - "name" : "order", - "type" : "text", - "optional" : true, - "description" : "Sort order. The valid options are ASC and DESC, the default is ASC." - } - ], - "variadic" : false, - "returnType" : "boolean" - }, { "params" : [ { @@ -58,24 +40,6 @@ "variadic" : false, "returnType" : "date" }, - { - "params" : [ - { - "name" : "field", - "type" : "date", - "optional" : false, - "description" : "Multivalue expression. If `null`, the function returns `null`." - }, - { - "name" : "order", - "type" : "text", - "optional" : true, - "description" : "Sort order. The valid options are ASC and DESC, the default is ASC." - } - ], - "variadic" : false, - "returnType" : "date" - }, { "params" : [ { @@ -94,24 +58,6 @@ "variadic" : false, "returnType" : "date_nanos" }, - { - "params" : [ - { - "name" : "field", - "type" : "date_nanos", - "optional" : false, - "description" : "Multivalue expression. If `null`, the function returns `null`." - }, - { - "name" : "order", - "type" : "text", - "optional" : true, - "description" : "Sort order. The valid options are ASC and DESC, the default is ASC." - } - ], - "variadic" : false, - "returnType" : "date_nanos" - }, { "params" : [ { @@ -130,24 +76,6 @@ "variadic" : false, "returnType" : "double" }, - { - "params" : [ - { - "name" : "field", - "type" : "double", - "optional" : false, - "description" : "Multivalue expression. If `null`, the function returns `null`." - }, - { - "name" : "order", - "type" : "text", - "optional" : true, - "description" : "Sort order. The valid options are ASC and DESC, the default is ASC." - } - ], - "variadic" : false, - "returnType" : "double" - }, { "params" : [ { @@ -166,24 +94,6 @@ "variadic" : false, "returnType" : "integer" }, - { - "params" : [ - { - "name" : "field", - "type" : "integer", - "optional" : false, - "description" : "Multivalue expression. If `null`, the function returns `null`." - }, - { - "name" : "order", - "type" : "text", - "optional" : true, - "description" : "Sort order. The valid options are ASC and DESC, the default is ASC." - } - ], - "variadic" : false, - "returnType" : "integer" - }, { "params" : [ { @@ -202,24 +112,6 @@ "variadic" : false, "returnType" : "ip" }, - { - "params" : [ - { - "name" : "field", - "type" : "ip", - "optional" : false, - "description" : "Multivalue expression. If `null`, the function returns `null`." - }, - { - "name" : "order", - "type" : "text", - "optional" : true, - "description" : "Sort order. The valid options are ASC and DESC, the default is ASC." - } - ], - "variadic" : false, - "returnType" : "ip" - }, { "params" : [ { @@ -238,24 +130,6 @@ "variadic" : false, "returnType" : "keyword" }, - { - "params" : [ - { - "name" : "field", - "type" : "keyword", - "optional" : false, - "description" : "Multivalue expression. If `null`, the function returns `null`." - }, - { - "name" : "order", - "type" : "text", - "optional" : true, - "description" : "Sort order. The valid options are ASC and DESC, the default is ASC." - } - ], - "variadic" : false, - "returnType" : "keyword" - }, { "params" : [ { @@ -274,24 +148,6 @@ "variadic" : false, "returnType" : "long" }, - { - "params" : [ - { - "name" : "field", - "type" : "long", - "optional" : false, - "description" : "Multivalue expression. If `null`, the function returns `null`." - }, - { - "name" : "order", - "type" : "text", - "optional" : true, - "description" : "Sort order. The valid options are ASC and DESC, the default is ASC." - } - ], - "variadic" : false, - "returnType" : "long" - }, { "params" : [ { @@ -310,24 +166,6 @@ "variadic" : false, "returnType" : "keyword" }, - { - "params" : [ - { - "name" : "field", - "type" : "text", - "optional" : false, - "description" : "Multivalue expression. If `null`, the function returns `null`." - }, - { - "name" : "order", - "type" : "text", - "optional" : true, - "description" : "Sort order. The valid options are ASC and DESC, the default is ASC." - } - ], - "variadic" : false, - "returnType" : "keyword" - }, { "params" : [ { @@ -345,24 +183,6 @@ ], "variadic" : false, "returnType" : "version" - }, - { - "params" : [ - { - "name" : "field", - "type" : "version", - "optional" : false, - "description" : "Multivalue expression. If `null`, the function returns `null`." - }, - { - "name" : "order", - "type" : "text", - "optional" : true, - "description" : "Sort order. The valid options are ASC and DESC, the default is ASC." - } - ], - "variadic" : false, - "returnType" : "version" } ], "examples" : [ diff --git a/docs/reference/esql/functions/types/mv_sort.asciidoc b/docs/reference/esql/functions/types/mv_sort.asciidoc index 97abe60c0ceab..83d3e45c7be02 100644 --- a/docs/reference/esql/functions/types/mv_sort.asciidoc +++ b/docs/reference/esql/functions/types/mv_sort.asciidoc @@ -6,23 +6,13 @@ |=== field | order | result boolean | keyword | boolean -boolean | text | boolean date | keyword | date -date | text | date date_nanos | keyword | date_nanos -date_nanos | text | date_nanos double | keyword | double -double | text | double integer | keyword | integer -integer | text | integer ip | keyword | ip -ip | text | ip keyword | keyword | keyword -keyword | text | keyword long | keyword | long -long | text | long text | keyword | keyword -text | text | keyword version | keyword | version -version | text | version |=== diff --git a/x-pack/plugin/esql/src/test/java/org/elasticsearch/xpack/esql/expression/function/AbstractScalarFunctionTestCase.java b/x-pack/plugin/esql/src/test/java/org/elasticsearch/xpack/esql/expression/function/AbstractScalarFunctionTestCase.java index 0a6a7050518cc..cc18a76e9a2f7 100644 --- a/x-pack/plugin/esql/src/test/java/org/elasticsearch/xpack/esql/expression/function/AbstractScalarFunctionTestCase.java +++ b/x-pack/plugin/esql/src/test/java/org/elasticsearch/xpack/esql/expression/function/AbstractScalarFunctionTestCase.java @@ -108,9 +108,15 @@ protected static Iterable parameterSuppliersFromTypedDataWithDefaultCh protected static Iterable parameterSuppliersFromTypedDataWithDefaultChecks( ExpectedType nullsExpectedType, ExpectedEvaluatorToString evaluatorToString, - List suppliers + List suppliers, + PositionalErrorMessageSupplier positionalErrorMessageSupplier ) { - return parameterSuppliersFromTypedData(anyNullIsNull(randomizeBytesRefsOffset(suppliers), nullsExpectedType, evaluatorToString)); + return parameterSuppliersFromTypedData( + errorsForCasesWithoutExamples( + anyNullIsNull(randomizeBytesRefsOffset(suppliers), nullsExpectedType, evaluatorToString), + positionalErrorMessageSupplier + ) + ); } public final void testEvaluate() { diff --git a/x-pack/plugin/esql/src/test/java/org/elasticsearch/xpack/esql/expression/function/scalar/multivalue/MvPercentileErrorTests.java b/x-pack/plugin/esql/src/test/java/org/elasticsearch/xpack/esql/expression/function/scalar/multivalue/MvPercentileErrorTests.java deleted file mode 100644 index 25e7100b7c418..0000000000000 --- a/x-pack/plugin/esql/src/test/java/org/elasticsearch/xpack/esql/expression/function/scalar/multivalue/MvPercentileErrorTests.java +++ /dev/null @@ -1,37 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License - * 2.0; you may not use this file except in compliance with the Elastic License - * 2.0. - */ - -package org.elasticsearch.xpack.esql.expression.function.scalar.multivalue; - -import org.elasticsearch.xpack.esql.core.expression.Expression; -import org.elasticsearch.xpack.esql.core.tree.Source; -import org.elasticsearch.xpack.esql.core.type.DataType; -import org.elasticsearch.xpack.esql.expression.function.ErrorsForCasesWithoutExamplesTestCase; -import org.elasticsearch.xpack.esql.expression.function.TestCaseSupplier; -import org.hamcrest.Matcher; - -import java.util.List; -import java.util.Set; - -import static org.hamcrest.Matchers.equalTo; - -public class MvPercentileErrorTests extends ErrorsForCasesWithoutExamplesTestCase { - @Override - protected List cases() { - return paramsToSuppliers(MvPercentileTests.parameters()); - } - - @Override - protected Expression build(Source source, List args) { - return new MvPercentile(source, args.get(0), args.get(1)); - } - - @Override - protected Matcher expectedTypeErrorMatcher(List> validPerPosition, List signature) { - return equalTo(typeErrorMessage(true, validPerPosition, signature, (v, p) -> "numeric except unsigned_long")); - } -} diff --git a/x-pack/plugin/esql/src/test/java/org/elasticsearch/xpack/esql/expression/function/scalar/multivalue/MvPercentileTests.java b/x-pack/plugin/esql/src/test/java/org/elasticsearch/xpack/esql/expression/function/scalar/multivalue/MvPercentileTests.java index 31bba744c16ef..0a419d44e3448 100644 --- a/x-pack/plugin/esql/src/test/java/org/elasticsearch/xpack/esql/expression/function/scalar/multivalue/MvPercentileTests.java +++ b/x-pack/plugin/esql/src/test/java/org/elasticsearch/xpack/esql/expression/function/scalar/multivalue/MvPercentileTests.java @@ -357,7 +357,8 @@ public static Iterable parameters() { ? DataType.NULL : original.expectedType(), (nullPosition, nullData, original) -> original, - cases + cases, + (v, p) -> "numeric except unsigned_long" ); }