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