blob: 7d301ff78340b14360ebf461bfc36cb707b100e4 [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.
*/
/* This file is based on source code from the Spark Project (http://spark.apache.org/), licensed
* by the Apache Software Foundation (ASF) under the Apache License, Version 2.0. See the NOTICE
* file distributed with this work for additional information regarding copyright ownership. */
#include "paimon/common/data/variant/variant_reassembler.h"
#include <string>
#include <utility>
#include <vector>
#include "arrow/api.h"
#include "arrow/util/checked_cast.h"
#include "fmt/format.h"
#include "paimon/common/data/variant/generic_variant.h"
#include "paimon/common/data/variant/variant_builder.h"
#include "paimon/common/data/variant/variant_type_utils.h"
#include "paimon/common/utils/arrow/status_utils.h"
#include "paimon/memory/memory_pool.h"
namespace paimon {
namespace {
// A row view over a shredded struct array, the Arrow analog of the Java `ShreddedRow`.
struct Cursor {
const arrow::StructArray* array;
int64_t row;
bool IsNullAt(int32_t field) const {
return array->field(field)->IsNull(row);
}
std::string_view GetBinary(int32_t field) const {
return static_cast<const arrow::BinaryArray&>(*array->field(field)).GetView(row);
}
};
Status Rebuild(const Cursor& cursor, std::string_view metadata, const VariantSchema& schema,
const std::shared_ptr<MemoryPool>& pool, VariantBuilder* builder);
Status RebuildTypedScalar(const Cursor& cursor, const VariantSchema& schema,
VariantBuilder* builder) {
int32_t typed_idx = schema.typed_idx;
const arrow::Array& typed_array = *cursor.array->field(typed_idx);
int64_t row = cursor.row;
const VariantSchema::ScalarType& scalar = schema.scalar_schema.value();
switch (scalar.kind) {
case VariantSchema::ScalarKind::kString:
return builder->AppendString(
static_cast<const arrow::StringArray&>(typed_array).GetView(row));
case VariantSchema::ScalarKind::kByte:
return builder->AppendLong(
static_cast<const arrow::Int8Array&>(typed_array).Value(row));
case VariantSchema::ScalarKind::kShort:
return builder->AppendLong(
static_cast<const arrow::Int16Array&>(typed_array).Value(row));
case VariantSchema::ScalarKind::kInt:
return builder->AppendLong(
static_cast<const arrow::Int32Array&>(typed_array).Value(row));
case VariantSchema::ScalarKind::kLong:
return builder->AppendLong(
static_cast<const arrow::Int64Array&>(typed_array).Value(row));
case VariantSchema::ScalarKind::kFloat:
return builder->AppendFloat(
static_cast<const arrow::FloatArray&>(typed_array).Value(row));
case VariantSchema::ScalarKind::kDouble:
return builder->AppendDouble(
static_cast<const arrow::DoubleArray&>(typed_array).Value(row));
case VariantSchema::ScalarKind::kBoolean:
return builder->AppendBoolean(
static_cast<const arrow::BooleanArray&>(typed_array).Value(row));
case VariantSchema::ScalarKind::kBinary:
return builder->AppendBinary(
static_cast<const arrow::BinaryArray&>(typed_array).GetView(row));
case VariantSchema::ScalarKind::kDecimal: {
const auto& decimal_array = static_cast<const arrow::Decimal128Array&>(typed_array);
arrow::Decimal128 value(decimal_array.GetValue(row));
VariantDecimal decimal;
decimal.unscaled = (static_cast<__int128_t>(value.high_bits()) << 64) |
static_cast<__int128_t>(static_cast<__uint128_t>(value.low_bits()));
decimal.scale = scalar.scale;
return builder->AppendDecimal(decimal);
}
case VariantSchema::ScalarKind::kDate:
return builder->AppendDate(
static_cast<const arrow::Date32Array&>(typed_array).Value(row));
case VariantSchema::ScalarKind::kTimestampLtz:
return builder->AppendTimestamp(
static_cast<const arrow::TimestampArray&>(typed_array).Value(row));
case VariantSchema::ScalarKind::kTimestampNtz:
return builder->AppendTimestampNtz(
static_cast<const arrow::TimestampArray&>(typed_array).Value(row));
default:
return Status::NotImplemented("unsupported variant scalar kind in reassembly");
}
}
// Rebuilds a variant value from the shredded data according to the reconstruction algorithm in
// the parquet-format VariantShredding.md specification, appending the result to `builder`.
Status Rebuild(const Cursor& cursor, std::string_view metadata, const VariantSchema& schema,
const std::shared_ptr<MemoryPool>& pool, VariantBuilder* builder) {
int32_t typed_idx = schema.typed_idx;
int32_t variant_idx = schema.variant_idx;
if (typed_idx >= 0 && !cursor.IsNullAt(typed_idx)) {
if (schema.scalar_schema.has_value()) {
return RebuildTypedScalar(cursor, schema, builder);
} else if (schema.array_schema != nullptr) {
const auto& list_array =
static_cast<const arrow::ListArray&>(*cursor.array->field(typed_idx));
const auto& element_array =
static_cast<const arrow::StructArray&>(*list_array.values());
int64_t element_start = list_array.value_offset(cursor.row);
int64_t element_end = list_array.value_offset(cursor.row + 1);
int32_t start = builder->GetWritePos();
std::vector<int32_t> offsets;
offsets.reserve(element_end - element_start);
for (int64_t i = element_start; i < element_end; ++i) {
offsets.push_back(builder->GetWritePos() - start);
PAIMON_RETURN_NOT_OK(Rebuild(Cursor{&element_array, i}, metadata,
*schema.array_schema, pool, builder));
}
return builder->FinishWritingArray(start, offsets);
} else {
const auto& object_array =
static_cast<const arrow::StructArray&>(*cursor.array->field(typed_idx));
Cursor object_cursor{&object_array, cursor.row};
std::vector<VariantBuilder::FieldEntry> fields;
int32_t start = builder->GetWritePos();
for (size_t field_idx = 0; field_idx < schema.object_schema.size(); ++field_idx) {
// Shredded fields must not be null.
if (object_cursor.IsNullAt(static_cast<int32_t>(field_idx))) {
return VariantBinaryUtil::MalformedVariant(
"a shredded object field group is null");
}
const std::string& field_name = schema.object_schema[field_idx].name;
const VariantSchema& field_schema = *schema.object_schema[field_idx].schema;
const auto& field_array = static_cast<const arrow::StructArray&>(
*object_array.field(static_cast<int32_t>(field_idx)));
Cursor field_cursor{&field_array, cursor.row};
// If the field doesn't have a non-null `typed_value` or `value`, it is missing.
if ((field_schema.typed_idx >= 0 &&
!field_cursor.IsNullAt(field_schema.typed_idx)) ||
(field_schema.variant_idx >= 0 &&
!field_cursor.IsNullAt(field_schema.variant_idx))) {
int32_t id = builder->AddKey(field_name);
fields.emplace_back(field_name, id, builder->GetWritePos() - start);
PAIMON_RETURN_NOT_OK(
Rebuild(field_cursor, metadata, field_schema, pool, builder));
}
}
if (variant_idx >= 0 && !cursor.IsNullAt(variant_idx)) {
// Add the leftover fields in the variant binary.
PAIMON_ASSIGN_OR_RAISE(
std::shared_ptr<GenericVariant> leftover,
GenericVariant::Create(cursor.GetBinary(variant_idx), metadata, pool));
PAIMON_ASSIGN_OR_RAISE(VariantValueType leftover_type, leftover->GetType());
if (leftover_type != VariantValueType::kObject) {
return VariantBinaryUtil::MalformedVariant(
"the value column of a shredded object is not an object");
}
PAIMON_ASSIGN_OR_RAISE(int32_t leftover_size, leftover->ObjectSize());
for (int32_t i = 0; i < leftover_size; ++i) {
PAIMON_ASSIGN_OR_RAISE(std::optional<GenericVariant::ObjectField> field,
leftover->GetFieldAtIndex(i));
if (!field.has_value()) {
return VariantBinaryUtil::MalformedVariant(
"a leftover object field is missing");
}
// `value` must not contain any shredded field.
if (schema.object_schema_map.count(field->key) > 0) {
return VariantBinaryUtil::MalformedVariant(fmt::format(
"the value column duplicates the shredded field '{}'", field->key));
}
int32_t id = builder->AddKey(field->key);
fields.emplace_back(field->key, id, builder->GetWritePos() - start);
PAIMON_RETURN_NOT_OK(builder->AppendVariant(*field->value));
}
}
return builder->FinishWritingObject(start, &fields);
}
} else if (variant_idx >= 0 && !cursor.IsNullAt(variant_idx)) {
// `typed_value` doesn't exist or is null. Read from `value`.
PAIMON_ASSIGN_OR_RAISE(
std::shared_ptr<GenericVariant> variant,
GenericVariant::Create(cursor.GetBinary(variant_idx), metadata, pool));
return builder->AppendVariant(*variant);
} else {
// The variant is missing in a context where it must be present; the data is invalid.
return VariantBinaryUtil::MalformedVariant(
"both typed_value and value of a required variant are null");
}
}
} // namespace
Status VariantReassembler::RebuildValue(const arrow::StructArray& shredded, int64_t row,
std::string_view metadata, const VariantSchema& schema,
const std::shared_ptr<MemoryPool>& pool,
VariantBuilder* builder) {
return Rebuild(Cursor{&shredded, row}, metadata, schema, pool, builder);
}
Result<std::shared_ptr<arrow::Array>> VariantReassembler::AssembleVariantArray(
const std::shared_ptr<arrow::StructArray>& shredded,
const std::shared_ptr<VariantSchema>& schema, const std::shared_ptr<MemoryPool>& pool,
arrow::MemoryPool* arrow_pool) {
if (schema->top_level_metadata_idx < 0) {
return VariantBinaryUtil::MalformedVariant("a shredded file column misses metadata");
}
auto output_type = VariantTypeUtils::UnshreddedStructType();
std::unique_ptr<arrow::ArrayBuilder> output_builder;
PAIMON_RETURN_NOT_OK_FROM_ARROW(arrow::MakeBuilder(arrow_pool, output_type, &output_builder));
auto* struct_builder = static_cast<arrow::StructBuilder*>(output_builder.get());
auto* value_builder = static_cast<arrow::BinaryBuilder*>(struct_builder->field_builder(0));
auto* metadata_builder = static_cast<arrow::BinaryBuilder*>(struct_builder->field_builder(1));
bool unshredded = schema->IsUnshredded();
for (int64_t row = 0; row < shredded->length(); ++row) {
if (shredded->IsNull(row)) {
PAIMON_RETURN_NOT_OK_FROM_ARROW(struct_builder->AppendNull());
continue;
}
Cursor cursor{shredded.get(), row};
if (cursor.IsNullAt(schema->top_level_metadata_idx)) {
return VariantBinaryUtil::MalformedVariant("the variant metadata column is null");
}
std::string_view metadata = cursor.GetBinary(schema->top_level_metadata_idx);
PAIMON_RETURN_NOT_OK_FROM_ARROW(struct_builder->Append());
if (unshredded) {
// Rebuilding is unnecessary for unshredded variants.
// TODO(nicholas): avoid copying the value/metadata binaries through the builder for
// unshredded files; the physical arrays could be returned directly with at most a
// per-row malformed-variant check.
if (cursor.IsNullAt(schema->variant_idx)) {
return VariantBinaryUtil::MalformedVariant(
"the value column of an unshredded variant is null");
}
PAIMON_RETURN_NOT_OK_FROM_ARROW(
value_builder->Append(cursor.GetBinary(schema->variant_idx)));
PAIMON_RETURN_NOT_OK_FROM_ARROW(metadata_builder->Append(metadata));
} else {
VariantBuilder builder(/*allow_duplicate_keys=*/false);
PAIMON_RETURN_NOT_OK(Rebuild(cursor, metadata, *schema, pool, &builder));
PAIMON_ASSIGN_OR_RAISE(std::shared_ptr<GenericVariant> variant, builder.Build(pool));
PAIMON_RETURN_NOT_OK_FROM_ARROW(value_builder->Append(variant->RawValue()));
PAIMON_RETURN_NOT_OK_FROM_ARROW(metadata_builder->Append(variant->Metadata()));
}
}
std::shared_ptr<arrow::Array> result;
PAIMON_RETURN_NOT_OK_FROM_ARROW(output_builder->Finish(&result));
return result;
}
} // namespace paimon