From 7f7a08e72153106846542204a27cf05ee6b265f0 Mon Sep 17 00:00:00 2001 From: dkp116 Date: Fri, 3 Jul 2026 22:41:48 +0100 Subject: [PATCH 1/8] [C++] added get Span function for ArrayData --- cpp/src/arrow/array/data.h | 31 +++++++++++++++++++++++++++++++ 1 file changed, 31 insertions(+) diff --git a/cpp/src/arrow/array/data.h b/cpp/src/arrow/array/data.h index 92308e8a010f..573ded629124 100644 --- a/cpp/src/arrow/array/data.h +++ b/cpp/src/arrow/array/data.h @@ -256,6 +256,37 @@ struct ARROW_EXPORT ArrayData { return NULLPTR; } } +/// \brief Access a buffer's data as a span + /// + /// \param i The buffer index + /// \param length The required length (in number of typed values) of the requested span + /// \pre i > 0 + /// \pre length <= the length of the buffer (in number of values) that's expected for + /// this array type + /// \return A span of the requested length + template + std::span GetSpan(int i, int64_t length) const { + const int64_t buffer_length = buffers[i]->size() / static_cast(sizeof(T)); + assert(i > 0 && length + offset <= buffer_length); + ARROW_UNUSED(buffer_length); + return std::span(buffers[i]->data_as() + this->offset, length); + } + + /// \brief Access a buffer's data as a span + /// + /// \param i The buffer index + /// \param length The required length (in number of typed values) of the requested span + /// \pre i > 0 + /// \pre length <= the length of the buffer (in number of values) that's expected for + /// this array type + /// \return A span of the requested length + template + std::span GetMutableSpan(int i, int64_t length) { + const int64_t buffer_length = buffers[i]->size() / static_cast(sizeof(T)); + assert(i > 0 && length + offset <= buffer_length); + ARROW_UNUSED(buffer_length); + return std::span(buffers[i]->mutable_data_as() + this->offset, length); + } /// \brief Access a buffer's data as a typed C pointer /// From 13da2f3d871e44155789622062c06996ed3cba2a Mon Sep 17 00:00:00 2001 From: dkp116 Date: Sat, 4 Jul 2026 22:05:12 +0100 Subject: [PATCH 2/8] [C++] added unit test for ArrayData member function GetSpan --- cpp/src/arrow/array/array_test.cc | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/cpp/src/arrow/array/array_test.cc b/cpp/src/arrow/array/array_test.cc index dcfe1c76c301..51d81c7f2405 100644 --- a/cpp/src/arrow/array/array_test.cc +++ b/cpp/src/arrow/array/array_test.cc @@ -1137,6 +1137,18 @@ TEST_F(TestArray, TestBinaryViewAppendArraySlice) { AssertArraysEqual(*src, *dst); } +TEST_F(TestArray, GetSpanRespectsOffset) { + auto data_buffer = Buffer::FromString("123456789abcdef0"); + + auto data = ArrayData::Make(uint8(), 3, {nullptr, data_buffer}, 0, 1); + + auto span = data->GetSpan(1, 3); + + EXPECT_EQ(span.size(), 3); + EXPECT_EQ(span[0], '2'); + EXPECT_EQ(span[1], '3'); + EXPECT_EQ(span[2], '4'); +} TEST_F(TestArray, ValidateBuffersPrimitive) { auto empty_buffer = std::make_shared(""); From c496e6af941251d69b9dd5d16d2146428f89e705 Mon Sep 17 00:00:00 2001 From: dkp116 Date: Sat, 4 Jul 2026 22:34:54 +0100 Subject: [PATCH 3/8] [C++] added test for ArrayData member function GetMutableSpan --- cpp/src/arrow/array/array_test.cc | 24 ++++++++++++++++++++++-- 1 file changed, 22 insertions(+), 2 deletions(-) diff --git a/cpp/src/arrow/array/array_test.cc b/cpp/src/arrow/array/array_test.cc index 51d81c7f2405..f4c657b91c4b 100644 --- a/cpp/src/arrow/array/array_test.cc +++ b/cpp/src/arrow/array/array_test.cc @@ -1139,9 +1139,7 @@ TEST_F(TestArray, TestBinaryViewAppendArraySlice) { } TEST_F(TestArray, GetSpanRespectsOffset) { auto data_buffer = Buffer::FromString("123456789abcdef0"); - auto data = ArrayData::Make(uint8(), 3, {nullptr, data_buffer}, 0, 1); - auto span = data->GetSpan(1, 3); EXPECT_EQ(span.size(), 3); @@ -1149,7 +1147,29 @@ TEST_F(TestArray, GetSpanRespectsOffset) { EXPECT_EQ(span[1], '3'); EXPECT_EQ(span[2], '4'); } +TEST_F(TestArray, GetMutableSpanRespectsOffset) { + const int64_t nbytes = 16; + ASSERT_OK_AND_ASSIGN(auto uniq_buffer, AllocateResizableBuffer(nbytes, pool_)); + memset(uniq_buffer->mutable_data(), '0', nbytes); + std::shared_ptr buffer(std::move(uniq_buffer)); + std::vector> buffers = {nullptr, buffer}; + auto data = ArrayData::Make(uint8(), 3, buffers, 0, 1); + auto span = data->template GetMutableSpan(1, 3); + + EXPECT_EQ(span.size(), 3); + EXPECT_EQ(span[0], '0'); + EXPECT_EQ(span[1], '0'); + EXPECT_EQ(span[2], '0'); + + span[0] = 'X'; + span[1] = 'Y'; + span[2] = 'Z'; + auto raw = buffer->mutable_data(); + EXPECT_EQ(raw[1], 'X'); + EXPECT_EQ(raw[2], 'Y'); + EXPECT_EQ(raw[3], 'Z'); +} TEST_F(TestArray, ValidateBuffersPrimitive) { auto empty_buffer = std::make_shared(""); auto null_buffer = Buffer::FromString("\xff"); From 2431b9cc3b21c34c0a120094dd930798c251b47d Mon Sep 17 00:00:00 2001 From: dkp116 Date: Sat, 4 Jul 2026 22:35:21 +0100 Subject: [PATCH 4/8] [C++] applied linting to data.h --- cpp/src/arrow/array/data.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cpp/src/arrow/array/data.h b/cpp/src/arrow/array/data.h index 573ded629124..b10a94fc869c 100644 --- a/cpp/src/arrow/array/data.h +++ b/cpp/src/arrow/array/data.h @@ -256,7 +256,7 @@ struct ARROW_EXPORT ArrayData { return NULLPTR; } } -/// \brief Access a buffer's data as a span + /// \brief Access a buffer's data as a span /// /// \param i The buffer index /// \param length The required length (in number of typed values) of the requested span From fc15394522ce757e5b3b73f92504e95cdb6a7fdf Mon Sep 17 00:00:00 2001 From: dkp116 Date: Mon, 6 Jul 2026 23:02:50 +0100 Subject: [PATCH 5/8] [C++] - changed ArrayData GetSpan tests to use non-byte sized data type --- cpp/src/arrow/array/array_test.cc | 57 ++++++++++++++++++++----------- 1 file changed, 37 insertions(+), 20 deletions(-) diff --git a/cpp/src/arrow/array/array_test.cc b/cpp/src/arrow/array/array_test.cc index f4c657b91c4b..381e550e18f6 100644 --- a/cpp/src/arrow/array/array_test.cc +++ b/cpp/src/arrow/array/array_test.cc @@ -1138,38 +1138,55 @@ TEST_F(TestArray, TestBinaryViewAppendArraySlice) { AssertArraysEqual(*src, *dst); } TEST_F(TestArray, GetSpanRespectsOffset) { - auto data_buffer = Buffer::FromString("123456789abcdef0"); - auto data = ArrayData::Make(uint8(), 3, {nullptr, data_buffer}, 0, 1); - auto span = data->GetSpan(1, 3); + std::vector values = {1, 2, 3, 4, 5}; + + auto data_buffer = Buffer::Wrap(values); + auto data = ArrayData::Make(uint16(), 3, {nullptr, data_buffer}, 0, 1); + auto span = data->GetSpan(1, 3); EXPECT_EQ(span.size(), 3); - EXPECT_EQ(span[0], '2'); - EXPECT_EQ(span[1], '3'); - EXPECT_EQ(span[2], '4'); + + EXPECT_EQ(span[0], 2); + + EXPECT_EQ(span[1], 3); + + EXPECT_EQ(span[2], 4); } + TEST_F(TestArray, GetMutableSpanRespectsOffset) { - const int64_t nbytes = 16; + std::vector values = {10, 20, 30, 40, 50}; + + const int64_t nbytes = values.size() * sizeof(uint16_t); ASSERT_OK_AND_ASSIGN(auto uniq_buffer, AllocateResizableBuffer(nbytes, pool_)); - memset(uniq_buffer->mutable_data(), '0', nbytes); + memcpy(uniq_buffer->mutable_data(), values.data(), nbytes); + std::shared_ptr buffer(std::move(uniq_buffer)); std::vector> buffers = {nullptr, buffer}; - auto data = ArrayData::Make(uint8(), 3, buffers, 0, 1); - auto span = data->template GetMutableSpan(1, 3); + + auto data = ArrayData::Make(uint16(), 3, buffers, 0, 1); + auto span = data->GetMutableSpan(1, 3); EXPECT_EQ(span.size(), 3); - EXPECT_EQ(span[0], '0'); - EXPECT_EQ(span[1], '0'); - EXPECT_EQ(span[2], '0'); - span[0] = 'X'; - span[1] = 'Y'; - span[2] = 'Z'; + EXPECT_EQ(span[0], 20); + + EXPECT_EQ(span[1], 30); + + EXPECT_EQ(span[2], 40); + + span[0] = 200; + span[1] = 300; + span[2] = 400; - auto raw = buffer->mutable_data(); - EXPECT_EQ(raw[1], 'X'); - EXPECT_EQ(raw[2], 'Y'); - EXPECT_EQ(raw[3], 'Z'); + auto raw = reinterpret_cast(buffer->mutable_data()); + + EXPECT_EQ(raw[1], 200); + + EXPECT_EQ(raw[2], 300); + + EXPECT_EQ(raw[3], 400); } + TEST_F(TestArray, ValidateBuffersPrimitive) { auto empty_buffer = std::make_shared(""); auto null_buffer = Buffer::FromString("\xff"); From 7812904eee076061aefe1f815fd23728821fbaa7 Mon Sep 17 00:00:00 2001 From: dkp116 Date: Tue, 7 Jul 2026 12:38:14 +0100 Subject: [PATCH 6/8] [C++] applied linting --- cpp/src/arrow/array/array_test.cc | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cpp/src/arrow/array/array_test.cc b/cpp/src/arrow/array/array_test.cc index 381e550e18f6..d82513913c60 100644 --- a/cpp/src/arrow/array/array_test.cc +++ b/cpp/src/arrow/array/array_test.cc @@ -1179,7 +1179,7 @@ TEST_F(TestArray, GetMutableSpanRespectsOffset) { span[2] = 400; auto raw = reinterpret_cast(buffer->mutable_data()); - + EXPECT_EQ(raw[1], 200); EXPECT_EQ(raw[2], 300); From 690a5687a91bff41ef69c986d5b284dc42281577 Mon Sep 17 00:00:00 2001 From: dkp116 Date: Tue, 7 Jul 2026 13:30:40 +0100 Subject: [PATCH 7/8] [C++] simplified test setup for Mutable and Immutable ArrayData GetSpan member functions --- cpp/src/arrow/array/array_test.cc | 20 +++++--------------- 1 file changed, 5 insertions(+), 15 deletions(-) diff --git a/cpp/src/arrow/array/array_test.cc b/cpp/src/arrow/array/array_test.cc index d82513913c60..33a766fcd2a7 100644 --- a/cpp/src/arrow/array/array_test.cc +++ b/cpp/src/arrow/array/array_test.cc @@ -1137,41 +1137,33 @@ TEST_F(TestArray, TestBinaryViewAppendArraySlice) { AssertArraysEqual(*src, *dst); } + TEST_F(TestArray, GetSpanRespectsOffset) { std::vector values = {1, 2, 3, 4, 5}; - auto data_buffer = Buffer::Wrap(values); - auto data = ArrayData::Make(uint16(), 3, {nullptr, data_buffer}, 0, 1); + auto buffer = Buffer::FromVector(std::move(values)); + auto data = ArrayData::Make(uint16(), 3, {nullptr, buffer}, 0, 1); auto span = data->GetSpan(1, 3); EXPECT_EQ(span.size(), 3); - EXPECT_EQ(span[0], 2); - EXPECT_EQ(span[1], 3); - EXPECT_EQ(span[2], 4); } TEST_F(TestArray, GetMutableSpanRespectsOffset) { std::vector values = {10, 20, 30, 40, 50}; - const int64_t nbytes = values.size() * sizeof(uint16_t); - ASSERT_OK_AND_ASSIGN(auto uniq_buffer, AllocateResizableBuffer(nbytes, pool_)); - memcpy(uniq_buffer->mutable_data(), values.data(), nbytes); - - std::shared_ptr buffer(std::move(uniq_buffer)); + auto buffer = std::make_shared(reinterpret_cast(values.data()), + values.size() * sizeof(uint16_t)); std::vector> buffers = {nullptr, buffer}; auto data = ArrayData::Make(uint16(), 3, buffers, 0, 1); auto span = data->GetMutableSpan(1, 3); EXPECT_EQ(span.size(), 3); - EXPECT_EQ(span[0], 20); - EXPECT_EQ(span[1], 30); - EXPECT_EQ(span[2], 40); span[0] = 200; @@ -1181,9 +1173,7 @@ TEST_F(TestArray, GetMutableSpanRespectsOffset) { auto raw = reinterpret_cast(buffer->mutable_data()); EXPECT_EQ(raw[1], 200); - EXPECT_EQ(raw[2], 300); - EXPECT_EQ(raw[3], 400); } From 9238d2086d834792e6638d7115baba1bc66f3078 Mon Sep 17 00:00:00 2001 From: Antoine Pitrou Date: Tue, 7 Jul 2026 16:22:41 +0200 Subject: [PATCH 8/8] Handle null buffers + test improvements --- cpp/src/arrow/array/array_test.cc | 39 ------------------- cpp/src/arrow/array/data.h | 8 ++++ cpp/src/arrow/array/data_test.cc | 65 ++++++++++++++++++++++++++++++- 3 files changed, 72 insertions(+), 40 deletions(-) diff --git a/cpp/src/arrow/array/array_test.cc b/cpp/src/arrow/array/array_test.cc index 33a766fcd2a7..dcfe1c76c301 100644 --- a/cpp/src/arrow/array/array_test.cc +++ b/cpp/src/arrow/array/array_test.cc @@ -1138,45 +1138,6 @@ TEST_F(TestArray, TestBinaryViewAppendArraySlice) { AssertArraysEqual(*src, *dst); } -TEST_F(TestArray, GetSpanRespectsOffset) { - std::vector values = {1, 2, 3, 4, 5}; - - auto buffer = Buffer::FromVector(std::move(values)); - auto data = ArrayData::Make(uint16(), 3, {nullptr, buffer}, 0, 1); - auto span = data->GetSpan(1, 3); - - EXPECT_EQ(span.size(), 3); - EXPECT_EQ(span[0], 2); - EXPECT_EQ(span[1], 3); - EXPECT_EQ(span[2], 4); -} - -TEST_F(TestArray, GetMutableSpanRespectsOffset) { - std::vector values = {10, 20, 30, 40, 50}; - - auto buffer = std::make_shared(reinterpret_cast(values.data()), - values.size() * sizeof(uint16_t)); - std::vector> buffers = {nullptr, buffer}; - - auto data = ArrayData::Make(uint16(), 3, buffers, 0, 1); - auto span = data->GetMutableSpan(1, 3); - - EXPECT_EQ(span.size(), 3); - EXPECT_EQ(span[0], 20); - EXPECT_EQ(span[1], 30); - EXPECT_EQ(span[2], 40); - - span[0] = 200; - span[1] = 300; - span[2] = 400; - - auto raw = reinterpret_cast(buffer->mutable_data()); - - EXPECT_EQ(raw[1], 200); - EXPECT_EQ(raw[2], 300); - EXPECT_EQ(raw[3], 400); -} - TEST_F(TestArray, ValidateBuffersPrimitive) { auto empty_buffer = std::make_shared(""); auto null_buffer = Buffer::FromString("\xff"); diff --git a/cpp/src/arrow/array/data.h b/cpp/src/arrow/array/data.h index b10a94fc869c..70ac850c018a 100644 --- a/cpp/src/arrow/array/data.h +++ b/cpp/src/arrow/array/data.h @@ -266,6 +266,10 @@ struct ARROW_EXPORT ArrayData { /// \return A span of the requested length template std::span GetSpan(int i, int64_t length) const { + if (!buffers[i]) { + assert(length == 0); + return {}; + } const int64_t buffer_length = buffers[i]->size() / static_cast(sizeof(T)); assert(i > 0 && length + offset <= buffer_length); ARROW_UNUSED(buffer_length); @@ -282,6 +286,10 @@ struct ARROW_EXPORT ArrayData { /// \return A span of the requested length template std::span GetMutableSpan(int i, int64_t length) { + if (!buffers[i]) { + assert(length == 0); + return {}; + } const int64_t buffer_length = buffers[i]->size() / static_cast(sizeof(T)); assert(i > 0 && length + offset <= buffer_length); ARROW_UNUSED(buffer_length); diff --git a/cpp/src/arrow/array/data_test.cc b/cpp/src/arrow/array/data_test.cc index 011249c54e00..9e103207d0f1 100644 --- a/cpp/src/arrow/array/data_test.cc +++ b/cpp/src/arrow/array/data_test.cc @@ -15,9 +15,11 @@ // specific language governing permissions and limitations // under the License. +#include + #include -#include "arrow/array.h" +#include "arrow/array/array_base.h" #include "arrow/array/data.h" #include "arrow/testing/gtest_util.h" @@ -43,4 +45,65 @@ TEST(ArraySpan, SetSlice) { ASSERT_EQ(span.GetNullCount(), 1); } +TEST(ArrayData, GetSpanRespectsOffset) { + std::vector values = {1, 2, 3, 4, 5}; + + auto buffer = Buffer::FromVector(std::move(values)); + auto data = ArrayData::Make(uint16(), /*length=*/3, {nullptr, buffer}, /*null_count=*/0, + /*offset=*/1); + auto span = data->GetSpan(1, 3); + + const bool is_const_pointer = std::is_same_v; + ASSERT_TRUE(is_const_pointer); + + EXPECT_EQ(span.size(), 3); + EXPECT_EQ(span[0], 2); + EXPECT_EQ(span[1], 3); + EXPECT_EQ(span[2], 4); +} + +TEST(ArrayData, GetMutableSpanRespectsOffset) { + std::vector values = {10, 20, 30, 40, 50}; + + auto buffer = std::make_shared(reinterpret_cast(values.data()), + values.size() * sizeof(uint16_t)); + std::vector> buffers = {nullptr, buffer}; + + auto data = + ArrayData::Make(uint16(), /*length=*/3, buffers, /*null_count=*/0, /*offset=*/1); + auto span = data->GetMutableSpan(1, 3); + + const bool is_mut_pointer = std::is_same_v; + ASSERT_TRUE(is_mut_pointer); + + EXPECT_EQ(span.size(), 3); + EXPECT_EQ(span[0], 20); + EXPECT_EQ(span[1], 30); + EXPECT_EQ(span[2], 40); + + span[0] = 200; + span[1] = 300; + span[2] = 400; + + auto raw = reinterpret_cast(buffer->mutable_data()); + + EXPECT_EQ(raw[1], 200); + EXPECT_EQ(raw[2], 300); + EXPECT_EQ(raw[3], 400); +} + +TEST(ArrayData, GetSpanNullBuffer) { + auto data = ArrayData::Make(uint16(), /*length=*/0, /*buffers=*/{nullptr, nullptr}, + /*null_count=*/0, /*offset=*/0); + auto span = data->GetSpan(1, 0); + EXPECT_EQ(span.size(), 0); +} + +TEST(ArrayData, GetMutableSpanNullBuffer) { + auto data = ArrayData::Make(uint16(), /*length=*/0, /*buffers=*/{nullptr, nullptr}, + /*null_count=*/0, /*offset=*/0); + auto span = data->GetMutableSpan(1, 0); + EXPECT_EQ(span.size(), 0); +} + } // namespace arrow