)
Three changes fix the type inference bug:
1. QualifiedNameResolver: Use NULL type (not VARCHAR) for null literals
created from unresolved field names inside COALESCE. This allows the
return type inference to correctly ignore NULL-typed operands.
2. EnhancedCoalesceFunction: Filter out NULL-typed operands before
calling leastRestrictive, so COALESCE(null, 42) infers INTEGER from
the non-null operand rather than falling back to VARCHAR.
3. CalciteRexNodeVisitor: Scope the inCoalesceFunction flag to direct
COALESCE arguments only, clearing it for nested function calls.
This prevents unresolved fields deep inside expressions like
array_compact(does_not_exist) from silently becoming null.
Signed-off-by: Heng Qian <qianheng@amazon.com>
Summary
Fixes #5175
COALESCE(null, 42)was returning"42"with schema typestringinstead of numeric42with typeinteger. The root cause involved three interacting issues in the Calcite engine's COALESCE handling:QualifiedNameResolver: When an unresolved field name (like the barenullkeyword, which PPL parses as a field name) was encountered inside COALESCE, it was replaced with anull:VARCHARliteral. This caused the return type inference to see VARCHAR as a concrete type and include it in the common type computation.EnhancedCoalesceFunction: The return type inference calledleastRestrictiveon all operand types including NULL-typed operands. Calcite'sleastRestrictivereturnsnullwhen it cannot reconcile NULL with other types, causing a fallback to VARCHAR.CalciteRexNodeVisitor: TheinCoalesceFunctioncontext flag was not scoped to direct COALESCE arguments — it leaked into nested function calls. This meant that commands likenomv does_not_exist(which internally rewrites to a COALESCE expression) would silently replace deeply-nested unresolved fields with null instead of throwing an error.Changes
QualifiedNameResolver.replaceWithNullLiteralInCoalesce: Changed the null literal type fromVARCHARtoNULL, allowing the return type inference to correctly derive the type from non-null operands.EnhancedCoalesceFunction.getReturnTypeInference: Filter outNULL-typed operands before callingleastRestrictive, then mark the result as nullable. If all operands are NULL, return nullable NULL type.CalciteRexNodeVisitor.visitFunction: Save/restore theinCoalesceFunctionflag so that only direct COALESCE arguments get the null-replacement behavior, not deeply nested expressions.Test plan
testCoalesceNullWithInteger— verifiesCOALESCE(null, 42)has return type INTEGERtestCoalesceIntegerWithNull— verifiesCOALESCE(42, null)has return type INTEGERtestCoalesceNullWithIntegerResult— verifies execution produces numeric42, not string"42"null:NULLinstead ofnull:VARCHARCalcitePPLNoMvTest.testNoMvNonExistentFieldcontinues to correctly throw an error