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
61 changes: 60 additions & 1 deletion pkg/sql/plan/function/func_binary.go
Original file line number Diff line number Diff line change
Expand Up @@ -896,11 +896,31 @@ type NormalType interface {
types.Datetime |
types.Decimal64 |
types.Decimal128 |
types.Decimal256 |
types.Timestamp |
types.Uuid |
constraints.Integer
}

// coalesceDecimalResult derives the common decimal result type for a coalesce
// over decimal/integer branches, preserving the maximum integral width and
// scale and promoting to a wider decimal family (decimal256) when the combined
// precision overflows decimal128. It then resolves the matching overload index.
// Returns ok=false when the required precision overflows decimal256 or no
// overload matches the resulting decimal family.
func coalesceDecimalResult(overloads []overload, minOid types.T, inputs []types.Type) (types.Type, int, bool) {
target := minOid.ToType()
if !setSafeDecimalWidthAndScaleFromSource(&target, inputs) {
return types.Type{}, -1, false
}
for i, over := range overloads {
if len(over.args) == 1 && over.args[0] == target.Oid {
return target, i, true
}
}
return types.Type{}, -1, false
}

func coalesceCheck(overloads []overload, inputs []types.Type) checkResult {
if len(inputs) > 0 {
if retType, ok := mixedStringNumericToVarchar(inputs); ok {
Expand Down Expand Up @@ -930,6 +950,19 @@ func coalesceCheck(overloads []overload, inputs []types.Type) checkResult {
if sta == matchFailed {
continue
} else if sta == matchDirectly {
// Decimals that match directly still need scale/width alignment
// across branches: without it the result inherits the first
// branch's scale while carrying another branch's raw value,
// magnifying the result (issue #24565). Keep them as a candidate
// and resolve the aligned type below instead of short-circuiting.
if requireOid.IsDecimal() {
if cos < minCost {
minIndex = i
minCost = cos
minOid = requireOid
}
continue
}
return newCheckResultWithSuccess(i)
} else {
if cos < minCost {
Expand All @@ -942,6 +975,32 @@ func coalesceCheck(overloads []overload, inputs []types.Type) checkResult {
if minIndex == -1 {
return newCheckResultWithFailure(failedFunctionParametersWrong)
}

// Decimal branches: choose a common type that keeps the maximum integral
// width and scale (promoting to decimal256 when needed), so the result
// neither loses integer capacity nor inherits a single branch's scale.
if minOid.IsDecimal() {
target, overloadIndex, ok := coalesceDecimalResult(overloads, minOid, inputs)
if !ok {
return newCheckResultWithFailure(failedFunctionParametersWrong)
}
aligned := true
for i := range inputs {
if inputs[i].Oid != target.Oid || inputs[i].Scale != target.Scale || inputs[i].Width != target.Width {
aligned = false
break
}
}
if aligned {
return newCheckResultWithSuccess(overloadIndex)
}
castType := make([]types.Type, len(inputs))
for i := range castType {
castType[i] = target
}
return newCheckResultWithCast(overloadIndex, castType)
}

castType := make([]types.Type, len(inputs))
for i := range castType {
if minOid == inputs[i].Oid {
Expand All @@ -952,7 +1011,7 @@ func coalesceCheck(overloads []overload, inputs []types.Type) checkResult {
}
}

if minOid.IsDecimal() || minOid.IsDateRelate() {
if minOid.IsDateRelate() {
setMaxScaleForAll(castType)
}
return newCheckResultWithCast(minIndex, castType)
Expand Down
Loading
Loading