blob: acce023dc88f65566dd6e6b715cce76b024e39b6 [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 "array"
require_relative "field"
require_relative "flatbuffers"
require_relative "record-batch"
require_relative "schema"
require_relative "type"
module ArrowFormat
module Readable
private
def read_custom_metadata(fb_custom_metadata)
return nil if fb_custom_metadata.nil?
metadata = {}
fb_custom_metadata.each do |key_value|
metadata[key_value.key] = key_value.value
end
metadata
end
def read_schema(fb_schema, fb_message_custom_metadata=nil)
fields = fb_schema.fields.collect do |fb_field|
read_field(fb_field)
end
message_metadata = read_custom_metadata(fb_message_custom_metadata)
Schema.new(fields,
metadata: read_custom_metadata(fb_schema.custom_metadata),
message_metadata: message_metadata)
end
def read_field(fb_field,
map_entries: false,
map_key: false,
map_value: false)
fb_type = fb_field.type
case fb_type
when FB::Null
type = NullType.singleton
when FB::Bool
type = BooleanType.singleton
when FB::Int
type = read_type_int(fb_type)
when FB::FloatingPoint
case fb_type.precision
when FB::Precision::SINGLE
type = Float32Type.singleton
when FB::Precision::DOUBLE
type = Float64Type.singleton
else
raise ReadError.new("Unsupported type: #{fb_type.inspect}")
end
when FB::Date
case fb_type.unit
when FB::DateUnit::DAY
type = Date32Type.singleton
when FB::DateUnit::MILLISECOND
type = Date64Type.singleton
else
raise ReadError.new("Unsupported type: #{fb_type.inspect}")
end
when FB::Time
case fb_type.bit_width
when 32
case fb_type.unit
when FB::TimeUnit::SECOND
type = Time32Type.new(:second)
when FB::TimeUnit::MILLISECOND
type = Time32Type.new(:millisecond)
else
raise ReadError.new("Unsupported type: #{fb_type.inspect}")
end
when 64
case fb_type.unit
when FB::TimeUnit::MICROSECOND
type = Time64Type.new(:microsecond)
when FB::TimeUnit::NANOSECOND
type = Time64Type.new(:nanosecond)
else
raise ReadError.new("Unsupported type: #{fb_type.inspect}")
end
end
when FB::Timestamp
unit = fb_type.unit.name.downcase.to_sym
type = TimestampType.new(unit, fb_type.timezone)
when FB::Interval
case fb_type.unit
when FB::IntervalUnit::YEAR_MONTH
type = YearMonthIntervalType.singleton
when FB::IntervalUnit::DAY_TIME
type = DayTimeIntervalType.singleton
when FB::IntervalUnit::MONTH_DAY_NANO
type = MonthDayNanoIntervalType.singleton
else
raise ReadError.new("Unsupported type: #{fb_type.inspect}")
end
when FB::Duration
unit = fb_type.unit.name.downcase.to_sym
type = DurationType.new(unit)
when FB::List
type = ListType.new(read_field(fb_field.children[0]))
when FB::LargeList
type = LargeListType.new(read_field(fb_field.children[0]))
when FB::FixedSizeList
type = FixedSizeListType.new(read_field(fb_field.children[0]),
fb_type.list_size)
when FB::Struct
if map_entries
fb_children = fb_field.children
children = [
read_field(fb_children[0], map_key: true),
read_field(fb_children[1], map_value: true),
]
else
children = fb_field.children.collect {|child| read_field(child)}
end
type = StructType.new(children)
when FB::Union
children = fb_field.children.collect {|child| read_field(child)}
type_ids = fb_type.type_ids
case fb_type.mode
when FB::UnionMode::DENSE
type = DenseUnionType.new(children, type_ids)
when FB::UnionMode::SPARSE
type = SparseUnionType.new(children, type_ids)
else
raise ReadError.new("Unsupported type: #{fb_type.inspect}")
end
when FB::Map
type = MapType.new(read_field(fb_field.children[0], map_entries: true),
fb_type.keys_sorted?)
when FB::Binary
type = BinaryType.singleton
when FB::LargeBinary
type = LargeBinaryType.singleton
when FB::Utf8
type = UTF8Type.singleton
when FB::LargeUtf8
type = LargeUTF8Type.singleton
when FB::FixedSizeBinary
type = FixedSizeBinaryType.new(fb_type.byte_width)
when FB::Decimal
case fb_type.bit_width
when 128
type = Decimal128Type.new(fb_type.precision, fb_type.scale)
when 256
type = Decimal256Type.new(fb_type.precision, fb_type.scale)
else
raise ReadError.new("Unsupported type: #{fb_type.inspect}")
end
else
raise ReadError.new("Unsupported type: #{fb_type.inspect}")
end
dictionary = fb_field.dictionary
if dictionary
dictionary_id = dictionary.id
index_type = read_type_int(dictionary.index_type)
value_type = type
type = DictionaryType.new(dictionary_id,
index_type,
value_type,
dictionary.ordered?)
end
# Map type uses static "entries"/"key"/"value" as field names
# instead of field names in FlatBuffers. It's based on the
# specification:
#
# The names of the child fields may be respectively "entries",
# "key", and "value", but this is not enforced.
if map_entries
name = "entries"
elsif map_key
name = "key"
elsif map_value
name = "value"
else
name = fb_field.name
end
Field.new(name,
type,
nullable: fb_field.nullable?,
metadata: read_custom_metadata(fb_field.custom_metadata))
end
def read_type_int(fb_type)
case fb_type.bit_width
when 8
if fb_type.signed?
Int8Type.singleton
else
UInt8Type.singleton
end
when 16
if fb_type.signed?
Int16Type.singleton
else
UInt16Type.singleton
end
when 32
if fb_type.signed?
Int32Type.singleton
else
UInt32Type.singleton
end
when 64
if fb_type.signed?
Int64Type.singleton
else
UInt64Type.singleton
end
end
end
def read_record_batch(version,
fb_record_batch,
fb_message_custom_metadata,
schema,
body)
message_metadata = read_custom_metadata(fb_message_custom_metadata)
n_rows = fb_record_batch.length
nodes = fb_record_batch.nodes
buffers = fb_record_batch.buffers
columns = schema.fields.collect do |field|
read_column(version, field, nodes, buffers, body)
end
RecordBatch.new(schema, n_rows, columns,
message_metadata: message_metadata)
end
def read_column(version, field, nodes, buffers, body)
node = nodes.shift
length = node.length
return field.type.build_array(length) if field.type.is_a?(NullType)
validity_buffer = buffers.shift
if validity_buffer.length.zero?
validity = nil
else
validity = body.slice(validity_buffer.offset, validity_buffer.length)
end
case field.type
when BooleanType,
NumberType,
TemporalType
values_buffer = buffers.shift
values = body&.slice(values_buffer.offset, values_buffer.length)
field.type.build_array(length, validity, values)
when VariableSizeBinaryType
offsets_buffer = buffers.shift
values_buffer = buffers.shift
offsets = body&.slice(offsets_buffer.offset, offsets_buffer.length)
values = body&.slice(values_buffer.offset, values_buffer.length)
field.type.build_array(length, validity, offsets, values)
when FixedSizeBinaryType
values_buffer = buffers.shift
values = body&.slice(values_buffer.offset, values_buffer.length)
field.type.build_array(length, validity, values)
when VariableSizeListType
offsets_buffer = buffers.shift
offsets = body&.slice(offsets_buffer.offset, offsets_buffer.length)
child = read_column(version, field.type.child, nodes, buffers, body)
field.type.build_array(length, validity, offsets, child)
when FixedSizeListType
child = read_column(version, field.type.child, nodes, buffers, body)
field.type.build_array(length, validity, child)
when StructType
children = field.type.children.collect do |child|
read_column(version, child, nodes, buffers, body)
end
field.type.build_array(length, validity, children)
when DenseUnionType
if version == FB::MetadataVersion::V4
# Dense union type has validity with V4.
types_buffer = buffers.shift
types = body&.slice(types_buffer.offset, types_buffer.length)
else
# Dense union type doesn't have validity.
types = validity
end
offsets_buffer = buffers.shift
offsets = body&.slice(offsets_buffer.offset, offsets_buffer.length)
children = field.type.children.collect do |child|
read_column(version, child, nodes, buffers, body)
end
field.type.build_array(length, types, offsets, children)
when SparseUnionType
if version == FB::MetadataVersion::V4
# Sparse union type has validity with V4.
types_buffer = buffers.shift
types = body&.slice(types_buffer.offset, types_buffer.length)
else
# Sparse union type doesn't have validity.
types = validity
end
children = field.type.children.collect do |child|
read_column(version, child, nodes, buffers, body)
end
field.type.build_array(length, types, children)
when DictionaryType
indices_buffer = buffers.shift
indices = body&.slice(indices_buffer.offset, indices_buffer.length)
dictionaries = find_dictionaries(field.type.id)
field.type.build_array(length, validity, indices, dictionaries)
end
end
end
end