Skip to content
Merged
Show file tree
Hide file tree
Changes from 18 commits
Commits
Show all changes
20 commits
Select commit Hold shift + click to select a range
b801ba8
implicit casting from string literals to datetime intervals
fang-xing-esql Oct 29, 2024
7c77785
Merge branch 'main' into implicit-casting-string-literal-to-intervals
fang-xing-esql Oct 29, 2024
b416e77
fix test
fang-xing-esql Oct 29, 2024
28c2fe8
clean up
fang-xing-esql Oct 29, 2024
d41b5d1
Update docs/changelog/115814.yaml
fang-xing-esql Oct 29, 2024
0ff8ca8
refactor
fang-xing-esql Oct 30, 2024
e0f7376
Merge branch 'main' into implicit-casting-string-literal-to-intervals
elasticmachine Oct 30, 2024
7cb0c7c
more tests
fang-xing-esql Oct 30, 2024
11fa25d
Merge branch 'main' into implicit-casting-string-literal-to-intervals
fang-xing-esql Oct 30, 2024
6a81ea6
Merge branch 'main' into implicit-casting-string-literal-to-intervals
fang-xing-esql Oct 31, 2024
f17b304
Merge branch 'main' into implicit-casting-string-literal-to-intervals
elasticmachine Oct 31, 2024
dc850d7
Merge branch 'main' into implicit-casting-string-literal-to-intervals
fang-xing-esql Nov 4, 2024
8b31b45
remove implicit casting to intervals for arithmetic operations
fang-xing-esql Nov 4, 2024
bace39a
make functions() static
fang-xing-esql Nov 5, 2024
4c39d94
Merge branch 'main' into implicit-casting-string-literal-to-intervals
fang-xing-esql Nov 5, 2024
a8b1b0f
updated docs and addressed review comments
fang-xing-esql Nov 5, 2024
26a0be7
Merge branch 'main' into implicit-casting-string-literal-to-intervals
elasticmachine Nov 6, 2024
0edf60f
Merge branch 'main' into implicit-casting-string-literal-to-intervals
elasticmachine Nov 6, 2024
9c9a95f
Merge branch 'main' into implicit-casting-string-literal-to-intervals
fang-xing-esql Nov 8, 2024
67b6885
make getTargetType private and update docs
fang-xing-esql Nov 8, 2024
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);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should this not go into NAME_OR_ALIAS_TO_TYPE below? date is an alias.

Copy link
Member Author

@fang-xing-esql fang-xing-esql Nov 13, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I looked into NAME_OR_ALIAS_TO_TYPE a bit further, and it is used by inline_cast only for now. It should have date I think, so that we can support ::date, today only ::datetime is supported. I opened a separate issue to do it, as it requires additional tests, and I'd like to address it separately.

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 @@ -459,7 +459,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