Skip to content

Commit 6570969

Browse files
authored
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 <kou@clear-code.com> Signed-off-by: Sutou Kouhei <kou@clear-code.com>
1 parent 5bf8934 commit 6570969

7 files changed

Lines changed: 262 additions & 12 deletions

File tree

ruby/red-arrow-format/lib/arrow-format/array.rb

Lines changed: 32 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
# Licensed to the Apache Software Foundation (ASF) under one
12
# or more contributor license agreements. See the NOTICE file
23
# distributed with this work for additional information
34
# regarding copyright ownership. The ASF licenses this file
@@ -17,6 +18,7 @@
1718
require "bigdecimal"
1819

1920
require_relative "bitmap"
21+
require_relative "bitmap-builder"
2022

2123
module ArrowFormat
2224
class Array
@@ -186,7 +188,16 @@ def element_size
186188
end
187189

188190
class BooleanArray < PrimitiveArray
189-
def initialize(size, validity_buffer, values_buffer)
191+
def initialize(*args)
192+
if args.size == 1
193+
args = build_data(args[0])
194+
end
195+
n_args = args.size
196+
if args.size != 3
197+
message = "wrong number of arguments (given #{n_args}, expected 1 or 3)"
198+
raise ArgumentError, message
199+
end
200+
size, validity_buffer, values_buffer = args
190201
super(BooleanType.singleton, size, validity_buffer, values_buffer)
191202
end
192203

@@ -210,6 +221,26 @@ def clear_cache
210221
super
211222
@values_bitmap = nil
212223
end
224+
225+
def build_data(data)
226+
n = 0
227+
validity_buffer_builder = nil
228+
values_buffer_builder = DenseBitmapBuilder.new
229+
data.each_with_index do |value, i|
230+
if value.nil?
231+
validity_buffer_builder ||= SparseBitmapBuilder.new
232+
validity_buffer_builder.unset(i)
233+
values_buffer_builder.append(false)
234+
elsif value
235+
values_buffer_builder.append(true)
236+
else
237+
values_buffer_builder.append(false)
238+
end
239+
n += 1
240+
end
241+
validity_buffer = validity_buffer_builder&.finish(n)
242+
return n, validity_buffer, values_buffer_builder.finish
243+
end
213244
end
214245

215246
class IntArray < PrimitiveArray
Lines changed: 88 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,88 @@
1+
# Licensed to the Apache Software Foundation (ASF) under one
2+
# or more contributor license agreements. See the NOTICE file
3+
# distributed with this work for additional information
4+
# regarding copyright ownership. The ASF licenses this file
5+
# to you under the Apache License, Version 2.0 (the
6+
# "License"); you may not use this file except in compliance
7+
# with the License. You may obtain a copy of the License at
8+
#
9+
# http://www.apache.org/licenses/LICENSE-2.0
10+
#
11+
# Unless required by applicable law or agreed to in writing,
12+
# software distributed under the License is distributed on an
13+
# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
14+
# KIND, either express or implied. See the License for the
15+
# specific language governing permissions and limitations
16+
# under the License.
17+
18+
require_relative "buffer-alignable"
19+
20+
module ArrowFormat
21+
class DenseBitmapBuilder
22+
include BufferAlignable
23+
24+
def initialize
25+
@buffer = +"".b
26+
@n_bits = 0
27+
@byte = 0
28+
end
29+
30+
def append(value)
31+
@byte |= 1 << @n_bits if value
32+
@n_bits += 1
33+
flush if @n_bits == 8
34+
self
35+
end
36+
37+
def finish
38+
flush if @n_bits > 0
39+
padding_size = buffer_padding_size(@buffer)
40+
@buffer.append_as_bytes(padding(padding_size)) if padding_size > 0
41+
@buffer.freeze
42+
IO::Buffer.for(@buffer)
43+
end
44+
45+
private
46+
def flush
47+
@buffer.append_as_bytes([@byte].pack("C"))
48+
@n_bits = 0
49+
@byte = 0
50+
end
51+
end
52+
53+
class SparseBitmapBuilder
54+
include BufferAlignable
55+
56+
def initialize
57+
@unset_indexes = {}
58+
end
59+
60+
def unset(index)
61+
@unset_indexes[index] = true
62+
end
63+
64+
def finish(size)
65+
builder = DenseBitmapBuilder.new
66+
set_value = true
67+
unset_value = false
68+
if @unset_indexes.empty?
69+
size.times do
70+
builder.append(set_value)
71+
end
72+
else
73+
previous_index = 0
74+
@unset_indexes.keys.sort.each do |index|
75+
previous_index.upto(index - 1) do
76+
builder.append(set_value)
77+
end
78+
builder.append(unset_value)
79+
previous_index = index + 1
80+
end
81+
(size - previous_index).times do
82+
builder.append(set_value)
83+
end
84+
end
85+
builder.finish
86+
end
87+
end
88+
end

ruby/red-arrow-format/lib/arrow-format/bitmap.rb

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
# Licensed to the Apache Software Foundation (ASF) under one
12
# or more contributor license agreements. See the NOTICE file
23
# distributed with this work for additional information
34
# regarding copyright ownership. The ASF licenses this file

ruby/red-arrow-format/lib/arrow-format/integration/json-reader.rb

Lines changed: 4 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -205,18 +205,11 @@ def read_schema
205205
end
206206

