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 6570969847 GH-50385: [Ruby] Add bitmap builder for red-arrow-format 
(#50386)
6570969847 is described below

commit 65709698477fd8a8405f97e7b28a0e903cb89bf1
Author: Sutou Kouhei <[email protected]>
AuthorDate: Mon Jul 6 21:33:21 2026 +0900

    GH-50385: [Ruby] Add bitmap builder for red-arrow-format (#50386)
    
    ### Rationale for this change
    
    It can be used for validity bitmap and boolean array.
    
    ### What changes are included in this PR?
    
    * Add `ArrowFormat::DenseBitmapBuilder`
    * Add `ArrowFormat::SparseBitmapBuilder`
    * Add `ArrowFormat::BooleanArray.new(values)`
    * Use them in integration test
    
    ### Are these changes tested?
    
    Yes.
    
    ### Are there any user-facing changes?
    
    Yes.
    * GitHub Issue: #50385
    
    Authored-by: Sutou Kouhei <[email protected]>
    Signed-off-by: Sutou Kouhei <[email protected]>
---
 ruby/red-arrow-format/lib/arrow-format/array.rb    | 33 +++++++-
 .../lib/arrow-format/bitmap-builder.rb             | 88 ++++++++++++++++++++++
 ruby/red-arrow-format/lib/arrow-format/bitmap.rb   |  1 +
 .../lib/arrow-format/integration/json-reader.rb    | 15 +---
 ruby/red-arrow-format/test/test-boolean-array.rb   | 34 +++++++++
 .../test/test-dense-bitmap-builder.rb              | 45 +++++++++++
 .../test/test-sparse-bitmap-builder.rb             | 58 ++++++++++++++
 7 files changed, 262 insertions(+), 12 deletions(-)

diff --git a/ruby/red-arrow-format/lib/arrow-format/array.rb 
b/ruby/red-arrow-format/lib/arrow-format/array.rb
index 9a248d279f..5addc730e5 100644
--- a/ruby/red-arrow-format/lib/arrow-format/array.rb
+++ b/ruby/red-arrow-format/lib/arrow-format/array.rb
@@ -1,3 +1,4 @@
+# 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
@@ -17,6 +18,7 @@
 require "bigdecimal"
 
 require_relative "bitmap"
+require_relative "bitmap-builder"
 
 module ArrowFormat
   class Array
@@ -186,7 +188,16 @@ module ArrowFormat
   end
 
   class BooleanArray < PrimitiveArray
-    def initialize(size, validity_buffer, values_buffer)
+    def initialize(*args)
+      if args.size == 1
+        args = build_data(args[0])
+      end
+      n_args = args.size
+      if args.size != 3
+        message = "wrong number of arguments (given #{n_args}, expected 1 or 
3)"
+        raise ArgumentError, message
+      end
+      size, validity_buffer, values_buffer = args
       super(BooleanType.singleton, size, validity_buffer, values_buffer)
     end
 
@@ -210,6 +221,26 @@ module ArrowFormat
       super
       @values_bitmap = nil
     end
+
+    def build_data(data)
+      n = 0
+      validity_buffer_builder = nil
+      values_buffer_builder = DenseBitmapBuilder.new
+      data.each_with_index do |value, i|
+        if value.nil?
+          validity_buffer_builder ||= SparseBitmapBuilder.new
+          validity_buffer_builder.unset(i)
+          values_buffer_builder.append(false)
+        elsif value
+          values_buffer_builder.append(true)
+        else
+          values_buffer_builder.append(false)
+        end
+        n += 1
+      end
+      validity_buffer = validity_buffer_builder&.finish(n)
+      return n, validity_buffer, values_buffer_builder.finish
+    end
   end
 
   class IntArray < PrimitiveArray
diff --git a/ruby/red-arrow-format/lib/arrow-format/bitmap-builder.rb 
b/ruby/red-arrow-format/lib/arrow-format/bitmap-builder.rb
new file mode 100644
index 0000000000..71e82e15d9
--- /dev/null
+++ b/ruby/red-arrow-format/lib/arrow-format/bitmap-builder.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.
+
+require_relative "buffer-alignable"
+
+module ArrowFormat
+  class DenseBitmapBuilder
+    include BufferAlignable
+
+    def initialize
+      @buffer = +"".b
+      @n_bits = 0
+      @byte = 0
+    end
+
+    def append(value)
+      @byte |= 1 << @n_bits if value
+      @n_bits += 1
+      flush if @n_bits == 8
+      self
+    end
+
+    def finish
+      flush if @n_bits > 0
+      padding_size = buffer_padding_size(@buffer)
+      @buffer.append_as_bytes(padding(padding_size)) if padding_size > 0
+      @buffer.freeze
+      IO::Buffer.for(@buffer)
+    end
+
+    private
+    def flush
+      @buffer.append_as_bytes([@byte].pack("C"))
+      @n_bits = 0
+      @byte = 0
+    end
+  end
+
+  class SparseBitmapBuilder
+    include BufferAlignable
+
+    def initialize
+      @unset_indexes = {}
+    end
+
+    def unset(index)
+      @unset_indexes[index] = true
+    end
+
+    def finish(size)
+      builder = DenseBitmapBuilder.new
+      set_value = true
+      unset_value = false
+      if @unset_indexes.empty?
+        size.times do
+          builder.append(set_value)
+        end
+      else
+        previous_index = 0
+        @unset_indexes.keys.sort.each do |index|
+          previous_index.upto(index - 1) do
+            builder.append(set_value)
+          end
+          builder.append(unset_value)
+          previous_index = index + 1
+        end
+        (size - previous_index).times do
+          builder.append(set_value)
+        end
+      end
+      builder.finish
+    end
+  end
+end
diff --git a/ruby/red-arrow-format/lib/arrow-format/bitmap.rb 
b/ruby/red-arrow-format/lib/arrow-format/bitmap.rb
index 183c3b28f3..450bb25651 100644
--- a/ruby/red-arrow-format/lib/arrow-format/bitmap.rb
+++ b/ruby/red-arrow-format/lib/arrow-format/bitmap.rb
@@ -1,3 +1,4 @@
+# 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
diff --git a/ruby/red-arrow-format/lib/arrow-format/integration/json-reader.rb 
b/ruby/red-arrow-format/lib/arrow-format/integration/json-reader.rb
index 162f36fb68..b8d84a5a8e 100644
--- a/ruby/red-arrow-format/lib/arrow-format/integration/json-reader.rb
+++ b/ruby/red-arrow-format/lib/arrow-format/integration/json-reader.rb
@@ -205,18 +205,11 @@ module ArrowFormat
       end
 
       def read_bitmap(bitmap)
-        buffer = +"".b
-        bitmap.each_slice(8) do |bits|
-          byte = 0
-          while bits.size < 8
-            bits << 0
-          end
-          bits.reverse_each do |bit|
-            byte = (byte << 1) + bit
-          end
-          buffer << [byte].pack("C")
+        builder = DenseBitmapBuilder.new
+        bitmap.each do |bit|
+          builder.append(bit == 1 ? true : false)
         end
-        IO::Buffer.for(buffer)
+        builder.finish
       end
 
       def read_types(types)
diff --git a/ruby/red-arrow-format/test/test-boolean-array.rb 
b/ruby/red-arrow-format/test/test-boolean-array.rb
new file mode 100644
index 0000000000..1d80966ddc
--- /dev/null
+++ b/ruby/red-arrow-format/test/test-boolean-array.rb
@@ -0,0 +1,34 @@
+# 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 TestBooleanArray < Test::Unit::TestCase
+  def test_mixed
+    assert_equal([true, nil, false],
+                 ArrowFormat::BooleanArray.new([true, nil, false]).to_a)
+  end
+
+  def test_no_null
+    assert_equal([true, false],
+                 ArrowFormat::BooleanArray.new([true, false]).to_a)
+  end
+
+  def test_more_8bits
+    values = [true] * 8 + [nil, false]
+    assert_equal(values,
+                 ArrowFormat::BooleanArray.new(values).to_a)
+  end
+end
diff --git a/ruby/red-arrow-format/test/test-dense-bitmap-builder.rb 
b/ruby/red-arrow-format/test/test-dense-bitmap-builder.rb
new file mode 100644
index 0000000000..526b14011d
--- /dev/null
+++ b/ruby/red-arrow-format/test/test-dense-bitmap-builder.rb
@@ -0,0 +1,45 @@
+# 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 TestDenseBitmapBuilder < Test::Unit::TestCase
+  def setup
+    @builder = ArrowFormat::DenseBitmapBuilder.new
+  end
+
+  def test_empty
+    assert_equal(IO::Buffer.for(""), @builder.finish)
+  end
+
+  def test_1byte
+    8.times do |i|
+      @builder.append(i.odd?)
+    end
+    buffer = [0b10101010].pack("C") + "\x00" * 63
+    assert_equal(IO::Buffer.for(buffer),
+                 @builder.finish)
+  end
+
+  def test_9bits
+    8.times do |i|
+      @builder.append(i.odd?)
+    end
+    @builder.append(true)
+    buffer = [0b10101010].pack("C") + [0b00000001].pack("C") + "\x00" * 62
+    assert_equal(IO::Buffer.for(buffer),
+                 @builder.finish)
+  end
+end
diff --git a/ruby/red-arrow-format/test/test-sparse-bitmap-builder.rb 
b/ruby/red-arrow-format/test/test-sparse-bitmap-builder.rb
new file mode 100644
index 0000000000..663f2c74a8
--- /dev/null
+++ b/ruby/red-arrow-format/test/test-sparse-bitmap-builder.rb
@@ -0,0 +1,58 @@
+# 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 TestSparseBitmapBuilder < Test::Unit::TestCase
+  def setup
+    @builder = ArrowFormat::SparseBitmapBuilder.new
+  end
+
+  def test_empty
+    buffer = [0b00000001].pack("C") + "\x00" * 63
+    assert_equal(IO::Buffer.for(buffer),
+                 @builder.finish(1))
+  end
+
+  def test_unset_0bit
+    @builder.unset(0)
+    buffer = [0b11111110].pack("C") + "\x00" * 63
+    assert_equal(IO::Buffer.for(buffer),
+                 @builder.finish(8))
+  end
+
+  def test_unset_multiple_bytes
+    @builder.unset(15)
+    @builder.unset(31)
+    buffer =  [0b11111111].pack("C")
+    buffer += [0b01111111].pack("C")
+    buffer += [0b11111111].pack("C")
+    buffer += [0b01111111].pack("C")
+    buffer += [0b11111111].pack("C")
+    buffer += "\x00" * 59
+    assert_equal(IO::Buffer.for(buffer),
+                 @builder.finish(40))
+  end
+
+  def test_unset_duplicated
+    @builder.unset(15)
+    @builder.unset(15)
+    buffer =  [0b11111111].pack("C")
+    buffer += [0b01111111].pack("C")
+    buffer += "\x00" * 62
+    assert_equal(IO::Buffer.for(buffer),
+                 @builder.finish(16))
+  end
+end

Reply via email to