| # 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 |