Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions docs/changelog/115814.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
pr: 115814
summary: "[ES|QL] Implicit casting string literal to intervals"
area: ES|QL
type: enhancement
issues:
- 115352
40 changes: 26 additions & 14 deletions docs/reference/esql/implicit-casting.asciidoc
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
<titleabbrev>Implicit casting</titleabbrev>
++++

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.
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.

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.

Expand All @@ -18,7 +18,7 @@ FROM employees
| LIMIT 1
----

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.
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.

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

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

[%header.monospaced.styled,format=dsv,separator=|]
|===
||ScalarFunction|BinaryComparison|ArithmeticOperation|InListPredicate|AggregateFunction
|DATETIME|Y|Y|Y|Y|N
|DOUBLE|Y|N|N|N|N
|LONG|Y|N|N|N|N
|INTEGER|Y|N|N|N|N
|IP|Y|Y|Y|Y|N
|VERSION|Y|Y|Y|Y|N
|GEO_POINT|Y|N|N|N|N
|GEO_SHAPE|Y|N|N|N|N
|CARTESIAN_POINT|Y|N|N|N|N
|CARTESIAN_SHAPE|Y|N|N|N|N
|BOOLEAN|Y|Y|Y|Y|N
||ScalarFunction*|Operator*|<<esql-group-functions, GroupingFunction>>|<<esql-agg-functions, AggregateFunction>>
|DATE|Y|Y|Y|N
|IP|Y|Y|Y|N
|VERSION|Y|Y|Y|N
|BOOLEAN|Y|Y|Y|N
|DATE_PERIOD/TIME_DURATION|Y|N|Y|N
|===

ScalarFunction* includes:

<<esql-conditional-functions-and-expressions, Conditional Functions and Expressions>>

<<esql-date-time-functions, Date and Time Functions>>

<<esql-ip-functions, IP Functions>>


Operator* includes:

<<esql-binary-operators, Binary Operators>>

<<esql-unary-operators, Unary Operator>>

<<esql-in-operator, IN>>

Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,6 @@
import java.util.function.Function;

import static java.util.stream.Collectors.toMap;
import static java.util.stream.Collectors.toUnmodifiableMap;
import static org.elasticsearch.xpack.esql.core.util.PlanStreamInput.readCachedStringWithVersionCheck;
import static org.elasticsearch.xpack.esql.core.util.PlanStreamOutput.writeCachedStringWithVersionCheck;

