blob: 2a9b69dc855c39ac1b3117b52a1ceb06a30902fa [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.
*/
#include "paimon/common/data/shredding/map_shared_shredding_batch_converter.h"
#include <map>
#include <string>
#include <utility>
#include "arrow/array.h"
#include "arrow/builder.h"
#include "arrow/c/bridge.h"
#include "arrow/type.h"
#include "fmt/format.h"
#include "paimon/common/data/shredding/lru_map_shared_shredding_column_allocator.h"
#include "paimon/common/data/shredding/map_shared_shredding_context.h"
#include "paimon/common/data/shredding/map_shared_shredding_utils.h"
#include "paimon/common/data/shredding/map_shredding_defs.h"
#include "paimon/common/data/shredding/plain_map_shared_shredding_column_allocator.h"
#include "paimon/common/data/shredding/sequential_map_shared_shredding_column_allocator.h"
#include "paimon/common/utils/arrow/mem_utils.h"
#include "paimon/common/utils/arrow/status_utils.h"
#include "paimon/core/core_options.h"
namespace paimon {
/// Checks that a dynamic_cast result is not null, returning Status::Invalid on failure.
#define PAIMON_CHECK_NOT_NULL(ptr, msg) \
do { \
if (PAIMON_UNLIKELY((ptr) == nullptr)) { \
return Status::Invalid(msg); \
} \
} while (false)
namespace {
Result<std::unique_ptr<MapSharedShreddingColumnAllocator>> CreateMapSharedShreddingColumnAllocator(
int32_t num_columns, MapSharedShreddingColumnPlacementPolicy placement_policy) {
switch (placement_policy) {
case MapSharedShreddingColumnPlacementPolicy::PLAIN:
return std::make_unique<PlainMapSharedShreddingColumnAllocator>(num_columns);
case MapSharedShreddingColumnPlacementPolicy::SEQUENTIAL:
return std::make_unique<SequentialMapSharedShreddingColumnAllocator>(num_columns);
case MapSharedShreddingColumnPlacementPolicy::LRU:
return std::make_unique<LruMapSharedShreddingColumnAllocator>(num_columns);
}
return Status::Invalid("unknown shared-shredding column placement policy");
}
} // namespace
Result<std::shared_ptr<MapSharedShreddingBatchConverter>> MapSharedShreddingBatchConverter::Create(
const std::shared_ptr<arrow::Schema>& logical_schema,
const std::shared_ptr<MapSharedShreddingContext>& context, const CoreOptions& options,
const std::shared_ptr<MemoryPool>& pool) {
std::map<std::string, int32_t> field_to_num_columns = context->ComputeNextK();
PAIMON_ASSIGN_OR_RAISE(
std::shared_ptr<arrow::Schema> physical_schema,
MapSharedShreddingUtils::LogicalToPhysicalSchema(logical_schema, field_to_num_columns));
std::vector<ColumnContext> contexts;
std::vector<std::string> shredding_field_names;
contexts.reserve(field_to_num_columns.size());
shredding_field_names.reserve(field_to_num_columns.size());
// Iterate in schema field order (not map order) so that shredding_field_names_
// matches the order in which shredding columns appear in the schema.
// This is critical for the sequential matching logic in Convert().
for (int32_t i = 0; i < logical_schema->num_fields(); ++i) {
const std::string& name = logical_schema->field(i)->name();
auto it = field_to_num_columns.find(name);
if (it != field_to_num_columns.end()) {
int32_t num_columns = it->second;
PAIMON_ASSIGN_OR_RAISE(MapSharedShreddingColumnPlacementPolicy placement_policy,
options.GetMapSharedShreddingColumnPlacementPolicy(name));
PAIMON_ASSIGN_OR_RAISE(
std::unique_ptr<MapSharedShreddingColumnAllocator> allocator,
CreateMapSharedShreddingColumnAllocator(num_columns, placement_policy));
contexts.emplace_back(name, num_columns, std::move(allocator));
shredding_field_names.push_back(name);
}
}
return std::shared_ptr<MapSharedShreddingBatchConverter>(
new MapSharedShreddingBatchConverter(logical_schema, physical_schema, std::move(contexts),
std::move(shredding_field_names), pool));
}
MapSharedShreddingBatchConverter::MapSharedShreddingBatchConverter(
const std::shared_ptr<arrow::Schema>& logical_schema,
const std::shared_ptr<arrow::Schema>& physical_schema, std::vector<ColumnContext>&& contexts,
std::vector<std::string>&& shredding_field_names, const std::shared_ptr<MemoryPool>& pool)
: logical_schema_(logical_schema),
physical_schema_(physical_schema),
contexts_(std::move(contexts)),
shredding_field_names_(std::move(shredding_field_names)),
pool_(GetArrowPool(pool)) {}
const std::shared_ptr<arrow::Schema>& MapSharedShreddingBatchConverter::GetPhysicalSchema() const {
return physical_schema_;
}
Result<std::unique_ptr<ArrowArray>> MapSharedShreddingBatchConverter::Convert(
ArrowArray* logical_batch) {
std::shared_ptr<arrow::DataType> logical_type = arrow::struct_(logical_schema_->fields());
PAIMON_ASSIGN_OR_RAISE_FROM_ARROW(std::shared_ptr<arrow::Array> logical_array,
arrow::ImportArray(logical_batch, logical_type));
auto logical_struct = std::dynamic_pointer_cast<arrow::StructArray>(logical_array);
PAIMON_CHECK_NOT_NULL(logical_struct,
"MapSharedShreddingBatchConverter: input is not a StructArray");
int32_t num_fields = logical_schema_->num_fields();
arrow::ArrayVector physical_columns;
physical_columns.reserve(num_fields);
size_t context_idx = 0;
for (int32_t col = 0; col < num_fields; ++col) {
auto column = logical_struct->field(col);
const std::string& field_name = logical_schema_->field(col)->name();
if (context_idx < shredding_field_names_.size() &&
shredding_field_names_[context_idx] == field_name) {
auto physical_struct_type = physical_schema_->field(col)->type();
PAIMON_ASSIGN_OR_RAISE(
std::shared_ptr<arrow::Array> physical_column,
ConvertOneColumn(column, physical_struct_type, &contexts_[context_idx]));
physical_columns.push_back(std::move(physical_column));
++context_idx;
} else {
physical_columns.push_back(column);
}
}
PAIMON_ASSIGN_OR_RAISE_FROM_ARROW(
std::shared_ptr<arrow::Array> physical_struct,
arrow::StructArray::Make(physical_columns, physical_schema_->field_names()));
std::unique_ptr<ArrowArray> result = std::make_unique<ArrowArray>();
PAIMON_RETURN_NOT_OK_FROM_ARROW(arrow::ExportArray(*physical_struct, result.get()));
return result;
}
Result<std::shared_ptr<arrow::Array>> MapSharedShreddingBatchConverter::ConvertOneColumn(
const std::shared_ptr<arrow::Array>& map_column,
const std::shared_ptr<arrow::DataType>& physical_struct_type, ColumnContext* context) const {
auto map_array = std::dynamic_pointer_cast<arrow::MapArray>(map_column);
PAIMON_CHECK_NOT_NULL(map_array, "MapSharedShreddingBatchConverter: column is not a MapArray");
int64_t num_rows = map_array->length();
int32_t num_cols = context->num_columns;
auto keys_array = std::dynamic_pointer_cast<arrow::StringArray>(map_array->keys());
PAIMON_CHECK_NOT_NULL(keys_array,
"MapSharedShreddingBatchConverter: MAP keys are not StringArray");
auto values_array = map_array->items();
// Create StructBuilder from physical struct type — it owns all child builders.
PAIMON_ASSIGN_OR_RAISE_FROM_ARROW(std::unique_ptr<arrow::ArrayBuilder> struct_builder_base,
arrow::MakeBuilder(physical_struct_type, pool_.get()));
auto* struct_builder = dynamic_cast<arrow::StructBuilder*>(struct_builder_base.get());
PAIMON_CHECK_NOT_NULL(struct_builder,
"MapSharedShreddingBatchConverter: failed to create StructBuilder");
PAIMON_RETURN_NOT_OK_FROM_ARROW(struct_builder->Reserve(num_rows));
// Extract child builders: [field_mapping, col_0..K-1, overflow]
auto* field_mapping_builder =
dynamic_cast<arrow::ListBuilder*>(struct_builder->field_builder(0));
PAIMON_CHECK_NOT_NULL(field_mapping_builder,
"MapSharedShreddingBatchConverter: field_mapping is not a ListBuilder");
auto* field_mapping_value_builder =
dynamic_cast<arrow::Int32Builder*>(field_mapping_builder->value_builder());
PAIMON_CHECK_NOT_NULL(
field_mapping_value_builder,
"MapSharedShreddingBatchConverter: field_mapping value is not Int32Builder");
PAIMON_RETURN_NOT_OK_FROM_ARROW(field_mapping_builder->Reserve(num_rows));
PAIMON_RETURN_NOT_OK_FROM_ARROW(field_mapping_value_builder->Reserve(num_rows * num_cols));
std::vector<arrow::ArrayBuilder*> col_builders_raw;
col_builders_raw.reserve(num_cols);
for (int32_t c = 0; c < num_cols; ++c) {
arrow::ArrayBuilder* col_builder = struct_builder->field_builder(1 + c);
PAIMON_CHECK_NOT_NULL(col_builder, "MapSharedShreddingBatchConverter: col builder is null");
PAIMON_RETURN_NOT_OK_FROM_ARROW(col_builder->Reserve(num_rows));
col_builders_raw.push_back(col_builder);
}
int32_t overflow_field_idx = 1 + num_cols;
auto* overflow_builder =
dynamic_cast<arrow::MapBuilder*>(struct_builder->field_builder(overflow_field_idx));
PAIMON_CHECK_NOT_NULL(overflow_builder,
"MapSharedShreddingBatchConverter: overflow is not a MapBuilder");
auto* overflow_key_builder =
dynamic_cast<arrow::Int32Builder*>(overflow_builder->key_builder());
PAIMON_CHECK_NOT_NULL(overflow_key_builder,
"MapSharedShreddingBatchConverter: overflow key is not Int32Builder");
arrow::ArrayBuilder* overflow_value_builder = overflow_builder->item_builder();
PAIMON_CHECK_NOT_NULL(overflow_value_builder,
"MapSharedShreddingBatchConverter: overflow value builder is null");
PAIMON_RETURN_NOT_OK_FROM_ARROW(overflow_builder->Reserve(num_rows));
// Process each row
for (int64_t row = 0; row < num_rows; ++row) {
if (map_array->IsNull(row)) {
// StructBuilder::AppendNull() auto-appends empty values to all children.
PAIMON_RETURN_NOT_OK_FROM_ARROW(struct_builder->AppendNull());
continue;
}
PAIMON_RETURN_NOT_OK_FROM_ARROW(struct_builder->Append());
int64_t start = map_array->value_offset(row);
int64_t length = map_array->value_length(row);
// Extract field ids and build lookup map
std::vector<int32_t> field_ids;
std::unordered_map<int32_t, int64_t> field_id_to_value_index;
ExtractRowFields(keys_array, start, length, &context->dict, &field_ids,
&field_id_to_value_index);
// Allocate columns
RowAllocation allocation = context->allocator->AllocateRow(field_ids);
// Fill sub-columns
PAIMON_RETURN_NOT_OK(AppendFieldMapping(allocation, num_cols, field_mapping_builder,
field_mapping_value_builder));
PAIMON_RETURN_NOT_OK(AppendColumnValues(values_array, allocation, field_id_to_value_index,
num_cols, col_builders_raw));
PAIMON_RETURN_NOT_OK(AppendOverflow(values_array, allocation, field_id_to_value_index,
overflow_builder, overflow_key_builder,
overflow_value_builder));
}
// Finalize
std::shared_ptr<arrow::StructArray> result;
PAIMON_RETURN_NOT_OK_FROM_ARROW(struct_builder->Finish(&result));
return result;
}
void MapSharedShreddingBatchConverter::ExtractRowFields(
const std::shared_ptr<arrow::StringArray>& keys_array, int64_t start, int64_t length,
MapSharedShreddingFieldDict* dict, std::vector<int32_t>* field_ids_out,
std::unordered_map<int32_t, int64_t>* field_id_to_value_index_out) const {
field_ids_out->clear();
field_ids_out->reserve(length);
field_id_to_value_index_out->clear();
field_id_to_value_index_out->reserve(length);
for (int64_t j = 0; j < length; ++j) {
std::string key_str = keys_array->GetString(start + j);
int32_t field_id = dict->GetOrAssign(key_str);
field_ids_out->push_back(field_id);
(*field_id_to_value_index_out)[field_id] = start + j;
}
}
Status MapSharedShreddingBatchConverter::AppendFieldMapping(
const RowAllocation& allocation, int32_t num_cols, arrow::ListBuilder* list_builder,
arrow::Int32Builder* value_builder) const {
PAIMON_RETURN_NOT_OK_FROM_ARROW(list_builder->Append());
for (int32_t c = 0; c < num_cols; ++c) {
PAIMON_RETURN_NOT_OK_FROM_ARROW(value_builder->Append(allocation.col_to_field[c]));
}
return Status::OK();
}
Status MapSharedShreddingBatchConverter::AppendColumnValues(
const std::shared_ptr<arrow::Array>& values_array, const RowAllocation& allocation,
const std::unordered_map<int32_t, int64_t>& field_id_to_value_index, int32_t num_cols,
const std::vector<arrow::ArrayBuilder*>& col_builders) const {
for (int32_t c = 0; c < num_cols; ++c) {
int32_t assigned_field_id = allocation.col_to_field[c];
if (assigned_field_id == -1) {
PAIMON_RETURN_NOT_OK_FROM_ARROW(col_builders[c]->AppendNull());
} else {
auto it = field_id_to_value_index.find(assigned_field_id);
if (PAIMON_UNLIKELY(it == field_id_to_value_index.end())) {
return Status::Invalid(
fmt::format("MapSharedShreddingBatchConverter: field_id {} assigned to col {} "
"but not found in current row",
assigned_field_id, c));
}
PAIMON_RETURN_NOT_OK_FROM_ARROW(
col_builders[c]->AppendArraySlice(*values_array->data(), it->second, 1));
}
}
return Status::OK();
}
Status MapSharedShreddingBatchConverter::AppendOverflow(
const std::shared_ptr<arrow::Array>& values_array, const RowAllocation& allocation,
const std::unordered_map<int32_t, int64_t>& field_id_to_value_index,
arrow::MapBuilder* overflow_builder, arrow::Int32Builder* overflow_key_builder,
arrow::ArrayBuilder* overflow_value_builder) const {
if (allocation.overflow_fields.empty()) {
PAIMON_RETURN_NOT_OK_FROM_ARROW(overflow_builder->AppendNull());
return Status::OK();
}
PAIMON_RETURN_NOT_OK_FROM_ARROW(overflow_builder->Append());
for (int32_t overflow_field_id : allocation.overflow_fields) {
auto it = field_id_to_value_index.find(overflow_field_id);
if (PAIMON_UNLIKELY(it == field_id_to_value_index.end())) {
return Status::Invalid(fmt::format(
"MapSharedShreddingBatchConverter: overflow field_id {} not found in current row",
overflow_field_id));
}
PAIMON_RETURN_NOT_OK_FROM_ARROW(overflow_key_builder->Append(overflow_field_id));
PAIMON_RETURN_NOT_OK_FROM_ARROW(
overflow_value_builder->AppendArraySlice(*values_array->data(), it->second, 1));
}
return Status::OK();
}
Result<MapSharedShreddingFieldMeta> MapSharedShreddingBatchConverter::BuildFieldMeta(
const std::string& field_name) const {
for (const auto& context : contexts_) {
if (context.field_name == field_name) {
MapSharedShreddingFieldMeta meta;
meta.name_to_id = context.dict.GetNameToId();
// Convert set<int32_t> -> vector<int32_t> for field_to_columns
for (const auto& [field_id, col_set] : context.allocator->GetFieldToColumns()) {
meta.field_to_columns[field_id] =
std::vector<int32_t>(col_set.begin(), col_set.end());
}
meta.overflow_field_set = context.allocator->GetOverflowFieldSet();
meta.num_columns = context.num_columns;
meta.max_row_width = context.allocator->GetMaxRowWidth();
return meta;
}
}
return Status::Invalid(fmt::format(
"cannot find field_name '{}' in MapSharedShreddingBatchConverter contexts", field_name));
}
Result<int32_t> MapSharedShreddingBatchConverter::GetMaxRowWidth(
const std::string& field_name) const {
for (const auto& context : contexts_) {
if (context.field_name == field_name) {
return context.allocator->GetMaxRowWidth();
}
}
return Status::Invalid(fmt::format(
"cannot find field_name '{}' in MapSharedShreddingBatchConverter contexts", field_name));
}
const std::vector<std::string>& MapSharedShreddingBatchConverter::GetShreddingColumnNames() const {
return shredding_field_names_;
}
} // namespace paimon