Skip to content

Commit 2e5be96

Browse files
fang-xing-esqlsmalyshev
authored andcommitted
[ES|QL] Implicit casting string literal to intervals in EsqlScalarFunction and GroupingFunction (#115814)
* implicit casting from string literals to datetime intervals
1 parent 1022eb3 commit 2e5be96

File tree

13 files changed

+458
-85
lines changed

13 files changed

+458
-85
lines changed

docs/changelog/115814.yaml

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
pr: 115814
2+
summary: "[ES|QL] Implicit casting string literal to intervals"
3+
area: ES|QL
4+
type: enhancement
5+
issues:
6+
- 115352

docs/reference/esql/implicit-casting.asciidoc

Lines changed: 26 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@
55
<titleabbrev>Implicit casting</titleabbrev>
66
++++
77

8-
Often users will input `datetime`, `ip`, `version`, or geospatial objects as simple strings in their queries for use in predicates, functions, or expressions. {esql} provides <<esql-type-conversion-functions, type conversion functions>> to explicitly convert these strings into the desired data types.
8+
Often users will input `date`, `ip`, `version`, `date_period` or `time_duration` as simple strings in their queries for use in predicates, functions, or expressions. {esql} provides <<esql-type-conversion-functions, type conversion functions>> to explicitly convert these strings into the desired data types.
99

1010
Without implicit casting users must explicitly code these `to_X` functions in their queries, when string literals don't match the target data types they are assigned or compared to. Here is an example of using `to_datetime` to explicitly perform a data type conversion.
1111

@@ -18,7 +18,7 @@ FROM employees
1818
| LIMIT 1
1919
----
2020

21-
Implicit casting improves usability, by automatically converting string literals to the target data type. This is most useful when the target data type is `datetime`, `ip`, `version` or a geo spatial. It is natural to specify these as a string in queries.
21+
Implicit casting improves usability, by automatically converting string literals to the target data type. This is most useful when the target data type is `date`, `ip`, `version`, `date_period` or `time_duration`. It is natural to specify these as a string in queries.
2222

2323
The first query can be coded without calling the `to_datetime` function, as follows:
2424

@@ -38,16 +38,28 @@ The following table details which {esql} operations support implicit casting for
3838

3939
[%header.monospaced.styled,format=dsv,separator=|]
4040
|===
41-
||ScalarFunction|BinaryComparison|ArithmeticOperation|InListPredicate|AggregateFunction
42-
|DATETIME|Y|Y|Y|Y|N
43-
|DOUBLE|Y|N|N|N|N
44-
|LONG|Y|N|N|N|N
45-
|INTEGER|Y|N|N|N|N
46-
|IP|Y|Y|Y|Y|N
47-
|VERSION|Y|Y|Y|Y|N
48-
|GEO_POINT|Y|N|N|N|N
49-
|GEO_SHAPE|Y|N|N|N|N
50-
|CARTESIAN_POINT|Y|N|N|N|N
51-
|CARTESIAN_SHAPE|Y|N|N|N|N
52-
|BOOLEAN|Y|Y|Y|Y|N
41+
||ScalarFunction*|Operator*|<<esql-group-functions, GroupingFunction>>|<<esql-agg-functions, AggregateFunction>>
42+
|DATE|Y|Y|Y|N
43+
|IP|Y|Y|Y|N
44+
|VERSION|Y|Y|Y|N
45+
|BOOLEAN|Y|Y|Y|N
46+
|DATE_PERIOD/TIME_DURATION|Y|N|Y|N
5347
|===
48+
49+
ScalarFunction* includes:
50+
51+
<<esql-conditional-functions-and-expressions, Conditional Functions and Expressions>>
52+
53+
<<esql-date-time-functions, Date and Time Functions>>
54+
55+
<<esql-ip-functions, IP Functions>>
56+
57+
58+
Operator* includes:
59+
60+
<<esql-binary-operators, Binary Operators>>
61+
62+
<<esql-unary-operators, Unary Operator>>
63+
64+
<<esql-in-operator, IN>>
65+

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

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -29,7 +29,6 @@
2929
import java.util.function.Function;
3030

3131
import static java.util.stream.Collectors.toMap;
32-
import static java.util.stream.Collectors.toUnmodifiableMap;
3332
import static org.elasticsearch.xpack.esql.core.util.PlanStreamInput.readCachedStringWithVersionCheck;
3433
import static org.elasticsearch.xpack.esql.core.util.PlanStreamOutput.writeCachedStringWithVersionCheck;
3534

@@ -276,7 +275,7 @@ public enum DataType {
276275

277276
private static final Collection<DataType> STRING_TYPES = DataType.types().stream().filter(DataType::isString).toList();
278277

279-
private static final Map<String, DataType> NAME_TO_TYPE = TYPES.stream().collect(toUnmodifiableMap(DataType::typeName, t -> t));
278+
private static final Map<String, DataType> NAME_TO_TYPE;
280279

281280
private static final Map<String, DataType> ES_TO_TYPE;
282281

@@ -287,6 +286,10 @@ public enum DataType {
287286
map.put("point", DataType.CARTESIAN_POINT);
288287
map.put("shape", DataType.CARTESIAN_SHAPE);
289288
ES_TO_TYPE = Collections.unmodifiableMap(map);
289+
// DATETIME has different esType and typeName, add an entry in NAME_TO_TYPE with date as key
290+
map = TYPES.stream().collect(toMap(DataType::typeName, t -> t));
291+
map.put("date", DataType.DATETIME);
292+
NAME_TO_TYPE = Collections.unmodifiableMap(map);
290293
}
291294

292295
private static final Map<String, DataType> NAME_OR_ALIAS_TO_TYPE;

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

Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -716,3 +716,47 @@ FROM employees
716716
2 |1985-10-01T00:00:00.000Z
717717
4 |1985-11-01T00:00:00.000Z
718718
;
719+
720+
bucketByWeekInString
721+
required_capability: implicit_casting_string_literal_to_temporal_amount
722+
FROM employees
723+
| WHERE hire_date >= "1985-01-01T00:00:00Z" AND hire_date < "1986-01-01T00:00:00Z"
724+
| STATS hires_per_week = COUNT(*) BY week = BUCKET(hire_date, "1 week")
725+
| SORT week
726+
;
727+
728+
hires_per_week:long | week:date
729+
2 |1985-02-18T00:00:00.000Z
730+
1 |1985-05-13T00:00:00.000Z
731+
1 |1985-07-08T00:00:00.000Z
732+
1 |1985-09-16T00:00:00.000Z
733+
2 |1985-10-14T00:00:00.000Z
734+
4 |1985-11-18T00:00:00.000Z
735+
;
736+
737+
bucketByMinuteInString
738+
required_capability: implicit_casting_string_literal_to_temporal_amount
739+
740+
FROM sample_data
741+
| STATS min = min(@timestamp), max = MAX(@timestamp) BY bucket = BUCKET(@timestamp, "30 minutes")
742+
| SORT min
743+
;
744+
745+
min:date | max:date | bucket:date
746+
2023-10-23T12:15:03.360Z|2023-10-23T12:27:28.948Z|2023-10-23T12:00:00.000Z
747+
2023-10-23T13:33:34.937Z|2023-10-23T13:55:01.543Z|2023-10-23T13:30:00.000Z
748+
;
749+
750+
bucketByMonthInString
751+
required_capability: implicit_casting_string_literal_to_temporal_amount
752+
753+
FROM sample_data
754+
| EVAL adjusted = CASE(TO_LONG(@timestamp) % 2 == 0, @timestamp + 1 month, @timestamp + 2 years)
755+
| STATS c = COUNT(*) BY b = BUCKET(adjusted, "1 month")
756+
| SORT c
757+
;
758+
759+
c:long |b:date
760+
3 |2025-10-01T00:00:00.000Z
761+
4 |2023-11-01T00:00:00.000Z
762+
;

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

Lines changed: 105 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1286,3 +1286,108 @@ ROW a = GREATEST(TO_DATETIME("1957-05-23T00:00:00Z"), TO_DATETIME("1958-02-19T00
12861286
a:datetime
12871287
1958-02-19T00:00:00
12881288
;
1289+
1290+
evalDateTruncMonthInString
1291+
required_capability: implicit_casting_string_literal_to_temporal_amount
1292+
1293+
FROM employees
1294+
| SORT hire_date
1295+
| EVAL x = date_trunc("1 month", hire_date)
1296+
| KEEP emp_no, hire_date, x
1297+
| LIMIT 5;
1298+
1299+
emp_no:integer | hire_date:date | x:date
1300+
10009 | 1985-02-18T00:00:00.000Z | 1985-02-01T00:00:00.000Z
1301+
10048 | 1985-02-24T00:00:00.000Z | 1985-02-01T00:00:00.000Z
1302+
10098 | 1985-05-13T00:00:00.000Z | 1985-05-01T00:00:00.000Z
1303+
10076 | 1985-07-09T00:00:00.000Z | 1985-07-01T00:00:00.000Z
1304+
10061 | 1985-09-17T00:00:00.000Z | 1985-09-01T00:00:00.000Z
1305+
;
1306+
1307+
evalDateTruncHourInString
1308+
required_capability: implicit_casting_string_literal_to_temporal_amount
1309+
1310+
FROM employees
1311+
| SORT hire_date
1312+
| EVAL x = date_trunc("240 hours", hire_date)
1313+
| KEEP emp_no, hire_date, x
1314+
| LIMIT 5;
1315+
1316+
emp_no:integer | hire_date:date | x:date
1317+
10009 | 1985-02-18T00:00:00.000Z | 1985-02-11T00:00:00.000Z
1318+
10048 | 1985-02-24T00:00:00.000Z | 1985-02-21T00:00:00.000Z
1319+
10098 | 1985-05-13T00:00:00.000Z | 1985-05-12T00:00:00.000Z
1320+
10076 | 1985-07-09T00:00:00.000Z | 1985-07-01T00:00:00.000Z
1321+
10061 | 1985-09-17T00:00:00.000Z | 1985-09-09T00:00:00.000Z
1322+
;
1323+
1324+
evalDateTruncDayInString
1325+
required_capability: implicit_casting_string_literal_to_temporal_amount
1326+
1327+
FROM sample_data
1328+
| SORT @timestamp ASC
1329+
| EVAL t = DATE_TRUNC("1 day", @timestamp)
1330+
| KEEP t;
1331+
1332+
t:date
1333+
2023-10-23T00:00:00
1334+
2023-10-23T00:00:00
1335+
2023-10-23T00:00:00
1336+
2023-10-23T00:00:00
1337+
2023-10-23T00:00:00
1338+
2023-10-23T00:00:00
1339+
2023-10-23T00:00:00
1340+
;
1341+
1342+
evalDateTruncMinuteInString
1343+
required_capability: implicit_casting_string_literal_to_temporal_amount
1344+
1345+
FROM sample_data
1346+
| SORT @timestamp ASC
1347+
| EVAL t = DATE_TRUNC("1 minute", @timestamp)
1348+
| KEEP t;
1349+
1350+
t:date
1351+
2023-10-23T12:15:00
1352+
2023-10-23T12:27:00
1353+
2023-10-23T13:33:00
1354+
2023-10-23T13:51:00
1355+
2023-10-23T13:52:00
1356+
2023-10-23T13:53:00
1357+
2023-10-23T13:55:00
1358+
;
1359+
1360+
evalDateTruncDayInStringNull
1361+
required_capability: implicit_casting_string_literal_to_temporal_amount
1362+
1363+
FROM employees
1364+
| WHERE emp_no == 10040
1365+
| EVAL x = date_trunc("1 day", birth_date)
1366+
| KEEP emp_no, birth_date, x;
1367+
1368+
emp_no:integer | birth_date:date | x:date
1369+
10040 | null | null
1370+
;
1371+
1372+
evalDateTruncYearInString
1373+
required_capability: implicit_casting_string_literal_to_temporal_amount
1374+
1375+
ROW a = 1
1376+
| EVAL year_hired = DATE_TRUNC("1 year", "1991-06-26T00:00:00.000Z")
1377+
;
1378+
1379+
a:integer | year_hired:date
1380+
1 | 1991-01-01T00:00:00.000Z
1381+
;
1382+
1383+
filteringWithTemporalAmountInString
1384+
required_capability: implicit_casting_string_literal_to_temporal_amount
1385+
1386+
FROM employees
1387+
| SORT emp_no
1388+
| WHERE birth_date < "2024-01-01" - 70 years
1389+
| STATS cnt = count(*);
1390+
1391+
cnt:long
1392+
19
1393+
;

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

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -481,7 +481,12 @@ public enum Cap {
481481
* - Introduce BinaryPlan and co
482482
* - Refactor INLINESTATS and LOOKUP as a JOIN block
483483
*/
484-
JOIN_PLANNING_V1(Build.current().isSnapshot());
484+
JOIN_PLANNING_V1(Build.current().isSnapshot()),
485+
486+
/**
487+
* Support implicit casting from string literal to DATE_PERIOD or TIME_DURATION.
488+
*/
489+
IMPLICIT_CASTING_STRING_LITERAL_TO_TEMPORAL_AMOUNT;
485490

486491
private final boolean enabled;
487492

0 commit comments

Comments
 (0)