diff --git a/ruby/red-arrow-format/lib/arrow-format/array.rb b/ruby/red-arrow-format/lib/arrow-format/array.rb index bc79edf9eb0..0ff88d23aea 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 @@ def build_data(data, type) 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 @@ def build_data(data, type) 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 @@ class IntervalArray < TemporalArray 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 @@ def to_a 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 @@ def to_a 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 1124a13f6a8..b17cdb8e8e8 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 @@ def buffer_type :s32 end + def pack_template + "l" + end + def build_array(...) - YearMonthIntervalArray.new(self, ...) + YearMonthIntervalArray.new(...) end end @@ -660,8 +664,12 @@ def buffer_type :s32 end + def pack_template + "ll" + end + def build_array(...) - DayTimeIntervalArray.new(self, ...) + DayTimeIntervalArray.new(...) end end @@ -678,8 +686,12 @@ def buffer_types @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 00000000000..477dde7cba8 --- /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 00000000000..1c965c6813c --- /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 00000000000..5004dfb9b02 --- /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