zanmato1984 commented on code in PR #45001:
URL: https://github.com/apache/arrow/pull/45001#discussion_r3922465195


##########
cpp/src/arrow/compute/kernels/scalar_hash_test.cc:
##########
@@ -0,0 +1,1665 @@
+// Licensed to the Apache Software Foundation (ASF) under one
+// or more contributor license agreements.  See the NOTICE file
+// distributed with this work for additional information
+// regarding copyright ownership.  The ASF licenses this file
+// to you under the Apache License, Version 2.0 (the
+// "License"); you may not use this file except in compliance
+// with the License.  You may obtain a copy of the License at
+//
+//   http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing,
+// software distributed under the License is distributed on an
+// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+// KIND, either express or implied.  See the License for the
+// specific language governing permissions and limitations
+// under the License.
+
+#include <gtest/gtest.h>
+#include <unordered_set>
+
+#include "arrow/array/builder_nested.h"
+#include "arrow/array/builder_primitive.h"
+#include "arrow/chunked_array.h"
+#include "arrow/compute/api.h"
+#include "arrow/compute/kernels/test_util_internal.h"
+#include "arrow/compute/key_hash_internal.h"
+#include "arrow/compute/util.h"
+#include "arrow/result.h"
+#include "arrow/status.h"
+#include "arrow/testing/extension_type.h"
+#include "arrow/testing/gtest_util.h"
+#include "arrow/testing/matchers.h"
+#include "arrow/testing/random.h"
+#include "arrow/testing/util.h"
+#include "arrow/util/bit_util.h"
+#include "arrow/util/cpu_info.h"
+#include "arrow/util/key_value_metadata.h"
+
+namespace arrow {
+namespace compute {
+
+constexpr auto kSeed = 0x94378165;
+constexpr auto kArrayLengths = {0, 50, 100};
+constexpr auto kNullProbabilities = {0.0, 0.5, 1.0};
+
+class TestScalarHash : public ::testing::Test {
+ public:
+  template <typename c_type>
+  void AssertHashesEqual(const std::shared_ptr<Array>& arr, Datum res,
+                         std::vector<c_type> exp) {
+    auto res_array = res.array();
+    for (int64_t val_ndx = 0; val_ndx < arr->length(); ++val_ndx) {
+      if (arr->IsNull(val_ndx)) {
+        ASSERT_TRUE(res_array->IsNull(val_ndx))
+            << "row " << val_ndx << " is null and should produce a null hash";
+      } else {
+        ASSERT_TRUE(res_array->IsValid(val_ndx))
+            << "row " << val_ndx << " is valid and should not produce a null 
hash";
+        c_type actual_hash = res_array->GetValues<c_type>(1)[val_ndx];
+        ASSERT_EQ(exp[val_ndx], actual_hash);
+      }
+    }
+  }
+
+  // Reference hash for valid rows only -- AssertHashesEqual never reads this 
vector's
+  // null-row entries, since a null row's validity (not its value) is what's 
checked
+  // there, and this raw HashFixed call doesn't handle nulls at all.
+  template <typename c_type>
+  std::vector<c_type> HashPrimitive(const std::shared_ptr<Array>& arr) {
+    std::vector<c_type> hashes(arr->length());
+    // Choose the Hasher type conditionally based on c_type
+
+    if constexpr (std::is_same_v<c_type, uint64_t>) {
+      Hashing64::HashFixed(false, static_cast<uint32_t>(arr->length()),
+                           arr->type()->bit_width() / 8,
+                           arr->data()->GetValues<uint8_t>(1), hashes.data());
+    } else {
+      
Hashing32::HashFixed(::arrow::internal::CpuInfo::GetInstance()->hardware_flags(),
+                           false, static_cast<uint32_t>(arr->length()),
+                           arr->type()->bit_width() / 8,
+                           arr->data()->GetValues<uint8_t>(1), hashes.data(), 
nullptr);
+    }
+
+    return hashes;
+  }
+
+  template <typename c_type>
+  std::vector<c_type> HashBinaryLike(const std::shared_ptr<Array>& arr) {
+    std::vector<c_type> hashes(arr->length());
+    auto length = static_cast<uint32_t>(arr->length());
+    auto values = arr->data()->GetValues<uint8_t>(2);
+    if constexpr (std::is_same_v<c_type, uint64_t>) {
+      if (arr->type_id() == Type::LARGE_BINARY || arr->type_id() == 
Type::LARGE_STRING) {
+        Hashing64::HashVarLen(false, length, 
arr->data()->GetValues<uint64_t>(1), values,
+                              hashes.data());
+      } else {
+        Hashing64::HashVarLen(false, length, 
arr->data()->GetValues<uint32_t>(1), values,
+                              hashes.data());
+      }
+    } else {
+      auto hw_flags = 
::arrow::internal::CpuInfo::GetInstance()->hardware_flags();
+      if (arr->type_id() == Type::LARGE_BINARY || arr->type_id() == 
Type::LARGE_STRING) {
+        Hashing32::HashVarLen(hw_flags, false, length,
+                              arr->data()->GetValues<uint64_t>(1), values, 
hashes.data(),
+                              nullptr);
+      } else {
+        Hashing32::HashVarLen(hw_flags, false, length,
+                              arr->data()->GetValues<uint32_t>(1), values, 
hashes.data(),
+                              nullptr);
+      }
+    }
+    return hashes;
+  }
+
+  void CheckDeterministic(const std::string& func, const 
std::shared_ptr<Array>& arr) {
+    // Check that the hash is deterministic between different runs
+    ASSERT_OK_AND_ASSIGN(Datum res1, CallFunction(func, {arr}));
+    ASSERT_OK_AND_ASSIGN(Datum res2, CallFunction(func, {arr}));
+    ValidateOutput(res1);
+    ValidateOutput(res2);
+    ASSERT_EQ(res1.length(), arr->length());
+    ASSERT_EQ(res2.length(), arr->length());
+    if (func == "hash64") {
+      ASSERT_EQ(res1.type()->id(), Type::UINT64);
+    } else if (func == "hash32") {
+      ASSERT_EQ(res1.type()->id(), Type::UINT32);
+    } else {
+      FAIL() << "Unknown function: " << func;
+    }
+    AssertDatumsEqual(res1, res2);
+
+    // Check that slicing the array does not affect the hash
+    auto hashes = res1.make_array();
+    if (arr->length() >= 1) {
+      auto in1 = arr->Slice(1);
+      ASSERT_OK_AND_ASSIGN(Datum out1, CallFunction(func, {in1}));
+      ValidateOutput(out1);
+      AssertArraysEqual(*out1.make_array(), *hashes->Slice(1));
+    }
+    if (arr->length() >= 4) {
+      auto in2 = arr->Slice(2, 2);
+      ASSERT_OK_AND_ASSIGN(Datum out2, CallFunction(func, {in2}));
+      ValidateOutput(out2);
+      AssertArraysEqual(*out2.make_array(), *hashes->Slice(2, 2));
+    }
+  }
+
+  void CheckHashQuality(const std::string& func, const std::shared_ptr<Array>& 
arr,
+                        double tolerance = 1.0) {
+    ASSERT_OK_AND_ASSIGN(Datum result, CallFunction(func, {arr}));
+    auto hashes = result.make_array();
+
+    auto expected = arr->length();
+    if (arr->null_count()) {
+      expected -= (arr->null_count() - 1);
+    }
+    if (func == "hash64") {
+      auto hashes64 = dynamic_cast<const UInt64Array*>(hashes.get());
+      std::unordered_set<uint64_t> hash_set;
+      for (int64_t i = 0; i < hashes64->length(); ++i) {
+        hash_set.insert(hashes64->Value(i));
+      }
+      ASSERT_LE(hash_set.size(), expected);
+      ASSERT_GE(hash_set.size(), expected * tolerance);
+    } else if (func == "hash32") {
+      auto hashes32 = dynamic_cast<const UInt32Array*>(hashes.get());
+      std::unordered_set<uint32_t> hash_set;
+      for (int64_t i = 0; i < hashes32->length(); ++i) {
+        // Read the raw value regardless of validity: every null row still 
stores a
+        // deterministic 0 internally, so nulls collapse into exactly one 
shared bucket
+        // here, matching `expected`'s `null_count - 1` above (same as hash64 
below).
+        hash_set.insert(hashes32->Value(i));
+      }
+      ASSERT_LE(hash_set.size(), expected);
+      ASSERT_GE(hash_set.size(), expected * tolerance);
+    } else {
+      FAIL() << "Unknown function: " << func;
+    }
+  }
+
+  void CheckPrimitive(const std::string& func, const std::shared_ptr<Array>& 
arr) {
+    ASSERT_OK_AND_ASSIGN(Datum hash_result, CallFunction(func, {arr}));
+    CheckDeterministic(func, arr);
+    if (func == "hash64") {
+      AssertHashesEqual<uint64_t>(arr, hash_result, 
HashPrimitive<uint64_t>(arr));
+    } else if (func == "hash32") {
+      AssertHashesEqual<uint32_t>(arr, hash_result, 
HashPrimitive<uint32_t>(arr));
+    } else {
+      FAIL() << "Unknown function: " << func;
+    }
+  }
+
+  void CheckBinary(const std::string& func, const std::shared_ptr<Array>& arr) 
{
+    ASSERT_OK_AND_ASSIGN(Datum hash_result, CallFunction(func, {arr}));
+    CheckDeterministic(func, arr);
+    if (func == "hash64") {
+      AssertHashesEqual<uint64_t>(arr, hash_result, 
HashBinaryLike<uint64_t>(arr));
+    } else if (func == "hash32") {
+      AssertHashesEqual<uint32_t>(arr, hash_result, 
HashBinaryLike<uint32_t>(arr));
+    } else {
+      FAIL() << "Unknown function: " << func;
+    }
+  }
+
+  // hash32/hash64 decode dictionaries to their logical values before hashing 
(rather
+  // than hashing the index buffer directly), so the result must match hashing 
the
+  // plain decoded array.
+  void CheckDictionary(const std::string& func, const std::shared_ptr<Array>& 
dict) {
+    CheckDeterministic(func, dict);
+    ASSERT_OK_AND_ASSIGN(Datum decoded, CallFunction("dictionary_decode", 
{dict}));
+    ASSERT_OK_AND_ASSIGN(Datum dict_hash, CallFunction(func, {dict}));
+    ASSERT_OK_AND_ASSIGN(Datum decoded_hash, CallFunction(func, {decoded}));
+    AssertDatumsEqual(dict_hash, decoded_hash);
+  }
+};
+
+TEST_F(TestScalarHash, Null) {
+  Datum res;
+  std::shared_ptr<Array> arr;
+  std::shared_ptr<Array> exp;
+
+  arr = ArrayFromJSON(null(), R"([])");
+  exp = ArrayFromJSON(uint32(), "[]");
+  ASSERT_OK_AND_ASSIGN(res, CallFunction("hash32", {arr}));
+  AssertArraysEqual(*res.make_array(), *exp);
+  CheckDeterministic("hash32", arr);
+
+  arr = ArrayFromJSON(null(), R"([])");
+  exp = ArrayFromJSON(uint64(), "[]");
+  ASSERT_OK_AND_ASSIGN(res, CallFunction("hash64", {arr}));
+  AssertArraysEqual(*res.make_array(), *exp);
+  CheckDeterministic("hash64", arr);
+
+  arr = ArrayFromJSON(null(), R"([null, null, null])");
+  exp = ArrayFromJSON(uint32(), "[null, null, null]");
+  ASSERT_OK_AND_ASSIGN(res, CallFunction("hash32", {arr}));
+  AssertArraysEqual(*res.make_array(), *exp);
+  CheckDeterministic("hash32", arr);
+
+  arr = ArrayFromJSON(null(), R"([null, null, null])");
+  exp = ArrayFromJSON(uint64(), "[null, null, null]");
+  ASSERT_OK_AND_ASSIGN(res, CallFunction("hash64", {arr}));
+  AssertArraysEqual(*res.make_array(), *exp);
+  CheckDeterministic("hash64", arr);
+}
+
+TEST_F(TestScalarHash, NullProducesNull) {
+  auto arr1 = ArrayFromJSON(int32(), R"([null, 0, 1])");
+  ASSERT_OK_AND_ASSIGN(auto res1, CallFunction("hash64", {arr1}));
+  auto res1_array = res1.array();
+  auto buf1 = res1_array->GetValues<uint64_t>(1);
+  ASSERT_TRUE(res1_array->IsNull(0));
+  ASSERT_TRUE(res1_array->IsValid(1));
+  ASSERT_TRUE(res1_array->IsValid(2));
+  ASSERT_NE(buf1[1], buf1[2]);
+
+  auto arr2 = ArrayFromJSON(int8(), R"([null, 0, 1])");
+  ASSERT_OK_AND_ASSIGN(auto res2, CallFunction("hash32", {arr2}));
+  auto res2_array = res2.array();
+  auto buf2 = res2_array->GetValues<uint32_t>(1);
+  ASSERT_TRUE(res2_array->IsNull(0));
+  ASSERT_TRUE(res2_array->IsValid(1));
+  ASSERT_TRUE(res2_array->IsValid(2));
+  ASSERT_NE(buf2[1], buf2[2]);
+}
+
+// HashIntImp (used for any fixed-width type whose byte width is a power of 2 
up to 8:
+// ints, floats, dates, times, timestamps, durations) doesn't special-case an
+// all-zero-bits key, so a legitimately valid "zero" value hashes to a raw 0 
-- same as
+// HashMultiColumn's own null handling would produce for an actually-null row. 
That's
+// fine: nullness is tracked via real, independent validity (see HashArray), 
not by
+// avoiding any particular hash value, so a valid row landing on 0 is just an 
ordinary
+// (if slightly more likely) hash collision, not a correctness problem. What 
must still
+// hold is that such a row is reported valid, not null. Checked across every 
affected
+// byte width, not just int8/int32 (see NullProducesNull).
+TEST_F(TestScalarHash, ZeroValueIsValid) {
+  std::vector<std::pair<std::shared_ptr<DataType>, std::string>> cases{
+      {int8(), R"([null, 0, 1])"},
+      {int16(), R"([null, 0, 1])"},
+      {int32(), R"([null, 0, 1])"},
+      {int64(), R"([null, 0, 1])"},
+      {uint8(), R"([null, 0, 1])"},
+      {uint16(), R"([null, 0, 1])"},
+      {uint32(), R"([null, 0, 1])"},
+      {uint64(), R"([null, 0, 1])"},
+      {float32(), R"([null, 0.0, 1.0])"},
+      {float64(), R"([null, 0.0, 1.0])"},
+      {date32(), R"([null, 0, 1])"},
+      {date64(), R"([null, 0, 86400000])"},
+      {time32(TimeUnit::SECOND), R"([null, 0, 1])"},
+      {time64(TimeUnit::NANO), R"([null, 0, 1])"},
+      {timestamp(TimeUnit::SECOND), R"([null, 0, 1])"},
+      {duration(TimeUnit::MILLI), R"([null, 0, 1])"},
+  };
+  for (const std::string func : {"hash32", "hash64"}) {
+    for (const auto& type_and_json : cases) {
+      auto arr = ArrayFromJSON(type_and_json.first, type_and_json.second);
+      ASSERT_OK_AND_ASSIGN(Datum result, CallFunction(func, {arr}));
+      auto hashes = result.make_array();
+      ASSERT_OK_AND_ASSIGN(auto null_hash, hashes->GetScalar(0));
+      ASSERT_OK_AND_ASSIGN(auto zero_hash, hashes->GetScalar(1));
+      ASSERT_OK_AND_ASSIGN(auto one_hash, hashes->GetScalar(2));
+      ASSERT_FALSE(null_hash->is_valid) << type_and_json.first->ToString();
+      ASSERT_TRUE(zero_hash->is_valid) << type_and_json.first->ToString();
+      ASSERT_TRUE(one_hash->is_valid) << type_and_json.first->ToString();
+      ASSERT_FALSE(zero_hash->Equals(*one_hash)) << 
type_and_json.first->ToString();
+    }
+  }
+}
+
+TEST_F(TestScalarHash, Boolean) {
+  Datum result;
+  std::shared_ptr<Array> array;
+  auto input = ArrayFromJSON(boolean(), R"([true, false, null, true, null, 
false])");
+  CheckDeterministic("hash32", input);
+  CheckDeterministic("hash64", input);
+
+  ASSERT_OK_AND_ASSIGN(result, CallFunction("hash32", {input}));
+
+  array = result.make_array();
+  auto array32 = checked_cast<const UInt32Array*>(array.get());
+  ASSERT_TRUE(array32->IsValid(0));
+  ASSERT_TRUE(array32->IsValid(1));
+  ASSERT_TRUE(array32->IsNull(2));
+  ASSERT_NE(array32->Value(0), array32->Value(1));
+  ASSERT_NE(array32->Value(0), array32->Value(2));
+  ASSERT_NE(array32->Value(1), array32->Value(2));
+  ASSERT_EQ(array32->Value(0), array32->Value(3));
+  ASSERT_EQ(array32->Value(2), array32->Value(4));
+  ASSERT_EQ(array32->Value(1), array32->Value(5));
+
+  ASSERT_OK_AND_ASSIGN(result, CallFunction("hash64", {input}));
+  array = result.make_array();
+  auto array64 = checked_cast<const UInt64Array*>(array.get());
+  ASSERT_TRUE(array64->IsValid(0));
+  ASSERT_TRUE(array64->IsValid(1));
+  ASSERT_TRUE(array64->IsNull(2));
+  ASSERT_NE(array64->Value(0), array64->Value(1));
+  ASSERT_NE(array64->Value(0), array64->Value(2));
+  ASSERT_NE(array64->Value(1), array64->Value(2));
+  ASSERT_EQ(array64->Value(0), array64->Value(3));
+  ASSERT_EQ(array64->Value(2), array64->Value(4));
+  ASSERT_EQ(array64->Value(1), array64->Value(5));
+}
+
+TEST_F(TestScalarHash, Primitive) {
+  auto types = {int8(),
+                int16(),
+                int32(),
+                int64(),
+                uint8(),
+                uint16(),
+                uint32(),
+                uint64(),
+                float16(),
+                float32(),
+                float64(),
+                time32(TimeUnit::SECOND),
+                time64(TimeUnit::NANO),
+                date32(),
+                date64(),
+                timestamp(TimeUnit::SECOND),
+                duration(TimeUnit::MILLI)};
+
+  for (auto func : {"hash32", "hash64"}) {
+    for (auto type : types) {
+      CheckPrimitive(func, ArrayFromJSON(type, R"([])"));
+      CheckPrimitive(func, ArrayFromJSON(type, R"([null])"));
+      CheckPrimitive(func, ArrayFromJSON(type, R"([1])"));
+      CheckPrimitive(func, ArrayFromJSON(type, R"([1, 2])"));
+      CheckPrimitive(func, ArrayFromJSON(type, R"([1, 2, null])"));
+      CheckPrimitive(func, ArrayFromJSON(type, R"([null, 2, 3])"));
+      CheckPrimitive(func, ArrayFromJSON(type, R"([1, 2, 3, 4])"));
+    }
+  }
+}
+
+TEST_F(TestScalarHash, BinaryLike) {
+  auto types = {binary(), utf8(), large_binary(), large_utf8()};
+  for (auto func : {"hash32", "hash64"}) {
+    for (auto type : types) {
+      CheckBinary(func, ArrayFromJSON(type, R"([])"));
+      CheckBinary(func, ArrayFromJSON(type, R"([null])"));
+      CheckBinary(func, ArrayFromJSON(type, R"([""])"));
+      CheckBinary(func, ArrayFromJSON(type, R"(["first", "second", null])"));
+      CheckBinary(func, ArrayFromJSON(type, R"(["first", "second", 
"third"])"));
+      CheckBinary(func, ArrayFromJSON(type, R"(["first", "second", 
"third"])"));
+    }
+  }
+  for (auto func : {"hash32", "hash64"}) {
+    auto type = fixed_size_binary(1);
+    CheckPrimitive(func, ArrayFromJSON(type, R"([])"));
+    CheckPrimitive(func, ArrayFromJSON(type, R"([null])"));
+    CheckPrimitive(func, ArrayFromJSON(type, R"(["a", "b"])"));
+    CheckPrimitive(func, ArrayFromJSON(type, R"([null, "b"])"));
+
+    type = fixed_size_binary(3);
+    CheckPrimitive(func, ArrayFromJSON(type, R"([])"));
+    CheckPrimitive(func, ArrayFromJSON(type, R"([null])"));
+    CheckPrimitive(func, ArrayFromJSON(type, R"(["alt", "blt"])"));
+    CheckPrimitive(func, ArrayFromJSON(type, R"([null, "blt"])"));
+  }
+}
+
+TEST_F(TestScalarHash, ExtensionType) {
+  auto storage = ArrayFromJSON(int16(), R"([1, 2, 3, 4, null])");
+  auto extension = ExtensionType::WrapArray(smallint(), storage);
+  CheckPrimitive("hash32", extension);
+  CheckPrimitive("hash64", extension);
+}
+
+TEST_F(TestScalarHash, DictionaryType) {
+  auto dict_type = dictionary(int8(), utf8());
+  auto dict = DictArrayFromJSON(dict_type, "[1, 2, null, 3, 0]",
+                                "[\"A0\", \"A1\", \"C2\", \"C3\"]");
+  CheckDictionary("hash32", dict);
+  CheckDictionary("hash64", dict);
+}
+
+TEST_F(TestScalarHash, DictionaryNullValueProducesNull) {
+  // A valid index pointing at a null dictionary entry (legal -- see the 
comment on
+  // ArrayData::IsNull) must produce a null in the output like any other null 
row, even
+  // though the index's own validity bit is set.
+  auto dict_type = dictionary(int8(), utf8());
+  auto dict = DictArrayFromJSON(dict_type, "[0, 1]", "[null, \"A1\"]");
+
+  for (const std::string func : {"hash32", "hash64"}) {
+    ASSERT_OK_AND_ASSIGN(Datum result, CallFunction(func, {dict}));
+    auto result_array = result.array();
+    ASSERT_TRUE(result_array->IsNull(0));
+    ASSERT_TRUE(result_array->IsValid(1));
+  }
+}
+
+TEST_F(TestScalarHash, DictionaryHashIndependentOfDictionaryLayout) {
+  // Two dictionary arrays encoding the same logical values via 
differently-ordered
+  // dictionaries must hash identically -- the hash reflects logical value, 
not index.
+  auto dict_type = dictionary(int8(), utf8());
+  auto dict1 = DictArrayFromJSON(dict_type, "[0, 1, 2]", "[\"A\", \"B\", 
\"C\"]");
+  auto dict2 = DictArrayFromJSON(dict_type, "[2, 1, 0]", "[\"C\", \"B\", 
\"A\"]");
+
+  ASSERT_OK_AND_ASSIGN(Datum hash1, CallFunction("hash64", {dict1}));
+  ASSERT_OK_AND_ASSIGN(Datum hash2, CallFunction("hash64", {dict2}));
+  AssertDatumsEqual(hash1, hash2);
+}
+
+TEST_F(TestScalarHash, RandomBinaryLike) {
+  auto rand = random::RandomArrayGenerator(kSeed);
+  auto types = {binary(), utf8(), large_binary(), large_utf8()};
+
+  for (auto length : kArrayLengths) {
+    for (auto null_probability : kNullProbabilities) {
+      for (auto type : types) {
+        auto arr = rand.ArrayOf(type, length, null_probability);
+        CheckBinary("hash32", arr);
+        CheckBinary("hash64", arr);
+      }
+      for (auto type : {fixed_size_binary(1), fixed_size_binary(3)}) {
+        auto arr = rand.ArrayOf(type, length, null_probability);
+        CheckPrimitive("hash32", arr);
+        CheckPrimitive("hash64", arr);
+      }
+      auto arr = rand.ArrayOf(fixed_size_binary(0), length, null_probability);
+      CheckDeterministic("hash32", arr);
+      CheckDeterministic("hash64", arr);
+    }
+  }
+}
+
+// A zero-width fixed_size_binary holds no data, so every value is the same 
empty byte
+// string and every row must hash identically. ToColumnArray can only describe 
it as a
+// fixed-width column of length 0 -- indistinguishable from a bit-packed 
boolean -- so
+// HashMultiColumn used to hash each row from a nonexistent bit, producing 
uninitialized
+// garbage that varied per row and per slice. Only reachable via a dictionary 
once
+// dictionaries started being decoded, but broken for the plain type all along.
+TEST_F(TestScalarHash, ZeroWidthFixedSizeBinaryRowsHashEqually) {
+  auto type = fixed_size_binary(0);
+  auto arr = ArrayFromJSON(type, R"(["", "", "", ""])");
+  auto dict = DictArrayFromJSON(dictionary(int8(), type), "[0, 0, 0, 0]", 
R"([""])");
+
+  for (const std::string func : {"hash32", "hash64"}) {
+    for (const auto& input : {arr, dict}) {
+      ASSERT_OK_AND_ASSIGN(Datum result, CallFunction(func, {input}));
+      auto hashes = result.make_array();
+      ASSERT_OK_AND_ASSIGN(auto first, hashes->GetScalar(0));
+      for (int64_t i = 1; i < hashes->length(); i++) {
+        ASSERT_OK_AND_ASSIGN(auto other, hashes->GetScalar(i));
+        ASSERT_TRUE(first->Equals(*other))
+            << "row " << i << " of " << input->type()->ToString()
+            << " holds the same empty value as row 0 and must hash the same";
+      }
+      // Hashing a slice must agree with slicing the hash (the garbage-bit 
read above
+      // depended on the row's absolute bit offset, so it did not).
+      auto sliced = input->Slice(2, 2);
+      ASSERT_OK_AND_ASSIGN(Datum sliced_result, CallFunction(func, {sliced}));
+      AssertArraysEqual(*sliced_result.make_array(), *hashes->Slice(2, 2));
+    }
+  }
+}
+
+// The same zero-width hazard, but as a struct field: a struct's non-nested 
children go

Review Comment:
   The zero-width comment above belongs to 
ZeroWidthFixedSizeBinaryStructFieldHashesEqually below. Could you move it 
immediately above that test? It currently reads as part of 
ListNullElementDoesNotCollideWithZeroElement.



-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: [email protected]

For queries about this service, please contact Infrastructure at:
[email protected]

Reply via email to