Skip to content
Merged
Show file tree
Hide file tree
Changes from 3 commits
Commits
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/122601.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
pr: 122601
summary: Implicit numeric casting for CASE/GREATEST/LEAST
area: ES|QL
type: bug
issues:
- 121890
Original file line number Diff line number Diff line change
Expand Up @@ -281,3 +281,111 @@ languages:integer| emp_no:integer|eval:keyword
null |10020 |languages is null
null |10021 |languages is null
;

caseWithMixedNumericValue
required_capability: mixed_numeric_types_in_case_greatest_least
FROM employees
| WHERE emp_no >= 10005 AND emp_no <= 10010
| EVAL g = case(gender == "F", 1.0, gender == "M", 2, 3.0)
| KEEP emp_no, gender, g
| SORT emp_no
;

emp_no:integer | gender:keyword | g:double
10005 | M | 2.0
10006 | F | 1.0
10007 | F | 1.0
10008 | M | 2.0
10009 | F | 1.0
10010 | null | 3.0
;

caseWithMixedNumericValueWithNull
required_capability: mixed_numeric_types_in_case_greatest_least
FROM employees
| WHERE emp_no >= 10005 AND emp_no <= 10010
| EVAL g = case(gender == "F", 1.0, gender == "M", 2, null)
| KEEP emp_no, gender, g
| SORT emp_no
;

emp_no:integer | gender:keyword | g:double
10005 | M | 2.0
10006 | F | 1.0
10007 | F | 1.0
10008 | M | 2.0
10009 | F | 1.0
10010 | null | null
;

caseWithMixedNumericField
required_capability: mixed_numeric_types_in_case_greatest_least
FROM employees
| WHERE emp_no >= 10005 AND emp_no <= 10010
| EVAL g = case(gender == "F", height, gender == "M", salary, languages)
| KEEP emp_no, gender, g
| SORT emp_no
;

emp_no:integer | gender:keyword | g:double
10005 | M | 63528.0
10006 | F | 1.56
10007 | F | 1.7
10008 | M | 43906.0
10009 | F | 1.85
10010 | null | 4.0
;

caseWithMixedNumericFieldWithNull
required_capability: mixed_numeric_types_in_case_greatest_least
FROM employees
| WHERE emp_no >= 10005 AND emp_no <= 10010
| EVAL g = case(gender == "F", height, gender == "M", salary, null)
| KEEP emp_no, gender, g
| SORT emp_no
;

emp_no:integer | gender:keyword | g:double
10005 | M | 63528.0
10006 | F | 1.56
10007 | F | 1.7
10008 | M | 43906.0
10009 | F | 1.85
10010 | null | null
;

caseWithMixedNumericFieldWithMV
required_capability: mixed_numeric_types_in_case_greatest_least
FROM employees
| WHERE emp_no >= 10005 AND emp_no <= 10010
| EVAL g = case(gender == "F", salary_change, gender == "M", salary, languages)
| KEEP emp_no, gender, g
| SORT emp_no
;

emp_no:integer | gender:keyword | g:double
10005 | M | 63528.0
10006 | F | -3.9
10007 | F | [-7.06, 0.57, 1.99]
10008 | M | 43906.0
10009 | F | null
10010 | null | 4.0
;

caseWithMixedNumericFieldWithNullWithMV
required_capability: mixed_numeric_types_in_case_greatest_least
FROM employees
| WHERE emp_no >= 10005 AND emp_no <= 10010
| EVAL g = case(gender == "F", salary_change, gender == "M", salary, null)
| KEEP emp_no, gender, g
| SORT emp_no
;

emp_no:integer | gender:keyword | g:double
10005 | M | 63528.0
10006 | F | -3.9
10007 | F | [-7.06, 0.57, 1.99]
10008 | M | 43906.0
10009 | F | null
10010 | null | null
;
Original file line number Diff line number Diff line change
Expand Up @@ -1583,3 +1583,95 @@ emp_no: integer | x:date | y:date
10001 | 2024-11-03 | 2024-11-06
10002 | 2024-11-03 | 2024-11-06
;

greatestWithMixedNumericValues
required_capability: mixed_numeric_types_in_case_greatest_least
ROW g1=GREATEST(10.0, 5.0, 1, -100.1, 0, 1234, -10000), g2=GREATEST(10.0, 5, 1, -100.1, null);

g1:double |g2:double
1234 |null
;

leastWithMixedNumericValues
required_capability: mixed_numeric_types_in_case_greatest_least
ROW l1=LEAST(10.0, 5.0, 1, -100.1, 0, 1234, -10000), l2=LEAST(10.0, 5, 1, -100.1, null);

l1:double |l2:double
-10000 |null
;

greatestWithMixedNumericFields
required_capability: mixed_numeric_types_in_case_greatest_least
FROM employees
| EVAL g1 = GREATEST(height, salary, languages), g2 = GREATEST(height, salary, languages, null)
| KEEP emp_no, g1, g2
| SORT emp_no
| LIMIT 3
;

emp_no:integer | g1:double | g2:double
10001 | 57305.0 | null
10002 | 56371.0 | null
10003 | 61805.0 | null
;

