Skip to content

Commit b343735

Browse files
[ES|QL] Implicit numeric casting for CASE/GREATEST/LEAST (#122601) (#123305)
* implicit numeric casting for conditional functions (cherry picked from commit 412e6c2) # Conflicts: # x-pack/plugin/esql/src/main/java/org/elasticsearch/xpack/esql/action/EsqlCapabilities.java
1 parent cb57995 commit b343735

File tree

7 files changed

+389
-97
lines changed

7 files changed

+389
-97
lines changed

docs/changelog/122601.yaml

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
pr: 122601
2+
summary: Implicit numeric casting for CASE/GREATEST/LEAST
3+
area: ES|QL
4+
type: bug
5+
issues:
6+
- 121890

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

Lines changed: 108 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -281,3 +281,111 @@ languages:integer| emp_no:integer|eval:keyword
281281
null |10020 |languages is null
282282
null |10021 |languages is null
283283
;
284+
285+
caseWithMixedNumericValue
286+
required_capability: mixed_numeric_types_in_case_greatest_least
287+
FROM employees
288+
| WHERE emp_no >= 10005 AND emp_no <= 10010
289+
| EVAL g = case(gender == "F", 1.0, gender == "M", 2, 3.0)
290+
| KEEP emp_no, gender, g
291+
| SORT emp_no
292+
;
293+
294+
emp_no:integer | gender:keyword | g:double
295+
10005 | M | 2.0
296+
10006 | F | 1.0
297+
10007 | F | 1.0
298+
10008 | M | 2.0
299+
10009 | F | 1.0
300+
10010 | null | 3.0
301+
;
302+
303+
caseWithMixedNumericValueWithNull
304+
required_capability: mixed_numeric_types_in_case_greatest_least
305+
FROM employees
306+
| WHERE emp_no >= 10005 AND emp_no <= 10010
307+
| EVAL g = case(gender == "F", 1.0, gender == "M", 2, null)
308+
| KEEP emp_no, gender, g
309+
| SORT emp_no
310+
;
311+
312+
emp_no:integer | gender:keyword | g:double
313+
10005 | M | 2.0
314+
10006 | F | 1.0
315+
10007 | F | 1.0
316+
10008 | M | 2.0
317+
10009 | F | 1.0
318+
10010 | null | null
319+
;
320+
321+
caseWithMixedNumericField
322+
required_capability: mixed_numeric_types_in_case_greatest_least
323+
FROM employees
324+
| WHERE emp_no >= 10005 AND emp_no <= 10010
325+
| EVAL g = case(gender == "F", height, gender == "M", salary, languages)
326+
| KEEP emp_no, gender, g
327+
| SORT emp_no
328+
;
329+
330+
emp_no:integer | gender:keyword | g:double
331+
10005 | M | 63528.0
332+
10006 | F | 1.56
333+
10007 | F | 1.7
334+
10008 | M | 43906.0
335+
10009 | F | 1.85
336+
10010 | null | 4.0
337+
;
338+
339+
caseWithMixedNumericFieldWithNull
340+
required_capability: mixed_numeric_types_in_case_greatest_least
341+
FROM employees
342+
| WHERE emp_no >= 10005 AND emp_no <= 10010
343+
| EVAL g = case(gender == "F", height, gender == "M", salary, null)
344+
| KEEP emp_no, gender, g
345+
| SORT emp_no
346+
;
347+
348+
emp_no:integer | gender:keyword | g:double
349+
10005 | M | 63528.0
350+
10006 | F | 1.56
351+
10007 | F | 1.7
352+
10008 | M | 43906.0
353+
10009 | F | 1.85
354+
10010 | null | null
355+
;
356+
357+
caseWithMixedNumericFieldWithMV
358+
required_capability: mixed_numeric_types_in_case_greatest_least
359+
FROM employees
360+
| WHERE emp_no >= 10005 AND emp_no <= 10010
361+
| EVAL g = case(gender == "F", salary_change, gender == "M", salary, languages)
362+
| KEEP emp_no, gender, g
363+
| SORT emp_no
364+
;
365+
366+
emp_no:integer | gender:keyword | g:double
367+
10005 | M | 63528.0
368+
10006 | F | -3.9
369+
10007 | F | [-7.06, 0.57, 1.99]
370+
10008 | M | 43906.0
371+
10009 | F | null
372+
10010 | null | 4.0
373+
;
374+
375+
caseWithMixedNumericFieldWithNullWithMV
376+
required_capability: mixed_numeric_types_in_case_greatest_least
377+
FROM employees
378+
| WHERE emp_no >= 10005 AND emp_no <= 10010
379+
| EVAL g = case(gender == "F", salary_change, gender == "M", salary, null)
380+
| KEEP emp_no, gender, g
381+
| SORT emp_no
382+
;
383+
384+
emp_no:integer | gender:keyword | g:double
385+
10005 | M | 63528.0
386+
10006 | F | -3.9
387+
10007 | F | [-7.06, 0.57, 1.99]
388+
10008 | M | 43906.0
389+
10009 | F | null
390+
10010 | null | null
391+
;

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

Lines changed: 92 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1603,3 +1603,95 @@ emp_no: integer | x:date | y:date
16031603
10001 | 2024-11-03 | 2024-11-06
16041604
10002 | 2024-11-03 | 2024-11-06
16051605
;
1606+
1607+
greatestWithMixedNumericValues
1608+
required_capability: mixed_numeric_types_in_case_greatest_least
1609+
ROW g1=GREATEST(10.0, 5.0, 1, -100.1, 0, 1234, -10000), g2=GREATEST(10.0, 5, 1, -100.1, null);
1610+
1611+
g1:double |g2:double
1612+
1234 |null
1613+
;
1614+
1615+
leastWithMixedNumericValues
1616+
required_capability: mixed_numeric_types_in_case_greatest_least
1617+
ROW l1=LEAST(10.0, 5.0, 1, -100.1, 0, 1234, -10000), l2=LEAST(10.0, 5, 1, -100.1, null);
1618+
1619+
l1:double |l2:double
1620+
-10000 |null
1621+
;
1622+
1623+
greatestWithMixedNumericFields
1624+
required_capability: mixed_numeric_types_in_case_greatest_least
1625+
FROM employees
1626+
| EVAL g1 = GREATEST(height, salary, languages), g2 = GREATEST(height, salary, languages, null)
1627+
| KEEP emp_no, g1, g2
1628+
| SORT emp_no
1629+
| LIMIT 3
1630+
;
1631+
1632+
emp_no:integer | g1:double | g2:double
1633+
10001 | 57305.0 | null
1634+
10002 | 56371.0 | null
1635+
10003 | 61805.0 | null
1636+
;
1637+
1638+
leastWithMixedNumericFields
1639+
required_capability: mixed_numeric_types_in_case_greatest_least
1640+
FROM employees
1641+
| EVAL l1 = LEAST(height, salary, languages), l2 = LEAST(height, salary, languages, null)
1642+
| KEEP emp_no, l1, l2
1643+
| SORT emp_no
1644+
| LIMIT 3
1645+
;
1646+
1647+
emp_no:integer | l1:double | l2:double
1648+
10001 | 2.0 | null
1649+
10002 | 2.08 | null
1650+
10003 | 1.83 | null
1651+
;
1652+
1653+
greatestWithMixedNumericValuesWithMV
1654+
required_capability: mixed_numeric_types_in_case_greatest_least
1655+
ROW g1=GREATEST([10.0, 4], 1), g2=GREATEST([10.0, 4], 1, null);
1656+
1657+
g1:double |g2:double
1658+
10 |null
1659+
;
1660+
1661+
leastWithMixedNumericValuesWithMV
1662+
required_capability: mixed_numeric_types_in_case_greatest_least
1663+
ROW l1=LEAST([10.0, 4], 1), l2=LEAST([10.0, 4], 1, null);
1664+
1665+
l1:double |l2:double
1666+
1 |null
1667+
;
1668+
1669+
greatestWithMixedNumericFieldsWithMV
1670+
required_capability: mixed_numeric_types_in_case_greatest_least
1671+
FROM employees
1672+
| EVAL g1 = GREATEST(salary_change, salary, languages), g2 = GREATEST(salary_change, salary, languages, null)
1673+
| KEEP emp_no, g1, g2
1674+
| SORT emp_no
1675+
| LIMIT 3
1676+
;
1677+
1678+
emp_no:integer | g1:double | g2:double
1679+
10001 | 57305.0 | null
1680+
10002 | 56371.0 | null
1681+
10003 | 61805.0 | null
1682+
;
1683+
1684+
leastWithMixedNumericFieldsWithMV
1685+
required_capability: mixed_numeric_types_in_case_greatest_least
1686+
FROM employees
1687+
| EVAL l1 = LEAST(salary_change, salary, languages), l2 = LEAST(salary_change, salary, languages, null)
1688+
| KEEP emp_no, l1, l2
1689+
| SORT emp_no
1690+
| LIMIT 3
1691+
;
1692+
1693+
emp_no:integer | l1:double | l2:double
1694+
10001 | 1.19 | null
1695+
10002 | -7.23 | null
1696+
10003 | 4.0 | null
1697+
;

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
@@ -674,7 +674,12 @@ public enum Cap {
674674
* and https://github.com/elastic/elasticsearch/issues/120803
675675
* Support for queries that have multiple SORTs that cannot become TopN
676676
*/
677-
REMOVE_REDUNDANT_SORT;
677+
REMOVE_REDUNDANT_SORT,
678+
679+
/**
680+
* Allow mixed numeric types in conditional functions - case, greatest and least
681+
*/
682+
MIXED_NUMERIC_TYPES_IN_CASE_GREATEST_LEAST;
678683

679684
private final boolean enabled;
680685

x-pack/plugin/esql/src/main/java/org/elasticsearch/xpack/esql/analysis/Analyzer.java

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -51,6 +51,9 @@
5151
import org.elasticsearch.xpack.esql.expression.function.UnsupportedAttribute;
5252
import org.elasticsearch.xpack.esql.expression.function.grouping.GroupingFunction;
5353
import org.elasticsearch.xpack.esql.expression.function.scalar.EsqlScalarFunction;
54+
import org.elasticsearch.xpack.esql.expression.function.scalar.conditional.Case;
55+
import org.elasticsearch.xpack.esql.expression.function.scalar.conditional.Greatest;
56+
import org.elasticsearch.xpack.esql.expression.function.scalar.conditional.Least;
5457
import org.elasticsearch.xpack.esql.expression.function.scalar.convert.AbstractConvertFunction;
5558
import org.elasticsearch.xpack.esql.expression.function.scalar.convert.FoldablesConvertFunction;
5659
import org.elasticsearch.xpack.esql.expression.function.scalar.convert.ToDouble;
@@ -1208,7 +1211,7 @@ private static Expression processIn(In in) {
12081211
}
12091212

12101213
private static boolean canCastMixedNumericTypes(org.elasticsearch.xpack.esql.core.expression.function.Function f) {
1211-
return f instanceof Coalesce;
1214+
return f instanceof Coalesce || f instanceof Case || f instanceof Greatest || f instanceof Least;
12121215
}
12131216

12141217
private static boolean canCastNumeric(DataType from, DataType to) {

x-pack/plugin/esql/src/test/java/org/elasticsearch/xpack/esql/analysis/AnalyzerTests.java

Lines changed: 25 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2325,13 +2325,37 @@ public void testRateRequiresCounterTypes() {
23252325
);
23262326
}
23272327

2328-
public void testCoalesceWithMixedNumericTypes() {
2328+
public void testConditionalFunctionsWithMixedNumericTypes() {
23292329
LogicalPlan plan = analyze("""
23302330
from test
23312331
| eval x = coalesce(salary_change, null, 0), y = coalesce(languages, null, 0), z = coalesce(languages.long, null, 0)
23322332
, w = coalesce(salary_change, null, 0::long)
23332333
| keep x, y, z, w
23342334
""", "mapping-default.json");
2335+
validateConditionalFunctions(plan);
2336+
2337+
plan = analyze("""
2338+
from test
2339+
| eval x = case(languages == 1, salary_change, languages == 2, salary, languages == 3, salary_change.long, 0)
2340+
, y = case(languages == 1, salary_change.int, languages == 2, salary, 0)
2341+
, z = case(languages == 1, salary_change.long, languages == 2, salary, 0::long)
2342+
, w = case(languages == 1, salary_change, languages == 2, salary, languages == 3, salary_change.long, null)
2343+
| keep x, y, z, w
2344+
""", "mapping-default.json");
2345+
validateConditionalFunctions(plan);
2346+
2347+
plan = analyze("""
2348+
from test
2349+
| eval x = greatest(salary_change, salary, salary_change.long)
2350+
, y = least(salary_change.int, salary)
2351+
, z = greatest(salary_change.long, salary, null)
2352+
, w = least(null, salary_change, salary_change.long, salary, null)
2353+
| keep x, y, z, w
2354+
""", "mapping-default.json");
2355+
validateConditionalFunctions(plan);
2356+
}
2357+
2358+
private void validateConditionalFunctions(LogicalPlan plan) {
23352359
var limit = as(plan, Limit.class);
23362360
var esqlProject = as(limit.child(), EsqlProject.class);
23372361
List<?> projections = esqlProject.projections();

0 commit comments

Comments
 (0)