Skip to content
Merged
Show file tree
Hide file tree
Changes from all 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 @@ -1603,3 +1603,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 @@ -674,7 +674,12 @@ public enum Cap {
* and https://github.com/elastic/elasticsearch/issues/120803
* Support for queries that have multiple SORTs that cannot become TopN
*/
REMOVE_REDUNDANT_SORT;
REMOVE_REDUNDANT_SORT,

/**
* 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 @@ -51,6 +51,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 @@ -1208,7 +1211,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