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 eea736989b GH-50437: [Ruby] Add 
`ArrowFormat::*IntervalArray.new(values)` (#50459)
eea736989b is described below

commit eea736989beb73271918da0a0ffff1a4d1396d23
Author: Sutou Kouhei <[email protected]>
AuthorDate: Fri Jul 10 13:35:38 2026 +0900

    GH-50437: [Ruby] Add `ArrowFormat::*IntervalArray.new(values)` (#50459)
    
    ### Rationale for this change
    
    Building a year month/day time/month day nano interval Arrow array from 
Ruby objects is convenient.
    
    ### What changes are included in this PR?
    
    * Accept `ArrowFormat::YearMonthIntervalArray.new(values)`
    * Accept `ArrowFormat::DayTimetntervalArray.new(values)`
    * Add `ArrowFormat::DayTimeIntervalArray#each`
    * Accept `ArrowFormat::MonthDayNanoIntervalArray.new(values)`
    * Add `ArrowFormat::MonthDayNanoIntervalArray#each`
    
    ### Are these changes tested?
    
    Yes.
    
    ### Are there any user-facing changes?
    
    Yes.
    * GitHub Issue: #50437
    
    Authored-by: Sutou Kouhei <[email protected]>
    Signed-off-by: Sutou Kouhei <[email protected]>
---
 ruby/red-arrow-format/lib/arrow-format/array.rb    | 99 +++++++++++++++++++++-
 ruby/red-arrow-format/lib/arrow-format/type.rb     | 18 +++-
 .../test/test-day-time-interval-array.rb           | 88 +++++++++++++++++++
 .../test/test-month-day-nano-interval-array.rb     | 93 ++++++++++++++++++++
 .../test/test-year-month-interval-array.rb         | 55 ++++++++++++
 5 files changed, 346 insertions(+), 7 deletions(-)

diff --git a/ruby/red-arrow-format/lib/arrow-format/array.rb 
b/ruby/red-arrow-format/lib/arrow-format/array.rb
index bc79edf9eb..0ff88d23ae 100644
--- a/ruby/red-arrow-format/lib/arrow-format/array.rb
+++ b/ruby/red-arrow-format/lib/arrow-format/array.rb
@@ -271,9 +271,9 @@ module ArrowFormat
         if value.nil?
           validity_buffer_builder ||= SparseBitmapBuilder.new
           validity_buffer_builder.unset(i)
-          buffer.append_as_bytes([0].pack(pack_template))
+          buffer.append_as_bytes(pack_value(nil, pack_template))
         else
-          buffer.append_as_bytes([value].pack(pack_template))
+          buffer.append_as_bytes(pack_value(value, pack_template))
         end
         n += 1
       end
@@ -282,6 +282,11 @@ module ArrowFormat
       buffer.freeze
       return n, validity_buffer, IO::Buffer.for(buffer)
     end
+
+    def pack_value(value, template)
+      value = 0 if value.nil?
+      [value].pack(template)
+    end
   end
 
   class BooleanArray < PrimitiveArray
@@ -491,9 +496,20 @@ module ArrowFormat
   end
 
   class YearMonthIntervalArray < IntervalArray
+    class << self
+      def type
+        YearMonthIntervalType.singleton
+      end
+    end
   end
 
   class DayTimeIntervalArray < IntervalArray
+    class << self
+      def type
+        DayTimeIntervalType.singleton
+      end
+    end
+
     def to_a
       return [] if empty?
 
@@ -501,19 +517,59 @@ module ArrowFormat
       values = @values_buffer.
                  each(@type.buffer_type, offset, @size * 2).
                  each_slice(2).
-                 collect do |(_, day), (_, time)|
-        [day, time]
+                 collect do |(_, day), (_, millisecond)|
+        [day, millisecond]
       end
       apply_validity(values)
     end
 
+    def each(&block)
+      return to_enum(__method__) {@size} unless block_given?
+
+      each_value = Enumerator.new(@size) do |yielder|
+        offset = element_size * @offset
+        @values_buffer.
+          each(@type.buffer_type, offset, @size * 2).
+          each_slice(2) do |(_, day), (_, millisecond)|
+          yielder << [day, millisecond]
+        end
+      end
+      if @validity_buffer.nil?
+        each_value.each(&block)
+      else
+        validity_bitmap.zip(each_value) do |is_valid, value|
+          if is_valid
+            yield(value)
+          else
+            yield(nil)
+          end
+        end
+      end
+    end
+
     private
     def element_size
       super * 2
     end
+
+    def pack_value(value, template)
+      if value.nil?
+        [0, 0].pack(template)
+      elsif value.is_a?(Hash)
+        [value[:day], value[:millisecond]].pack(template)
+      else
+        value.pack(template)
+      end
+    end
   end
 
   class MonthDayNanoIntervalArray < IntervalArray
+    class << self
+      def type
+        MonthDayNanoIntervalType.singleton
+      end
+    end
+
     def to_a
       return [] if empty?
 
@@ -527,10 +583,45 @@ module ArrowFormat
       apply_validity(values)
     end
 
+    def each(&block)
+      return to_enum(__method__) {@size} unless block_given?
+
+      each_value = Enumerator.new(@size) do |yielder|
+        buffer_types = @type.buffer_types
+        value_size = IO::Buffer.size_of(buffer_types)
+        base_offset = value_size * @offset
+        @size.times do |i|
+          offset = base_offset + value_size * i
+          yielder << @values_buffer.get_values(buffer_types, offset)
+        end
+      end
+      if @validity_buffer.nil?
+        each_value.each(&block)
+      else
+        validity_bitmap.zip(each_value) do |is_valid, value|
+          if is_valid
+            yield(value)
+          else
+            yield(nil)
+          end
+        end
+      end
+    end
+
     private
     def element_size
       IO::Buffer.size_of(@type.buffer_types)
     end
+
+    def pack_value(value, template)
+      if value.nil?
+        [0, 0, 0].pack(template)
+      elsif value.is_a?(Hash)
+        [value[:month], value[:day], value[:nanosecond]].pack(template)
+      else
+        value.pack(template)
+      end
+    end
   end
 
   class DurationArray < TemporalArray
diff --git a/ruby/red-arrow-format/lib/arrow-format/type.rb 
b/ruby/red-arrow-format/lib/arrow-format/type.rb
index 1124a13f6a..b17cdb8e8e 100644
--- a/ruby/red-arrow-format/lib/arrow-format/type.rb
+++ b/ruby/red-arrow-format/lib/arrow-format/type.rb
@@ -642,8 +642,12 @@ module ArrowFormat
       :s32
     end
 
+    def pack_template
+      "l"
+    end
+
     def build_array(...)
-      YearMonthIntervalArray.new(self, ...)
+      YearMonthIntervalArray.new(...)
     end
   end
 
@@ -660,8 +664,12 @@ module ArrowFormat
       :s32
     end
 
+    def pack_template
+      "ll"
+    end
+
     def build_array(...)
-      DayTimeIntervalArray.new(self, ...)
+      DayTimeIntervalArray.new(...)
     end
   end
 
@@ -678,8 +686,12 @@ module ArrowFormat
       @buffer_types ||= [:s32, :s32, :s64]
     end
 
+    def pack_template
+      "llq"
+    end
+
     def build_array(...)
-      MonthDayNanoIntervalArray.new(self, ...)
+      MonthDayNanoIntervalArray.new(...)
     end
   end
 
diff --git a/ruby/red-arrow-format/test/test-day-time-interval-array.rb 
b/ruby/red-arrow-format/test/test-day-time-interval-array.rb
new file mode 100644
index 0000000000..477dde7cba
--- /dev/null
+++ b/ruby/red-arrow-format/test/test-day-time-interval-array.rb
@@ -0,0 +1,88 @@
+# 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.
+
+class TestDayTimeIntervalArray < Test::Unit::TestCase
+  sub_test_case("#initialize") do
+    def test_no_null
+      values = [
+        [1, 100],
+        [3, 300],
+      ]
+      assert_equal(values,
+                   ArrowFormat::DayTimeIntervalArray.new(values).to_a)
+    end
+
+    def test_mixed
+      values = [
+        [1, 100],
+        nil,
+        [3, 300],
+      ]
+      assert_equal(values,
+                   ArrowFormat::DayTimeIntervalArray.new(values).to_a)
+    end
+
+    def test_hash
+      values = [
+        {day: 1, millisecond: 100},
+        nil,
+        {day: 3, millisecond: 300},
+      ]
+      assert_equal([
+                     [1, 100],
+                     nil,
+                     [3, 300],
+                   ],
+                   ArrowFormat::DayTimeIntervalArray.new(values).to_a)
+    end
+  end
+
+  sub_test_case("#==") do
+    def test_no_slice
+      values = [
+        [1, 100],
+        nil,
+        [3, 300],
+      ]
+      array1 = ArrowFormat::DayTimeIntervalArray.new(values)
+      array2 = ArrowFormat::DayTimeIntervalArray.new(values)
+      assert_equal(array1, array2)
+    end
+
+    def test_sliced
+      values = [
+        [1, 100],
+        nil,
+        [3, 300],
+      ]
+      array1 = ArrowFormat::DayTimeIntervalArray.new(values)
+      array2 = ArrowFormat::DayTimeIntervalArray.new([nil, *values, nil])
+      assert_equal(array1, array2.slice(1, 3))
+    end
+
+    def test_sliced_different_content
+      values = [
+        [1, 100],
+        nil,
+        [3, 300],
+      ]
+      array1 = ArrowFormat::DayTimeIntervalArray.new(values)
+      array2 = ArrowFormat::DayTimeIntervalArray.new([nil, nil, *values, nil])
+      assert_not_equal(array1, array2.slice(1, 3))
+    end
+  end
+end
diff --git a/ruby/red-arrow-format/test/test-month-day-nano-interval-array.rb 
b/ruby/red-arrow-format/test/test-month-day-nano-interval-array.rb
new file mode 100644
index 0000000000..1c965c6813
--- /dev/null
+++ b/ruby/red-arrow-format/test/test-month-day-nano-interval-array.rb
@@ -0,0 +1,93 @@
+# 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.
+
+class TestMonthDayNanoIntervalArray < Test::Unit::TestCase
+  sub_test_case("#initialize") do
+    def test_no_null
+      values = [
+        [1, 1, 100],
+        [3, 3, 300],
+      ]
+      assert_equal(values,
+                   ArrowFormat::MonthDayNanoIntervalArray.new(values).to_a)
+    end
+
+    def test_mixed
+      values = [
+        [1, 1, 100],
+        nil,
+        [3, 3, 300],
+      ]
+      assert_equal(values,
+                   ArrowFormat::MonthDayNanoIntervalArray.new(values).to_a)
+    end
+
+    def test_hash
+      values = [
+        {month: 1, day: 1, nanosecond: 100},
+        nil,
+        {month: 3, day: 3, nanosecond: 300},
+      ]
+      assert_equal([
+                     [1, 1, 100],
+                     nil,
+                     [3, 3, 300],
+                   ],
+                   ArrowFormat::MonthDayNanoIntervalArray.new(values).to_a)
+    end
+  end
+
+  sub_test_case("#==") do
+    def test_no_slice
+      values = [
+        [1, 1, 100],
+        nil,
+        [3, 3, 300],
+      ]
+      array1 = ArrowFormat::MonthDayNanoIntervalArray.new(values)
+      array2 = ArrowFormat::MonthDayNanoIntervalArray.new(values)
+      assert_equal(array1, array2)
+    end
+
+    def test_sliced
+      values = [
+        [1, 1, 100],
+        nil,
+        [3, 3, 300],
+      ]
+      array1 = ArrowFormat::MonthDayNanoIntervalArray.new(values)
+      array2 = ArrowFormat::MonthDayNanoIntervalArray.new([nil, *values, nil])
+      assert_equal(array1, array2.slice(1, 3))
+    end
+
+    def test_sliced_different_content
+      values = [
+        [1, 1, 100],
+        nil,
+        [3, 3, 300],
+      ]
+      array1 = ArrowFormat::MonthDayNanoIntervalArray.new(values)
+      array2 = ArrowFormat::MonthDayNanoIntervalArray.new([
+                                                            nil,
+                                                            nil,
+                                                            *values,
+                                                            nil,
+                                                          ])
+      assert_not_equal(array1, array2.slice(1, 3))
+    end
+  end
+end
diff --git a/ruby/red-arrow-format/test/test-year-month-interval-array.rb 
b/ruby/red-arrow-format/test/test-year-month-interval-array.rb
new file mode 100644
index 0000000000..5004dfb9b0
--- /dev/null
+++ b/ruby/red-arrow-format/test/test-year-month-interval-array.rb
@@ -0,0 +1,55 @@
+# 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.
+
+class TestYearMonthIntervalArray < Test::Unit::TestCase
+  sub_test_case("#initialize") do
+    def test_no_null
+      values = [0, 100]
+      assert_equal(values,
+                   ArrowFormat::YearMonthIntervalArray.new(values).to_a)
+    end
+
+    def test_mixed
+      values = [0, nil, 100]
+      assert_equal(values,
+                   ArrowFormat::YearMonthIntervalArray.new(values).to_a)
+    end
+  end
+
+  sub_test_case("#==") do
+    def test_no_slice
+      values = [0, nil, 100]
+      array1 = ArrowFormat::YearMonthIntervalArray.new(values)
+      array2 = ArrowFormat::YearMonthIntervalArray.new(values)
+      assert_equal(array1, array2)
+    end
+
+    def test_sliced
+      values = [0, nil, 100]
+      array1 = ArrowFormat::YearMonthIntervalArray.new(values)
+      array2 = ArrowFormat::YearMonthIntervalArray.new([0, *values, 0])
+      assert_equal(array1, array2.slice(1, 3))
+    end
+
+    def test_sliced_different_content
+      values = [0, nil, 100]
+      array1 = ArrowFormat::YearMonthIntervalArray.new(values)
+      array2 = ArrowFormat::YearMonthIntervalArray.new([0, 0, *values, 0])
+      assert_not_equal(array1, array2.slice(1, 3))
+    end
+  end
+end

Reply via email to