From 57cd326471189e0ac6c79249be6481b7a0e5e202 Mon Sep 17 00:00:00 2001 From: Nilanjan Chatterjee Date: Sun, 11 Oct 2026 14:50:24 -0500 Subject: [PATCH] GH-39635: [C++] Keep null in unique/dictionary output for view types The binary view dictionary builder copied every memo table entry as a string, so the null entry came out as an empty string. Co-Authored-By: Claude Opus 5.5 --- cpp/src/arrow/array/dict_internal.h | 11 +++++++++-- cpp/src/arrow/compute/kernels/vector_hash_test.cc | 12 ++++++++++++ 2 files changed, 21 insertions(+), 2 deletions(-) diff --git a/cpp/src/arrow/array/dict_internal.h b/cpp/src/arrow/array/dict_internal.h index 8ddc4b448a7e..bbefe9568009 100644 --- a/cpp/src/arrow/array/dict_internal.h +++ b/cpp/src/arrow/array/dict_internal.h @@ -166,8 +166,15 @@ struct DictionaryTraits> { BinaryViewBuilder builder(pool); RETURN_NOT_OK(builder.Resize(memo_table.size() - start_offset)); RETURN_NOT_OK(builder.ReserveData(memo_table.values_size())); - memo_table.VisitValues(static_cast(start_offset), - [&](std::string_view s) { builder.UnsafeAppend(s); }); + const int32_t null_index = memo_table.GetNull(); + int32_t index = static_cast(start_offset); + memo_table.VisitValues(static_cast(start_offset), [&](std::string_view s) { + if (index++ == null_index) { + builder.UnsafeAppendNull(); + } else { + builder.UnsafeAppend(s); + } + }); std::shared_ptr out; RETURN_NOT_OK(builder.FinishInternal(&out)); diff --git a/cpp/src/arrow/compute/kernels/vector_hash_test.cc b/cpp/src/arrow/compute/kernels/vector_hash_test.cc index b11e0a722a57..b342979f2caf 100644 --- a/cpp/src/arrow/compute/kernels/vector_hash_test.cc +++ b/cpp/src/arrow/compute/kernels/vector_hash_test.cc @@ -538,6 +538,18 @@ TYPED_TEST(TestHashKernelBinaryTypes, BinaryResizeTable) { this->CheckDictEncodeP(values, {}, uniques, {}, indices); } +TEST_F(TestHashKernel, UniqueBinaryViewWithNull) { + // GH-39635: the null slot must stay null in the unique values + for (const auto& type : {binary_view(), utf8_view()}) { + ARROW_SCOPED_TRACE(type->ToString()); + CheckUnique(ArrayFromJSON(type, R"(["test", "a string that is not inlined", null, + "test"])"), + ArrayFromJSON(type, R"(["test", "a string that is not inlined", null])")); + CheckUnique(ArrayFromJSON(type, R"(["ab", null, "cd", "ab", null])")->Slice(1, 3), + ArrayFromJSON(type, R"([null, "cd", "ab"])")); + } +} + TEST_F(TestHashKernel, UniqueFixedSizeBinary) { auto type = fixed_size_binary(3);