Repository navigation
fix: nvl2 result type should not depend on its first argument - #26498
Open
CuteChuanChuan wants to merge 1 commit into
Open
CuteChuanChuan wants to merge 1 commit into
CuteChuanChuan wants to merge 1 commit into
Conversation
neilconway
reviewed
Oct 19, 2026
neilconway
left a comment
Contributor
There was a problem hiding this comment.
lgtm overall, two minor suggestions! Thanks @CuteChuanChuan
| })?; | ||
| Ok(vec![new_type; arg_types.len()]) | ||
| let Some(new_type) = type_union_coercion(if_non_null, if_null) else { | ||
| return plan_err!("Coercion from {if_non_null} to {if_null} failed."); |
Contributor
There was a problem hiding this comment.
Technically the user didn't ask for a coercion; and this results in a doubled period. Howabout:
nvl2 cannot coerce {if_non_null} and {if_null} to a common type
| } | ||
| })?; | ||
| Ok(vec![new_type; arg_types.len()]) | ||
| let Some(new_type) = type_union_coercion(if_non_null, if_null) else { |
Contributor
There was a problem hiding this comment.
type_union_coercion has the property that it depends on argument order (it takes the TZ of the leftmost argument if the timezones of the inputs disagree):
CREATE TABLE t AS
SELECT i, a, arrow_cast(a, 'Timestamp(Nanosecond, Some("+05:00"))') AS b
FROM (SELECT 1 AS i,
arrow_cast(TIMESTAMP '2024-01-01 22:00:00', 'Timestamp(Nanosecond, Some("+00:00"))') AS a);
-- a and b are the same instant, typed with different zones
SELECT a, b, a = b AS same_instant FROM t;
-- +----------------------+---------------------------+--------------+
-- | a | b | same_instant |
-- +----------------------+---------------------------+--------------+
-- | 2024-01-01T22:00:00Z | 2024-01-02T03:00:00+05:00 | true |
-- +----------------------+---------------------------+--------------+
SELECT nvl2(i, a, b) AS v,
arrow_typeof(nvl2(i, a, b)) AS v_type,
CAST(nvl2(i, a, b) AS DATE) AS v_date
FROM t;
-- +----------------------+-------------------------+------------+
-- | v | v_type | v_date |
-- +----------------------+-------------------------+------------+
-- | 2024-01-01T22:00:00Z | Timestamp(ns, "+00:00") | 2024-01-01 |
-- +----------------------+-------------------------+------------+
SELECT CASE WHEN i IS NOT NULL THEN a ELSE b END AS v,
arrow_typeof(CASE WHEN i IS NOT NULL THEN a ELSE b END) AS v_type,
CAST(CASE WHEN i IS NOT NULL THEN a ELSE b END AS DATE) AS v_date
FROM t;
-- +---------------------------+-------------------------+------------+
-- | v | v_type | v_date |
-- +---------------------------+-------------------------+------------+
-- | 2024-01-02T03:00:00+05:00 | Timestamp(ns, "+05:00") | 2024-01-02 |
-- +---------------------------+-------------------------+------------+
To match CASE's behavior, can we call type_union_coercion(if_null, if_non_null) instead? And add a test case for this.
(type_union_coercion's behavior here is probably not optimal but that's a separate issue.)
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Which issue does this PR close?
nvl2result type depends on the type of its first argument #26024.Rationale for this change
The result type of
nvl2(x, a, b)depended on the type ofx, even thoughxis only checked for NULL. This caused decimal results to be widened (e.g.Decimal128(22, 2)) or turned into strings, and made timestamp results fail during planning or optimization. The equivalentCASE WHEN x IS NOT NULL THEN a ELSE b ENDworked in all of these cases.What changes are included in this PR?
NVL2Func::coerce_typesnow finds the common type ofaandbonly, usingtype_union_coercion(the same ruleCASEuses for its THEN/ELSE branches).xkeeps its original type.aandbhave no common type, it now returns a plan error instead of an internal error.What is the testing strategy for this PR?
Added sqllogictest cases to
nvl2.sltthat cover the reproducers from the issue (decimal with int/float/string tests, timestamp with int/string-literal tests). Each case checks both the value andarrow_typeof.Are there any user-facing changes?
Yes.
nvl2now returns the same type as the equivalentCASEexpression. Queries that used to get a widened decimal orUtf8result now get the type ofa/b, and the timestamp cases that used to error now work. No public API changes.