207207
def read_bitmap(bitmap)
208-
buffer = +"".b
209-
bitmap.each_slice(8) do |bits|
210-
byte = 0
211-
while bits.size < 8
212-
bits << 0
213-
end
214-
bits.reverse_each do |bit|
215-
byte = (byte << 1) + bit
216-
end
217-
buffer << [byte].pack("C")
208+
builder = DenseBitmapBuilder.new
209+
bitmap.each do |bit|
210+
builder.append(bit == 1 ? true : false)
218211
end
219-
IO::Buffer.for(buffer)
212+
builder.finish
220213
end
221214

222215
def read_types(types)
Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
# Licensed to the Apache Software Foundation (ASF) under one
2+
# or more contributor license agreements. See the NOTICE file
3+
# distributed with this work for additional information
4+
# regarding copyright ownership. The ASF licenses this file
5+
# to you under the Apache License, Version 2.0 (the
6+
# "License"); you may not use this file except in compliance
7+
# with the License. You may obtain a copy of the License at
8+
#
9+
# http://www.apache.org/licenses/LICENSE-2.0
10+
#
11+
# Unless required by applicable law or agreed to in writing,
12+
# software distributed under the License is distributed on an
13+
# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
14+
# KIND, either express or implied. See the License for the
15+
# specific language governing permissions and limitations
16+
# under the License.
17+
18+
class TestBooleanArray < Test::Unit::TestCase
19+
def test_mixed
20+
assert_equal([true, nil, false],
21+
ArrowFormat::BooleanArray.new([true, nil, false]).to_a)
22+
end
23+
24+
def test_no_null
25+
assert_equal([true, false],
26+
ArrowFormat::BooleanArray.new([true, false]).to_a)
27+
end
28+
29+
def test_more_8bits
30+
values = [true] * 8 + [nil, false]
31+
assert_equal(values,
32+
ArrowFormat::BooleanArray.new(values).to_a)
33+
end
34+
end
Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,45 @@
1+
# Licensed to the Apache Software Foundation (ASF) under one
2+
# or more contributor license agreements. See the NOTICE file
3+
# distributed with this work for additional information
4+
# regarding copyright ownership. The ASF licenses this file
5+
# to you under the Apache License, Version 2.0 (the
6+
# "License"); you may not use this file except in compliance
7+
# with the License. You may obtain a copy of the License at
8+
#
9+
# http://www.apache.org/licenses/LICENSE-2.0
10+
#
11+
# Unless required by applicable law or agreed to in writing,
12+
# software distributed under the License is distributed on an
13+
# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
14+
# KIND, either express or implied. See the License for the
15+
# specific language governing permissions and limitations
16+
# under the License.
17+
18+
class TestDenseBitmapBuilder < Test::Unit::TestCase
19+
def setup
20+
@builder = ArrowFormat::DenseBitmapBuilder.new
21+
end
22+
23+
def test_empty
24+
assert_equal(IO::Buffer.for(""), @builder.finish)
25+
end
26+
27+
def test_1byte
28+
8.times do |i|
29+
@builder.append(i.odd?)
30+
end
31+
buffer = [0b10101010].pack("C") + "\x00" * 63
32+
assert_equal(IO::Buffer.for(buffer),
33+
@builder.finish)
34+
end
35+
36+
def test_9bits
37+
8.times do |i|
38+
@builder.append(i.odd?)
39+
end
40+
@builder.append(true)
41+
buffer = [0b10101010].pack("C") + [0b00000001].pack("C") + "\x00" * 62
42+
assert_equal(IO::Buffer.for(buffer),
43+
@builder.finish)
44+
end
45+
end
Lines changed: 58 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,58 @@
1+
# Licensed to the Apache Software Foundation (ASF) under one
2+
# or more contributor license agreements. See the NOTICE file
3+
# distributed with this work for additional information
4+
# regarding copyright ownership. The ASF licenses this file
5+
# to you under the Apache License, Version 2.0 (the
6+
# "License"); you may not use this file except in compliance
7+
# with the License. You may obtain a copy of the License at
8+
#
9+
# http://www.apache.org/licenses/LICENSE-2.0
10+
#
11+
# Unless required by applicable law or agreed to in writing,
12+
# software distributed under the License is distributed on an
13+
# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
14+
# KIND, either express or implied. See the License for the
15+
# specific language governing permissions and limitations
16+
# under the License.
17+
18+
class TestSparseBitmapBuilder < Test::Unit::TestCase
19+
def setup
20+
@builder = ArrowFormat::SparseBitmapBuilder.new
21+
end
22+
23+
def test_empty
24+
buffer = [0b00000001].pack("C") + "\x00" * 63
25+
assert_equal(IO::Buffer.for(buffer),
26+
@builder.finish(1))
27+
end
28+
29+
def test_unset_0bit
30+
@builder.unset(0)
31+
buffer = [0b11111110].pack("C") + "\x00" * 63
32+
assert_equal(IO::Buffer.for(buffer),
33+
@builder.finish(8))
34+
end
35+
36+
def test_unset_multiple_bytes
37+
@builder.unset(15)
38+
@builder.unset(31)
39+
buffer = [0b11111111].pack("C")
40+
buffer += [0b01111111].pack("C")
41+
buffer += [0b11111111].pack("C")
42+
buffer += [0b01111111].pack("C")
43+
buffer += [0b11111111].pack("C")
44+
buffer += "\x00" * 59
45+
assert_equal(IO::Buffer.for(buffer),
46+
@builder.finish(40))
47+
end
48+
49+
def test_unset_duplicated
50+
@builder.unset(15)
51+
@builder.unset(15)
52+
buffer = [0b11111111].pack("C")
53+
buffer += [0b01111111].pack("C")
54+
buffer += "\x00" * 62
55+
assert_equal(IO::Buffer.for(buffer),
56+
@builder.finish(16))
57+
end
58+
end

0 commit comments

Comments
 (0)