Skip to content

Commit 7f05bee

Browse files
[ESQL] Support datetime data type in Least and Greatest functions (#113961) (#114138)
While working on Date Nanos, I noticed that Least and Greatest didn't have support for datetime. This PR corrects that and adds tests for it. It seems to me that resolveType() is doing the wrong thing for these functions, as it accepts types that then do not have evaluator mappings, but refactoring that seems out of scope right now. --------- Co-authored-by: Elastic Machine <[email protected]>
1 parent 1243704 commit 7f05bee

File tree

12 files changed

+109
-14
lines changed

12 files changed

+109
-14
lines changed

docs/changelog/113961.yaml

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
pr: 113961
2+
summary: "[ESQL] Support datetime data type in Least and Greatest functions"
3+
area: ES|QL
4+
type: bug
5+
issues: []

docs/reference/esql/functions/kibana/definition/greatest.json

Lines changed: 18 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

docs/reference/esql/functions/kibana/definition/least.json

Lines changed: 18 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

docs/reference/esql/functions/types/greatest.asciidoc

Lines changed: 1 addition & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

docs/reference/esql/functions/types/least.asciidoc

Lines changed: 1 addition & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

x-pack/plugin/esql/qa/testFixtures/src/main/resources/date.csv-spec

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1172,3 +1172,20 @@ from employees
11721172
a:datetime
11731173
null
11741174
;
1175+
1176+
1177+
Least for dates
1178+
required_capability: least_greatest_for_dates
1179+
ROW a = LEAST(TO_DATETIME("1957-05-23T00:00:00Z"), TO_DATETIME("1958-02-19T00:00:00Z"));
1180+
1181+
a:datetime
1182+
1957-05-23T00:00:00
1183+
;
1184+
1185+
GREATEST for dates
1186+
required_capability: least_greatest_for_dates
1187+
ROW a = GREATEST(TO_DATETIME("1957-05-23T00:00:00Z"), TO_DATETIME("1958-02-19T00:00:00Z"));
1188+
1189+
a:datetime
1190+
1958-02-19T00:00:00
1191+
;

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

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -29,9 +29,9 @@ double e()
2929
"boolean ends_with(str:keyword|text, suffix:keyword|text)"
3030
"double|integer|long|unsigned_long floor(number:double|integer|long|unsigned_long)"
3131
"keyword from_base64(string:keyword|text)"
32-
"boolean|double|integer|ip|keyword|long|text|version greatest(first:boolean|double|integer|ip|keyword|long|text|version, ?rest...:boolean|double|integer|ip|keyword|long|text|version)"
32+
"boolean|date|double|integer|ip|keyword|long|text|version greatest(first:boolean|date|double|integer|ip|keyword|long|text|version, ?rest...:boolean|date|double|integer|ip|keyword|long|text|version)"
3333
"ip ip_prefix(ip:ip, prefixLengthV4:integer, prefixLengthV6:integer)"
34-
"boolean|double|integer|ip|keyword|long|text|version least(first:boolean|double|integer|ip|keyword|long|text|version, ?rest...:boolean|double|integer|ip|keyword|long|text|version)"
34+
"boolean|date|double|integer|ip|keyword|long|text|version least(first:boolean|date|double|integer|ip|keyword|long|text|version, ?rest...:boolean|date|double|integer|ip|keyword|long|text|version)"
3535
"keyword left(string:keyword|text, length:integer)"
3636
"integer length(string:keyword|text)"
3737
"integer locate(string:keyword|text, substring:keyword|text, ?start:integer)"
@@ -149,9 +149,9 @@ e |null |null
149149
ends_with |[str, suffix] |["keyword|text", "keyword|text"] |[String expression. If `null`\, the function returns `null`., String expression. If `null`\, the function returns `null`.]
150150
floor |number |"double|integer|long|unsigned_long" |Numeric expression. If `null`, the function returns `null`.
151151
from_base64 |string |"keyword|text" |A base64 string.
152-
greatest |first |"boolean|double|integer|ip|keyword|long|text|version" |First of the columns to evaluate.
152+
greatest |first |"boolean|date|double|integer|ip|keyword|long|text|version" |First of the columns to evaluate.
153153
ip_prefix |[ip, prefixLengthV4, prefixLengthV6]|[ip, integer, integer] |[IP address of type `ip` (both IPv4 and IPv6 are supported)., Prefix length for IPv4 addresses., Prefix length for IPv6 addresses.]
154-
least |first |"boolean|double|integer|ip|keyword|long|text|version" |First of the columns to evaluate.
154+
least |first |"boolean|date|double|integer|ip|keyword|long|text|version" |First of the columns to evaluate.
155155
left |[string, length] |["keyword|text", integer] |[The string from which to return a substring., The number of characters to return.]
156156
length |string |"keyword|text" |String expression. If `null`, the function returns `null`.
157157
locate |[string, substring, start] |["keyword|text", "keyword|text", "integer"] |[An input string, A substring to locate in the input string, The start index]
@@ -392,9 +392,9 @@ e |double
392392
ends_with |boolean |[false, false] |false |false
393393
floor |"double|integer|long|unsigned_long" |false |false |false
394394
from_base64 |keyword |false |false |false
395-
greatest |"boolean|double|integer|ip|keyword|long|text|version" |false |true |false
395+
greatest |"boolean|date|double|integer|ip|keyword|long|text|version" |false |true |false
396396
ip_prefix |ip |[false, false, false] |false |false
397-
least |"boolean|double|integer|ip|keyword|long|text|version" |false |true |false
397+
least |"boolean|date|double|integer|ip|keyword|long|text|version" |false |true |false
398398
left |keyword |[false, false] |false |false
399399
length |integer |false |false |false
400400
locate |integer |[false, false, true] |false |false

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

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -168,6 +168,11 @@ public enum Cap {
168168
*/
169169
FIXED_PUSHDOWN_PAST_PROJECT,
170170

171+
/**
172+
* Support for datetime in least and greatest functions
173+
*/
174+
LEAST_GREATEST_FOR_DATES,
175+
171176
/**
172177
* Changed error messages for fields with conflicting types in different indices.
173178
*/

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

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -44,7 +44,7 @@ public class Greatest extends EsqlScalarFunction implements OptionalArgument {
4444
private DataType dataType;
4545

4646
@FunctionInfo(
47-
returnType = { "boolean", "double", "integer", "ip", "keyword", "long", "text", "version" },
47+
returnType = { "boolean", "date", "double", "integer", "ip", "keyword", "long", "text", "version" },
4848
description = "Returns the maximum value from multiple columns. This is similar to <<esql-mv_max>>\n"
4949
+ "except it is intended to run on multiple columns at once.",
5050
note = "When run on `keyword` or `text` fields, this returns the last string in alphabetical order. "
@@ -55,12 +55,12 @@ public Greatest(
5555
Source source,
5656
@Param(
5757
name = "first",
58-
type = { "boolean", "double", "integer", "ip", "keyword", "long", "text", "version" },
58+
type = { "boolean", "date", "double", "integer", "ip", "keyword", "long", "text", "version" },
5959
description = "First of the columns to evaluate."
6060
) Expression first,
6161
@Param(
6262
name = "rest",
63-
type = { "boolean", "double", "integer", "ip", "keyword", "long", "text", "version" },
63+
type = { "boolean", "date", "double", "integer", "ip", "keyword", "long", "text", "version" },
6464
description = "The rest of the columns to evaluate.",
6565
optional = true
6666
) List<Expression> rest
@@ -153,7 +153,7 @@ public ExpressionEvaluator.Factory toEvaluator(Function<Expression, ExpressionEv
153153
if (dataType == DataType.INTEGER) {
154154
return new GreatestIntEvaluator.Factory(source(), factories);
155155
}
156-
if (dataType == DataType.LONG) {
156+
if (dataType == DataType.LONG || dataType == DataType.DATETIME) {
157157
return new GreatestLongEvaluator.Factory(source(), factories);
158158
}
159159
if (dataType == DataType.KEYWORD

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

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -44,7 +44,7 @@ public class Least extends EsqlScalarFunction implements OptionalArgument {
4444
private DataType dataType;
4545

4646
@FunctionInfo(
47-
returnType = { "boolean", "double", "integer", "ip", "keyword", "long", "text", "version" },
47+
returnType = { "boolean", "date", "double", "integer", "ip", "keyword", "long", "text", "version" },
4848
description = "Returns the minimum value from multiple columns. "
4949
+ "This is similar to <<esql-mv_min>> except it is intended to run on multiple columns at once.",
5050
examples = @Example(file = "math", tag = "least")
@@ -53,12 +53,12 @@ public Least(
5353
Source source,
5454
@Param(
5555
name = "first",
56-
type = { "boolean", "double", "integer", "ip", "keyword", "long", "text", "version" },
56+
type = { "boolean", "date", "double", "integer", "ip", "keyword", "long", "text", "version" },
5757
description = "First of the columns to evaluate."
5858
) Expression first,
5959
@Param(
6060
name = "rest",
61-
type = { "boolean", "double", "integer", "ip", "keyword", "long", "text", "version" },
61+
type = { "boolean", "date", "double", "integer", "ip", "keyword", "long", "text", "version" },
6262
description = "The rest of the columns to evaluate.",
6363
optional = true
6464
) List<Expression> rest
@@ -152,7 +152,7 @@ public ExpressionEvaluator.Factory toEvaluator(Function<Expression, ExpressionEv
152152
if (dataType == DataType.INTEGER) {
153153
return new LeastIntEvaluator.Factory(source(), factories);
154154
}
155-
if (dataType == DataType.LONG) {
155+
if (dataType == DataType.LONG || dataType == DataType.DATETIME) {
156156
return new LeastLongEvaluator.Factory(source(), factories);
157157
}
158158
if (dataType == DataType.KEYWORD

0 commit comments

Comments
 (0)