Skip to content

Commit 231fb2a

Browse files
[ES|QL] Implicit numeric casting for CASE/GREATEST/LEAST (elastic#122601) (elastic#123091)
* 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 730b538 commit 231fb2a

File tree

7 files changed

+404
-97
lines changed

7 files changed

+404
-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
@@ -1583,3 +1583,95 @@ emp_no: integer | x:date | y:date
15831583
10001 | 2024-11-03 | 2024-11-06
15841584
10002 | 2024-11-03 | 2024-11-06
15851585
;
1586+
1587+
greatestWithMixedNumericValues
1588+
required_capability: mixed_numeric_types_in_case_greatest_least
1589+
ROW g1=GREATEST(10.0, 5.0, 1, -100.1, 0, 1234, -10000), g2=GREATEST(10.0, 5, 1, -100.1, null);
1590+
1591+
g1:double |g2:double
1592+
1234 |null
1593+
;
1594+
1595+
leastWithMixedNumericValues
1596+
required_capability: mixed_numeric_types_in_case_greatest_least
1597+
ROW l1=LEAST(10.0, 5.0, 1, -100.1, 0, 1234, -10000), l2=LEAST(10.0, 5, 1, -100.1, null);
1598+
1599+
l1:double |l2:double
1600+
-10000 |null
1601+
;
1602+
1603+
greatestWithMixedNumericFields
1604+
required_capability: mixed_numeric_types_in_case_greatest_least
1605+
FROM employees
1606+
| EVAL g1 = GREATEST(height, salary, languages), g2 = GREATEST(height, salary, languages, null)
1607+
| KEEP emp_no, g1, g2
1608+
| SORT emp_no
1609+
| LIMIT 3
1610+
;
1611+
1612+
emp_no:integer | g1:double | g2:double
1613+
10001 | 57305.0 | null
1614+
10002 | 56371.0 | null
1615+
10003 | 61805.0 | null
1616+
;
1617+
1618+
leastWithMixedNumericFields
1619+
required_capability: mixed_numeric_types_in_case_greatest_least
1620+
FROM employees
1621+
| EVAL l1 = LEAST(height, salary, languages), l2 = LEAST(height, salary, languages, null)
1622+
| KEEP emp_no, l1, l2
1623+
| SORT emp_no
1624+
| LIMIT 3
1625+
;
1626+
1627+
emp_no:integer | l1:double | l2:double
1628+
10001 | 2.0 | null
1629+
10002 | 2.08 | null
1630+
10003 | 1.83 | null
1631+
;
1632+
1633+
greatestWithMixedNumericValuesWithMV
1634+
required_capability: mixed_numeric_types_in_case_greatest_least
1635+
ROW g1=GREATEST([10.0, 4], 1), g2=GREATEST([10.0, 4], 1, null);
1636+
1637+
g1:double |g2:double
1638+
10 |null
1639+
;
1640+
1641+
leastWithMixedNumericValuesWithMV
1642+
required_capability: mixed_numeric_types_in_case_greatest_least
1643+
ROW l1=LEAST([10.0, 4], 1), l2=LEAST([10.0, 4], 1, null);
1644+
1645+
l1:double |l2:double
1646+
1 |null
1647+
;
1648+
1649+
greatestWithMixedNumericFieldsWithMV
1650+
required_capability: mixed_numeric_types_in_case_greatest_least
1651+
FROM employees
1652+
| EVAL g1 = GREATEST(salary_change, salary, languages), g2 = GREATEST(salary_change, salary, languages, null)
1653+
| KEEP emp_no, g1, g2
1654+
| SORT emp_no
1655+
| LIMIT 3
1656+
;
1657+
1658+
emp_no:integer | g1:double | g2:double
1659+
10001 | 57305.0 | null
1660+
10002 | 56371.0 | null
1661+
10003 | 61805.0 | null
1662+
;
1663+
1664+
leastWithMixedNumericFieldsWithMV
1665+
required_capability: mixed_numeric_types_in_case_greatest_least
1666+
FROM employees
1667+
| EVAL l1 = LEAST(salary_change, salary, languages), l2 = LEAST(salary_change, salary, languages, null)
1668+
| KEEP emp_no, l1, l2
1669+
| SORT emp_no
1670+
| LIMIT 3
1671+
;
1672+
1673+
emp_no:integer | l1:double | l2:double
1674+
10001 | 1.19 | null
1675+
10002 | -7.23 | null
1676+
10003 | 4.0 | null
1677+
;

x-pack/plugin/esql/src/main/java/org/elasticsearch/xpack/esql/action/EsqlCapabilities.java

Lines changed: 21 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -814,7 +814,27 @@ public enum Cap {
814814
* Fixes a series of issues with inlinestats which had an incomplete implementation after lookup and inlinestats
815815
* were refactored.
816816
*/
817-
INLINESTATS_V3(EsqlPlugin.INLINESTATS_FEATURE_FLAG);
817+
INLINESTATS_V3(EsqlPlugin.INLINESTATS_FEATURE_FLAG),
818+
819+
/**
820+
* Support partial_results
821+
*/
822+
SUPPORT_PARTIAL_RESULTS,
823+
824+
/**
825+
* Support for rendering aggregate_metric_double type
826+
*/
827+
AGGREGATE_METRIC_DOUBLE_RENDERING(AGGREGATE_METRIC_DOUBLE_FEATURE_FLAG),
828+
829+
/**
830+
* Support for FORK command
831+
*/
832+
FORK(Build.current().isSnapshot()),
833+
834+
/**
835+
* Allow mixed numeric types in conditional functions - case, greatest and least
836+
*/
837+
MIXED_NUMERIC_TYPES_IN_CASE_GREATEST_LEAST;
818838

819839
private final boolean enabled;
820840

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
@@ -52,6 +52,9 @@
5252
import org.elasticsearch.xpack.esql.expression.function.UnsupportedAttribute;
5353
import org.elasticsearch.xpack.esql.expression.function.grouping.GroupingFunction;
5454
import org.elasticsearch.xpack.esql.expression.function.scalar.EsqlScalarFunction;
55+
import org.elasticsearch.xpack.esql.expression.function.scalar.conditional.Case;
56+
import org.elasticsearch.xpack.esql.expression.function.scalar.conditional.Greatest;
57+
import org.elasticsearch.xpack.esql.expression.function.scalar.conditional.Least;
5558
import org.elasticsearch.xpack.esql.expression.function.scalar.convert.AbstractConvertFunction;
5659
import org.elasticsearch.xpack.esql.expression.function.scalar.convert.FoldablesConvertFunction;
5760
import org.elasticsearch.xpack.esql.expression.function.scalar.convert.ToDouble;
@@ -1257,7 +1260,7 @@ private static Expression processIn(In in) {
12571260
}
12581261

12591262
private static boolean canCastMixedNumericTypes(org.elasticsearch.xpack.esql.core.expression.function.Function f) {
1260-
return f instanceof Coalesce;
1263+
return f instanceof Coalesce || f instanceof Case || f instanceof Greatest || f instanceof Least;
12611264
}
12621265

12631266
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)