leastWithMixedNumericFields
required_capability: mixed_numeric_types_in_case_greatest_least
FROM employees
| EVAL l1 = LEAST(height, salary, languages), l2 = LEAST(height, salary, languages, null)
| KEEP emp_no, l1, l2
| SORT emp_no
| LIMIT 3
;

emp_no:integer | l1:double | l2:double
10001 | 2.0 | null
10002 | 2.08 | null
10003 | 1.83 | null
;

greatestWithMixedNumericValuesWithMV
required_capability: mixed_numeric_types_in_case_greatest_least
ROW g1=GREATEST([10.0, 4], 1), g2=GREATEST([10.0, 4], 1, null);

g1:double |g2:double
10 |null
;

leastWithMixedNumericValuesWithMV
required_capability: mixed_numeric_types_in_case_greatest_least
ROW l1=LEAST([10.0, 4], 1), l2=LEAST([10.0, 4], 1, null);

l1:double |l2:double
1 |null
;

greatestWithMixedNumericFieldsWithMV
required_capability: mixed_numeric_types_in_case_greatest_least
FROM employees
| EVAL g1 = GREATEST(salary_change, salary, languages), g2 = GREATEST(salary_change, salary, languages, null)
| KEEP emp_no, g1, g2
| SORT emp_no
| LIMIT 3
;

emp_no:integer | g1:double | g2:double
10001 | 57305.0 | null
10002 | 56371.0 | null
10003 | 61805.0 | null
;

leastWithMixedNumericFieldsWithMV
required_capability: mixed_numeric_types_in_case_greatest_least
FROM employees
| EVAL l1 = LEAST(salary_change, salary, languages), l2 = LEAST(salary_change, salary, languages, null)
| KEEP emp_no, l1, l2
| SORT emp_no
| LIMIT 3
;

emp_no:integer | l1:double | l2:double
10001 | 1.19 | null
10002 | -7.23 | null
10003 | 4.0 | null
;
Original file line number Diff line number Diff line change
Expand Up @@ -824,7 +824,12 @@ public enum Cap {
/**
* Support partial_results
*/
SUPPORT_PARTIAL_RESULTS;
SUPPORT_PARTIAL_RESULTS,

/**
* Allow mixed numeric types in conditional functions - case, greatest and least
*/
MIXED_NUMERIC_TYPES_IN_CASE_GREATEST_LEAST;

private final boolean enabled;

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,9 @@
import org.elasticsearch.xpack.esql.expression.function.UnsupportedAttribute;
import org.elasticsearch.xpack.esql.expression.function.grouping.GroupingFunction;
import org.elasticsearch.xpack.esql.expression.function.scalar.EsqlScalarFunction;
import org.elasticsearch.xpack.esql.expression.function.scalar.conditional.Case;
import org.elasticsearch.xpack.esql.expression.function.scalar.conditional.Greatest;
import org.elasticsearch.xpack.esql.expression.function.scalar.conditional.Least;
import org.elasticsearch.xpack.esql.expression.function.scalar.convert.AbstractConvertFunction;
import org.elasticsearch.xpack.esql.expression.function.scalar.convert.FoldablesConvertFunction;
import org.elasticsearch.xpack.esql.expression.function.scalar.convert.ToDouble;
Expand Down Expand Up @@ -1257,7 +1260,7 @@ private static Expression processIn(In in) {
}

private static boolean canCastMixedNumericTypes(org.elasticsearch.xpack.esql.core.expression.function.Function f) {
return f instanceof Coalesce;
return f instanceof Coalesce || f instanceof Case || f instanceof Greatest || f instanceof Least;
}

private static boolean canCastNumeric(DataType from, DataType to) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2325,13 +2325,37 @@ public void testRateRequiresCounterTypes() {
);
}

public void testCoalesceWithMixedNumericTypes() {
public void testConditionalFunctionsWithMixedNumericTypes() {
LogicalPlan plan = analyze("""
from test
| eval x = coalesce(salary_change, null, 0), y = coalesce(languages, null, 0), z = coalesce(languages.long, null, 0)
, w = coalesce(salary_change, null, 0::long)
| keep x, y, z, w
""", "mapping-default.json");
validateConditionalFunctions(plan);

plan = analyze("""
from test
| eval x = case(languages == 1, salary_change, languages == 2, salary, languages == 3, salary_change.long, 0)
, y = case(languages == 1, salary_change.int, languages == 2, salary, 0)
, z = case(languages == 1, salary_change.long, languages == 2, salary, 0::long)
, w = case(languages == 1, salary_change, languages == 2, salary, languages == 3, salary_change.long, null)
| keep x, y, z, w
""", "mapping-default.json");
validateConditionalFunctions(plan);

plan = analyze("""
from test
| eval x = greatest(salary_change, salary, salary_change.long)
, y = least(salary_change.int, salary)
, z = greatest(salary_change.long, salary, null)
, w = least(null, salary_change, salary_change.long, salary, null)
| keep x, y, z, w
""", "mapping-default.json");
validateConditionalFunctions(plan);
}

private void validateConditionalFunctions(LogicalPlan plan) {
var limit = as(plan, Limit.class);
var esqlProject = as(limit.child(), EsqlProject.class);
List<?> projections = esqlProject.projections();
Expand Down
Loading