This is an automated email from the ASF dual-hosted git repository.

zanmato1984 pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/arrow.git


The following commit(s) were added to refs/heads/main by this push:
     new 974c3fc1a77 GH-50624: [C++][Compute] Tighten case_when exact dispatch 
for parameterized types (#50625)
974c3fc1a77 is described below

commit 974c3fc1a772eccc85bd3500bad8573a57ff5089
Author: Rossi Sun <[email protected]>
AuthorDate: Mon Jul 27 11:31:51 2026 -0700

    GH-50624: [C++][Compute] Tighten case_when exact dispatch for parameterized 
types (#50625)
    
    ### Rationale for this change
    
    `case_when` exact dispatch was too permissive for parameterized value 
types. When the value arguments were registered by type id only, 
`DispatchExact` could incorrectly accept incompatible concrete types and return 
successful but corrupted results instead of failing or falling back to 
`DispatchBest`.
    
    This fixes #50624.
    
    ### What changes are included in this PR?
    
    - rename the case_when exact-match helper to 
`AllValueTypesMatchConstraint()` to make its intent explicit
    - apply the same exact-match constraint across case_when value kernels so 
exact dispatch only accepts identical value `DataType`s
    - add regression coverage for mismatched parameterized value types, 
including `fixed_size_binary`, `list`, `fixed_size_list`, `struct`, and 
dictionary exact-dispatch mismatches
    
    ### Are these changes tested?
    
    Yes.
    
    This PR extends `TestCaseWhen.DispatchExact` and adds 
`TestCaseWhen.ParameterizedValueTypeMismatch`. I also ran targeted 
`arrow-compute-scalar-if-else-test` coverage for:
    
    - `TestCaseWhen.DispatchExact`
    - `TestCaseWhen.DispatchBest`
    - `TestCaseWhen.ParameterizedValueTypeMismatch`
    
    ### Are there any user-facing changes?
    
    Yes.
    
    `case_when` now rejects incompatible parameterized value types that 
previously could be incorrectly exact-dispatched, which could lead to corrupted 
results.
    
    **This PR contains a "Critical Fix".** It fixes a bug where `case_when` 
could return successful but corrupted results for incompatible parameterized 
value types.
    
    * GitHub Issue: #50624
    
    Authored-by: Rossi Sun <[email protected]>
    Signed-off-by: Rossi Sun <[email protected]>
---
 cpp/src/arrow/compute/kernels/scalar_if_else.cc    | 70 ++++++++++++----------
 .../arrow/compute/kernels/scalar_if_else_test.cc   | 52 ++++++++++++++++
 2 files changed, 89 insertions(+), 33 deletions(-)

diff --git a/cpp/src/arrow/compute/kernels/scalar_if_else.cc 
b/cpp/src/arrow/compute/kernels/scalar_if_else.cc
index 26f880fc0c6..1510dd9fc83 100644
--- a/cpp/src/arrow/compute/kernels/scalar_if_else.cc
+++ b/cpp/src/arrow/compute/kernels/scalar_if_else.cc
@@ -1495,13 +1495,11 @@ struct CaseWhenFunction : ScalarFunction {
     return arrow::compute::detail::NoMatchingKernel(this, *types);
   }
 
-  static std::shared_ptr<MatchConstraint> DecimalMatchConstraint() {
+  // For case_when exact dispatch, all value arguments must have identical 
DataType.
+  static std::shared_ptr<MatchConstraint> AllValueTypesMatchConstraint() {
     static auto constraint =
         MatchConstraint::Make([](const std::vector<TypeHolder>& types) -> bool 
{
           DCHECK_GE(types.size(), 2);
-          DCHECK(std::all_of(types.begin() + 1, types.end(), [](const 
TypeHolder& type) {
-            return is_decimal(type.id());
-          }));
           return std::all_of(
               types.begin() + 2, types.end(),
               [&types](const TypeHolder& type) { return type == types[1]; });
@@ -2738,10 +2736,10 @@ struct ChooseFunction : ScalarFunction {
 
 void AddCaseWhenKernel(const std::shared_ptr<CaseWhenFunction>& 
scalar_function,
                        detail::GetTypeId get_id, ArrayKernelExec exec,
-                       std::shared_ptr<MatchConstraint> constraint = nullptr) {
+                       const std::shared_ptr<MatchConstraint>& constraint) {
   ScalarKernel kernel(
       KernelSignature::Make({InputType(Type::STRUCT), InputType(get_id.id)}, 
LastType,
-                            /*is_varargs=*/true, std::move(constraint)),
+                            /*is_varargs=*/true, constraint),
       exec);
   if (is_fixed_width(get_id.id)) {
     kernel.null_handling = NullHandling::COMPUTED_PREALLOCATE;
@@ -2756,38 +2754,42 @@ void AddCaseWhenKernel(const 
std::shared_ptr<CaseWhenFunction>& scalar_function,
 }
 
 void AddPrimitiveCaseWhenKernels(const std::shared_ptr<CaseWhenFunction>& 
scalar_function,
-                                 const std::vector<std::shared_ptr<DataType>>& 
types) {
+                                 const std::vector<std::shared_ptr<DataType>>& 
types,
+                                 const std::shared_ptr<MatchConstraint>& 
constraint) {
   for (auto&& type : types) {
     auto exec = GenerateTypeAgnosticPrimitive<CaseWhenFunctor>(*type);
-    AddCaseWhenKernel(scalar_function, type, std::move(exec));
+    AddCaseWhenKernel(scalar_function, type, std::move(exec), constraint);
   }
 }
 
 void AddBinaryCaseWhenKernels(const std::shared_ptr<CaseWhenFunction>& 
scalar_function,
-                              const std::vector<std::shared_ptr<DataType>>& 
types) {
+                              const std::vector<std::shared_ptr<DataType>>& 
types,
+                              const std::shared_ptr<MatchConstraint>& 
constraint) {
   for (auto&& type : types) {
     auto exec = GenerateTypeAgnosticVarBinaryBase<CaseWhenFunctor>(*type);
-    AddCaseWhenKernel(scalar_function, type, std::move(exec));
+    AddCaseWhenKernel(scalar_function, type, std::move(exec), constraint);
   }
 }
 
 template <typename ArrowNestedType>
-void AddNestedCaseWhenKernel(const std::shared_ptr<CaseWhenFunction>& 
scalar_function) {
+void AddNestedCaseWhenKernel(const std::shared_ptr<CaseWhenFunction>& 
scalar_function,
+                             const std::shared_ptr<MatchConstraint>& 
constraint) {
   AddCaseWhenKernel(scalar_function, ArrowNestedType::type_id,
-                    CaseWhenFunctor<ArrowNestedType>::Exec);
+                    CaseWhenFunctor<ArrowNestedType>::Exec, constraint);
 }
 
-void AddNestedCaseWhenKernels(const std::shared_ptr<CaseWhenFunction>& 
scalar_function) {
-  AddNestedCaseWhenKernel<FixedSizeListType>(scalar_function);
-  AddNestedCaseWhenKernel<ListType>(scalar_function);
-  AddNestedCaseWhenKernel<LargeListType>(scalar_function);
-  AddNestedCaseWhenKernel<ListViewType>(scalar_function);
-  AddNestedCaseWhenKernel<LargeListViewType>(scalar_function);
-  AddNestedCaseWhenKernel<MapType>(scalar_function);
-  AddNestedCaseWhenKernel<StructType>(scalar_function);
-  AddNestedCaseWhenKernel<DenseUnionType>(scalar_function);
-  AddNestedCaseWhenKernel<SparseUnionType>(scalar_function);
-  AddNestedCaseWhenKernel<DictionaryType>(scalar_function);
+void AddNestedCaseWhenKernels(const std::shared_ptr<CaseWhenFunction>& 
scalar_function,
+                              const std::shared_ptr<MatchConstraint>& 
constraint) {
+  AddNestedCaseWhenKernel<FixedSizeListType>(scalar_function, constraint);
+  AddNestedCaseWhenKernel<ListType>(scalar_function, constraint);
+  AddNestedCaseWhenKernel<LargeListType>(scalar_function, constraint);
+  AddNestedCaseWhenKernel<ListViewType>(scalar_function, constraint);
+  AddNestedCaseWhenKernel<LargeListViewType>(scalar_function, constraint);
+  AddNestedCaseWhenKernel<MapType>(scalar_function, constraint);
+  AddNestedCaseWhenKernel<StructType>(scalar_function, constraint);
+  AddNestedCaseWhenKernel<DenseUnionType>(scalar_function, constraint);
+  AddNestedCaseWhenKernel<SparseUnionType>(scalar_function, constraint);
+  AddNestedCaseWhenKernel<DictionaryType>(scalar_function, constraint);
 }
 
 void AddCoalesceKernel(const std::shared_ptr<ScalarFunction>& scalar_function,
@@ -2909,19 +2911,21 @@ void RegisterScalarIfElse(FunctionRegistry* registry) {
   {
     auto func = std::make_shared<CaseWhenFunction>(
         "case_when", Arity::VarArgs(/*min_args=*/2), case_when_doc);
-    AddPrimitiveCaseWhenKernels(func, NumericTypes());
-    AddPrimitiveCaseWhenKernels(func, TemporalTypes());
-    AddPrimitiveCaseWhenKernels(func, IntervalTypes());
-    AddPrimitiveCaseWhenKernels(func, DurationTypes());
-    AddPrimitiveCaseWhenKernels(func, {boolean(), null(), float16()});
+    auto all_value_types_match = 
CaseWhenFunction::AllValueTypesMatchConstraint();
+    AddPrimitiveCaseWhenKernels(func, NumericTypes(), all_value_types_match);
+    AddPrimitiveCaseWhenKernels(func, TemporalTypes(), all_value_types_match);
+    AddPrimitiveCaseWhenKernels(func, IntervalTypes(), all_value_types_match);
+    AddPrimitiveCaseWhenKernels(func, DurationTypes(), all_value_types_match);
+    AddPrimitiveCaseWhenKernels(func, {boolean(), null(), float16()},
+                                all_value_types_match);
     AddCaseWhenKernel(func, Type::FIXED_SIZE_BINARY,
-                      CaseWhenFunctor<FixedSizeBinaryType>::Exec);
+                      CaseWhenFunctor<FixedSizeBinaryType>::Exec, 
all_value_types_match);
     AddCaseWhenKernel(func, Type::DECIMAL128, 
CaseWhenFunctor<FixedSizeBinaryType>::Exec,
-                      CaseWhenFunction::DecimalMatchConstraint());
+                      all_value_types_match);
     AddCaseWhenKernel(func, Type::DECIMAL256, 
CaseWhenFunctor<FixedSizeBinaryType>::Exec,
-                      CaseWhenFunction::DecimalMatchConstraint());
-    AddBinaryCaseWhenKernels(func, BaseBinaryTypes());
-    AddNestedCaseWhenKernels(func);
+                      all_value_types_match);
+    AddBinaryCaseWhenKernels(func, BaseBinaryTypes(), all_value_types_match);
+    AddNestedCaseWhenKernels(func, all_value_types_match);
     DCHECK_OK(registry->AddFunction(std::move(func)));
   }
   {
diff --git a/cpp/src/arrow/compute/kernels/scalar_if_else_test.cc 
b/cpp/src/arrow/compute/kernels/scalar_if_else_test.cc
index 6fdcff8d970..a1ef82383e2 100644
--- a/cpp/src/arrow/compute/kernels/scalar_if_else_test.cc
+++ b/cpp/src/arrow/compute/kernels/scalar_if_else_test.cc
@@ -2726,6 +2726,36 @@ TEST(TestCaseWhen, UnionBoolStringRandom) {
 }
 
 TEST(TestCaseWhen, DispatchExact) {
+  // Matching parameterized types should exact-match.
+  CheckDispatchExact("case_when", {struct_({field("", boolean())}), 
fixed_size_binary(4),
+                                   fixed_size_binary(4)});
+  CheckDispatchExact("case_when",
+                     {struct_({field("", boolean())}), list(int32()), 
list(int32())});
+  CheckDispatchExact("case_when",
+                     {struct_({field("", boolean())}), 
fixed_size_list(int32(), 2),
+                      fixed_size_list(int32(), 2)});
+  CheckDispatchExact("case_when",
+                     {struct_({field("", boolean())}), dictionary(int8(), 
utf8()),
+                      dictionary(int8(), utf8())});
+
+  // Mismatched parameterized types should not exact-match.
+  CheckDispatchExactFails("case_when", {struct_({field("", boolean())}),
+                                        fixed_size_binary(4), 
fixed_size_binary(5)});
+  CheckDispatchExactFails(
+      "case_when", {struct_({field("", boolean())}), list(int16()), 
list(int32())});
+  CheckDispatchExactFails("case_when",
+                          {struct_({field("", boolean())}), 
fixed_size_list(int32(), 2),
+                           fixed_size_list(int32(), 3)});
+  CheckDispatchExactFails(
+      "case_when", {struct_({field("", boolean())}), struct_({field("a", 
int32())}),
+                    struct_({field("a", int64())})});
+  CheckDispatchExactFails("case_when",
+                          {struct_({field("", boolean())}), dictionary(int8(), 
utf8()),
+                           dictionary(int8(), large_utf8())});
+  CheckDispatchExactFails("case_when",
+                          {struct_({field("", boolean())}), dictionary(int8(), 
utf8()),
+                           dictionary(int16(), utf8())});
+
   // Decimal types with same (p, s)
   CheckDispatchExact("case_when", {struct_({field("", boolean())}), 
decimal128(20, 3),
                                    decimal128(20, 3)});
@@ -2825,6 +2855,28 @@ TEST(TestCaseWhen, DispatchBest) {
       {struct_({field("", boolean())}), decimal256(23, 3), decimal256(23, 3)});
 }
 
+TEST(TestCaseWhen, ParameterizedValueTypeMismatch) {
+  auto cond = MakeStruct({ArrayFromJSON(boolean(), "[true]")});
+
+  ASSERT_RAISES(
+      NotImplemented,
+      CallFunction("case_when", {cond, ArrayFromJSON(fixed_size_binary(4), 
R"(["abcd"])"),
+                                 ArrayFromJSON(fixed_size_binary(5), 
R"(["efghi"])")}));
+  ASSERT_RAISES(NotImplemented,
+                CallFunction("case_when", {cond, ArrayFromJSON(list(int16()), 
"[[1, 2]]"),
+                                           ArrayFromJSON(list(int32()), "[[3, 
4]]")}));
+  ASSERT_RAISES(
+      NotImplemented,
+      CallFunction("case_when",
+                   {cond, ArrayFromJSON(fixed_size_list(int32(), 2), "[[1, 
2]]"),
+                    ArrayFromJSON(fixed_size_list(int32(), 3), "[[3, 4, 
5]]")}));
+  ASSERT_RAISES(
+      NotImplemented,
+      CallFunction("case_when",
+                   {cond, ArrayFromJSON(struct_({field("a", int32())}), 
R"([{"a": 1}])"),
+                    ArrayFromJSON(struct_({field("a", int64())}), R"([{"a": 
2}])")}));
+}
+
 template <typename Type>
 class TestCoalesceNumeric : public ::testing::Test {};
 template <typename Type>

Reply via email to