Expand Down Expand Up @@ -276,7 +275,7 @@ public enum DataType {

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

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

private static final Map<String, DataType> ES_TO_TYPE;

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

private static final Map<String, DataType> NAME_OR_ALIAS_TO_TYPE;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -716,3 +716,47 @@ FROM employees
2 |1985-10-01T00:00:00.000Z
4 |1985-11-01T00:00:00.000Z
;

bucketByWeekInString
required_capability: implicit_casting_string_literal_to_temporal_amount
FROM employees
| WHERE hire_date >= "1985-01-01T00:00:00Z" AND hire_date < "1986-01-01T00:00:00Z"
| STATS hires_per_week = COUNT(*) BY week = BUCKET(hire_date, "1 week")
| SORT week
;

hires_per_week:long | week:date
2 |1985-02-18T00:00:00.000Z
1 |1985-05-13T00:00:00.000Z
1 |1985-07-08T00:00:00.000Z
1 |1985-09-16T00:00:00.000Z
2 |1985-10-14T00:00:00.000Z
4 |1985-11-18T00:00:00.000Z
;

bucketByMinuteInString
required_capability: implicit_casting_string_literal_to_temporal_amount

FROM sample_data
| STATS min = min(@timestamp), max = MAX(@timestamp) BY bucket = BUCKET(@timestamp, "30 minutes")
| SORT min
;

min:date | max:date | bucket:date
2023-10-23T12:15:03.360Z|2023-10-23T12:27:28.948Z|2023-10-23T12:00:00.000Z
2023-10-23T13:33:34.937Z|2023-10-23T13:55:01.543Z|2023-10-23T13:30:00.000Z
;

bucketByMonthInString
required_capability: implicit_casting_string_literal_to_temporal_amount

FROM sample_data
| EVAL adjusted = CASE(TO_LONG(@timestamp) % 2 == 0, @timestamp + 1 month, @timestamp + 2 years)
| STATS c = COUNT(*) BY b = BUCKET(adjusted, "1 month")
| SORT c
;

c:long |b:date
3 |2025-10-01T00:00:00.000Z
4 |2023-11-01T00:00:00.000Z
;
105 changes: 105 additions & 0 deletions x-pack/plugin/esql/qa/testFixtures/src/main/resources/date.csv-spec
Original file line number Diff line number Diff line change
Expand Up @@ -1286,3 +1286,108 @@ ROW a = GREATEST(TO_DATETIME("1957-05-23T00:00:00Z"), TO_DATETIME("1958-02-19T00
a:datetime
1958-02-19T00:00:00
;

evalDateTruncMonthInString
required_capability: implicit_casting_string_literal_to_temporal_amount

FROM employees
| SORT hire_date
| EVAL x = date_trunc("1 month", hire_date)
| KEEP emp_no, hire_date, x
| LIMIT 5;

emp_no:integer | hire_date:date | x:date
10009 | 1985-02-18T00:00:00.000Z | 1985-02-01T00:00:00.000Z
10048 | 1985-02-24T00:00:00.000Z | 1985-02-01T00:00:00.000Z
10098 | 1985-05-13T00:00:00.000Z | 1985-05-01T00:00:00.000Z
10076 | 1985-07-09T00:00:00.000Z | 1985-07-01T00:00:00.000Z
10061 | 1985-09-17T00:00:00.000Z | 1985-09-01T00:00:00.000Z
;

evalDateTruncHourInString
required_capability: implicit_casting_string_literal_to_temporal_amount

FROM employees
| SORT hire_date
| EVAL x = date_trunc("240 hours", hire_date)
| KEEP emp_no, hire_date, x
| LIMIT 5;

emp_no:integer | hire_date:date | x:date
10009 | 1985-02-18T00:00:00.000Z | 1985-02-11T00:00:00.000Z
10048 | 1985-02-24T00:00:00.000Z | 1985-02-21T00:00:00.000Z
10098 | 1985-05-13T00:00:00.000Z | 1985-05-12T00:00:00.000Z
10076 | 1985-07-09T00:00:00.000Z | 1985-07-01T00:00:00.000Z
10061 | 1985-09-17T00:00:00.000Z | 1985-09-09T00:00:00.000Z
;

evalDateTruncDayInString
required_capability: implicit_casting_string_literal_to_temporal_amount

FROM sample_data
| SORT @timestamp ASC
| EVAL t = DATE_TRUNC("1 day", @timestamp)
| KEEP t;

t:date
2023-10-23T00:00:00
2023-10-23T00:00:00
2023-10-23T00:00:00
2023-10-23T00:00:00
2023-10-23T00:00:00
2023-10-23T00:00:00
2023-10-23T00:00:00
;

evalDateTruncMinuteInString
required_capability: implicit_casting_string_literal_to_temporal_amount

FROM sample_data
| SORT @timestamp ASC
| EVAL t = DATE_TRUNC("1 minute", @timestamp)
| KEEP t;

t:date
2023-10-23T12:15:00
2023-10-23T12:27:00
2023-10-23T13:33:00
2023-10-23T13:51:00
2023-10-23T13:52:00
2023-10-23T13:53:00
2023-10-23T13:55:00
;

evalDateTruncDayInStringNull
required_capability: implicit_casting_string_literal_to_temporal_amount

FROM employees
| WHERE emp_no == 10040
| EVAL x = date_trunc("1 day", birth_date)
| KEEP emp_no, birth_date, x;

emp_no:integer | birth_date:date | x:date
10040 | null | null
;

evalDateTruncYearInString
required_capability: implicit_casting_string_literal_to_temporal_amount

ROW a = 1
| EVAL year_hired = DATE_TRUNC("1 year", "1991-06-26T00:00:00.000Z")
;

a:integer | year_hired:date
1 | 1991-01-01T00:00:00.000Z
;

filteringWithTemporalAmountInString
required_capability: implicit_casting_string_literal_to_temporal_amount

FROM employees
| SORT emp_no
| WHERE birth_date < "2024-01-01" - 70 years
| STATS cnt = count(*);

cnt:long
19
;
Original file line number Diff line number Diff line change
Expand Up @@ -475,7 +475,12 @@ public enum Cap {
* - Introduce BinaryPlan and co
* - Refactor INLINESTATS and LOOKUP as a JOIN block
*/
JOIN_PLANNING_V1(Build.current().isSnapshot());
JOIN_PLANNING_V1(Build.current().isSnapshot()),

/**
* Support implicit casting from string literal to DATE_PERIOD or TIME_DURATION.
*/
IMPLICIT_CASTING_STRING_LITERAL_TO_TEMPORAL_AMOUNT;

private final boolean enabled;

Expand Down
Loading
Loading