Skip to content

Commit 6ff953e

Browse files
authored
SQL: make date/datetime and interval types compatible in conditional functions (#47595)
1 parent 448d19f commit 6ff953e

File tree

11 files changed

+162
-57
lines changed

11 files changed

+162
-57
lines changed

x-pack/plugin/sql/qa/src/main/resources/conditionals.csv-spec

Lines changed: 101 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -348,3 +348,104 @@ SELECT CONVERT(IIF(languages > 1, IIF(languages = 3, '3')), SQL_BIGINT) AS cond
348348
3
349349
null
350350
;
351+
352+
ifNullWithCompatibleDateBasedValues
353+
schema::replacement:ts
354+
SELECT IFNULL(birth_date, {d '2110-04-12'}) AS replacement FROM test_emp GROUP BY 1 ORDER BY replacement DESC LIMIT 5;
355+
356+
replacement
357+
------------------------
358+
2110-04-12T00:00:00.000Z
359+
1965-01-03T00:00:00.000Z
360+
1964-10-18T00:00:00.000Z
361+
1964-06-11T00:00:00.000Z
362+
1964-06-02T00:00:00.000Z
363+
;
364+
365+
caseWithCompatibleIntervals_1
366+
schema::date_math:ts|c:l
367+
SELECT birth_date + (CASE WHEN gender='M' THEN INTERVAL 1 YEAR ELSE INTERVAL 6 MONTH END) AS date_math, COUNT(*) c FROM test_emp GROUP BY 1 ORDER BY 1 DESC LIMIT 5;
368+
369+
date_math | c
370+
------------------------+---------------
371+
1966-01-03T00:00:00.000Z|1
372+
1965-06-11T00:00:00.000Z|1
373+
1965-04-18T00:00:00.000Z|2
374+
1964-12-02T00:00:00.000Z|1
375+
1964-11-26T00:00:00.000Z|1
376+
;
377+
378+
caseWithCompatibleIntervals_2
379+
SELECT hire_date, birth_date, (CASE WHEN birth_date > {d '1960-01-01'} THEN INTERVAL 1 YEAR ELSE INTERVAL 1 MONTH END) AS x FROM test_emp WHERE x + hire_date > {d '1995-01-01'} ORDER BY hire_date;
380+
381+
hire_date | birth_date | x
382+
------------------------+------------------------+---------------
383+
1994-04-09T00:00:00.000Z|1962-11-07T00:00:00.000Z|+1-0
384+
1995-01-27T00:00:00.000Z|1961-05-02T00:00:00.000Z|+1-0
385+
1995-03-13T00:00:00.000Z|1957-04-04T00:00:00.000Z|+0-1
386+
1995-03-20T00:00:00.000Z|1953-04-03T00:00:00.000Z|+0-1
387+
1995-08-22T00:00:00.000Z|1952-07-08T00:00:00.000Z|+0-1
388+
1995-12-15T00:00:00.000Z|1960-05-25T00:00:00.000Z|+1-0
389+
1996-11-05T00:00:00.000Z|1964-06-11T00:00:00.000Z|+1-0
390+
1997-05-19T00:00:00.000Z|1958-09-05T00:00:00.000Z|+0-1
391+
1999-04-30T00:00:00.000Z|1953-01-23T00:00:00.000Z|+0-1
392+
;
393+
394+
iifWithCompatibleIntervals
395+
schema::hire_date + IIF(salary > 70000, INTERVAL 2 HOURS, INTERVAL 2 DAYS):ts|salary:i
396+
SELECT hire_date + IIF(salary > 70000, INTERVAL 2 HOURS, INTERVAL 2 DAYS), salary FROM test_emp ORDER BY salary DESC LIMIT 10;
397+
398+
hire_date + IIF(salary > 70000, INTERVAL 2 HOURS, INTERVAL 2 DAYS)| salary
399+
------------------------------------------------------------------+---------------
400+
1985-11-20T02:00:00.000Z |74999
401+
1989-09-02T02:00:00.000Z |74970
402+
1989-02-10T02:00:00.000Z |74572
403+
1989-07-07T02:00:00.000Z |73851
404+
1999-04-30T02:00:00.000Z |73717
405+
1988-10-18T02:00:00.000Z |73578
406+
1990-09-15T02:00:00.000Z |71165
407+
1987-03-18T02:00:00.000Z |70011
408+
1987-05-28T00:00:00.000Z |69904
409+
1990-02-18T00:00:00.000Z |68547
410+
;
411+
412+
isNullWithIntervalMath
413+
SELECT ISNULL(birth_date, INTERVAL '23:45' HOUR TO MINUTES + {d '2019-09-17'}) AS c, salary, birth_date, hire_date FROM test_emp ORDER BY salary DESC LIMIT 5;
414+
415+
c:ts | salary:i | birth_date:ts | hire_date:ts
416+
------------------------+-----------------+------------------------+------------------------
417+
1956-12-13T00:00:00.000Z|74999 |1956-12-13T00:00:00.000Z|1985-11-20T00:00:00.000Z
418+
2019-09-17T00:00:00.000Z|74970 |null |1989-09-02T00:00:00.000Z
419+
1957-05-23T00:00:00.000Z|74572 |1957-05-23T00:00:00.000Z|1989-02-10T00:00:00.000Z
420+
1962-07-10T00:00:00.000Z|73851 |1962-07-10T00:00:00.000Z|1989-07-07T00:00:00.000Z
421+
1953-01-23T00:00:00.000Z|73717 |1953-01-23T00:00:00.000Z|1999-04-30T00:00:00.000Z
422+
;
423+
424+
coalesceWithCompatibleDateBasedTypes
425+
SELECT COALESCE(birth_date, CAST(birth_date AS DATE), CAST(hire_date AS DATETIME)) AS coalesce FROM test_emp ORDER BY 1 LIMIT 5;
426+
427+
coalesce:ts
428+
------------------------
429+
1952-02-27T00:00:00.000Z
430+
1952-04-19T00:00:00.000Z
431+
1952-05-15T00:00:00.000Z
432+
1952-06-13T00:00:00.000Z
433+
1952-07-08T00:00:00.000Z
434+
;
435+
436+
greatestWithCompatibleDateBasedTypes
437+
SELECT GREATEST(null, null, birth_date + INTERVAL 25 YEARS, hire_date + INTERVAL 2 DAYS, CAST(hire_date + INTERVAL 2 DAYS AS DATE)) AS greatest, birth_date, hire_date FROM test_emp ORDER BY 1 LIMIT 10;
438+
439+
greatest:ts | birth_date:ts | hire_date:ts
440+
------------------------+------------------------+------------------------
441+
1985-02-20T00:00:00.000Z|1952-04-19T00:00:00.000Z|1985-02-18T00:00:00.000Z
442+
1985-02-26T00:00:00.000Z|null |1985-02-24T00:00:00.000Z
443+
1985-07-11T00:00:00.000Z|1952-06-13T00:00:00.000Z|1985-07-09T00:00:00.000Z
444+
1985-10-16T00:00:00.000Z|1955-08-20T00:00:00.000Z|1985-10-14T00:00:00.000Z
445+
1985-11-21T00:00:00.000Z|1957-12-03T00:00:00.000Z|1985-11-19T00:00:00.000Z
446+
1985-11-22T00:00:00.000Z|1956-12-13T00:00:00.000Z|1985-11-20T00:00:00.000Z
447+
1985-11-22T00:00:00.000Z|1959-04-07T00:00:00.000Z|1985-11-20T00:00:00.000Z
448+
1986-02-06T00:00:00.000Z|1954-09-13T00:00:00.000Z|1986-02-04T00:00:00.000Z
449+
1986-02-28T00:00:00.000Z|1952-11-13T00:00:00.000Z|1986-02-26T00:00:00.000Z
450+
1986-05-30T00:00:00.000Z|1961-05-30T00:00:00.000Z|1986-03-14T00:00:00.000Z
451+
;

x-pack/plugin/sql/qa/src/main/resources/filter.csv-spec

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -119,3 +119,23 @@ SELECT COUNT(*), TRUNCATE(emp_no, -2) t FROM test_emp WHERE 'aaabbb' RLIKE 'a{2,
119119
99 |10000
120120
1 |10100
121121
;
122+
123+
inWithCompatibleDateTypes
124+
SELECT birth_date FROM test_emp WHERE birth_date IN ({d '1959-07-23'},CAST('1959-12-25T12:12:12' AS TIMESTAMP)) OR birth_date IS NULL ORDER BY birth_date;
125+
126+
birth_date:ts
127+
------------------------
128+
1959-07-23T00:00:00.000Z
129+
1959-07-23T00:00:00.000Z
130+
1959-12-25T00:00:00.000Z
131+
null
132+
null
133+
null
134+
null
135+
null
136+
null
137+
null
138+
null
139+
null
140+
null
141+
;

x-pack/plugin/sql/src/main/java/org/elasticsearch/xpack/sql/expression/function/grouping/Histogram.java

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,6 @@
1212
import org.elasticsearch.xpack.sql.tree.NodeInfo;
1313
import org.elasticsearch.xpack.sql.tree.Source;
1414
import org.elasticsearch.xpack.sql.type.DataType;
15-
import org.elasticsearch.xpack.sql.type.DataTypes;
1615

1716
import java.time.ZoneId;
1817
import java.util.Collections;
@@ -48,7 +47,7 @@ protected TypeResolution resolveType() {
4847
if (resolution == TypeResolution.TYPE_RESOLVED) {
4948
// interval must be Literal interval
5049
if (field().dataType().isDateBased()) {
51-
resolution = isType(interval, DataTypes::isInterval, "(Date) HISTOGRAM", ParamOrdinal.SECOND, "interval");
50+
resolution = isType(interval, DataType::isInterval, "(Date) HISTOGRAM", ParamOrdinal.SECOND, "interval");
5251
} else {
5352
resolution = isNumeric(interval, "(Numeric) HISTOGRAM", ParamOrdinal.SECOND);
5453
}

x-pack/plugin/sql/src/main/java/org/elasticsearch/xpack/sql/expression/predicate/operator/arithmetic/DateTimeArithmeticOperation.java

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,6 @@
1111
import org.elasticsearch.xpack.sql.tree.Source;
1212
import org.elasticsearch.xpack.sql.type.DataType;
1313
import org.elasticsearch.xpack.sql.type.DataTypeConversion;
14-
import org.elasticsearch.xpack.sql.type.DataTypes;
1514

1615
import static org.elasticsearch.common.logging.LoggerMessageFormat.format;
1716

@@ -41,7 +40,7 @@ protected TypeResolution resolveType() {
4140
return TypeResolution.TYPE_RESOLVED;
4241
}
4342
// 2. 3. 4. intervals
44-
if ((DataTypes.isInterval(l) || DataTypes.isInterval(r))) {
43+
if (l.isInterval() || r.isInterval()) {
4544
if (DataTypeConversion.commonType(l, r) == null) {
4645
return new TypeResolution(format(null, "[{}] has arguments with incompatible types [{}] and [{}]", symbol(), l, r));
4746
} else {
@@ -57,7 +56,7 @@ protected TypeResolution resolveWithIntervals() {
5756
DataType l = left().dataType();
5857
DataType r = right().dataType();
5958

60-
if (!(r.isDateOrTimeBased() || DataTypes.isInterval(r))|| !(l.isDateOrTimeBased() || DataTypes.isInterval(l))) {
59+
if (!(r.isDateOrTimeBased() || r.isInterval())|| !(l.isDateOrTimeBased() || l.isInterval())) {
6160
return new TypeResolution(format(null, "[{}] has arguments with incompatible types [{}] and [{}]", symbol(), l, r));
6261
}
6362
return TypeResolution.TYPE_RESOLVED;

x-pack/plugin/sql/src/main/java/org/elasticsearch/xpack/sql/expression/predicate/operator/arithmetic/Mul.java

Lines changed: 3 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -7,10 +7,9 @@
77

88
import org.elasticsearch.xpack.sql.expression.Expression;
99
import org.elasticsearch.xpack.sql.expression.predicate.operator.arithmetic.BinaryArithmeticProcessor.BinaryArithmeticOperation;
10-
import org.elasticsearch.xpack.sql.tree.Source;
1110
import org.elasticsearch.xpack.sql.tree.NodeInfo;
11+
import org.elasticsearch.xpack.sql.tree.Source;
1212
import org.elasticsearch.xpack.sql.type.DataType;
13-
import org.elasticsearch.xpack.sql.type.DataTypes;
1413

1514
import static org.elasticsearch.common.logging.LoggerMessageFormat.format;
1615

@@ -39,10 +38,10 @@ protected TypeResolution resolveType() {
3938
return TypeResolution.TYPE_RESOLVED;
4039
}
4140

42-
if (DataTypes.isInterval(l) && r.isInteger()) {
41+
if (l.isInterval() && r.isInteger()) {
4342
dataType = l;
4443
return TypeResolution.TYPE_RESOLVED;
45-
} else if (DataTypes.isInterval(r) && l.isInteger()) {
44+
} else if (r.isInterval() && l.isInteger()) {
4645
dataType = r;
4746
return TypeResolution.TYPE_RESOLVED;
4847
}

x-pack/plugin/sql/src/main/java/org/elasticsearch/xpack/sql/expression/predicate/operator/arithmetic/Sub.java

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,6 @@
99
import org.elasticsearch.xpack.sql.expression.predicate.operator.arithmetic.BinaryArithmeticProcessor.BinaryArithmeticOperation;
1010
import org.elasticsearch.xpack.sql.tree.NodeInfo;
1111
import org.elasticsearch.xpack.sql.tree.Source;
12-
import org.elasticsearch.xpack.sql.type.DataTypes;
1312

1413
import static org.elasticsearch.common.logging.LoggerMessageFormat.format;
1514

@@ -38,7 +37,7 @@ protected TypeResolution resolveWithIntervals() {
3837
if (resolution.unresolved()) {
3938
return resolution;
4039
}
41-
if ((right().dataType().isDateOrTimeBased()) && DataTypes.isInterval(left().dataType())) {
40+
if ((right().dataType().isDateOrTimeBased()) && left().dataType().isInterval()) {
4241
return new TypeResolution(format(null, "Cannot subtract a {}[{}] from an interval[{}]; do you mean the reverse?",
4342
right().dataType().typeName, right().source().text(), left().source().text()));
4443
}

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

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -271,6 +271,21 @@ public boolean isDateOrTimeBased() {
271271
return isDateBased() || isTimeBased();
272272
}
273273

274+
public boolean isInterval() {
275+
int ordinal = this.ordinal();
276+
return ordinal >= INTERVAL_YEAR.ordinal() && ordinal <= INTERVAL_MINUTE_TO_SECOND.ordinal();
277+
}
278+
279+
public boolean isYearMonthInterval() {
280+
return this == INTERVAL_YEAR || this == INTERVAL_MONTH || this == INTERVAL_YEAR_TO_MONTH;
281+
}
282+
283+
public boolean isDayTimeInterval() {
284+
int ordinal = this.ordinal();
285+
return (ordinal >= INTERVAL_DAY.ordinal() && ordinal <= INTERVAL_SECOND.ordinal())
286+
|| (ordinal >= INTERVAL_DAY_TO_HOUR.ordinal() && ordinal <= INTERVAL_MINUTE_TO_SECOND.ordinal());
287+
}
288+
274289
// data type extract-able from _source or from docvalue_fields
275290
public boolean isFromDocValuesOnly() {
276291
return this == KEYWORD // because of ignore_above. Extracting this from _source wouldn't make sense if it wasn't indexed at all.

x-pack/plugin/sql/src/main/java/org/elasticsearch/xpack/sql/type/DataTypeConversion.java

Lines changed: 10 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -87,61 +87,61 @@ public static DataType commonType(DataType left, DataType right) {
8787

8888
// interval and dates
8989
if (left == DATE) {
90-
if (DataTypes.isInterval(right)) {
90+
if (right.isInterval()) {
9191
return left;
9292
}
9393
}
9494
if (right == DATE) {
95-
if (DataTypes.isInterval(left)) {
95+
if (left.isInterval()) {
9696
return right;
9797
}
9898
}
9999
if (left == TIME) {
100100
if (right == DATE) {
101101
return DATETIME;
102102
}
103-
if (DataTypes.isInterval(right)) {
103+
if (right.isInterval()) {
104104
return left;
105105
}
106106
}
107107
if (right == TIME) {
108108
if (left == DATE) {
109109
return DATETIME;
110110
}
111-
if (DataTypes.isInterval(left)) {
111+
if (left.isInterval()) {
112112
return right;
113113
}
114114
}
115115
if (left == DATETIME) {
116116
if (right == DATE || right == TIME) {
117117
return left;
118118
}
119-
if (DataTypes.isInterval(right)) {
119+
if (right.isInterval()) {
120120
return left;
121121
}
122122
}
123123
if (right == DATETIME) {
124124
if (left == DATE || left == TIME) {
125125
return right;
126126
}
127-
if (DataTypes.isInterval(left)) {
127+
if (left.isInterval()) {
128128
return right;
129129
}
130130
}
131131
// Interval * integer is a valid operation
132-
if (DataTypes.isInterval(left)) {
132+
if (left.isInterval()) {
133133
if (right.isInteger()) {
134134
return left;
135135
}
136136
}
137-
if (DataTypes.isInterval(right)) {
137+
if (right.isInterval()) {
138138
if (left.isInteger()) {
139139
return right;
140140
}
141141
}
142-
if (DataTypes.isInterval(left)) {
142+
if (left.isInterval()) {
143143
// intervals widening
144-
if (DataTypes.isInterval(right)) {
144+
if (right.isInterval()) {
145145
// null returned for incompatible intervals
146146
return DataTypes.compatibleInterval(left, right);
147147
}

x-pack/plugin/sql/src/main/java/org/elasticsearch/xpack/sql/type/DataTypes.java

Lines changed: 7 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -18,12 +18,6 @@
1818
import static org.elasticsearch.xpack.sql.type.DataType.DOUBLE;
1919
import static org.elasticsearch.xpack.sql.type.DataType.FLOAT;
2020
import static org.elasticsearch.xpack.sql.type.DataType.INTEGER;
21-
import static org.elasticsearch.xpack.sql.type.DataType.INTERVAL_DAY;
22-
import static org.elasticsearch.xpack.sql.type.DataType.INTERVAL_DAY_TO_HOUR;
23-
import static org.elasticsearch.xpack.sql.type.DataType.INTERVAL_MINUTE_TO_SECOND;
24-
import static org.elasticsearch.xpack.sql.type.DataType.INTERVAL_MONTH;
25-
import static org.elasticsearch.xpack.sql.type.DataType.INTERVAL_SECOND;
26-
import static org.elasticsearch.xpack.sql.type.DataType.INTERVAL_YEAR;
2721
import static org.elasticsearch.xpack.sql.type.DataType.INTERVAL_YEAR_TO_MONTH;
2822
import static org.elasticsearch.xpack.sql.type.DataType.KEYWORD;
2923
import static org.elasticsearch.xpack.sql.type.DataType.LONG;
@@ -88,18 +82,6 @@ public static DataType fromJava(Object value) {
8882
throw new SqlIllegalArgumentException("No idea what's the DataType for {}", value.getClass());
8983
}
9084

91-
92-
//
93-
// Interval utilities
94-
//
95-
// some of the methods below could have used an EnumSet however isDayTime would have required a large initialization block
96-
// for this reason, these use the ordinal directly (and thus avoid the type check in EnumSet)
97-
98-
public static boolean isInterval(DataType type) {
99-
int ordinal = type.ordinal();
100-
return ordinal >= INTERVAL_YEAR.ordinal() && ordinal <= INTERVAL_MINUTE_TO_SECOND.ordinal();
101-
}
102-
10385
// return the compatible interval between the two - it is assumed the types are intervals
10486
// YEAR and MONTH -> YEAR_TO_MONTH
10587
// DAY... SECOND -> DAY_TIME
@@ -108,11 +90,11 @@ public static DataType compatibleInterval(DataType left, DataType right) {
10890
if (left == right) {
10991
return left;
11092
}
111-
if (isYearMonthInterval(left) && isYearMonthInterval(right)) {
93+
if (left.isYearMonthInterval() && right.isYearMonthInterval()) {
11294
// no need to look at YEAR/YEAR or MONTH/MONTH as these are equal and already handled
11395
return INTERVAL_YEAR_TO_MONTH;
11496
}
115-
if (isDayTimeInterval(left) && isDayTimeInterval(right)) {
97+
if (left.isDayTimeInterval() && right.isDayTimeInterval()) {
11698
// to avoid specifying the combinations, extract the leading and trailing unit from the name
11799
// D > H > S > M which is also the alphabetical order
118100
String lName = left.name().substring(9);
@@ -141,16 +123,6 @@ public static DataType compatibleInterval(DataType left, DataType right) {
141123
return null;
142124
}
143125

144-
private static boolean isYearMonthInterval(DataType type) {
145-
return type == INTERVAL_YEAR || type == INTERVAL_MONTH || type == INTERVAL_YEAR_TO_MONTH;
146-
}
147-
148-
private static boolean isDayTimeInterval(DataType type) {
149-
int ordinal = type.ordinal();
150-
return (ordinal >= INTERVAL_DAY.ordinal() && ordinal <= INTERVAL_SECOND.ordinal())
151-
|| (ordinal >= INTERVAL_DAY_TO_HOUR.ordinal() && ordinal <= INTERVAL_MINUTE_TO_SECOND.ordinal());
152-
}
153-
154126
private static String intervalUnit(char unitChar) {
155127
switch (unitChar) {
156128
case 'D':
@@ -240,9 +212,11 @@ public static boolean areTypesCompatible(DataType left, DataType right) {
240212
return true;
241213
} else {
242214
return
243-
(left == DataType.NULL || right == DataType.NULL) ||
244-
(left.isString() && right.isString()) ||
245-
(left.isNumeric() && right.isNumeric());
215+
(left == DataType.NULL || right == DataType.NULL)
216+
|| (left.isString() && right.isString())
217+
|| (left.isNumeric() && right.isNumeric())
218+
|| (left.isDateBased() && right.isDateBased())
219+
|| (left.isInterval() && right.isInterval() && compatibleInterval(left, right) != null);
246220
}
247221
}
248222
}

x-pack/plugin/sql/src/test/java/org/elasticsearch/xpack/sql/type/DataTypeConversionTests.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -685,6 +685,6 @@ public void testIpToString() {
685685
}
686686

687687
private DataType randomInterval() {
688-
return randomFrom(Stream.of(DataType.values()).filter(DataTypes::isInterval).collect(Collectors.toList()));
688+
return randomFrom(Stream.of(DataType.values()).filter(DataType::isInterval).collect(Collectors.toList()));
689689
}
690690
}

0 commit comments

Comments
 (0)