blob: b1eec2fc55422d269b2f2985ca2ed44168db29c0 [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 "json"
require "test-unit"
ENV["TEST_UNIT_MAX_DIFF_TARGET_STRING_SIZE"] ||= "1_000_000"
class ArrowFormatIntegrationTest < Test::Unit::TestCase
module ToHashable
refine ArrowFormat::Type do
def to_h
{
"name" => normalized_name,
}
end
private
def normalized_name
name.downcase
end
end
refine ArrowFormat::BooleanType do
private
def normalized_name
"bool"
end
end
refine ArrowFormat::IntType do
def to_h
super.merge("bitWidth" => @bit_width,
"isSigned" => @signed)
end
private
def normalized_name
"int"
end
end
refine ArrowFormat::FloatingPointType do
def to_h
super.merge("precision" => normalized_precision)
end
private
def normalized_name
"floatingpoint"
end
def normalized_precision
@precision.to_s.upcase
end
end
refine ArrowFormat::DateType do
def to_h
super.merge("unit" => normalized_unit)
end
private
def normalized_name
"date"
end
def normalized_unit
@unit.to_s.upcase
end
end
refine ArrowFormat::TimeType do
def to_h
super.merge("bitWidth" => @bit_width,
"unit" => normalized_unit)
end
private
def normalized_name
"time"
end
def normalized_unit
@unit.to_s.upcase
end
end
refine ArrowFormat::TimestampType do
def to_h
hash = super
hash["unit"] = normalized_unit
hash["timezone"] = @time_zone if @time_zone
hash
end
private
def normalized_name
"timestamp"
end
def normalized_unit
@unit.to_s.upcase
end
end
refine ArrowFormat::IntervalType do
def to_h
super.merge("unit" => normalized_unit)
end
private
def normalized_name
"interval"
end
def normalized_unit
@unit.to_s.upcase
end
end
refine ArrowFormat::DurationType do
def to_h
super.merge("unit" => normalized_unit)
end
private
def normalized_unit
@unit.to_s.upcase
end
end
refine ArrowFormat::FixedSizeBinaryType do
def to_h
super.merge("byteWidth" => @byte_width)
end
end
refine ArrowFormat::FixedSizeListType do
def to_h
super.merge("listSize" => @size)
end
end
refine ArrowFormat::DecimalType do
def to_h
hash = super
hash.delete("byteWidth")
hash["bitWidth"] = @byte_width * 8
hash["precision"] = @precision
hash["scale"] = @scale
hash
end
private
def normalized_name
"decimal"
end
end
refine ArrowFormat::UnionType do
def to_h
super.merge("mode" => normalized_mode,
"typeIds" => normalized_type_ids)
end
private
def normalized_name
"union"
end
def normalized_mode
@mode.to_s.upcase
end
def normalized_type_ids
@type_ids
end
end
refine ArrowFormat::MapType do
def to_h
super.merge("keysSorted" => @keys_sorted)
end
end
module MetadataNormalizable
private
def normalized_metadata
metadata_list = @metadata.collect do |key, value|
{"key" => key, "value" => value}
end
metadata_list.sort_by do |metadatum|
metadatum["key"]
end
end
end
refine ArrowFormat::Field do
import_methods MetadataNormalizable
def to_h
hash = {
"children" => normalized_children,
"name" => @name,
"nullable" => @nullable,
}
if @type.is_a?(ArrowFormat::DictionaryType)
hash["type"] = @type.value_type.to_h
hash["dictionary"] = {
"id" => @type.id,
"indexType" => @type.index_type.to_h,
"isOrdered" => @type.ordered?,
}
else
hash["type"] = @type.to_h
end
hash["metadata"] = normalized_metadata if @metadata
hash
end
private
def normalized_children
if @type.respond_to?(:children)
@type.children.collect(&:to_h)
elsif @type.respond_to?(:child)
[@type.child.to_h]
else
[]
end
end
end
refine ArrowFormat::Schema do
import_methods MetadataNormalizable
def to_h
hash = {
"fields" => @fields.collect(&:to_h),
}
hash["metadata"] = normalized_metadata if @metadata
hash
end
end
refine ArrowFormat::Array do
def to_h
hash = {
"count" => size,
}
to_h_data(hash)
hash
end
private
def to_h_data(hash)
hash["DATA"] = normalized_data
hash["VALIDITY"] = normalized_validity
end
def normalized_data
to_a
end
def normalized_validity
if @validity_buffer
validity_bitmap.collect {|valid| valid ? 1 : 0}
else
[1] * @size
end
end
def stringify_data(data)
data.collect do |value|
if value.nil?
nil
else
value.to_s
end
end
end
def hexify(value)
value.each_byte.collect {|byte| "%02X" % byte}.join("")
end
def normalized_children
@children.zip(@type.children).collect do |child, field|
normalize_child(child, field)
end
end
def normalize_child(child, field)
child.to_h.merge("name" => field.name)
end
end
refine ArrowFormat::NullArray do
private
def to_h_data(hash)
end
end
refine ArrowFormat::Int64Array do
private
def normalized_data
stringify_data(super)
end
end
refine ArrowFormat::UInt64Array do
private
def normalized_data
stringify_data(super)
end
end
refine ArrowFormat::Float32Array do
private
def normalized_data
super.collect do |value|
if value.nil?
nil
else
value.round(3)
end
end
end
end
refine ArrowFormat::Date64Array do
private
def normalized_data
stringify_data(super)
end
end
refine ArrowFormat::Time64Array do
private
def normalized_data
stringify_data(super)
end
end
refine ArrowFormat::TimestampArray do
private
def normalized_data
stringify_data(super)
end
end
refine ArrowFormat::DayTimeIntervalArray do
private
def normalized_data
super.collect do |value|
if value.nil?
nil
else
day, time = value
{"days" => day, "milliseconds" => time}
end
end
end
end
refine ArrowFormat::MonthDayNanoIntervalArray do
private
def normalized_data
super.collect do |value|
if value.nil?
nil
else
month, day, nano = value
{"months" => month, "days" => day, "nanoseconds" => nano}
end
end
end
end
refine ArrowFormat::MonthDayNanoIntervalArray do
private
def normalized_data
super.collect do |value|
if value.nil?
nil
else
month, day, nano = value
{"months" => month, "days" => day, "nanoseconds" => nano}
end
end
end
end
refine ArrowFormat::DurationArray do
private
def normalized_data
stringify_data(super)
end
end
refine ArrowFormat::VariableSizeBinaryArray do
private
def to_h_data(hash)
super(hash)
hash["OFFSET"] = normalized_offsets
end
def normalized_data
super.collect do |value|
if value.nil?
nil
else
normalize_value(value)
end
end
end
def normalize_value(value)
hexify(value)
end
def normalized_offsets
offsets
end
end
refine ArrowFormat::LargeBinaryArray do
private
def normalized_offsets
offsets.collect(&:to_s)
end
end
refine ArrowFormat::VariableSizeUTF8Array do
private
def normalize_value(value)
value
end
end
refine ArrowFormat::LargeUTF8Array do
private
def normalized_offsets
offsets.collect(&:to_s)
end
end
refine ArrowFormat::FixedSizeBinaryArray do
private
def normalized_data
super.collect do |value|
if value.nil?
nil
else
normalize_value(value)
end
end
end
def normalize_value(value)
hexify(value)
end
end
refine ArrowFormat::DecimalArray do
private
def normalize_value(value)
(value * (10 ** (@type.scale))).to_s("f").delete_suffix(".0")
end
end
refine ArrowFormat::VariableSizeListArray do
private
def to_h_data(hash)
hash["OFFSET"] = normalized_offsets
hash["VALIDITY"] = normalized_validity
hash["children"] = [normalize_child(@child, @type.child)]
end
def normalized_offsets
offsets
end
end
refine ArrowFormat::FixedSizeListArray do
private
def to_h_data(hash)
hash["VALIDITY"] = normalized_validity
hash["children"] = [normalize_child(@child, @type.child)]
end
end
refine ArrowFormat::LargeListArray do
private
def normalized_offsets
offsets.collect(&:to_s)
end
end
refine ArrowFormat::StructArray do
private
def to_h_data(hash)
hash["VALIDITY"] = normalized_validity
hash["children"] = normalized_children
end
end
refine ArrowFormat::UnionArray do
private
def to_h_data(hash)
hash["TYPE_ID"] = each_type.to_a
hash["children"] = normalized_children
end
end
refine ArrowFormat::DenseUnionArray do
private
def to_h_data(hash)
super
hash["OFFSET"] = each_offset.to_a
end
end
refine ArrowFormat::DictionaryArray do
private
def normalized_data
indices
end
end
refine ArrowFormat::RecordBatch do
def to_h
{
"columns" => normalized_columns,
"count" => @n_rows,
}
end
private
def normalized_columns
@schema.fields.zip(@columns).collect do |field, column|
column.to_h.merge("name" => field.name)
end
end
end
end
using ToHashable
def setup
@options = ArrowFormat::Integration::Options.singleton
end
def normalize_field!(field)
metadata = field["metadata"]
if metadata
field["metadata"] = metadata.sort_by do |metadatum|
metadatum["key"]
end
end
case field["type"]["name"]
when "decimal"
field["type"]["bitWidth"] ||= 128 unless @options.validate_decimal?
when "map"
entries = field["children"][0]
entries["name"] = "entries"
entries["children"][0]["name"] = "key"
entries["children"][1]["name"] = "value"
end
end
def normalize_schema!(schema)
schema["fields"].each do |field|
normalize_field!(field)
end
end
def normalize_array!(array, field)
case field["type"]["name"]
when "map"
entries = array["children"][0]
entries["name"] = "entries"
entries["children"][0]["name"] = "key"
entries["children"][1]["name"] = "value"
when "union"
# V4 data has VALIDITY.
array.delete("VALIDITY")
end
data = array["DATA"]
validity = array["VALIDITY"]
if data and validity
array["DATA"] = data.zip(validity).collect do |value, valid_bit|
if (valid_bit == 1)
value
else
nil
end
end
end
child_arrays = array["children"]
if child_arrays
child_fields = field["children"]
child_arrays.zip(child_fields) do |child_array, child_field|
normalize_array!(child_array, child_field)
end
end
end
def normalize_record_batch!(record_batch, schema)
record_batch["columns"].zip(schema["fields"]) do |column, field|
normalize_array!(column, field)
end
end
def test_validate
expected = JSON.parse(File.read(@options.json))
expected_schema = expected["schema"]
normalize_schema!(expected_schema)
File.open(@options.arrow, "rb") do |input|
reader = ArrowFormat::FileReader.new(input)
expected_record_batches = []
actual_record_batches = []
reader.each.with_index do |record_batch, i|
expected_record_batch = expected["batches"][i]
normalize_record_batch!(expected_record_batch, expected_schema)
expected_record_batches << expected_record_batch
actual_record_batches << record_batch.to_h
end
assert_equal({
schema: expected_schema,
record_batches: expected_record_batches,
},
{
schema: reader.schema.to_h,
record_batches: actual_record_batches,
})
end
end
end