Skip to content
Open
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
47 changes: 47 additions & 0 deletions cpp/src/arrow/ipc/read_write_test.cc
Original file line number Diff line number Diff line change
Expand Up @@ -3330,6 +3330,53 @@ TEST(PreBuffering, MixedAccess) {
ASSERT_EQ(2, stats.num_record_batches);
}

TEST(TestRecordBatchPayload, ZeroLengthArrayValueOffsets) {
auto schema = arrow::schema({field("str", utf8())});
const auto options = IpcWriteOptions::Defaults();

// Zero-length array with null offsets buffer
{
auto array_data = ArrayData::Make(utf8(), /*length=*/0,
/*buffers=*/{nullptr, nullptr, nullptr});
auto batch =
RecordBatch::Make(schema, /*num_rows=*/0, {MakeArray(std::move(array_data))});
IpcPayload payload;
ASSERT_OK(GetRecordBatchPayload(*batch, options, &payload));
ASSERT_EQ(payload.body_buffers.size(), 3);
ASSERT_EQ(payload.body_buffers[1], nullptr);
}

// Zero-length array with empty offsets buffer
{
auto empty_buf = Buffer::FromString("");
auto array_data = ArrayData::Make(utf8(), /*length=*/0,
/*buffers=*/{nullptr, empty_buf, empty_buf});
auto batch =
RecordBatch::Make(schema, /*num_rows=*/0, {MakeArray(std::move(array_data))});
IpcPayload payload;
ASSERT_OK(GetRecordBatchPayload(*batch, options, &payload));
ASSERT_EQ(payload.body_buffers.size(), 3);
ASSERT_NE(payload.body_buffers[1], nullptr);
ASSERT_EQ(payload.body_buffers[1]->size(), 0);
}

// Zero-length slice of a non-empty array should not retain the parent offsets
{
auto full_array = ArrayFromJSON(utf8(), R"(["ab", "cd", "ef"])");
ASSERT_GT(full_array->data()->buffers[1]->size(),
static_cast<int64_t>(sizeof(int32_t)));

auto empty_slice = full_array->Slice(3, 0);
auto batch = RecordBatch::Make(schema, /*num_rows=*/0, {empty_slice});
IpcPayload payload;
ASSERT_OK(GetRecordBatchPayload(*batch, options, &payload));
ASSERT_EQ(payload.body_buffers.size(), 3);
ASSERT_NE(payload.body_buffers[1], nullptr);
ASSERT_EQ(payload.body_buffers[1]->size(), sizeof(int32_t));
ASSERT_EQ(payload.body_buffers[1]->data_as<int32_t>()[0], 0);
}
}

} // namespace test
} // namespace ipc
} // namespace arrow
12 changes: 11 additions & 1 deletion cpp/src/arrow/ipc/writer.cc
Original file line number Diff line number Diff line change
Expand Up @@ -325,7 +325,17 @@ class RecordBatchSerializer {
using offset_type = typename ArrayType::offset_type;

if (array.length() == 0) {
*value_offsets = array.value_offsets();
if (array.value_offsets() == nullptr || array.value_offsets()->size() == 0) {
*value_offsets = array.value_offsets();
return Status::OK();
}
// An offsets buffer for an array of length N contains N + 1 elements, so a
// 0-length slice of a non-empty array only needs a single zero offset. Use a
// non-owning view over static zero bytes to avoid both a heap allocation and
// retaining the full unsliced parent buffer.
static constexpr uint64_t kZeroOffset = 0;
*value_offsets = std::make_shared<Buffer>(
reinterpret_cast<const uint8_t*>(&kZeroOffset), sizeof(offset_type));
return Status::OK();
}

Expand Down
Loading