| /* | |
| * 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/testing/utils/data_generator.h" | |
| #include <algorithm> | |
| #include <cassert> | |
| #include <cmath> | |
| #include <cstdlib> | |
| #include <map> | |
| #include <utility> | |
| #include "arrow/api.h" | |
| #include "arrow/array/builder_binary.h" | |
| #include "arrow/array/builder_nested.h" | |
| #include "arrow/array/builder_primitive.h" | |
| #include "arrow/c/abi.h" | |
| #include "arrow/c/bridge.h" | |
| #include "fmt/format.h" | |
| #include "paimon/common/data/binary_row_writer.h" | |
| #include "paimon/common/data/binary_string.h" | |
| #include "paimon/common/types/row_kind.h" | |
| #include "paimon/common/utils/arrow/status_utils.h" | |
| #include "paimon/common/utils/checked_cast.h" | |
| #include "paimon/core/schema/table_schema.h" | |
| #include "paimon/core/utils/file_store_path_factory.h" | |
| namespace arrow { | |
| class Array; | |
| class ArrayBuilder; | |
| } // namespace arrow | |
| namespace paimon { | |
| class MemoryPool; | |
| } // namespace paimon | |
| namespace paimon::test { | |
| DataGenerator::DataGenerator(const std::shared_ptr<TableSchema>& table_schema, | |
| const std::shared_ptr<MemoryPool>& memory_pool) | |
| : table_schema_(table_schema), memory_pool_(memory_pool) { | |
| assert(table_schema->Id() == 0); | |
| } | |
| Status DataGenerator::WriteBinaryRow(const BinaryRow& src_row, int32_t src_field_id, | |
| const std::shared_ptr<arrow::DataType>& src_type, | |
| int32_t target_field_id, BinaryRowWriter* target_row_writer) { | |
| arrow::Type::type type_id = src_type->id(); | |
| switch (type_id) { | |
| case arrow::Type::type::BOOL: { | |
| target_row_writer->WriteBoolean(target_field_id, src_row.GetBoolean(src_field_id)); | |
| break; | |
| } | |
| case arrow::Type::type::INT8: { | |
| target_row_writer->WriteByte(target_field_id, src_row.GetByte(src_field_id)); | |
| break; | |
| } | |
| case arrow::Type::type::INT16: { | |
| target_row_writer->WriteShort(target_field_id, src_row.GetShort(src_field_id)); | |
| break; | |
| } | |
| case arrow::Type::type::INT32: { | |
| target_row_writer->WriteInt(target_field_id, src_row.GetInt(src_field_id)); | |
| break; | |
| } | |
| case arrow::Type::type::INT64: { | |
| target_row_writer->WriteLong(target_field_id, src_row.GetLong(src_field_id)); | |
| break; | |
| } | |
| case arrow::Type::type::FLOAT: { | |
| target_row_writer->WriteFloat(target_field_id, src_row.GetFloat(src_field_id)); | |
| break; | |
| } | |
| case arrow::Type::type::DOUBLE: { | |
| target_row_writer->WriteDouble(target_field_id, src_row.GetDouble(src_field_id)); | |
| break; | |
| } | |
| case arrow::Type::type::STRING: { | |
| target_row_writer->WriteString(target_field_id, src_row.GetString(src_field_id)); | |
| break; | |
| } | |
| default: | |
| return Status::Invalid( | |
| fmt::format("type {} not support in write partial row", src_type->ToString())); | |
| } | |
| return Status::OK(); | |
| } | |
| Result<BinaryRow> DataGenerator::ExtractPartialRow(const BinaryRow& binary_row, | |
| const std::vector<DataField>& partition_fields) { | |
| BinaryRow partial_row(static_cast<int32_t>(partition_fields.size())); | |
| BinaryRowWriter writer(&partial_row, /*initial_size=*/0, memory_pool_.get()); | |
| for (size_t field_idx = 0; field_idx < partition_fields.size(); field_idx++) { | |
| int32_t id = partition_fields[field_idx].Id(); | |
| auto type = partition_fields[field_idx].Type(); | |
| PAIMON_RETURN_NOT_OK(WriteBinaryRow(binary_row, id, type, field_idx, &writer)); | |
| } | |
| writer.Complete(); | |
| return partial_row; | |
| } | |
| Status DataGenerator::AppendValue(const BinaryRow& row, int32_t field_id, | |
| const std::shared_ptr<arrow::DataType>& type, | |
| arrow::StructBuilder* struct_builder) { | |
| arrow::Type::type type_id = type->id(); | |
| switch (type_id) { | |
| case arrow::Type::type::BOOL: { | |
| auto builder = | |
| checked_cast<arrow::BooleanBuilder*>(struct_builder->field_builder(field_id)); | |
| PAIMON_RETURN_NOT_OK_FROM_ARROW(builder->Append(row.GetBoolean(field_id))); | |
| break; | |
| } | |
| case arrow::Type::type::INT8: { | |
| auto builder = | |
| checked_cast<arrow::Int8Builder*>(struct_builder->field_builder(field_id)); | |
| PAIMON_RETURN_NOT_OK_FROM_ARROW(builder->Append(row.GetByte(field_id))); | |
| break; | |
| } | |
| case arrow::Type::type::INT16: { | |
| auto builder = | |
| checked_cast<arrow::Int16Builder*>(struct_builder->field_builder(field_id)); | |
| PAIMON_RETURN_NOT_OK_FROM_ARROW(builder->Append(row.GetShort(field_id))); | |
| break; | |
| } | |
| case arrow::Type::type::INT32: { | |
| auto builder = | |
| checked_cast<arrow::Int32Builder*>(struct_builder->field_builder(field_id)); | |
| PAIMON_RETURN_NOT_OK_FROM_ARROW(builder->Append(row.GetInt(field_id))); | |
| break; | |
| } | |
| case arrow::Type::type::INT64: { | |
| auto builder = | |
| checked_cast<arrow::Int64Builder*>(struct_builder->field_builder(field_id)); | |
| PAIMON_RETURN_NOT_OK_FROM_ARROW(builder->Append(row.GetLong(field_id))); | |
| break; | |
| } | |
| case arrow::Type::type::FLOAT: { | |
| auto builder = | |
| checked_cast<arrow::FloatBuilder*>(struct_builder->field_builder(field_id)); | |
| PAIMON_RETURN_NOT_OK_FROM_ARROW(builder->Append(row.GetFloat(field_id))); | |
| break; | |
| } | |
| case arrow::Type::type::DOUBLE: { | |
| auto builder = | |
| checked_cast<arrow::DoubleBuilder*>(struct_builder->field_builder(field_id)); | |
| PAIMON_RETURN_NOT_OK_FROM_ARROW(builder->Append(row.GetDouble(field_id))); | |
| break; | |
| } | |
| case arrow::Type::type::STRING: { | |
| auto builder = | |
| checked_cast<arrow::StringBuilder*>(struct_builder->field_builder(field_id)); | |
| PAIMON_RETURN_NOT_OK_FROM_ARROW(builder->Append(row.GetString(field_id).ToString())); | |
| break; | |
| } | |
| default: | |
| return Status::Invalid( | |
| fmt::format("type {} not support in append value", type->ToString())); | |
| } | |
| return Status::OK(); | |
| } | |
| Result<std::shared_ptr<arrow::StructBuilder>> DataGenerator::MakeStructBuilder( | |
| const std::vector<DataField>& fields) { | |
| auto data_type = DataField::ConvertDataFieldsToArrowStructType(fields); | |
| std::vector<std::shared_ptr<arrow::ArrayBuilder>> field_builders; | |
| for (const auto& field : fields) { | |
| auto type_id = field.Type()->id(); | |
| switch (type_id) { | |
| case arrow::Type::type::BOOL: { | |
| field_builders.push_back(std::make_shared<arrow::BooleanBuilder>()); | |
| break; | |
| } | |
| case arrow::Type::type::INT8: { | |
| field_builders.push_back(std::make_shared<arrow::Int8Builder>()); | |
| break; | |
| } | |
| case arrow::Type::type::INT16: { | |
| field_builders.push_back(std::make_shared<arrow::Int16Builder>()); | |
| break; | |
| } | |
| case arrow::Type::type::INT32: { | |
| field_builders.push_back(std::make_shared<arrow::Int32Builder>()); | |
| break; | |
| } | |
| case arrow::Type::type::INT64: { | |
| field_builders.push_back(std::make_shared<arrow::Int64Builder>()); | |
| break; | |
| } | |
| case arrow::Type::type::FLOAT: { | |
| field_builders.push_back(std::make_shared<arrow::FloatBuilder>()); | |
| break; | |
| } | |
| case arrow::Type::type::DOUBLE: { | |
| field_builders.push_back(std::make_shared<arrow::DoubleBuilder>()); | |
| break; | |
| } | |
| case arrow::Type::type::STRING: { | |
| field_builders.push_back(std::make_shared<arrow::StringBuilder>()); | |
| break; | |
| } | |
| default: | |
| return Status::Invalid(fmt::format("type {} not support in make struct builder", | |
| field.Type()->ToString())); | |
| } | |
| } | |
| return std::make_shared<arrow::StructBuilder>(data_type, arrow::default_memory_pool(), | |
| field_builders); | |
| } | |
| Result<std::vector<std::unique_ptr<RecordBatch>>> DataGenerator::SplitArrayByPartitionAndBucket( | |
| const std::vector<BinaryRow>& binary_rows) { | |
| auto fields = table_schema_->Fields(); | |
| auto partition_keys = table_schema_->PartitionKeys(); | |
| PAIMON_ASSIGN_OR_RAISE(auto partition_fields, table_schema_->GetFields(partition_keys)); | |
| PAIMON_ASSIGN_OR_RAISE(auto bucket_fields, | |
| table_schema_->GetFields(table_schema_->BucketKeys())); | |
| int32_t num_buckets = table_schema_->NumBuckets(); | |
| // map: {partition_map, bucket_id} -> arrow::StructBuilder | |
| std::map<std::pair<std::map<std::string, std::string>, int32_t>, | |
| std::shared_ptr<arrow::StructBuilder>> | |
| struct_builder_holder; | |
| std::map<std::pair<std::map<std::string, std::string>, int32_t>, | |
| std::vector<RecordBatch::RowKind>> | |
| row_kinds_holder; | |
| auto schema = DataField::ConvertDataFieldsToArrowSchema(fields); | |
| PAIMON_ASSIGN_OR_RAISE( | |
| auto path_factory, | |
| FileStorePathFactory::Create( | |
| /*root=*/"/tmp", schema, partition_keys, | |
| /*default_part_value=*/"__DEFAULT_PARTITION__", | |
| /*identifier=*/"orc", /*data_file_prefix=*/"data-", | |
| /*legacy_partition_name_enabled=*/true, /*external_paths=*/std::vector<std::string>(), | |
| /*global_index_external_path=*/std::nullopt, | |
| /*index_file_in_data_file_dir=*/false, memory_pool_)); | |
| for (const auto& binary_row : binary_rows) { | |
| PAIMON_ASSIGN_OR_RAISE(BinaryRow partition_row, | |
| ExtractPartialRow(binary_row, partition_fields)); | |
| std::vector<std::pair<std::string, std::string>> part_values; | |
| PAIMON_ASSIGN_OR_RAISE(part_values, path_factory->GeneratePartitionVector(partition_row)); | |
| std::map<std::string, std::string> partition_map; | |
| for (const auto& part_value : part_values) { | |
| partition_map[part_value.first] = part_value.second; | |
| } | |
| PAIMON_ASSIGN_OR_RAISE(BinaryRow bucket_row, ExtractPartialRow(binary_row, bucket_fields)); | |
| int32_t bucket_id = std::abs(bucket_row.HashCode() % num_buckets); | |
| auto struct_builder_iter = struct_builder_holder.find({partition_map, bucket_id}); | |
| if (struct_builder_iter == struct_builder_holder.end()) { | |
| PAIMON_ASSIGN_OR_RAISE(auto struct_builder, MakeStructBuilder(fields)); | |
| auto insert_result = struct_builder_holder.emplace( | |
| std::make_pair(partition_map, bucket_id), struct_builder); | |
| if (!insert_result.second) { | |
| return Status::Invalid("insert element to struct_builder_holder failed"); | |
| } | |
| struct_builder_iter = insert_result.first; | |
| } | |
| auto struct_builder = struct_builder_iter->second; | |
| PAIMON_RETURN_NOT_OK_FROM_ARROW(struct_builder->Append()); | |
| for (size_t i = 0; i < fields.size(); i++) { | |
| if (binary_row.IsNullAt(i)) { | |
| PAIMON_RETURN_NOT_OK_FROM_ARROW(struct_builder->field_builder(i)->AppendNull()); | |
| } else { | |
| PAIMON_RETURN_NOT_OK( | |
| AppendValue(binary_row, i, fields[i].Type(), struct_builder.get())); | |
| } | |
| } | |
| auto row_kinds_iter = row_kinds_holder.find({partition_map, bucket_id}); | |
| if (row_kinds_iter == row_kinds_holder.end()) { | |
| auto insert_result = row_kinds_holder.emplace(std::make_pair(partition_map, bucket_id), | |
| std::vector<RecordBatch::RowKind>()); | |
| if (!insert_result.second) { | |
| return Status::Invalid("insert element to row_kinds_holder failed"); | |
| } | |
| row_kinds_iter = insert_result.first; | |
| } | |
| auto& row_kinds = row_kinds_iter->second; | |
| PAIMON_ASSIGN_OR_RAISE(auto row_kind, binary_row.GetRowKind()); | |
| row_kinds.push_back(static_cast<RecordBatch::RowKind>(row_kind->ToByteValue())); | |
| } | |
| std::vector<std::unique_ptr<RecordBatch>> batches; | |
| for (const auto& [identifier, struct_builder] : struct_builder_holder) { | |
| std::shared_ptr<arrow::Array> array; | |
| PAIMON_RETURN_NOT_OK_FROM_ARROW(struct_builder->Finish(&array)); | |
| auto& row_kinds = row_kinds_holder[identifier]; | |
| ArrowArray arrow_array; | |
| PAIMON_RETURN_NOT_OK_FROM_ARROW(arrow::ExportArray(*array, &arrow_array)); | |
| RecordBatchBuilder batch_builder(&arrow_array); | |
| PAIMON_ASSIGN_OR_RAISE(auto record_batch, batch_builder.SetPartition(identifier.first) | |
| .SetBucket(identifier.second) | |
| .SetRowKinds(row_kinds) | |
| .Finish()); | |
| batches.push_back(std::move(record_batch)); | |
| } | |
| return batches; | |
| } | |
| } // namespace paimon::test |