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

thisisnic 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 fcac3dfd8e2 GH-48072: [C++][Acero] fix a bug of materialize boolean 
(#48073)
fcac3dfd8e2 is described below

commit fcac3dfd8e2016512505fd86880ce6cf39304e94
Author: waruto <[email protected]>
AuthorDate: Thu Oct 1 00:23:26 2026 +0800

    GH-48072: [C++][Acero] fix a bug of materialize boolean (#48073)
    
    
    ### Rationale for this change
    Use the correct offset when accessing boolean values from the buffer.
    
    ### What changes are included in this PR?
    In `unmaterialized_table_internal.h`, use the correct offset when accessing 
boolean values from the buffer. Also, add a unit test to regress this bug.
    ### Are these changes tested?
    Yes
    ### Are there any user-facing changes?
    No
    
    **This PR contains a "Critical Fix".**
    
    Yes, fix a bug that caused incorrect data
    
    * GitHub Issue: #48072
    
    Lead-authored-by: jsonwu <[email protected]>
    Co-authored-by: Nic Crane <[email protected]>
    Co-authored-by: Rossi Sun <[email protected]>
    Signed-off-by: Nic Crane <[email protected]>
---
 cpp/src/arrow/acero/sorted_merge_node_test.cc      | 116 +++++++++++++++++++++
 .../arrow/acero/unmaterialized_table_internal.h    |   5 +-
 2 files changed, 120 insertions(+), 1 deletion(-)

diff --git a/cpp/src/arrow/acero/sorted_merge_node_test.cc 
b/cpp/src/arrow/acero/sorted_merge_node_test.cc
index 82b630420c4..b3f26356418 100644
--- a/cpp/src/arrow/acero/sorted_merge_node_test.cc
+++ b/cpp/src/arrow/acero/sorted_merge_node_test.cc
@@ -16,16 +16,22 @@
 // under the License.
 
 #include <gtest/gtest.h>
+#include <limits>
+#include <memory>
+#include <vector>
 
 #include "arrow/acero/exec_plan.h"
 #include "arrow/acero/map_node.h"
 #include "arrow/acero/options.h"
 #include "arrow/acero/test_nodes.h"
 #include "arrow/array/builder_base.h"
+#include "arrow/array/builder_primitive.h"
 #include "arrow/array/concatenate.h"
 #include "arrow/compute/ordering.h"
+#include "arrow/record_batch.h"
 #include "arrow/result.h"
 #include "arrow/scalar.h"
+#include "arrow/status.h"
 #include "arrow/table.h"
 #include "arrow/testing/generator.h"
 #include "arrow/testing/gtest_util.h"
@@ -83,4 +89,114 @@ TEST(SortedMergeNode, Basic) {
   AssertArraysEqual(*expected_ts, *output_ts);
 }
 
+TEST(SortedMergeNode, TestSortedMergeTwoInputsWithBool) {
+  const int64_t row_count = (16 << 10);  // 16k rows per input
+  // Not a multiple of 8, so that batches start at non-byte-aligned offsets
+  const int64_t batch_size = 1001;
+
+  // Create schema with int column A and bool column B
+  auto test_schema = arrow::schema(
+      {arrow::field("col_a", arrow::int32()), arrow::field("col_b", 
arrow::boolean())});
+
+  // Helper lambda to create table source with specific pattern
+  auto make_source = [&](int64_t cnt, int offset) -> 
arrow::Result<Declaration> {
+    // Create column A (int) - values from offset to offset+cnt-1
+    arrow::Int32Builder col_a_builder;
+    std::vector<int32_t> col_a_values;
+    col_a_values.reserve(cnt);
+    for (int64_t i = 0; i < cnt; ++i) {
+      col_a_values.push_back(static_cast<int32_t>(offset + i));
+    }
+    ARROW_RETURN_NOT_OK(col_a_builder.AppendValues(col_a_values));
+    std::shared_ptr<arrow::Array> col_a_arr;
+    ARROW_RETURN_NOT_OK(col_a_builder.Finish(&col_a_arr));
+
+    // Create column B (bool) - pattern: null if col_a % 7 == 0, otherwise
+    // true if col_a % 5 == 0, false otherwise
+    arrow::BooleanBuilder col_b_builder;
+    for (int64_t i = 0; i < cnt; ++i) {
+      int32_t a_value = static_cast<int32_t>(offset + i);
+      if (a_value % 7 == 0) {
+        ARROW_RETURN_NOT_OK(col_b_builder.AppendNull());
+        continue;
+      }
+      bool b_value = (a_value % 5 == 0);
+      ARROW_RETURN_NOT_OK(col_b_builder.Append(b_value));
+    }
+    std::shared_ptr<arrow::Array> col_b_arr;
+    ARROW_RETURN_NOT_OK(col_b_builder.Finish(&col_b_arr));
+
+    auto table = arrow::Table::Make(test_schema, {col_a_arr, col_b_arr});
+    auto table_source =
+        Declaration("table_source", TableSourceNodeOptions(table, batch_size));
+    return table_source;
+  };
+
+  ASSERT_OK_AND_ASSIGN(auto source1, make_source(row_count, 0));
+  ASSERT_OK_AND_ASSIGN(auto source2, make_source(row_count, 8192));
+
+  // Create sorted merge by column A
+  auto ops = 
OrderByNodeOptions(compute::Ordering({compute::SortKey("col_a")}));
+  Declaration sorted_merge{"sorted_merge", {source1, source2}, ops};
+
+  // Execute plan and collect result
+  ASSERT_OK_AND_ASSIGN(auto result_table,
+                       arrow::acero::DeclarationToTable(sorted_merge, false));
+
+  ASSERT_TRUE(result_table != nullptr);
+
+  // Verify results
+  auto col_a = result_table->GetColumnByName("col_a");
+  auto col_b = result_table->GetColumnByName("col_b");
+  ASSERT_TRUE(col_a != nullptr);
+  ASSERT_TRUE(col_b != nullptr);
+
+  // Verify sorting and bool values
+  int32_t last_a_value = std::numeric_limits<int32_t>::min();
+  int64_t total_rows_checked = 0;
+
+  for (int i = 0; i < col_a->num_chunks(); i++) {
+    auto a_chunk = 
std::static_pointer_cast<arrow::Int32Array>(col_a->chunk(i));
+    auto b_chunk = 
std::static_pointer_cast<arrow::BooleanArray>(col_b->chunk(i));
+
+    ASSERT_EQ(a_chunk->length(), b_chunk->length())
+        << "Column A and B must have same length in chunk " << i;
+
+    for (int64_t j = 0; j < a_chunk->length(); j++) {
+      ASSERT_FALSE(a_chunk->IsNull(j)) << "Column A should not have null 
values";
+
+      int32_t a_value = a_chunk->Value(j);
+
+      // Verify sorting by column A
+      ASSERT_GE(a_value, last_a_value)
+          << "Values not sorted at chunk " << i << ", row " << j
+          << ": current=" << a_value << ", last=" << last_a_value;
+      last_a_value = a_value;
+
+      // Verify bool validity: should be null if a_value % 7 == 0
+      bool expected_b_null = (a_value % 7 == 0);
+      ASSERT_EQ(b_chunk->IsNull(j), expected_b_null)
+          << "Bool validity incorrect at chunk " << i << ", row " << j
+          << ": col_a=" << a_value;
+
+      if (!expected_b_null) {
+        // Verify bool value correctness: should be true if a_value % 5 == 0
+        bool b_value = b_chunk->Value(j);
+        bool expected_b_value = (a_value % 5 == 0);
+        ASSERT_EQ(b_value, expected_b_value)
+            << "Bool value incorrect at chunk " << i << ", row " << j
+            << ": col_a=" << a_value << ", col_b=" << b_value
+            << ", expected=" << expected_b_value;
+      }
+
+      total_rows_checked++;
+    }
+  }
+
+  ASSERT_EQ(last_a_value, 24575);
+
+  ASSERT_EQ(total_rows_checked, row_count * 2)
+      << "Expected " << row_count * 2 << " rows after merge";
+}
+
 }  // namespace arrow::acero
diff --git a/cpp/src/arrow/acero/unmaterialized_table_internal.h 
b/cpp/src/arrow/acero/unmaterialized_table_internal.h
index 86b1a763a60..335d421500e 100644
--- a/cpp/src/arrow/acero/unmaterialized_table_internal.h
+++ b/cpp/src/arrow/acero/unmaterialized_table_internal.h
@@ -172,7 +172,10 @@ class UnmaterializedCompositeTable {
       builder.UnsafeAppendNull();
       return Status::OK();
     }
-    builder.UnsafeAppend(bit_util::GetBit(source->template 
GetValues<uint8_t>(1), row));
+
+    const int64_t bit_offset = source->offset + static_cast<int64_t>(row);
+    builder.UnsafeAppend(
+        bit_util::GetBit(source->template GetValues<uint8_t>(1, 0), 
bit_offset));
     return Status::OK();
   }
 

Reply via email to