Skip to content

Commit 770fc19

Browse files
ESQL: add date_diff function (#104118)
Same as #103208 Fixes #101942 We had to revert it after a Checkstyle failure (strange it didn't pop up in the CI before merging)
1 parent eecac06 commit 770fc19

File tree

13 files changed

+924
-2
lines changed

13 files changed

+924
-2
lines changed

docs/changelog/104118.yaml

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
pr: 104118
2+
summary: "ESQL: add `date_diff` function"
3+
area: ES|QL
4+
type: enhancement
5+
issues:
6+
- 101942
Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
[discrete]
2+
[[esql-date_diff]]
3+
=== `DATE_DIFF`
4+
Subtract the second argument from the third argument and return their difference in multiples of the unit specified in the first argument.
5+
If the second argument (start) is greater than the third argument (end), then negative values are returned.
6+
7+
[cols="^,^"]
8+
|===
9+
2+h|Datetime difference units
10+
11+
s|unit
12+
s|abbreviations
13+
14+
| year | years, yy, yyyy
15+
| quarter | quarters, qq, q
16+
| month | months, mm, m
17+
| dayofyear | dy, y
18+
| day | days, dd, d
19+
| week | weeks, wk, ww
20+
| weekday | weekdays, dw
21+
| hour | hours, hh
22+
| minute | minutes, mi, n
23+
| second | seconds, ss, s
24+
| millisecond | milliseconds, ms
25+
| microsecond | microseconds, mcs
26+
| nanosecond | nanoseconds, ns
27+
|===
28+
29+
[source.merge.styled,esql]
30+
----
31+
include::{esql-specs}/docs.csv-spec[tag=dateDiff]
32+
----
33+
[%header.monospaced.styled,format=dsv,separator=|]
34+
|===
35+
include::{esql-specs}/docs.csv-spec[tag=dateDiff-result]
36+
|===
37+
Lines changed: 1 addition & 0 deletions
Loading
Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
[%header.monospaced.styled,format=dsv,separator=|]
2+
|===
3+
unit | startTimestamp | endTimestamp | result
4+
keyword | datetime | datetime | integer
5+
text | datetime | datetime | integer
6+
|===

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

Lines changed: 61 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -337,6 +337,67 @@ AVG(salary):double | bucket:date
337337
// end::auto_bucket_in_agg-result[]
338338
;
339339

340+
evalDateDiffInNanoAndMicroAndMilliSeconds#[skip:-8.12.99, reason:date_diff added in 8.13]
341+
ROW date1=to_datetime("2023-12-02T11:00:00.000Z"), date2=to_datetime("2023-12-02T11:00:00.001Z")
342+
| EVAL dd_ns1=date_diff("nanoseconds", date1, date2), dd_ns2=date_diff("ns", date1, date2)
343+
| EVAL dd_mcs1=date_diff("microseconds", date1, date2), dd_mcs2=date_diff("mcs", date1, date2)
344+
| EVAL dd_ms1=date_diff("milliseconds", date1, date2), dd_ms2=date_diff("ms", date1, date2)
345+
| keep dd_ns1, dd_ns2, dd_mcs1, dd_mcs2, dd_ms1, dd_ms2
346+
;
347+
348+
dd_ns1:integer | dd_ns2:integer | dd_mcs1:integer | dd_mcs2:integer | dd_ms1:integer | dd_ms2:integer
349+
1000000 | 1000000 | 1000 | 1000 | 1 | 1
350+
;
351+
352+
evalDateDiffInSecondsAndMinutesAndHours#[skip:-8.12.99, reason:date_diff added in 8.13]
353+
ROW date1=to_datetime("2023-12-02T11:00:00.000Z"), date2=to_datetime("2023-12-02T12:00:00.000Z")
354+
| EVAL dd_s1=date_diff("seconds", date1, date2), dd_s2=date_diff("ss", date1, date2), dd_s3=date_diff("s", date1, date2)
355+
| EVAL dd_m1=date_diff("minutes", date1, date2), dd_m2=date_diff("mi", date1, date2), dd_m3=date_diff("n", date1, date2)
356+
| EVAL dd_h1=date_diff("hours", date1, date2), dd_h2=date_diff("hh", date1, date2)
357+
| keep dd_s1, dd_s2, dd_s3, dd_m1, dd_m2, dd_m3, dd_h1, dd_h2
358+
;
359+
360+
dd_s1:integer | dd_s2:integer | dd_s3:integer | dd_m1:integer | dd_m2:integer | dd_m3:integer | dd_h1:integer | dd_h2:integer
361+
3600 | 3600 | 3600 | 60 | 60 | 60 | 1 | 1
362+
;
363+
364+
evalDateDiffInDaysAndWeeks#[skip:-8.12.99, reason:date_diff added in 8.13]
365+
ROW date1=to_datetime("2023-12-02T11:00:00.000Z"), date2=to_datetime("2023-12-24T11:00:00.000Z")
366+
| EVAL dd_wd1=date_diff("weekdays", date1, date2), dd_wd2=date_diff("dw", date1, date2)
367+
| EVAL dd_w1=date_diff("weeks", date1, date2), dd_w2=date_diff("wk", date1, date2), dd_w3=date_diff("ww", date1, date2)
368+
| EVAL dd_d1=date_diff("dy", date1, date2), dd_d2=date_diff("y", date1, date2)
369+
| EVAL dd_dy1=date_diff("days", date1, date2), dd_dy2=date_diff("dd", date1, date2), dd_dy3=date_diff("d", date1, date2)
370+
| keep dd_wd1, dd_wd2, dd_w1, dd_w2, dd_w3, dd_d1, dd_d2, dd_dy1, dd_dy2, dd_dy3
371+
;
372+
373+
dd_wd1:integer | dd_wd2:integer | dd_w1:integer | dd_w2:integer | dd_w3:integer | dd_d1:integer | dd_d2:integer | dd_dy1:integer | dd_dy2:integer | dd_dy3:integer
374+
22 | 22 | 3 | 3 | 3 | 22 | 22 | 22 | 22 | 22
375+
;
376+
377+
evalDateDiffInMonthsAndQuartersAndYears#[skip:-8.12.99, reason:date_diff added in 8.13]
378+
ROW date1=to_datetime("2023-12-02T11:00:00.000Z"), date2=to_datetime("2024-12-24T11:00:00.000Z")
379+
| EVAL dd_m1=date_diff("months", date1, date2), dd_m2=date_diff("mm", date1, date2), dd_m3=date_diff("m", date1, date2)
380+
| EVAL dd_q1=date_diff("quarters", date1, date2), dd_q2=date_diff("qq", date1, date2), dd_q3=date_diff("q", date1, date2)
381+
| EVAL dd_y1=date_diff("years", date1, date2), dd_y2=date_diff("yyyy", date1, date2), dd_y3=date_diff("yy", date1, date2)
382+
| keep dd_m1, dd_m2, dd_m3, dd_q1, dd_q2, dd_q3, dd_y1, dd_y2, dd_y3
383+
;
384+
385+
dd_m1:integer | dd_m2:integer | dd_m3:integer | dd_q1:integer | dd_q2:integer | dd_q3:integer | dd_y1:integer | dd_y2:integer | dd_y3:integer
386+
12 | 12 | 12 | 4 | 4 | 4 | 1 | 1 | 1
387+
;
388+
389+
evalDateDiffErrorOutOfIntegerRange#[skip:-8.12.99, reason:date_diff added in 8.13]
390+
ROW date1=to_datetime("2023-12-02T11:00:00.000Z"), date2=to_datetime("2023-12-23T11:00:00.000Z")
391+
| EVAL dd_oo=date_diff("nanoseconds", date1, date2)
392+
| keep dd_oo
393+
;
394+
warning: Line 2:14: evaluation of [date_diff(\"nanoseconds\", date1, date2)] failed, treating result as null. Only first 20 failures recorded.
395+
warning: Line 2:14: org.elasticsearch.xpack.ql.InvalidArgumentException: [1814400000000000] out of [integer] range
396+
397+
dd_oo:integer
398+
null
399+
;
400+
340401
evalDateParseWithSimpleDate
341402
row a = "2023-02-01" | eval b = date_parse("yyyy-MM-dd", a) | keep b;
342403

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

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@ cos |"double cos(n:integer|long|double|unsigned_long)"
2626
cosh |"double cosh(n:integer|long|double|unsigned_long)" |n |"integer|long|double|unsigned_long" | "The number who's hyperbolic cosine is to be returned" |double | "Returns the hyperbolic cosine of a number" | false | false
2727
count |? count(arg1:?) |arg1 |? | "" |? | "" | false | false
2828
count_distinct |? count_distinct(arg1:?, arg2:?) |[arg1, arg2] |[?, ?] |["", ""] |? | "" | [false, false] | false
29+
date_diff |"integer date_diff(unit:keyword|text, startTimestamp:date, endTimestamp:date)"|[unit, startTimestamp, endTimestamp] |["keyword|text", "date", "date"] |["A valid date unit", "A string representing a start timestamp", "A string representing an end timestamp"] |integer | "Subtract 2 dates and return their difference in multiples of a unit specified in the 1st argument" | [false, false, false] | false
2930
date_extract |? date_extract(arg1:?, arg2:?) |[arg1, arg2] |[?, ?] |["", ""] |? | "" | [false, false] | false
3031
date_format |? date_format(arg1:?, arg2:?) |[arg1, arg2] |[?, ?] |["", ""] |? | "" | [false, false] | false
3132
date_parse |"date date_parse(?datePattern:keyword, dateString:keyword|text)"|[datePattern, dateString]|["keyword", "keyword|text"]|[A valid date pattern, A string representing a date]|date |Parses a string into a date value | [true, false] | false
@@ -117,6 +118,7 @@ synopsis:keyword
117118
"double cosh(n:integer|long|double|unsigned_long)"
118119
? count(arg1:?)
119120
? count_distinct(arg1:?, arg2:?)
121+
"integer date_diff(unit:keyword|text, startTimestamp:date, endTimestamp:date)"
120122
? date_extract(arg1:?, arg2:?)
121123
? date_format(arg1:?, arg2:?)
122124
"date date_parse(?datePattern:keyword, dateString:keyword|text)"
@@ -205,9 +207,9 @@ is_nan |boolean is_nan(n:double)
205207

206208

207209
// see https://github.com/elastic/elasticsearch/issues/102120
208-
countFunctions#[skip:-8.11.99]
210+
countFunctions#[skip:-8.12.99]
209211
show functions | stats a = count(*), b = count(*), c = count(*) | mv_expand c;
210212

211213
a:long | b:long | c:long
212-
84 | 84 | 84
214+
85 | 85 | 85
213215
;
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,154 @@
1+
// Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
2+
// or more contributor license agreements. Licensed under the Elastic License
3+
// 2.0; you may not use this file except in compliance with the Elastic License
4+
// 2.0.
5+
package org.elasticsearch.xpack.esql.expression.function.scalar.date;
6+
7+
import java.lang.IllegalArgumentException;
8+
import java.lang.Override;
9+
import java.lang.String;
10+
import org.elasticsearch.compute.data.Block;
11+
import org.elasticsearch.compute.data.IntBlock;
12+
import org.elasticsearch.compute.data.LongBlock;
13+
import org.elasticsearch.compute.data.LongVector;
14+
import org.elasticsearch.compute.data.Page;
15+
import org.elasticsearch.compute.operator.DriverContext;
16+
import org.elasticsearch.compute.operator.EvalOperator;
17+
import org.elasticsearch.core.Releasables;
18+
import org.elasticsearch.xpack.esql.expression.function.Warnings;
19+
import org.elasticsearch.xpack.ql.InvalidArgumentException;
20+
import org.elasticsearch.xpack.ql.tree.Source;
21+
22+
/**
23+
* {@link EvalOperator.ExpressionEvaluator} implementation for {@link DateDiff}.
24+
* This class is generated. Do not edit it.
25+
*/
26+
public final class DateDiffConstantEvaluator implements EvalOperator.ExpressionEvaluator {
27+
private final Warnings warnings;
28+
29+
private final DateDiff.Part datePartFieldUnit;
30+
31+
private final EvalOperator.ExpressionEvaluator startTimestamp;
32+
33+
private final EvalOperator.ExpressionEvaluator endTimestamp;
34+
35+
private final DriverContext driverContext;
36+
37+
public DateDiffConstantEvaluator(Source source, DateDiff.Part datePartFieldUnit,
38+
EvalOperator.ExpressionEvaluator startTimestamp,
39+
EvalOperator.ExpressionEvaluator endTimestamp, DriverContext driverContext) {
40+
this.warnings = new Warnings(source);
41+
this.datePartFieldUnit = datePartFieldUnit;
42+
this.startTimestamp = startTimestamp;
43+
this.endTimestamp = endTimestamp;
44+
this.driverContext = driverContext;
45+
}
46+
47+
@Override
48+
public Block eval(Page page) {
49+
try (LongBlock startTimestampBlock = (LongBlock) startTimestamp.eval(page)) {
50+
try (LongBlock endTimestampBlock = (LongBlock) endTimestamp.eval(page)) {
51+
LongVector startTimestampVector = startTimestampBlock.asVector();
52+
if (startTimestampVector == null) {
53+
return eval(page.getPositionCount(), startTimestampBlock, endTimestampBlock);
54+
}
55+
LongVector endTimestampVector = endTimestampBlock.asVector();
56+
if (endTimestampVector == null) {
57+
return eval(page.getPositionCount(), startTimestampBlock, endTimestampBlock);
58+
}
59+
return eval(page.getPositionCount(), startTimestampVector, endTimestampVector);
60+
}
61+
}
62+
}
63+
64+
public IntBlock eval(int positionCount, LongBlock startTimestampBlock,
65+
LongBlock endTimestampBlock) {
66+
try(IntBlock.Builder result = driverContext.blockFactory().newIntBlockBuilder(positionCount)) {
67+
position: for (int p = 0; p < positionCount; p++) {
68+
if (startTimestampBlock.isNull(p)) {
69+
result.appendNull();
70+
continue position;
71+
}
72+
if (startTimestampBlock.getValueCount(p) != 1) {
73+
if (startTimestampBlock.getValueCount(p) > 1) {
74+
warnings.registerException(new IllegalArgumentException("single-value function encountered multi-value"));
75+
}
76+
result.appendNull();
77+
continue position;
78+
}
79+
if (endTimestampBlock.isNull(p)) {
80+
result.appendNull();
81+
continue position;
82+
}
83+
if (endTimestampBlock.getValueCount(p) != 1) {
84+
if (endTimestampBlock.getValueCount(p) > 1) {
85+
warnings.registerException(new IllegalArgumentException("single-value function encountered multi-value"));
86+
}
87+
result.appendNull();
88+
continue position;
89+
}
90+
try {
91+
result.appendInt(DateDiff.process(datePartFieldUnit, startTimestampBlock.getLong(startTimestampBlock.getFirstValueIndex(p)), endTimestampBlock.getLong(endTimestampBlock.getFirstValueIndex(p))));
92+
} catch (IllegalArgumentException | InvalidArgumentException e) {
93+
warnings.registerException(e);
94+
result.appendNull();
95+
}
96+
}
97+
return result.build();
98+
}
99+
}
100+
101+
public IntBlock eval(int positionCount, LongVector startTimestampVector,
102+
LongVector endTimestampVector) {
103+
try(IntBlock.Builder result = driverContext.blockFactory().newIntBlockBuilder(positionCount)) {
104+
position: for (int p = 0; p < positionCount; p++) {
105+
try {
106+
result.appendInt(DateDiff.process(datePartFieldUnit, startTimestampVector.getLong(p), endTimestampVector.getLong(p)));
107+
} catch (IllegalArgumentException | InvalidArgumentException e) {
108+
warnings.registerException(e);
109+
result.appendNull();
110+
}
111+
}
112+
return result.build();
113+
}
114+
}
115+
116+
@Override
117+
public String toString() {
118+
return "DateDiffConstantEvaluator[" + "datePartFieldUnit=" + datePartFieldUnit + ", startTimestamp=" + startTimestamp + ", endTimestamp=" + endTimestamp + "]";
119+
}
120+
121+
@Override
122+
public void close() {
123+
Releasables.closeExpectNoException(startTimestamp, endTimestamp);
124+
}
125+
126+
static class Factory implements EvalOperator.ExpressionEvaluator.Factory {
127+
private final Source source;
128+
129+
private final DateDiff.Part datePartFieldUnit;
130+
131+
private final EvalOperator.ExpressionEvaluator.Factory startTimestamp;
132+
133+
private final EvalOperator.ExpressionEvaluator.Factory endTimestamp;
134+
135+
public Factory(Source source, DateDiff.Part datePartFieldUnit,
136+
EvalOperator.ExpressionEvaluator.Factory startTimestamp,
137+
EvalOperator.ExpressionEvaluator.Factory endTimestamp) {
138+
this.source = source;
139+
this.datePartFieldUnit = datePartFieldUnit;
140+
this.startTimestamp = startTimestamp;
141+
this.endTimestamp = endTimestamp;
142+
}
143+
144+
@Override
145+
public DateDiffConstantEvaluator get(DriverContext context) {
146+
return new DateDiffConstantEvaluator(source, datePartFieldUnit, startTimestamp.get(context), endTimestamp.get(context), context);
147+
}
148+
149+
@Override
150+
public String toString() {
151+
return "DateDiffConstantEvaluator[" + "datePartFieldUnit=" + datePartFieldUnit + ", startTimestamp=" + startTimestamp + ", endTimestamp=" + endTimestamp + "]";
152+
}
153+
}
154+
}

0 commit comments

Comments
 (0)