blob: 8399663b72243b3038c0217ad40b903ebc34978a [file]
# 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 StreamingWriter
include BufferAlignable
ALIGNMENT_SIZE = IO::Buffer.size_of(:u64)
CONTINUATION = "\xFF\xFF\xFF\xFF".b.freeze
EOS = "\xFF\xFF\xFF\xFF\x00\x00\x00\x00".b.freeze
METADATA_LARGEST_PADDING = "\x00" * 7
def initialize(output)
@output = output
@offset = 0
@fb_dictionary_blocks = []
@fb_record_batch_blocks = []
@written_dictionary_offsets = {}
end
def start(schema)
write_message(build_metadata(schema.to_flatbuffers,
custom_metadata: schema.message_metadata))
end
def write_record_batch(record_batch)
record_batch.schema.fields.each_with_index do |field, i|
next unless field.type.is_a?(DictionaryType)
dictionary_array = record_batch.columns[i]
write_dictionary(field.type.id, dictionary_array)
end
write_record_batch_based_message(record_batch,
record_batch.to_flatbuffers,
@fb_record_batch_blocks)
end
def finish
write_data(EOS)
@output
end
private
def write_data(data)
case data
when IO::Buffer
# TODO: We should use IO::Buffer#write to avoid needless copy.
# data.write(@output)
@output << data.get_string
@offset += data.size
else
@output << data
@offset += data.bytesize
end
end
def write_buffer(buffer)
write_data(buffer)
padding_size = buffer_padding_size(buffer)
write_data(padding(padding_size)) if padding_size > 0
end
def build_metadata(header, body_length=0, custom_metadata: nil)
fb_message = FB::Message::Data.new
fb_message.version = FB::MetadataVersion::V5
fb_message.header = header
fb_message.body_length = body_length
fb_message.custom_metadata = FB.build_custom_metadata(custom_metadata)
metadata = FB::Message.serialize(fb_message)
metadata_size = metadata.bytesize
padding_size = compute_padding_size(metadata_size, ALIGNMENT_SIZE)
metadata_size += padding_size
align!(metadata, ALIGNMENT_SIZE)
metadata
end
def write_record_batch_based_message(record_batch, fb_header, fb_blocks)
body_length = 0
record_batch.all_buffers_enumerator.each do |buffer|
body_length += aligned_buffer_size(buffer) if buffer
end
metadata = build_metadata(fb_header, body_length,
custom_metadata: record_batch.message_metadata)
fb_block = FB::Block::Data.new
fb_block.offset = @offset
fb_block.meta_data_length =
CONTINUATION.bytesize +
MessagePullReader::METADATA_LENGTH_SIZE +
metadata.bytesize
fb_block.body_length = body_length
fb_blocks << fb_block
write_message(metadata) do
record_batch.all_buffers_enumerator.each do |buffer|
write_buffer(buffer) if buffer
end
end
end
def write_dictionary(id, dictionary_array)
value_type = dictionary_array.type.value_type
base_offset = 0
dictionary_array.dictionaries.each do |dictionary|
data = dictionary.array
written_offset = @written_dictionary_offsets[id] || 0
current_base_offset = base_offset
next_base_offset = base_offset + data.size
base_offset = next_base_offset
next if next_base_offset <= written_offset
is_delta = (not written_offset.zero?)
if current_base_offset < written_offset
data = data.slice(written_offset - current_base_offset)
end
schema = Schema.new([Field.new("dummy", value_type)])
size = data.size
record_batch =
RecordBatch.new(schema, size, [data],
message_metadata: dictionary.message_metadata)
fb_dictionary_batch = FB::DictionaryBatch::Data.new
fb_dictionary_batch.id = id
fb_dictionary_batch.data = record_batch.to_flatbuffers
fb_dictionary_batch.delta = is_delta
write_record_batch_based_message(record_batch,
fb_dictionary_batch,
@fb_dictionary_blocks)
@written_dictionary_offsets[id] = written_offset + data.size
end
end
def write_message(metadata)
write_data(CONTINUATION)
metadata_size = metadata.bytesize
write_data([metadata_size].pack("l<"))
write_data(metadata)
yield if block_given?
end
end
end