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

kou 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 c68e0aa1fd GH-52021: [Ruby] Add support for sliced arrays in 
MemoryView (#52286)
c68e0aa1fd is described below

commit c68e0aa1fd662abbd0f4acc56c4b9aad84446381
Author: Edmond Abraham <[email protected]>
AuthorDate: Thu Oct 8 21:01:12 2026 -0400

    GH-52021: [Ruby] Add support for sliced arrays in MemoryView (#52286)
    
    ### Rationale for this change
    
    MemoryView export of `Arrow::PrimitiveArray` didn't support arrays with 
non-zero offset (sliced arrays). For example, exporting 
`Arrow::Int32Array.new([0, 1, 2, 3]).slice(1, 3)` returned wrong values because 
the data pointer was advanced by `offset` bytes instead of `offset * byte 
width` bytes.
    
    ### What changes are included in this PR?
    
    * Use `offset * bit width / 8` as the byte offset of the data pointer. This 
supports sliced fixed-width arrays.
    * Support sliced boolean arrays only when the offset is byte aligned. 
MemoryView can't represent a bit offset.
    * Implement `available_p` for `Arrow::PrimitiveArray`. It returns `false` 
for arrays that can't be represented by MemoryView: unsupported types and 
boolean arrays with not byte aligned offset.
    
    ### Are these changes tested?
    
    Ran the complete `test-memory-view.rb` file through the native Ruby/Fiddle 
interface using Ruby 3.2.3, Fiddle 1.1.8, and Arrow C++/GLib 25.0.1.
    
    The original MemoryView implementation in the 25.0.1 release is identical 
to the current checkout. The extension was rebuilt with this PR's updated 
`memory-view.cpp`.
    
    - Revised complete test file: 29 tests, 31 assertions, 0 failures, 0 
errors, 0 omissions.
    - Both new Boolean cases fail against the previous PR implementation and 
pass after the revision.
    - `git diff --check` passes.
    
    The full repository test suite and a complete build of the 26.0.0-SNAPSHOT 
dependencies were not run.
    
    ### Are there any user-facing changes?
    
    * Sliced fixed-width arrays and byte-aligned sliced boolean arrays can be 
exported by MemoryView.
    * `rb_memory_view_available_p()` returns `false` for unsupported arrays.
    
    ### Was AI used for this PR?
    
    AI assisted with the investigation, implementation, regression tests, 
validation, and PR description.
    
    **PR code and description written by:**
    
    - [ ] Human
    - [x] AI
    
    **Reviewed before submission by:**
    
    - [ ] Human
    - [x] AI
    - [ ] Not reviewed
    
    * GitHub Issue: #52021
    
    Lead-authored-by: Edmond Abraham 
<[email protected]>
    Co-authored-by: Sutou Kouhei <[email protected]>
    Signed-off-by: Sutou Kouhei <[email protected]>
---
 ruby/red-arrow/ext/arrow/memory-view.cpp |  89 ++++----
 ruby/red-arrow/test/test-memory-view.rb  | 342 ++++++++++++++++++++++---------
 2 files changed, 290 insertions(+), 141 deletions(-)

diff --git a/ruby/red-arrow/ext/arrow/memory-view.cpp 
b/ruby/red-arrow/ext/arrow/memory-view.cpp
index 3869b45583..27c8f1ffe1 100644
--- a/ruby/red-arrow/ext/arrow/memory-view.cpp
+++ b/ruby/red-arrow/ext/arrow/memory-view.cpp
@@ -68,19 +68,26 @@ namespace red_arrow {
 
     class PrimitiveArrayGetter : public arrow::ArrayVisitor {
     public:
+      // If view is nullptr, this only validates whether the visited
+      // array can be represented by memory view.
       explicit PrimitiveArrayGetter(memory_view *view)
         : view_(view) {
       }
 
       arrow::Status Visit(const arrow::BooleanArray& array) override {
-        fill(static_cast<const arrow::Array&>(array));
+        if (array.offset() % 8 != 0) {
+          return arrow::Status::Invalid("Boolean array offset must be byte 
aligned");
+        }
+        if (!view_) {
+          return arrow::Status::OK();
+        }
         // Memory view doesn't support bit stream. We use one byte
         // for 8 elements. Users can't calculate the number of
         // elements from memory view but it's limitation of memory view.
 #ifdef ARROW_LITTLE_ENDIAN
-        view_->format = "b8";
+        fill(static_cast<const arrow::Array&>(array), "b8");
 #else
-        view_->format = "B8";
+        fill(static_cast<const arrow::Array&>(array), "B8");
 #endif
         view_->item_size = 1;
         view_->byte_size = (array.length() + 7) / 8;
@@ -88,67 +95,59 @@ namespace red_arrow {
       }
 
       arrow::Status Visit(const arrow::Int8Array& array) override {
-        fill(static_cast<const arrow::Array&>(array));
-        view_->format = "c";
+        fill(static_cast<const arrow::Array&>(array), "c");
         return arrow::Status::OK();
       }
 
       arrow::Status Visit(const arrow::Int16Array& array) override {
-        fill(static_cast<const arrow::Array&>(array));
-        view_->format = "s";
+        fill(static_cast<const arrow::Array&>(array), "s");
         return arrow::Status::OK();
       }
 
       arrow::Status Visit(const arrow::Int32Array& array) override {
-        fill(static_cast<const arrow::Array&>(array));
-        view_->format = "l";
+        fill(static_cast<const arrow::Array&>(array), "l");
         return arrow::Status::OK();
       }
 
       arrow::Status Visit(const arrow::Int64Array& array) override {
-        fill(static_cast<const arrow::Array&>(array));
-        view_->format = "q";
+        fill(static_cast<const arrow::Array&>(array), "q");
         return arrow::Status::OK();
       }
 
       arrow::Status Visit(const arrow::UInt8Array& array) override {
-        fill(static_cast<const arrow::Array&>(array));
-        view_->format = "C";
+        fill(static_cast<const arrow::Array&>(array), "C");
         return arrow::Status::OK();
       }
 
       arrow::Status Visit(const arrow::UInt16Array& array) override {
-        fill(static_cast<const arrow::Array&>(array));
-        view_->format = "S";
+        fill(static_cast<const arrow::Array&>(array), "S");
         return arrow::Status::OK();
       }
 
       arrow::Status Visit(const arrow::UInt32Array& array) override {
-        fill(static_cast<const arrow::Array&>(array));
-        view_->format = "L";
+        fill(static_cast<const arrow::Array&>(array), "L");
         return arrow::Status::OK();
       }
 
       arrow::Status Visit(const arrow::UInt64Array& array) override {
-        fill(static_cast<const arrow::Array&>(array));
-        view_->format = "Q";
+        fill(static_cast<const arrow::Array&>(array), "Q");
         return arrow::Status::OK();
       }
 
       arrow::Status Visit(const arrow::FloatArray& array) override {
-        fill(static_cast<const arrow::Array&>(array));
-        view_->format = "f";
+        fill(static_cast<const arrow::Array&>(array), "f");
         return arrow::Status::OK();
       }
 
       arrow::Status Visit(const arrow::DoubleArray& array) override {
-        fill(static_cast<const arrow::Array&>(array));
-        view_->format = "d";
+        fill(static_cast<const arrow::Array&>(array), "d");
         return arrow::Status::OK();
       }
 
       arrow::Status Visit(const arrow::FixedSizeBinaryArray& array) override {
-        fill(static_cast<const arrow::Array&>(array));
+        if (!view_) {
+          return arrow::Status::OK();
+        }
         auto priv = static_cast<PrivateData *>(view_->private_data);
         const auto type =
           std::static_pointer_cast<const arrow::FixedSizeBinaryType>(
@@ -156,60 +155,59 @@ namespace red_arrow {
         std::ostringstream output;
         output << "C" << type->byte_width();
         priv->format = output.str();
-        view_->format = priv->format.c_str();
+        fill(static_cast<const arrow::Array&>(array), priv->format.c_str());
         return arrow::Status::OK();
       }
 
       arrow::Status Visit(const arrow::Date32Array& array) override {
-        fill(static_cast<const arrow::Array&>(array));
-        view_->format = "l";
+        fill(static_cast<const arrow::Array&>(array), "l");
         return arrow::Status::OK();
       }
 
       arrow::Status Visit(const arrow::Date64Array& array) override {
-        fill(static_cast<const arrow::Array&>(array));
-        view_->format = "q";
+        fill(static_cast<const arrow::Array&>(array), "q");
         return arrow::Status::OK();
       }
 
       arrow::Status Visit(const arrow::Time32Array& array) override {
-        fill(static_cast<const arrow::Array&>(array));
-        view_->format = "l";
+        fill(static_cast<const arrow::Array&>(array), "l");
         return arrow::Status::OK();
       }
 
       arrow::Status Visit(const arrow::Time64Array& array) override {
-        fill(static_cast<const arrow::Array&>(array));
-        view_->format = "q";
+        fill(static_cast<const arrow::Array&>(array), "q");
         return arrow::Status::OK();
       }
 
       arrow::Status Visit(const arrow::TimestampArray& array) override {
-        fill(static_cast<const arrow::Array&>(array));
-        view_->format = "q";
+        fill(static_cast<const arrow::Array&>(array), "q");
         return arrow::Status::OK();
       }
 
       arrow::Status Visit(const arrow::Decimal128Array& array) override {
-        fill(static_cast<const arrow::Array&>(array));
-        view_->format = "q2";
+        fill(static_cast<const arrow::Array&>(array), "q2");
         return arrow::Status::OK();
       }
 
       arrow::Status Visit(const arrow::Decimal256Array& array) override {
-        fill(static_cast<const arrow::Array&>(array));
-        view_->format = "q4";
+        fill(static_cast<const arrow::Array&>(array), "q4");
         return arrow::Status::OK();
       }
 
       private:
-      void fill(const arrow::Array& array) {
+      void fill(const arrow::Array& array, const char *format) {
+        if (!view_) {
+          return;
+        }
+        view_->format = format;
         const auto array_data = array.data();
-        const auto data = array_data->GetValuesSafe<uint8_t>(1);
-        view_->data = const_cast<void *>(reinterpret_cast<const void *>(data));
         const auto type =
           std::static_pointer_cast<const arrow::FixedWidthType>(array.type());
         view_->item_size = type->bit_width() / 8;
+        const auto byte_offset =
+          array_data->offset * type->bit_width() / 8;
+        const auto data = array_data->GetValuesSafe<uint8_t>(1, byte_offset);
+        view_->data = const_cast<void *>(reinterpret_cast<const void *>(data));
         view_->byte_size = view_->item_size * array.length();
       }
 
@@ -229,6 +227,8 @@ namespace red_arrow {
       PrimitiveArrayGetter getter(view_);
       auto status = arrow_array->Accept(&getter);
       if (!status.ok()) {
+        delete static_cast<PrivateData *>(view_->private_data);
+        view_->private_data = nullptr;
         return false;
       }
       view_->readonly = true;
@@ -243,7 +243,10 @@ namespace red_arrow {
     }
 
     bool primitive_array_available_p(VALUE obj) {
-      return true;
+      auto array = GARROW_ARRAY(RVAL2GOBJ(obj));
+      auto arrow_array = garrow_array_get_raw(array);
+      PrimitiveArrayGetter validator(nullptr);
+      return arrow_array->Accept(&validator).ok();
     }
 
     rb_memory_view_entry_t primitive_array_entry = {
diff --git a/ruby/red-arrow/test/test-memory-view.rb 
b/ruby/red-arrow/test/test-memory-view.rb
index 8aa5728a56..408ff68d8f 100644
--- a/ruby/red-arrow/test/test-memory-view.rb
+++ b/ruby/red-arrow/test/test-memory-view.rb
@@ -29,102 +29,226 @@ class MemoryViewTest < Test::Unit::TestCase
     [1].pack("s") == [1].pack("s<")
   end
 
-  test("BooleanArray") do
-    array = Arrow::BooleanArray.new([true] * 9)
-    Fiddle::MemoryView.export(array) do |memory_view|
+  def memory_view_available?(target)
+    libruby = Fiddle.dlopen(nil)
+    rb_memory_view_available_p = Fiddle::Function.new(
+      libruby["rb_memory_view_available_p"],
+      [Fiddle::TYPE_UINTPTR_T],
+      Fiddle::TYPE_BOOL
+    )
+    rb_memory_view_available_p.call(Fiddle.dlwrap(target))
+  end
+
+  sub_test_case("BooleanArray") do
+    def template
       if little_endian?
-        template = "b"
+        "b"
       else
-        template = "B"
+        "B"
+      end
+    end
+
+    test("offset: 0") do
+      array = Arrow::BooleanArray.new([true] * 9)
+      Fiddle::MemoryView.export(array) do |memory_view|
+        assert_equal([
+                       "#{template}8",
+                       1,
+                       2,
+                       [(("1" * 9) + ("0" * 7))].pack("#{template}*"),
+                     ],
+                     [
+                       memory_view.format,
+                       memory_view.item_size,
+                       memory_view.byte_size,
+                       memory_view.to_s,
+                     ])
+      end
+    end
+
+    test("offset: byte aligned") do
+      array = Arrow::BooleanArray.new(([false] * 8) + ([true] * 9)).slice(8, 9)
+      Fiddle::MemoryView.export(array) do |memory_view|
+        assert_equal([
+                       "#{template}8",
+                       1,
+                       2,
+                       [("1" * 9) + ("0" * 7)].pack("#{template}*"),
+                     ],
+                     [
+                       memory_view.format,
+                       memory_view.item_size,
+                       memory_view.byte_size,
+                       memory_view.to_s,
+                     ])
+      end
+    end
+
+    test("offset: not byte aligned") do
+      array = Arrow::BooleanArray.new([false, true, false]).slice(1, 2)
+      assert do
+        not memory_view_available?(array)
       end
-      assert_equal([
-                     "#{template}8",
-                     1,
-                     2,
-                     [(("1" * 9) + ("0" * 7))].pack("#{template}*"),
-                   ],
-                   [
-                     memory_view.format,
-                     memory_view.item_size,
-                     memory_view.byte_size,
-                     memory_view.to_s,
-                   ])
     end
   end
 
-  test("Int8Array") do
-    values = [-(2 ** 7), 0, (2 ** 7) - 1]
-    array = Arrow::Int8Array.new(values)
-    Fiddle::MemoryView.export(array) do |memory_view|
-      assert_equal([
-                     "c",
-                     1,
-                     values.size,
-                     values.pack("c*"),
-                   ],
-                   [
-                     memory_view.format,
-                     memory_view.item_size,
-                     memory_view.byte_size,
-                     memory_view.to_s,
-                   ])
+  sub_test_case("Int8Array") do
+    test("offset: 0") do
+      values = [-(2 ** 7), 0, (2 ** 7) - 1]
+      array = Arrow::Int8Array.new(values)
+      Fiddle::MemoryView.export(array) do |memory_view|
+        assert_equal([
+                       "c",
+                       1,
+                       values.size,
+                       values.pack("c*"),
+                     ],
+                     [
+                       memory_view.format,
+                       memory_view.item_size,
+                       memory_view.byte_size,
+                       memory_view.to_s,
+                     ])
+      end
+    end
+
+    test("offset: non-0") do
+      values = [0, 1, 2]
+      array = Arrow::Int8Array.new(values).slice(1, 2)
+      Fiddle::MemoryView.export(array) do |memory_view|
+        assert_equal([
+                       "c",
+                       1,
+                       1 * 2,
+                       values.slice(1, 2).pack("c*"),
+                     ],
+                     [
+                       memory_view.format,
+                       memory_view.item_size,
+                       memory_view.byte_size,
+                       memory_view.to_s,
+                     ])
+      end
     end
   end
 
-  test("Int16Array") do
-    values = [-(2 ** 15), 0, (2 ** 15) - 1]
-    array = Arrow::Int16Array.new(values)
-    Fiddle::MemoryView.export(array) do |memory_view|
-      assert_equal([
-                     "s",
-                     2,
-                     2 * values.size,
-                     values.pack("s*"),
-                   ],
-                   [
-                     memory_view.format,
-                     memory_view.item_size,
-                     memory_view.byte_size,
-                     memory_view.to_s,
-                   ])
+  sub_test_case("Int16Array") do
+    test("offset: 0") do
+      values = [-(2 ** 15), 0, (2 ** 15) - 1]
+      array = Arrow::Int16Array.new(values)
+      Fiddle::MemoryView.export(array) do |memory_view|
+        assert_equal([
+                       "s",
+                       2,
+                       2 * values.size,
+                       values.pack("s*"),
+                     ],
+                     [
+                       memory_view.format,
+                       memory_view.item_size,
+                       memory_view.byte_size,
+                       memory_view.to_s,
+                     ])
+      end
+    end
+
+    test("offset: non-0") do
+      values = [0, 1, 2]
+      array = Arrow::Int16Array.new(values).slice(1, 2)
+      Fiddle::MemoryView.export(array) do |memory_view|
+        assert_equal([
+                       "s",
+                       2,
+                       2 * 2,
+                       values.slice(1, 2).pack("s*"),
+                     ],
+                     [
+                       memory_view.format,
+                       memory_view.item_size,
+                       memory_view.byte_size,
+                       memory_view.to_s,
+                     ])
+      end
     end
   end
 
-  test("Int32Array") do
-    values = [-(2 ** 31), 0, (2 ** 31) - 1]
-    array = Arrow::Int32Array.new(values)
-    Fiddle::MemoryView.export(array) do |memory_view|
-      assert_equal([
-                     "l",
-                     4,
-                     4 * values.size,
-                     values.pack("l*"),
-                   ],
-                   [
-                     memory_view.format,
-                     memory_view.item_size,
-                     memory_view.byte_size,
-                     memory_view.to_s,
-                   ])
+  sub_test_case("Int32Array") do
+    test("offset: 0") do
+      values = [-(2 ** 31), 0, (2 ** 31) - 1]
+      array = Arrow::Int32Array.new(values)
+      Fiddle::MemoryView.export(array) do |memory_view|
+        assert_equal([
+                       "l",
+                       4,
+                       4 * values.size,
+                       values.pack("l*"),
+                     ],
+                     [
+                       memory_view.format,
+                       memory_view.item_size,
+                       memory_view.byte_size,
+                       memory_view.to_s,
+                     ])
+      end
+    end
+
+    test("offset: non-0") do
+      values = [0, 1, 2]
+      array = Arrow::Int32Array.new(values).slice(1, 2)
+      Fiddle::MemoryView.export(array) do |memory_view|
+        assert_equal([
+                       "l",
+                       4,
+                       4 * 2,
+                       values.slice(1, 2).pack("l*"),
+                     ],
+                     [
+                       memory_view.format,
+                       memory_view.item_size,
+                       memory_view.byte_size,
+                       memory_view.to_s,
+                     ])
+      end
     end
   end
 
-  test("Int64Array") do
-    values = [-(2 ** 63), 0, (2 ** 63) - 1]
-    array = Arrow::Int64Array.new(values)
-    Fiddle::MemoryView.export(array) do |memory_view|
-      assert_equal([
-                     "q",
-                     8,
-                     8 * values.size,
-                     values.pack("q*"),
-                   ],
-                   [
-                     memory_view.format,
-                     memory_view.item_size,
-                     memory_view.byte_size,
-                     memory_view.to_s,
-                   ])
+  sub_test_case("Int64Array") do
+    test("offset: 0") do
+      values = [-(2 ** 63), 0, (2 ** 63) - 1]
+      array = Arrow::Int64Array.new(values)
+      Fiddle::MemoryView.export(array) do |memory_view|
+        assert_equal([
+                       "q",
+                       8,
+                       8 * values.size,
+                       values.pack("q*"),
+                     ],
+                     [
+                       memory_view.format,
+                       memory_view.item_size,
+                       memory_view.byte_size,
+                       memory_view.to_s,
+                     ])
+      end
+    end
+
+    test("offset: non-0") do
+      values = [0, 1, 2]
+      array = Arrow::Int64Array.new(values).slice(1, 2)
+      Fiddle::MemoryView.export(array) do |memory_view|
+        assert_equal([
+                       "q",
+                       8,
+                       8 * 2,
+                       values.slice(1, 2).pack("q*"),
+                     ],
+                     [
+                       memory_view.format,
+                       memory_view.item_size,
+                       memory_view.byte_size,
+                       memory_view.to_s,
+                     ])
+      end
     end
   end
 
@@ -242,23 +366,45 @@ class MemoryViewTest < Test::Unit::TestCase
     end
   end
 
-  test("FixedSizeBinaryArray") do
-    values = ["\x01\x02", "\x03\x04", "\x05\x06"]
-    data_type = Arrow::FixedSizeBinaryDataType.new(2)
-    array = Arrow::FixedSizeBinaryArray.new(data_type, values)
-    Fiddle::MemoryView.export(array) do |memory_view|
-      assert_equal([
-                     "C2",
-                     2,
-                     2 * values.size,
-                     values.join("").b,
-                   ],
-                   [
-                     memory_view.format,
-                     memory_view.item_size,
-                     memory_view.byte_size,
-                     memory_view.to_s,
-                   ])
+  sub_test_case("FixedSizeBinaryArray") do
+    test("offset: 0") do
+      values = ["\x01\x02", "\x03\x04", "\x05\x06"]
+      data_type = Arrow::FixedSizeBinaryDataType.new(2)
+      array = Arrow::FixedSizeBinaryArray.new(data_type, values)
+      Fiddle::MemoryView.export(array) do |memory_view|
+        assert_equal([
+                       "C2",
+                       2,
+                       2 * values.size,
+                       values.join("").b,
+                     ],
+                     [
+                       memory_view.format,
+                       memory_view.item_size,
+                       memory_view.byte_size,
+                       memory_view.to_s,
+                     ])
+      end
+    end
+
+    test("offset: non-0") do
+      values = ["\x01\x02", "\x03\x04", "\x05\x06"]
+      data_type = Arrow::FixedSizeBinaryDataType.new(2)
+      array = Arrow::FixedSizeBinaryArray.new(data_type, values).slice(1, 2)
+      Fiddle::MemoryView.export(array) do |memory_view|
+        assert_equal([
+                       "C2",
+                       2,
+                       2 * 2,
+                       values.slice(1, 2).join("").b,
+                     ],
+                     [
+                       memory_view.format,
+                       memory_view.item_size,
+                       memory_view.byte_size,
+                       memory_view.to_s,
+                     ])
+      end
     end
   end
 

Reply via email to