This is an automated email from the ASF dual-hosted git repository.
SteNicholas pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/paimon-cpp.git
The following commit(s) were added to refs/heads/main by this push:
new 67e6acac feat(shredding): support selected-key pushdown by
MapSharedShreddingAccessBuilder (#195)
67e6acac is described below
commit 67e6acac7a884af68ced8921e875b642fc02eb9e
Author: lszskye <[email protected]>
AuthorDate: Thu Aug 13 02:43:03 2026 -0700
feat(shredding): support selected-key pushdown by
MapSharedShreddingAccessBuilder (#195)
* feat(shredding): support selected-key pushdown by
MapSharedShreddingAccessBuilder
* fix pre-commit
* fix comment
---
.../shredding/map_shared_shredding_schema_utils.h | 42 ++
include/paimon/read_context.h | 6 +
.../shredding/map_shared_shredding_file_reader.cpp | 536 ++++++++++++++++-----
.../shredding/map_shared_shredding_file_reader.h | 66 ++-
.../map_shared_shredding_file_reader_test.cpp | 136 +++++-
.../map_shared_shredding_schema_utils.cpp | 81 ++++
.../map_shared_shredding_schema_utils_test.cpp | 76 +++
src/paimon/core/io/field_mapping_reader.cpp | 17 +
src/paimon/core/operation/abstract_split_read.cpp | 49 +-
.../core/operation/internal_read_context.cpp | 36 ++
.../core/operation/internal_read_context_test.cpp | 31 ++
src/paimon/core/utils/field_mapping.cpp | 13 +-
src/paimon/core/utils/nested_projection_utils.cpp | 140 +++---
src/paimon/core/utils/nested_projection_utils.h | 21 +
.../core/utils/nested_projection_utils_test.cpp | 56 +++
test/inte/write_and_read_inte_test.cpp | 278 +++++++++++
16 files changed, 1347 insertions(+), 237 deletions(-)
diff --git a/include/paimon/data/shredding/map_shared_shredding_schema_utils.h
b/include/paimon/data/shredding/map_shared_shredding_schema_utils.h
index b8470c4d..bce0b653 100644
--- a/include/paimon/data/shredding/map_shared_shredding_schema_utils.h
+++ b/include/paimon/data/shredding/map_shared_shredding_schema_utils.h
@@ -56,6 +56,48 @@ struct PAIMON_EXPORT MapSharedShreddingFieldMeta {
}
};
+/// Builds a selected-key projection field for a top-level shared-shredding
MAP column.
+///
+/// The built field is a STRUCT which replaces the MAP field in the read
schema. Each child
+/// corresponds to one selected key and contains that key's MAP value, or NULL
when the key is
+/// absent. Children use the selected keys as their names and preserve
insertion order.
+///
+/// Example: read keys "age" and "score" from MAP column `attributes`:
+///
+/// auto builder =
MapSharedShreddingAccessBuilder::Create(attributes_field);
+/// builder->AddKey("age");
+/// builder->AddKey("score");
+/// auto field = builder->Build();
+///
+/// Use the returned field in `ReadContextBuilder::SetReadSchema`.
+class PAIMON_EXPORT MapSharedShreddingAccessBuilder {
+ public:
+ /// Creates a builder bound to the original MAP field.
+ ///
+ /// The field must be a MAP with STRING keys. Its name, nullability, and
value type are
+ /// retained for the selected-key projection. Ownership of the Arrow C
schema resources is
+ /// transferred to this method.
+ static Result<std::unique_ptr<MapSharedShreddingAccessBuilder>> Create(
+ struct ArrowSchema* map_field);
+
+ ~MapSharedShreddingAccessBuilder();
+
+ /// Adds a selected MAP key.
+ ///
+ /// @param key The string MAP key. Keys are returned in insertion order.
+ Status AddKey(const std::string& key);
+
+ /// Builds a STRUCT projection field which retains the original MAP
field's name and
+ /// nullability. Every selected-key child uses the complete MAP value type
and is nullable.
+ Result<std::unique_ptr<struct ArrowSchema>> Build() const;
+
+ private:
+ class Impl;
+ explicit MapSharedShreddingAccessBuilder(std::unique_ptr<Impl>&& impl);
+
+ std::unique_ptr<Impl> impl_;
+};
+
class PAIMON_EXPORT MapSharedShreddingSchemaUtils {
public:
MapSharedShreddingSchemaUtils() = delete;
diff --git a/include/paimon/read_context.h b/include/paimon/read_context.h
index 8fdac7b3..91a1033f 100644
--- a/include/paimon/read_context.h
+++ b/include/paimon/read_context.h
@@ -226,6 +226,12 @@ class PAIMON_EXPORT ReadContextBuilder {
/// key list, for example: "k1,k2". Only map fields with string key type
/// (Arrow utf8) are supported.
///
+ /// Attaching this metadata to a MAP field only filters the returned MAP
after
+ /// reading. To push down selected keys from a shared-shredding MAP and
return
+ /// them as STRUCT children, build the field with
+ /// `MapSharedShreddingAccessBuilder`. To read selected paths from a
VARIANT
+ /// field, build the field with `VariantAccessBuilder`.
+ ///
/// Example:
/// @code{.cpp}
/// auto map_field = arrow::field("m", arrow::map(arrow::utf8(),
arrow::int32()));
diff --git
a/src/paimon/common/data/shredding/map_shared_shredding_file_reader.cpp
b/src/paimon/common/data/shredding/map_shared_shredding_file_reader.cpp
index dcceddc7..342c6adb 100644
--- a/src/paimon/common/data/shredding/map_shared_shredding_file_reader.cpp
+++ b/src/paimon/common/data/shredding/map_shared_shredding_file_reader.cpp
@@ -21,6 +21,7 @@
#include <optional>
#include <set>
+#include <string_view>
#include <utility>
#include <vector>
@@ -30,18 +31,211 @@
#include "paimon/common/reader/reader_utils.h"
#include "paimon/common/utils/arrow/mem_utils.h"
#include "paimon/common/utils/arrow/status_utils.h"
-#include "paimon/common/utils/string_utils.h"
#include "paimon/core/casting/casting_utils.h"
+#include "paimon/core/utils/nested_projection_utils.h"
namespace paimon {
+namespace {
+
+std::vector<std::pair<std::string, int32_t>> ResolveSelectedKeyIds(
+ const MapSharedShreddingFieldMeta& meta, const std::vector<std::string>&
selected_keys) {
+ std::vector<std::pair<std::string, int32_t>> selected_key_ids;
+ selected_key_ids.reserve(selected_keys.size());
+ for (const auto& selected_key : selected_keys) {
+ auto id_iter = meta.name_to_id.find(selected_key);
+ if (id_iter != meta.name_to_id.end()) {
+ selected_key_ids.emplace_back(selected_key, id_iter->second);
+ }
+ }
+ return selected_key_ids;
+}
+
+void CollectPhysicalColumns(
+ const std::shared_ptr<arrow::StructArray>& physical_struct_array,
+ std::map<std::string, std::shared_ptr<arrow::Array>>*
physical_column_name_to_array,
+ std::shared_ptr<arrow::MapArray>* overflow_array) {
+ const auto& struct_type = physical_struct_array->struct_type();
+ for (int32_t i = 0; i < struct_type->num_fields(); ++i) {
+ const auto& sub_field = struct_type->field(i);
+ if (sub_field->name() == MapSharedShreddingDefine::kFieldMapping) {
+ continue;
+ }
+ if (sub_field->name() == MapSharedShreddingDefine::kOverflow) {
+ *overflow_array =
arrow::internal::checked_pointer_cast<arrow::MapArray>(
+ physical_struct_array->field(i));
+ continue;
+ }
+ (*physical_column_name_to_array)[sub_field->name()] =
physical_struct_array->field(i);
+ }
+}
+
+class FullMapReadPlan : public MapFieldReadPlan {
+ public:
+ FullMapReadPlan(const std::shared_ptr<arrow::Field>& logical_field,
+ const std::shared_ptr<arrow::Field>& physical_read_field,
+ std::vector<std::pair<std::string, int32_t>>&&
selected_key_ids)
+ : MapFieldReadPlan(logical_field, physical_read_field),
+ selected_key_ids_(std::move(selected_key_ids)),
+ logical_map_type_(
+
arrow::internal::checked_pointer_cast<arrow::MapType>(logical_field->type())) {}
+
+ Result<std::shared_ptr<arrow::Array>> Materialize(
+ const std::shared_ptr<arrow::Array>& physical_array,
+ arrow::MemoryPool* arrow_pool) const override;
+
+ private:
+ std::vector<std::pair<std::string, int32_t>> selected_key_ids_;
+ std::shared_ptr<arrow::MapType> logical_map_type_;
+};
+
+class SharedSelectedKeysReadPlan : public MapFieldReadPlan {
+ public:
+ struct SelectedKey {
+ int32_t field_id = -1;
+ std::vector<int32_t> candidate_columns;
+ bool may_use_overflow = false;
+ };
+
+ SharedSelectedKeysReadPlan(const std::shared_ptr<arrow::Field>&
logical_field,
+ const std::shared_ptr<arrow::Field>&
physical_read_field,
+ std::vector<SelectedKey>&& selected_keys)
+ : MapFieldReadPlan(logical_field, physical_read_field),
+ selected_keys_(std::move(selected_keys)) {}
+
+ Result<std::shared_ptr<arrow::Array>> Materialize(
+ const std::shared_ptr<arrow::Array>& physical_array,
+ arrow::MemoryPool* arrow_pool) const override;
+
+ private:
+ std::vector<SelectedKey> selected_keys_;
+};
+
+class DefaultSelectedKeysReadPlan : public MapFieldReadPlan {
+ public:
+ DefaultSelectedKeysReadPlan(const std::shared_ptr<arrow::Field>&
logical_field,
+ const std::shared_ptr<arrow::Field>&
physical_read_field,
+ const std::vector<std::string>& selected_keys)
+ : MapFieldReadPlan(logical_field, physical_read_field),
selected_keys_(selected_keys) {}
+
+ Result<std::shared_ptr<arrow::Array>> Materialize(
+ const std::shared_ptr<arrow::Array>& physical_array,
+ arrow::MemoryPool* arrow_pool) const override;
+
+ private:
+ std::vector<std::string> selected_keys_;
+};
+
+} // namespace
+
+Result<std::unique_ptr<MapFieldReadPlan>>
MapFieldReadPlanFactory::CreateMapReadPlan(
+ const std::shared_ptr<arrow::Field>& logical_map_field,
+ const MapSharedShreddingFieldMeta& meta) {
+ if (logical_map_field->type()->id() != arrow::Type::MAP) {
+ return Status::Invalid(fmt::format("full MAP read plan requires MAP
field {}, got {}",
+ logical_map_field->name(),
+
logical_map_field->type()->ToString()));
+ }
+ auto logical_map_type =
+
arrow::internal::checked_pointer_cast<arrow::MapType>(logical_map_field->type());
+ PAIMON_ASSIGN_OR_RAISE(std::vector<std::string> selected_keys,
+
NestedProjectionUtils::GetMapSelectedKeys(logical_map_field));
+ if (selected_keys.empty()) {
+ selected_keys.reserve(meta.name_to_id.size());
+ for (const auto& [key_name, _] : meta.name_to_id) {
+ selected_keys.push_back(key_name);
+ }
+ }
+ std::set<int32_t> selected_physical_column_ids;
+ bool include_overflow = false;
+ for (const auto& selected_key : selected_keys) {
+ auto field_id_iter = meta.name_to_id.find(selected_key);
+ if (field_id_iter == meta.name_to_id.end()) {
+ continue;
+ }
+ int32_t field_id = field_id_iter->second;
+ include_overflow = include_overflow ||
meta.overflow_field_set.count(field_id) > 0;
+ auto columns_iter = meta.field_to_columns.find(field_id);
+ if (columns_iter != meta.field_to_columns.end()) {
+ selected_physical_column_ids.insert(columns_iter->second.begin(),
+ columns_iter->second.end());
+ }
+ }
+ std::shared_ptr<arrow::DataType> physical_type =
+ MapSharedShreddingUtils::BuildSpecificPhysicalStructType(
+ logical_map_type->item_type(), selected_physical_column_ids,
+ logical_map_type->item_field()->nullable(), include_overflow);
+ auto physical_read_field = logical_map_field->WithType(physical_type);
+ std::unique_ptr<MapFieldReadPlan> read_plan =
std::make_unique<FullMapReadPlan>(
+ logical_map_field, physical_read_field, ResolveSelectedKeyIds(meta,
selected_keys));
+ return read_plan;
+}
+
+Result<std::unique_ptr<MapFieldReadPlan>>
MapFieldReadPlanFactory::CreateSharedSelectedKeysReadPlan(
+ const std::shared_ptr<arrow::Field>& selected_keys_field,
+ const MapSharedShreddingFieldMeta& meta) {
+ PAIMON_ASSIGN_OR_RAISE(
+ std::vector<std::string> selected_keys,
+
NestedProjectionUtils::ValidateMapSharedShreddingAccessField(selected_keys_field));
+ auto selected_keys_type =
+
arrow::internal::checked_pointer_cast<arrow::StructType>(selected_keys_field->type());
+ const auto& value_field = selected_keys_type->field(0);
+
+ std::set<int32_t> selected_physical_column_ids;
+ bool include_overflow = false;
+ std::vector<SharedSelectedKeysReadPlan::SelectedKey> selected_key_plans;
+ selected_key_plans.reserve(selected_keys.size());
+ for (const auto& selected_key : selected_keys) {
+ SharedSelectedKeysReadPlan::SelectedKey selected_key_plan;
+ auto field_id_iter = meta.name_to_id.find(selected_key);
+ if (field_id_iter != meta.name_to_id.end()) {
+ selected_key_plan.field_id = field_id_iter->second;
+ auto columns_iter =
meta.field_to_columns.find(selected_key_plan.field_id);
+ if (columns_iter != meta.field_to_columns.end()) {
+ selected_key_plan.candidate_columns = columns_iter->second;
+
selected_physical_column_ids.insert(columns_iter->second.begin(),
+
columns_iter->second.end());
+ }
+ selected_key_plan.may_use_overflow =
+ meta.overflow_field_set.count(selected_key_plan.field_id) > 0;
+ include_overflow = include_overflow ||
selected_key_plan.may_use_overflow;
+ }
+ selected_key_plans.push_back(std::move(selected_key_plan));
+ }
+ std::shared_ptr<arrow::DataType> physical_type =
+ MapSharedShreddingUtils::BuildSpecificPhysicalStructType(
+ value_field->type(), selected_physical_column_ids,
value_field->nullable(),
+ include_overflow);
+ auto physical_read_field = selected_keys_field->WithType(physical_type);
+ std::unique_ptr<MapFieldReadPlan> read_plan =
std::make_unique<SharedSelectedKeysReadPlan>(
+ selected_keys_field, physical_read_field,
std::move(selected_key_plans));
+ return read_plan;
+}
+
+Result<std::unique_ptr<MapFieldReadPlan>>
+MapFieldReadPlanFactory::CreateDefaultSelectedKeysReadPlan(
+ const std::shared_ptr<arrow::Field>& file_map_field,
+ const std::shared_ptr<arrow::Field>& selected_keys_field) {
+ if (file_map_field->type()->id() != arrow::Type::MAP) {
+ return Status::Invalid(
+ fmt::format("selected-key MAP projection {} requires MAP file
field, got {}",
+ selected_keys_field->name(),
file_map_field->type()->ToString()));
+ }
+ PAIMON_ASSIGN_OR_RAISE(
+ std::vector<std::string> selected_keys,
+
NestedProjectionUtils::ValidateMapSharedShreddingAccessField(selected_keys_field));
+ auto physical_read_field =
selected_keys_field->WithType(file_map_field->type());
+ std::unique_ptr<MapFieldReadPlan> read_plan =
std::make_unique<DefaultSelectedKeysReadPlan>(
+ selected_keys_field, physical_read_field, selected_keys);
+ return read_plan;
+}
+
MapSharedShreddingFileReader::MapSharedShreddingFileReader(
std::unique_ptr<FileBatchReader>&& reader,
- std::map<std::string,
MapSharedShreddingFileReader::SharedShreddingContext>&&
- shared_shredding_name_to_context,
+ std::map<std::string, std::unique_ptr<MapFieldReadPlan>>&&
field_read_plans,
const std::shared_ptr<MemoryPool>& pool)
: arrow_pool_(GetArrowPool(pool)),
reader_(std::move(reader)),
-
shared_shredding_name_to_context_(std::move(shared_shredding_name_to_context))
{}
+ field_read_plans_(std::move(field_read_plans)) {}
Result<std::unique_ptr<::ArrowSchema>>
MapSharedShreddingFileReader::GetFileSchema() const {
PAIMON_ASSIGN_OR_RAISE(std::unique_ptr<::ArrowSchema> physical_schema,
@@ -68,7 +262,8 @@ Result<std::unique_ptr<::ArrowSchema>>
MapSharedShreddingFileReader::GetFileSche
Result<std::shared_ptr<arrow::Field>>
MapSharedShreddingFileReader::ToLogicalMapField(
const std::shared_ptr<arrow::Field>& physical_field) {
- auto physical_type =
std::dynamic_pointer_cast<arrow::StructType>(physical_field->type());
+ auto physical_type =
+
arrow::internal::checked_pointer_cast<arrow::StructType>(physical_field->type());
if (!physical_type) {
return Status::Invalid(fmt::format("shared-shredding field {} is not a
physical struct",
physical_field->name()));
@@ -103,62 +298,24 @@ Status MapSharedShreddingFileReader::SetReadSchema(
}
PAIMON_ASSIGN_OR_RAISE_FROM_ARROW(std::shared_ptr<arrow::Schema>
logical_read_schema,
arrow::ImportSchema(read_schema));
- std::vector<std::string> shared_shredding_names;
- for (const auto& field : logical_read_schema->fields()) {
- if (shared_shredding_name_to_context_.find(field->name()) !=
- shared_shredding_name_to_context_.end()) {
- shared_shredding_names.push_back(field->name());
+ bool converted = false;
+ arrow::FieldVector physical_read_fields = logical_read_schema->fields();
+ for (size_t i = 0; i < logical_read_schema->fields().size(); ++i) {
+ const auto& field = logical_read_schema->field(i);
+ auto plan_iter = field_read_plans_.find(field->name());
+ if (plan_iter != field_read_plans_.end()) {
+ physical_read_fields[i] = plan_iter->second->PhysicalReadField();
+ converted = true;
}
}
- if (shared_shredding_names.empty()) {
- // suppose not fall into MapSharedShreddingFileReader
- return Status::Invalid("do not exist shared shredding columns in read
schema");
+ if (!converted) {
+ return Status::Invalid("suppose not fall into
MapSharedShreddingFileReader");
}
- arrow::FieldVector resolved_fields = logical_read_schema->fields();
- for (const auto& name : shared_shredding_names) {
- const auto& field = logical_read_schema->GetFieldByName(name);
- if (!field) {
- return Status::Invalid(
- fmt::format("cannot find shared-shredding field {} in read
schema", name));
- }
- auto context_iter =
shared_shredding_name_to_context_.find(field->name());
- if (context_iter == shared_shredding_name_to_context_.end()) {
- return Status::Invalid(
- fmt::format("cannot find shared-shredding metadata for field
{}", field->name()));
- }
- std::set<int32_t> selected_physical_column_ids;
- bool include_overflow = false;
- for (const auto& selected_key : context_iter->second.selected_keys) {
- // check if selected_key in file
- auto name_iter =
context_iter->second.meta.name_to_id.find(selected_key);
- if (name_iter == context_iter->second.meta.name_to_id.end()) {
- continue;
- }
- // check if selected_key in overflow_field
- PAIMON_ASSIGN_OR_RAISE(
- bool is_overflow_field,
-
MapSharedShreddingUtils::IsOverflowField(context_iter->second.meta,
selected_key));
- include_overflow = include_overflow || is_overflow_field;
- // check if selected_key in field_to_columns
- auto column_iter =
context_iter->second.meta.field_to_columns.find(name_iter->second);
- if (column_iter ==
context_iter->second.meta.field_to_columns.end()) {
- continue;
- }
- const std::vector<int32_t>& physical_column_ids =
column_iter->second;
- selected_physical_column_ids.insert(physical_column_ids.begin(),
- physical_column_ids.end());
- }
- std::shared_ptr<arrow::DataType> resolved_type =
- MapSharedShreddingUtils::BuildSpecificPhysicalStructType(
- context_iter->second.map_type->item_type(),
selected_physical_column_ids,
- context_iter->second.map_type->item_field()->nullable(),
include_overflow);
- resolved_fields[logical_read_schema->GetFieldIndex(name)] =
- arrow::field(field->name(), resolved_type, field->nullable());
- }
- auto resolved_schema = arrow::schema(std::move(resolved_fields));
- std::unique_ptr<ArrowSchema> c_resolved_schema =
std::make_unique<ArrowSchema>();
- PAIMON_RETURN_NOT_OK_FROM_ARROW(arrow::ExportSchema(*resolved_schema,
c_resolved_schema.get()));
- return reader_->SetReadSchema(c_resolved_schema.get(), predicate,
selection_bitmap);
+ auto physical_read_schema = arrow::schema(std::move(physical_read_fields));
+ std::unique_ptr<ArrowSchema> c_physical_read_schema =
std::make_unique<ArrowSchema>();
+ PAIMON_RETURN_NOT_OK_FROM_ARROW(
+ arrow::ExportSchema(*physical_read_schema,
c_physical_read_schema.get()));
+ return reader_->SetReadSchema(c_physical_read_schema.get(), predicate,
selection_bitmap);
}
Result<BatchReader::ReadBatch> MapSharedShreddingFileReader::NextBatch() {
@@ -177,7 +334,7 @@ Result<BatchReader::ReadBatchWithBitmap>
MapSharedShreddingFileReader::NextBatch
auto& [c_array, c_schema] = batch;
PAIMON_ASSIGN_OR_RAISE_FROM_ARROW(std::shared_ptr<arrow::Array>
arrow_array,
arrow::ImportArray(c_array.get(),
c_schema.get()));
- auto struct_array =
std::dynamic_pointer_cast<arrow::StructArray>(arrow_array);
+ auto struct_array =
arrow::internal::checked_pointer_cast<arrow::StructArray>(arrow_array);
if (!struct_array) {
return Status::Invalid("cannot cast batch to StructArray in
MapSharedShreddingFileReader");
}
@@ -186,21 +343,14 @@ Result<BatchReader::ReadBatchWithBitmap>
MapSharedShreddingFileReader::NextBatch
arrow::FieldVector resolved_fields = struct_array->struct_type()->fields();
for (int32_t field_idx = 0; field_idx < struct_array->num_fields();
++field_idx) {
const auto& physical_field =
struct_array->struct_type()->field(field_idx);
- auto iter =
shared_shredding_name_to_context_.find(physical_field->name());
- if (iter == shared_shredding_name_to_context_.end()) {
+ auto plan_iter = field_read_plans_.find(physical_field->name());
+ if (plan_iter == field_read_plans_.end()) {
continue;
}
- auto physical_struct_array =
-
std::dynamic_pointer_cast<arrow::StructArray>(struct_array->field(field_idx));
- if (!physical_struct_array) {
- return Status::Invalid(fmt::format(
- "cannot cast physical shredding field {} to StructArray",
physical_field->name()));
- }
- PAIMON_ASSIGN_OR_RAISE(std::shared_ptr<arrow::Array> logical_map_array,
- RebuildLogicalMapArray(physical_field,
physical_struct_array));
- resolved_arrays[field_idx] = logical_map_array;
- resolved_fields[field_idx] = arrow::field(physical_field->name(),
logical_map_array->type(),
- physical_field->nullable());
+ PAIMON_ASSIGN_OR_RAISE(
+ resolved_arrays[field_idx],
+ plan_iter->second->Materialize(struct_array->field(field_idx),
arrow_pool_.get()));
+ resolved_fields[field_idx] = plan_iter->second->LogicalField();
}
PAIMON_ASSIGN_OR_RAISE_FROM_ARROW(std::shared_ptr<arrow::StructArray>
new_struct_array,
arrow::StructArray::Make(resolved_arrays, resolved_fields));
@@ -212,32 +362,28 @@ Result<BatchReader::ReadBatchWithBitmap>
MapSharedShreddingFileReader::NextBatch
return batch_with_bitmap;
}
-Result<std::shared_ptr<arrow::Array>>
MapSharedShreddingFileReader::RebuildLogicalMapArray(
- const std::shared_ptr<arrow::Field>& physical_field,
- const std::shared_ptr<arrow::StructArray>& physical_struct_array) const {
- std::string shredding_field_name = physical_field->name();
- auto iter = shared_shredding_name_to_context_.find(shredding_field_name);
- if (iter == shared_shredding_name_to_context_.end()) {
- return Status::Invalid(
- fmt::format("cannot find shared-shredding context for field {}",
shredding_field_name));
+Result<std::shared_ptr<arrow::Array>> FullMapReadPlan::Materialize(
+ const std::shared_ptr<arrow::Array>& physical_array, arrow::MemoryPool*
arrow_pool) const {
+ auto physical_struct_array =
+
arrow::internal::checked_pointer_cast<arrow::StructArray>(physical_array);
+ if (!physical_struct_array) {
+ return Status::Invalid(fmt::format("cannot cast physical shredding
field {} to StructArray",
+ LogicalField()->name()));
}
- const MapSharedShreddingFieldMeta& meta = iter->second.meta;
- const std::vector<std::string>& selected_keys = iter->second.selected_keys;
- const auto& map_type = iter->second.map_type;
+ const std::string& shredding_field_name = LogicalField()->name();
- auto field_mapping_array = std::dynamic_pointer_cast<arrow::ListArray>(
+ auto field_mapping_array =
arrow::internal::checked_pointer_cast<arrow::ListArray>(
physical_struct_array->GetFieldByName(MapSharedShreddingDefine::kFieldMapping));
if (!field_mapping_array) {
return Status::Invalid(
fmt::format("cannot find __field_mapping for field {}",
shredding_field_name));
}
auto field_mapping_values =
-
std::dynamic_pointer_cast<arrow::Int32Array>(field_mapping_array->values());
+
arrow::internal::checked_pointer_cast<arrow::Int32Array>(field_mapping_array->values());
if (!field_mapping_values) {
return Status::Invalid("__field_mapping values is not an Int32Array");
}
- auto selected_key_ids = ResolveSelectedKeyIds(meta, selected_keys);
std::map<std::string, std::shared_ptr<arrow::Array>>
physical_column_name_to_array;
std::shared_ptr<arrow::MapArray> overflow_array;
CollectPhysicalColumns(physical_struct_array,
&physical_column_name_to_array, &overflow_array);
@@ -245,8 +391,8 @@ Result<std::shared_ptr<arrow::Array>>
MapSharedShreddingFileReader::RebuildLogic
if (physical_column_array->type_id() == arrow::Type::DICTIONARY) {
PAIMON_ASSIGN_OR_RAISE(
physical_column_array,
- CastingUtils::Cast(physical_column_array,
map_type->item_type(),
- arrow::compute::CastOptions::Safe(),
arrow_pool_.get()));
+ CastingUtils::Cast(physical_column_array,
logical_map_type_->item_type(),
+ arrow::compute::CastOptions::Safe(),
arrow_pool));
}
}
@@ -262,13 +408,13 @@ Result<std::shared_ptr<arrow::Array>>
MapSharedShreddingFileReader::RebuildLogic
if (overflow_items->type_id() == arrow::Type::DICTIONARY) {
PAIMON_ASSIGN_OR_RAISE(
overflow_items,
- CastingUtils::Cast(overflow_items, map_type->item_type(),
- arrow::compute::CastOptions::Safe(),
arrow_pool_.get()));
+ CastingUtils::Cast(overflow_items,
logical_map_type_->item_type(),
+ arrow::compute::CastOptions::Safe(),
arrow_pool));
}
}
PAIMON_ASSIGN_OR_RAISE_FROM_ARROW(std::unique_ptr<arrow::ArrayBuilder>
map_builder_base,
- arrow::MakeBuilder(map_type,
arrow_pool_.get()));
+ arrow::MakeBuilder(logical_map_type_,
arrow_pool));
auto* map_builder =
dynamic_cast<arrow::MapBuilder*>(map_builder_base.get());
if (!map_builder) {
return Status::Invalid(
@@ -286,7 +432,7 @@ Result<std::shared_ptr<arrow::Array>>
MapSharedShreddingFileReader::RebuildLogic
}
int64_t row_count = physical_struct_array->length();
- int64_t max_item_count = row_count *
static_cast<int64_t>(selected_key_ids.size());
+ int64_t max_item_count = row_count *
static_cast<int64_t>(selected_key_ids_.size());
PAIMON_RETURN_NOT_OK_FROM_ARROW(map_builder->Reserve(row_count));
PAIMON_RETURN_NOT_OK_FROM_ARROW(key_builder->Reserve(max_item_count));
PAIMON_RETURN_NOT_OK_FROM_ARROW(item_builder->Reserve(max_item_count));
@@ -306,7 +452,7 @@ Result<std::shared_ptr<arrow::Array>>
MapSharedShreddingFileReader::RebuildLogic
int32_t mapping_offset = field_mapping_array->value_offset(row);
int32_t mapping_length = field_mapping_array->value_length(row);
// follow the sequence in paimon.map.selected-keys
- for (const auto& [selected_key, selected_field_id] : selected_key_ids)
{
+ for (const auto& [selected_key, selected_field_id] :
selected_key_ids_) {
bool found = false;
for (int32_t pos = 0; pos < mapping_length; ++pos) {
int32_t mapping_index = mapping_offset + pos;
@@ -353,37 +499,197 @@ Result<std::shared_ptr<arrow::Array>>
MapSharedShreddingFileReader::RebuildLogic
return map_array;
}
-std::vector<std::pair<std::string, int32_t>>
MapSharedShreddingFileReader::ResolveSelectedKeyIds(
- const MapSharedShreddingFieldMeta& meta, const std::vector<std::string>&
selected_keys) {
- std::vector<std::pair<std::string, int32_t>> selected_key_ids;
- selected_key_ids.reserve(selected_keys.size());
- for (const auto& selected_key : selected_keys) {
- auto id_iter = meta.name_to_id.find(selected_key);
- if (id_iter == meta.name_to_id.end()) {
+Result<std::shared_ptr<arrow::Array>> SharedSelectedKeysReadPlan::Materialize(
+ const std::shared_ptr<arrow::Array>& physical_array, arrow::MemoryPool*
arrow_pool) const {
+ auto physical_struct_array =
+
arrow::internal::checked_pointer_cast<arrow::StructArray>(physical_array);
+ if (!physical_struct_array) {
+ return Status::Invalid(fmt::format("cannot cast physical shredding
field {} to StructArray",
+ LogicalField()->name()));
+ }
+ auto selected_keys_type =
+
arrow::internal::checked_pointer_cast<arrow::StructType>(LogicalField()->type());
+
+ auto field_mapping_array =
arrow::internal::checked_pointer_cast<arrow::ListArray>(
+
physical_struct_array->GetFieldByName(MapSharedShreddingDefine::kFieldMapping));
+ if (!field_mapping_array) {
+ return Status::Invalid(
+ fmt::format("cannot find __field_mapping for field {}",
LogicalField()->name()));
+ }
+ auto field_mapping_values =
+
arrow::internal::checked_pointer_cast<arrow::Int32Array>(field_mapping_array->values());
+ if (!field_mapping_values) {
+ return Status::Invalid("__field_mapping values is not an Int32Array");
+ }
+
+ std::shared_ptr<arrow::DataType> value_type =
selected_keys_type->field(0)->type();
+ std::map<std::string, std::shared_ptr<arrow::Array>>
physical_column_name_to_array;
+ std::shared_ptr<arrow::MapArray> overflow_array;
+ CollectPhysicalColumns(physical_struct_array,
&physical_column_name_to_array, &overflow_array);
+ for (auto& [_, physical_column_array] : physical_column_name_to_array) {
+ if (physical_column_array->type_id() == arrow::Type::DICTIONARY) {
+ PAIMON_ASSIGN_OR_RAISE(
+ physical_column_array,
+ CastingUtils::Cast(physical_column_array, value_type,
+ arrow::compute::CastOptions::Safe(),
arrow_pool));
+ }
+ }
+
+ std::shared_ptr<arrow::Int32Array> overflow_keys;
+ std::shared_ptr<arrow::Array> overflow_items;
+ if (overflow_array) {
+ overflow_keys =
+
arrow::internal::checked_pointer_cast<arrow::Int32Array>(overflow_array->keys());
+ overflow_items = overflow_array->items();
+ if (!overflow_keys || !overflow_items) {
+ return Status::Invalid("__overflow map has invalid key or item
array");
+ }
+ if (overflow_items->type_id() == arrow::Type::DICTIONARY) {
+ PAIMON_ASSIGN_OR_RAISE(
+ overflow_items,
+ CastingUtils::Cast(overflow_items, value_type,
arrow::compute::CastOptions::Safe(),
+ arrow_pool));
+ }
+ }
+
+ std::unique_ptr<arrow::ArrayBuilder> access_builder_base;
+ PAIMON_ASSIGN_OR_RAISE_FROM_ARROW(access_builder_base,
+
arrow::MakeBuilder(LogicalField()->type(), arrow_pool));
+ auto* access_builder =
dynamic_cast<arrow::StructBuilder*>(access_builder_base.get());
+ if (!access_builder) {
+ return Status::Invalid(
+ fmt::format("selected-key MAP field {} is not a STRUCT",
LogicalField()->name()));
+ }
+
PAIMON_RETURN_NOT_OK_FROM_ARROW(access_builder->Reserve(physical_struct_array->length()));
+
+ for (int64_t row = 0; row < physical_struct_array->length(); ++row) {
+ if (physical_struct_array->IsNull(row)) {
+ PAIMON_RETURN_NOT_OK_FROM_ARROW(access_builder->AppendNull());
continue;
}
- selected_key_ids.emplace_back(selected_key, id_iter->second);
+ if (field_mapping_array->IsNull(row)) {
+ return Status::Invalid(fmt::format(
+ "__field_mapping cannot be null in non-null shared-shredding
row for field {}",
+ LogicalField()->name()));
+ }
+ int32_t mapping_offset = field_mapping_array->value_offset(row);
+
+ PAIMON_RETURN_NOT_OK_FROM_ARROW(access_builder->Append());
+ for (int32_t key_index = 0; key_index <
selected_keys_type->num_fields(); ++key_index) {
+ arrow::ArrayBuilder* value_builder =
access_builder->field_builder(key_index);
+ const SelectedKey& selected_key = selected_keys_[key_index];
+ bool appended = false;
+ if (selected_key.field_id >= 0) {
+ for (int32_t physical_column_id :
selected_key.candidate_columns) {
+ int32_t mapping_index = mapping_offset +
physical_column_id;
+ if (field_mapping_values->IsNull(mapping_index)) {
+ return Status::Invalid("__field_mapping element cannot
be null");
+ }
+ if (field_mapping_values->Value(mapping_index) !=
selected_key.field_id) {
+ continue;
+ }
+ std::string physical_column_name =
+
MapSharedShreddingDefine::PhysicalColumnName(physical_column_id);
+ auto physical_column_iter =
+
physical_column_name_to_array.find(physical_column_name);
+ if (physical_column_iter ==
physical_column_name_to_array.end()) {
+ return Status::Invalid(
+ fmt::format("cannot find selected physical column
{} for field {}",
+ physical_column_name,
LogicalField()->name()));
+ }
+
PAIMON_RETURN_NOT_OK_FROM_ARROW(value_builder->AppendArraySlice(
+ *physical_column_iter->second->data(), row, 1));
+ appended = true;
+ break;
+ }
+ }
+
+ if (!appended && selected_key.may_use_overflow && overflow_array &&
+ !overflow_array->IsNull(row)) {
+ int32_t overflow_offset = overflow_array->value_offset(row);
+ int32_t overflow_length = overflow_array->value_length(row);
+ for (int32_t pos = 0; pos < overflow_length; ++pos) {
+ int32_t overflow_index = overflow_offset + pos;
+ if (!overflow_keys->IsNull(overflow_index) &&
+ overflow_keys->Value(overflow_index) ==
selected_key.field_id) {
+
PAIMON_RETURN_NOT_OK_FROM_ARROW(value_builder->AppendArraySlice(
+ *overflow_items->data(), overflow_index, 1));
+ appended = true;
+ break;
+ }
+ }
+ }
+ if (!appended) {
+ PAIMON_RETURN_NOT_OK_FROM_ARROW(value_builder->AppendNull());
+ }
+ }
}
- return selected_key_ids;
+ std::shared_ptr<arrow::Array> result;
+ PAIMON_RETURN_NOT_OK_FROM_ARROW(access_builder->Finish(&result));
+ return result;
}
-void MapSharedShreddingFileReader::CollectPhysicalColumns(
- const std::shared_ptr<arrow::StructArray>& physical_struct_array,
- std::map<std::string, std::shared_ptr<arrow::Array>>*
physical_column_name_to_array,
- std::shared_ptr<arrow::MapArray>* overflow_array) {
- const auto& struct_type = physical_struct_array->struct_type();
- for (int32_t i = 0; i < struct_type->num_fields(); ++i) {
- const auto& sub_field = struct_type->field(i);
- if (sub_field->name() == MapSharedShreddingDefine::kFieldMapping) {
+Result<std::shared_ptr<arrow::Array>> DefaultSelectedKeysReadPlan::Materialize(
+ const std::shared_ptr<arrow::Array>& physical_array, arrow::MemoryPool*
arrow_pool) const {
+ auto map_array =
arrow::internal::checked_pointer_cast<arrow::MapArray>(physical_array);
+ if (!map_array) {
+ return Status::Invalid(
+ fmt::format("cannot cast default-layout selected-key field {} to "
+ "MapArray",
+ LogicalField()->name()));
+ }
+ auto selected_keys_type =
+
arrow::internal::checked_pointer_cast<arrow::StructType>(LogicalField()->type());
+ auto physical_map_type =
+
arrow::internal::checked_pointer_cast<arrow::MapType>(PhysicalReadField()->type());
+
+ std::shared_ptr<arrow::Array> items = map_array->items();
+ if (items->type_id() == arrow::Type::DICTIONARY) {
+ PAIMON_ASSIGN_OR_RAISE(items,
+ CastingUtils::Cast(items,
physical_map_type->item_type(),
+
arrow::compute::CastOptions::Safe(), arrow_pool));
+ }
+ std::shared_ptr<arrow::Array> keys = map_array->keys();
+ std::unique_ptr<arrow::ArrayBuilder> access_builder_base;
+ PAIMON_ASSIGN_OR_RAISE_FROM_ARROW(access_builder_base,
+
arrow::MakeBuilder(LogicalField()->type(), arrow_pool));
+ auto* access_builder =
dynamic_cast<arrow::StructBuilder*>(access_builder_base.get());
+ if (!access_builder) {
+ return Status::Invalid(
+ fmt::format("selected-key MAP field {} is not a STRUCT",
LogicalField()->name()));
+ }
+
PAIMON_RETURN_NOT_OK_FROM_ARROW(access_builder->Reserve(map_array->length()));
+
+ for (int64_t row = 0; row < map_array->length(); ++row) {
+ if (map_array->IsNull(row)) {
+ PAIMON_RETURN_NOT_OK_FROM_ARROW(access_builder->AppendNull());
continue;
}
- if (sub_field->name() == MapSharedShreddingDefine::kOverflow) {
- *overflow_array =
arrow::internal::checked_pointer_cast<arrow::MapArray>(
- physical_struct_array->field(i));
- continue;
+ PAIMON_RETURN_NOT_OK_FROM_ARROW(access_builder->Append());
+ int64_t begin = map_array->value_offset(row);
+ int64_t end = map_array->value_offset(row + 1);
+ for (int32_t key_index = 0; key_index <
selected_keys_type->num_fields(); ++key_index) {
+ arrow::ArrayBuilder* value_builder =
access_builder->field_builder(key_index);
+ bool appended = false;
+ for (int64_t entry = begin; entry < end; ++entry) {
+ PAIMON_ASSIGN_OR_RAISE(std::string_view key,
+
NestedProjectionUtils::GetMapKeyViewAt(keys, entry));
+ if (key != selected_keys_[key_index]) {
+ continue;
+ }
+ PAIMON_RETURN_NOT_OK_FROM_ARROW(
+ value_builder->AppendArraySlice(*items->data(), entry, 1));
+ appended = true;
+ break;
+ }
+ if (!appended) {
+ PAIMON_RETURN_NOT_OK_FROM_ARROW(value_builder->AppendNull());
+ }
}
- (*physical_column_name_to_array)[sub_field->name()] =
physical_struct_array->field(i);
}
+ std::shared_ptr<arrow::Array> result;
+ PAIMON_RETURN_NOT_OK_FROM_ARROW(access_builder->Finish(&result));
+ return result;
}
std::shared_ptr<Metrics> MapSharedShreddingFileReader::GetReaderMetrics()
const {
diff --git
a/src/paimon/common/data/shredding/map_shared_shredding_file_reader.h
b/src/paimon/common/data/shredding/map_shared_shredding_file_reader.h
index ba608cf0..744d09fd 100644
--- a/src/paimon/common/data/shredding/map_shared_shredding_file_reader.h
+++ b/src/paimon/common/data/shredding/map_shared_shredding_file_reader.h
@@ -33,21 +33,51 @@
namespace paimon {
-class MapSharedShreddingFileReader : public FileBatchReader {
+class MapFieldReadPlan {
+ public:
+ virtual ~MapFieldReadPlan() = default;
+
+ MapFieldReadPlan(const std::shared_ptr<arrow::Field>& logical_field,
+ const std::shared_ptr<arrow::Field>& physical_read_field)
+ : logical_field_(logical_field),
physical_read_field_(physical_read_field) {}
+
+ const std::shared_ptr<arrow::Field>& LogicalField() const {
+ return logical_field_;
+ }
+
+ const std::shared_ptr<arrow::Field>& PhysicalReadField() const {
+ return physical_read_field_;
+ }
+
+ virtual Result<std::shared_ptr<arrow::Array>> Materialize(
+ const std::shared_ptr<arrow::Array>& physical_array,
+ arrow::MemoryPool* arrow_pool) const = 0;
+
+ private:
+ std::shared_ptr<arrow::Field> logical_field_;
+ std::shared_ptr<arrow::Field> physical_read_field_;
+};
+
+class MapFieldReadPlanFactory {
public:
- struct SharedShreddingContext {
- SharedShreddingContext(const MapSharedShreddingFieldMeta& _meta,
- const std::vector<std::string>& _selected_keys,
- const std::shared_ptr<arrow::MapType>&
_map_type)
- : meta(_meta), selected_keys(_selected_keys), map_type(_map_type)
{}
- MapSharedShreddingFieldMeta meta;
- std::vector<std::string> selected_keys;
- std::shared_ptr<arrow::MapType> map_type;
- };
+ static Result<std::unique_ptr<MapFieldReadPlan>> CreateMapReadPlan(
+ const std::shared_ptr<arrow::Field>& logical_map_field,
+ const MapSharedShreddingFieldMeta& meta);
+
+ static Result<std::unique_ptr<MapFieldReadPlan>>
CreateSharedSelectedKeysReadPlan(
+ const std::shared_ptr<arrow::Field>& selected_keys_field,
+ const MapSharedShreddingFieldMeta& meta);
+ static Result<std::unique_ptr<MapFieldReadPlan>>
CreateDefaultSelectedKeysReadPlan(
+ const std::shared_ptr<arrow::Field>& file_map_field,
+ const std::shared_ptr<arrow::Field>& selected_keys_field);
+};
+
+class MapSharedShreddingFileReader : public FileBatchReader {
+ public:
MapSharedShreddingFileReader(
std::unique_ptr<FileBatchReader>&& reader,
- std::map<std::string, SharedShreddingContext>&&
shared_shredding_name_to_context,
+ std::map<std::string, std::unique_ptr<MapFieldReadPlan>>&&
field_read_plans,
const std::shared_ptr<MemoryPool>& pool);
Result<std::unique_ptr<::ArrowSchema>> GetFileSchema() const override;
@@ -70,25 +100,13 @@ class MapSharedShreddingFileReader : public
FileBatchReader {
bool SupportPreciseBitmapSelection() const override;
private:
- Result<std::shared_ptr<arrow::Array>> RebuildLogicalMapArray(
- const std::shared_ptr<arrow::Field>& physical_field,
- const std::shared_ptr<arrow::StructArray>& physical_struct_array)
const;
-
- static std::vector<std::pair<std::string, int32_t>> ResolveSelectedKeyIds(
- const MapSharedShreddingFieldMeta& meta, const
std::vector<std::string>& selected_keys);
-
- static void CollectPhysicalColumns(
- const std::shared_ptr<arrow::StructArray>& physical_struct_array,
- std::map<std::string, std::shared_ptr<arrow::Array>>*
physical_column_name_to_array,
- std::shared_ptr<arrow::MapArray>* overflow_array);
-
static Result<std::shared_ptr<arrow::Field>> ToLogicalMapField(
const std::shared_ptr<arrow::Field>& physical_field);
private:
std::shared_ptr<arrow::MemoryPool> arrow_pool_;
std::unique_ptr<FileBatchReader> reader_;
- std::map<std::string, SharedShreddingContext>
shared_shredding_name_to_context_;
+ std::map<std::string, std::unique_ptr<MapFieldReadPlan>> field_read_plans_;
};
} // namespace paimon
diff --git
a/src/paimon/common/data/shredding/map_shared_shredding_file_reader_test.cpp
b/src/paimon/common/data/shredding/map_shared_shredding_file_reader_test.cpp
index c8ab3d83..408469e3 100644
--- a/src/paimon/common/data/shredding/map_shared_shredding_file_reader_test.cpp
+++ b/src/paimon/common/data/shredding/map_shared_shredding_file_reader_test.cpp
@@ -104,8 +104,7 @@ class MapSharedShreddingFileReaderTest : public
::testing::Test {
const std::optional<std::string>& selected_keys_str = std::nullopt)
const {
EXPECT_OK_AND_ASSIGN(auto c_file_schema, reader->GetFileSchema());
auto file_schema =
arrow::ImportSchema(c_file_schema.get()).ValueOrDie();
- std::map<std::string,
MapSharedShreddingFileReader::SharedShreddingContext>
- shared_shredding_name_to_context;
+ std::map<std::string, std::unique_ptr<MapFieldReadPlan>>
field_read_plans;
for (const auto& field : file_schema->fields()) {
auto metadata =
std::const_pointer_cast<arrow::KeyValueMetadata>(field->metadata());
if (!MapSharedShreddingUtils::HasShreddingMetadata(metadata)) {
@@ -125,22 +124,17 @@ class MapSharedShreddingFileReaderTest : public
::testing::Test {
EXPECT_TRUE(item_field);
auto map_type =
arrow::internal::checked_pointer_cast<arrow::MapType>(arrow::map(
arrow::utf8(), arrow::field("value", item_field->type(),
item_field->nullable())));
- std::vector<std::string> selected_keys;
+ std::shared_ptr<arrow::Field> logical_map_field =
field->WithType(map_type);
if (selected_keys_str.has_value()) {
- selected_keys = StringUtils::Split(selected_keys_str.value(),
",",
- /*ignore_empty=*/false);
- } else {
- selected_keys.reserve(meta.name_to_id.size());
- for (const auto& [key_name, _] : meta.name_to_id) {
- selected_keys.push_back(key_name);
- }
+ logical_map_field =
logical_map_field->WithMetadata(arrow::KeyValueMetadata::Make(
+ {DataField::MAP_SELECTED_KEYS},
{selected_keys_str.value()}));
}
- shared_shredding_name_to_context.emplace(
- field->name(),
MapSharedShreddingFileReader::SharedShreddingContext(
- meta, selected_keys, map_type));
+ EXPECT_OK_AND_ASSIGN(auto field_read_plan,
MapFieldReadPlanFactory::CreateMapReadPlan(
+ logical_map_field,
meta));
+ field_read_plans.emplace(field->name(),
std::move(field_read_plan));
}
- return std::make_unique<MapSharedShreddingFileReader>(
- std::move(reader), std::move(shared_shredding_name_to_context),
pool_);
+ return
std::make_unique<MapSharedShreddingFileReader>(std::move(reader),
+
std::move(field_read_plans), pool_);
}
Result<std::unique_ptr<MapSharedShreddingFileReader>> CreateReader(
@@ -299,6 +293,118 @@ TEST_F(MapSharedShreddingFileReaderTest,
TestAllExistSelectedKeysWithOverflow) {
AssertChunkedArrayEquals(expected, actual);
}
+TEST_F(MapSharedShreddingFileReaderTest, TestSelectedKeysStructProjection) {
+ ASSERT_OK_AND_ASSIGN(auto physical_schema, PhysicalSchemaWithMetadata());
+ ASSERT_OK_AND_ASSIGN(auto physical_array, PhysicalArray());
+ auto mock_reader = std::make_unique<MockFileBatchReader>(
+ physical_array, arrow::struct_(physical_schema->fields()),
/*read_batch_size=*/10);
+ mock_reader->EnableRandomizeBatchSize(false);
+
+ auto selected_type =
+ arrow::struct_({arrow::field("a", arrow::int64()), arrow::field("c",
arrow::int64()),
+ arrow::field("missing", arrow::int64())});
+ auto selected_field = arrow::field(
+ "tags", selected_type, /*nullable=*/true,
+ arrow::KeyValueMetadata::Make({DataField::MAP_SELECTED_KEYS},
{"a,c,missing"}));
+ ASSERT_OK_AND_ASSIGN(
+ auto field_read_plan,
+
MapFieldReadPlanFactory::CreateSharedSelectedKeysReadPlan(selected_field,
TagsMeta()));
+ std::map<std::string, std::unique_ptr<MapFieldReadPlan>> contexts;
+ contexts.emplace("tags", std::move(field_read_plan));
+ auto reader =
std::make_unique<MapSharedShreddingFileReader>(std::move(mock_reader),
+
std::move(contexts), pool_);
+
+ auto read_schema =
+ ExportSchema(arrow::schema({arrow::field("id", arrow::int32()),
selected_field}));
+ ASSERT_OK(reader->SetReadSchema(read_schema.get(), /*predicate=*/nullptr,
+ /*selection_bitmap=*/std::nullopt));
+ ASSERT_OK_AND_ASSIGN(auto actual,
ReadResultCollector::CollectResult(reader.get()));
+
+ auto expected_type = arrow::struct_({arrow::field("id", arrow::int32()),
selected_field});
+ std::shared_ptr<arrow::ChunkedArray> expected;
+
ASSERT_TRUE(arrow::ipc::internal::json::ChunkedArrayFromJSON(expected_type,
{R"([
+ [1, [10, null, null]],
+ [2, [40, 30, null]],
+ [3, null],
+ [4, [80, null, null]]
+ ])"},
+ &expected)
+ .ok());
+ AssertChunkedArrayEquals(expected, actual);
+}
+
+TEST_F(MapSharedShreddingFileReaderTest,
TestSelectedKeysStructProjectionFromDefaultMap) {
+ auto map_type = arrow::internal::checked_pointer_cast<arrow::MapType>(
+ arrow::map(arrow::utf8(), arrow::field("value", arrow::int64())));
+ auto file_schema =
+ arrow::schema({arrow::field("id", arrow::int32()),
arrow::field("tags", map_type)});
+ auto file_array =
+
arrow::ipc::internal::json::ArrayFromJSON(arrow::struct_(file_schema->fields()),
+ R"([
+ [1, [["a", 10], ["c", null]]],
+ [2, [["b", 20]]],
+ [3, null]
+ ])")
+ .ValueOrDie();
+ auto mock_reader = std::make_unique<MockFileBatchReader>(
+ file_array, arrow::struct_(file_schema->fields()),
/*read_batch_size=*/10);
+ mock_reader->EnableRandomizeBatchSize(false);
+
+ auto selected_type = arrow::struct_(
+ {arrow::field("a", arrow::int64()), arrow::field("missing",
arrow::int64())});
+ auto selected_field =
+ arrow::field("tags", selected_type, /*nullable=*/true,
+
arrow::KeyValueMetadata::Make({DataField::MAP_SELECTED_KEYS}, {"a,missing"}));
+ ASSERT_OK_AND_ASSIGN(auto field_read_plan,
+
MapFieldReadPlanFactory::CreateDefaultSelectedKeysReadPlan(
+ file_schema->field(1), selected_field));
+ std::map<std::string, std::unique_ptr<MapFieldReadPlan>> contexts;
+ contexts.emplace("tags", std::move(field_read_plan));
+ auto reader =
std::make_unique<MapSharedShreddingFileReader>(std::move(mock_reader),
+
std::move(contexts), pool_);
+
+ auto read_schema =
+ ExportSchema(arrow::schema({arrow::field("id", arrow::int32()),
selected_field}));
+ ASSERT_OK(reader->SetReadSchema(read_schema.get(), /*predicate=*/nullptr,
+ /*selection_bitmap=*/std::nullopt));
+ ASSERT_OK_AND_ASSIGN(auto actual,
ReadResultCollector::CollectResult(reader.get()));
+
+ auto expected_type = arrow::struct_({arrow::field("id", arrow::int32()),
selected_field});
+ std::shared_ptr<arrow::ChunkedArray> expected;
+
ASSERT_TRUE(arrow::ipc::internal::json::ChunkedArrayFromJSON(expected_type,
{R"([
+ [1, [10, null]],
+ [2, [null, null]],
+ [3, null]
+ ])"},
+ &expected)
+ .ok());
+ AssertChunkedArrayEquals(expected, actual);
+}
+
+TEST_F(MapSharedShreddingFileReaderTest,
TestInvalidSelectedKeysStructProjection) {
+ auto file_map_field = arrow::field("tags", arrow::map(arrow::utf8(),
arrow::int64()));
+ auto mismatched_count_field =
+ arrow::field("tags", arrow::struct_({arrow::field("a",
arrow::int64())}), /*nullable=*/true,
+
arrow::KeyValueMetadata::Make({DataField::MAP_SELECTED_KEYS}, {"a,b"}));
+
ASSERT_NOK_WITH_MSG(MapFieldReadPlanFactory::CreateSharedSelectedKeysReadPlan(
+ mismatched_count_field, TagsMeta()),
+ "metadata size 2 does not match STRUCT field count 1");
+
ASSERT_NOK_WITH_MSG(MapFieldReadPlanFactory::CreateDefaultSelectedKeysReadPlan(
+ file_map_field, mismatched_count_field),
+ "metadata size 2 does not match STRUCT field count 1");
+
+ auto mismatched_type_field = arrow::field(
+ "tags",
+ arrow::struct_({arrow::field("a", arrow::int64()), arrow::field("b",
arrow::utf8())}),
+ /*nullable=*/true,
arrow::KeyValueMetadata::Make({DataField::MAP_SELECTED_KEYS}, {"a,b"}));
+
ASSERT_NOK_WITH_MSG(MapFieldReadPlanFactory::CreateSharedSelectedKeysReadPlan(
+ mismatched_type_field, TagsMeta()),
+ "must have the same value type");
+
ASSERT_NOK_WITH_MSG(MapFieldReadPlanFactory::CreateDefaultSelectedKeysReadPlan(
+ file_map_field, mismatched_type_field),
+ "must have the same value type");
+}
+
TEST_F(MapSharedShreddingFileReaderTest, TestPartialExistSelectedKeys) {
ASSERT_OK_AND_ASSIGN(auto reader,
CreateReader(/*physical_array=*/nullptr,
/*physical_schema=*/nullptr,
diff --git
a/src/paimon/common/data/shredding/map_shared_shredding_schema_utils.cpp
b/src/paimon/common/data/shredding/map_shared_shredding_schema_utils.cpp
index c8dc5412..36978d04 100644
--- a/src/paimon/common/data/shredding/map_shared_shredding_schema_utils.cpp
+++ b/src/paimon/common/data/shredding/map_shared_shredding_schema_utils.cpp
@@ -19,15 +19,96 @@
#include "paimon/data/shredding/map_shared_shredding_schema_utils.h"
+#include <unordered_set>
+#include <utility>
+#include <vector>
+
#include "arrow/c/bridge.h"
#include "arrow/type.h"
#include "arrow/util/key_value_metadata.h"
#include "fmt/format.h"
#include "paimon/common/data/shredding/map_shared_shredding_utils.h"
+#include "paimon/common/types/data_field.h"
#include "paimon/common/utils/arrow/status_utils.h"
namespace paimon {
+class MapSharedShreddingAccessBuilder::Impl {
+ public:
+ Impl(const std::shared_ptr<arrow::Field>& _map_field,
+ const std::shared_ptr<arrow::MapType>& _map_type)
+ : map_field(_map_field), map_type(_map_type) {}
+
+ std::shared_ptr<arrow::Field> map_field;
+ std::shared_ptr<arrow::MapType> map_type;
+ std::vector<std::string> keys;
+ std::unordered_set<std::string> unique_keys;
+};
+
+MapSharedShreddingAccessBuilder::~MapSharedShreddingAccessBuilder() = default;
+
+MapSharedShreddingAccessBuilder::MapSharedShreddingAccessBuilder(std::unique_ptr<Impl>&&
impl)
+ : impl_(std::move(impl)) {}
+
+Result<std::unique_ptr<MapSharedShreddingAccessBuilder>>
MapSharedShreddingAccessBuilder::Create(
+ struct ArrowSchema* map_field) {
+ if (!map_field) {
+ return Status::Invalid("MAP field is null");
+ }
+ PAIMON_ASSIGN_OR_RAISE_FROM_ARROW(std::shared_ptr<arrow::Field> field,
+ arrow::ImportField(map_field));
+ if (field->type()->id() != arrow::Type::MAP) {
+ return Status::Invalid(
+ fmt::format("MapSharedShreddingAccessBuilder requires MAP field,
got {}",
+ field->type()->ToString()));
+ }
+ auto map_type =
arrow::internal::checked_pointer_cast<arrow::MapType>(field->type());
+ if (map_type->key_type()->id() != arrow::Type::STRING) {
+ return Status::Invalid(fmt::format(
+ "MapSharedShreddingAccessBuilder only supports MAP with STRING
keys, got {}",
+ map_type->key_type()->ToString()));
+ }
+ auto impl = std::make_unique<Impl>(field, map_type);
+ return std::unique_ptr<MapSharedShreddingAccessBuilder>(
+ new MapSharedShreddingAccessBuilder(std::move(impl)));
+}
+
+Status MapSharedShreddingAccessBuilder::AddKey(const std::string& key) {
+ if (key.find(',') != std::string::npos) {
+ return Status::Invalid(
+ fmt::format("selected MAP key {} must not contain the ','
delimiter", key));
+ }
+ if (!impl_->unique_keys.insert(key).second) {
+ return Status::Invalid(fmt::format("selected MAP key must not be
duplicated: {}", key));
+ }
+ impl_->keys.push_back(key);
+ return Status::OK();
+}
+
+Result<std::unique_ptr<struct ArrowSchema>>
MapSharedShreddingAccessBuilder::Build() const {
+ if (impl_->keys.empty()) {
+ return Status::Invalid(
+ "shared shredding MAP selected-key projection needs at least one
key");
+ }
+ arrow::FieldVector fields;
+ fields.reserve(impl_->keys.size());
+ std::string encoded_keys;
+ for (size_t i = 0; i < impl_->keys.size(); ++i) {
+ if (i != 0) {
+ encoded_keys.push_back(',');
+ }
+ encoded_keys.append(impl_->keys[i]);
+ fields.push_back(arrow::field(impl_->keys[i],
impl_->map_type->item_type(),
+ /*nullable=*/true));
+ }
+ auto metadata =
arrow::KeyValueMetadata::Make({DataField::MAP_SELECTED_KEYS}, {encoded_keys});
+ auto access_field =
impl_->map_field->WithType(arrow::struct_(std::move(fields)))
+ ->WithMetadata(std::move(metadata));
+ auto field = std::make_unique<struct ArrowSchema>();
+ PAIMON_RETURN_NOT_OK_FROM_ARROW(arrow::ExportField(*access_field,
field.get()));
+ return field;
+}
+
Result<std::unique_ptr<::ArrowSchema>>
MapSharedShreddingSchemaUtils::LogicalToPhysicalSchema(
std::unique_ptr<::ArrowSchema> logical_schema,
const std::map<std::string, int32_t>& field_to_num_columns) {
diff --git
a/src/paimon/common/data/shredding/map_shared_shredding_schema_utils_test.cpp
b/src/paimon/common/data/shredding/map_shared_shredding_schema_utils_test.cpp
index e421bc3f..bfe26a37 100644
---
a/src/paimon/common/data/shredding/map_shared_shredding_schema_utils_test.cpp
+++
b/src/paimon/common/data/shredding/map_shared_shredding_schema_utils_test.cpp
@@ -27,9 +27,85 @@
#include "arrow/util/key_value_metadata.h"
#include "gtest/gtest.h"
#include "paimon/common/data/shredding/map_shared_shredding_utils.h"
+#include "paimon/common/types/data_field.h"
#include "paimon/testing/utils/testharness.h"
namespace paimon::test {
+namespace {
+
+std::unique_ptr<ArrowSchema> ExportField(const std::shared_ptr<arrow::Field>&
field) {
+ auto c_field = std::make_unique<ArrowSchema>();
+ EXPECT_TRUE(arrow::ExportField(*field, c_field.get()).ok());
+ return c_field;
+}
+
+} // namespace
+
+TEST(MapSharedShreddingAccessBuilderTest, BuildSelectedKeysField) {
+ auto original_metadata =
+ arrow::KeyValueMetadata::Make({DataField::FIELD_ID,
DataField::DESCRIPTION, "custom.key"},
+ {"7", "original description",
"custom.value"});
+ auto map_type = arrow::map(arrow::utf8(), arrow::field("value",
arrow::int64(), false));
+ auto map_field = arrow::field("attributes", map_type, /*nullable=*/false,
original_metadata);
+ ASSERT_OK_AND_ASSIGN(std::unique_ptr<MapSharedShreddingAccessBuilder>
builder,
+
MapSharedShreddingAccessBuilder::Create(ExportField(map_field).get()));
+ ASSERT_OK(builder->AddKey("age"));
+ ASSERT_OK(builder->AddKey("score"));
+
+ ASSERT_OK_AND_ASSIGN(std::unique_ptr<ArrowSchema> c_field,
builder->Build());
+ auto imported_field = arrow::ImportField(c_field.get());
+ ASSERT_TRUE(imported_field.ok());
+ std::shared_ptr<arrow::Field> field = imported_field.ValueOrDie();
+ ASSERT_EQ(field->name(), "attributes");
+ ASSERT_EQ(field->type()->id(), arrow::Type::STRUCT);
+ ASSERT_FALSE(field->nullable());
+
+ auto struct_type =
arrow::internal::checked_pointer_cast<arrow::StructType>(field->type());
+ ASSERT_EQ(struct_type->num_fields(), 2);
+ ASSERT_EQ(struct_type->field(0)->name(), "age");
+ ASSERT_EQ(struct_type->field(1)->name(), "score");
+ ASSERT_TRUE(struct_type->field(0)->type()->Equals(arrow::int64()));
+ ASSERT_TRUE(struct_type->field(1)->type()->Equals(arrow::int64()));
+ ASSERT_TRUE(struct_type->field(0)->nullable());
+ ASSERT_TRUE(struct_type->field(1)->nullable());
+ ASSERT_FALSE(field->metadata()->Contains(DataField::FIELD_ID));
+ ASSERT_FALSE(field->metadata()->Contains(DataField::DESCRIPTION));
+ ASSERT_FALSE(field->metadata()->Contains("custom.key"));
+ ASSERT_TRUE(field->metadata()->Contains(DataField::MAP_SELECTED_KEYS));
+
ASSERT_EQ(field->metadata()->Get(DataField::MAP_SELECTED_KEYS).ValueOrDie(),
"age,score");
+}
+
+TEST(MapSharedShreddingAccessBuilderTest, RejectInvalidKeys) {
+ {
+ auto map_field = arrow::field("attributes", arrow::map(arrow::utf8(),
arrow::int64()));
+ ASSERT_OK_AND_ASSIGN(std::unique_ptr<MapSharedShreddingAccessBuilder>
builder,
+
MapSharedShreddingAccessBuilder::Create(ExportField(map_field).get()));
+ ASSERT_NOK_WITH_MSG(builder->Build(), "at least one key");
+ }
+ {
+ auto map_field = arrow::field("attributes", arrow::map(arrow::utf8(),
arrow::int64()));
+ ASSERT_OK_AND_ASSIGN(std::unique_ptr<MapSharedShreddingAccessBuilder>
builder,
+
MapSharedShreddingAccessBuilder::Create(ExportField(map_field).get()));
+ ASSERT_OK(builder->AddKey("a"));
+ ASSERT_NOK_WITH_MSG(builder->AddKey("a"), "must not be duplicated");
+ }
+ {
+ auto map_field = arrow::field("attributes", arrow::map(arrow::utf8(),
arrow::int64()));
+ ASSERT_OK_AND_ASSIGN(std::unique_ptr<MapSharedShreddingAccessBuilder>
builder,
+
MapSharedShreddingAccessBuilder::Create(ExportField(map_field).get()));
+ ASSERT_NOK_WITH_MSG(builder->AddKey("a,b"), "must not contain the ','
delimiter");
+ }
+}
+
+TEST(MapSharedShreddingAccessBuilderTest, RejectInvalidMapField) {
+ ASSERT_NOK_WITH_MSG(MapSharedShreddingAccessBuilder::Create(nullptr), "MAP
field is null");
+ ASSERT_NOK_WITH_MSG(MapSharedShreddingAccessBuilder::Create(
+ ExportField(arrow::field("v",
arrow::int64())).get()),
+ "requires MAP field");
+ auto non_string_map = arrow::field("attributes",
arrow::map(arrow::int32(), arrow::int64()));
+
ASSERT_NOK_WITH_MSG(MapSharedShreddingAccessBuilder::Create(ExportField(non_string_map).get()),
+ "only supports MAP with STRING keys");
+}
TEST(MapSharedShreddingSchemaUtilsTest, AttachMetadataToSchemaBasic) {
MapSharedShreddingFieldMeta tags_meta;
diff --git a/src/paimon/core/io/field_mapping_reader.cpp
b/src/paimon/core/io/field_mapping_reader.cpp
index 3bd04b49..947853c3 100644
--- a/src/paimon/core/io/field_mapping_reader.cpp
+++ b/src/paimon/core/io/field_mapping_reader.cpp
@@ -51,6 +51,18 @@ Result<bool>
FieldMappingReader::HasMapSelectedKeysRecursively(
return false;
}
auto type_id = read_field->type()->id();
+ if (NestedProjectionUtils::IsMapSharedShreddingAccessField(read_field)) {
+ PAIMON_ASSIGN_OR_RAISE(std::vector<std::string> selected_keys,
+
NestedProjectionUtils::GetMapSelectedKeys(read_field));
+ auto read_struct =
+
arrow::internal::checked_pointer_cast<arrow::StructType>(read_field->type());
+ if (selected_keys.size() !=
static_cast<size_t>(read_struct->num_fields())) {
+ return Status::Invalid(fmt::format(
+ "selected-key metadata size {} does not match STRUCT field
count {} for {}",
+ selected_keys.size(), read_struct->num_fields(),
read_field->name()));
+ }
+ return true;
+ }
if (type_id == arrow::Type::MAP) {
PAIMON_ASSIGN_OR_RAISE(std::vector<std::string> selected_keys,
NestedProjectionUtils::GetMapSelectedKeys(read_field));
@@ -75,6 +87,11 @@ Result<std::shared_ptr<arrow::Array>>
FieldMappingReader::FilterMapSelectedKeysR
}
auto type_id = read_field->type()->id();
+ if (NestedProjectionUtils::IsMapSharedShreddingAccessField(read_field)) {
+ // The shared-shredding wrapper (including its default MAP fallback)
has already
+ // materialized this projection as a STRUCT.
+ return array;
+ }
if (type_id == arrow::Type::MAP) {
PAIMON_ASSIGN_OR_RAISE(std::vector<std::string> selected_keys,
NestedProjectionUtils::GetMapSelectedKeys(read_field));
diff --git a/src/paimon/core/operation/abstract_split_read.cpp
b/src/paimon/core/operation/abstract_split_read.cpp
index 3a42f7da..2a3d9e10 100644
--- a/src/paimon/core/operation/abstract_split_read.cpp
+++ b/src/paimon/core/operation/abstract_split_read.cpp
@@ -257,8 +257,7 @@ AbstractSplitRead::ApplySharedShreddingReaderIfNeeded(
file_reader->GetFileSchema());
PAIMON_ASSIGN_OR_RAISE_FROM_ARROW(std::shared_ptr<arrow::Schema>
file_arrow_schema,
arrow::ImportSchema(file_schema.get()));
- std::map<std::string, MapSharedShreddingFileReader::SharedShreddingContext>
- shared_shredding_name_to_context;
+ std::map<std::string, std::unique_ptr<MapFieldReadPlan>> field_read_plans;
for (const auto& read_field : read_schema->fields()) {
const auto& field_name = read_field->name();
auto file_field = file_arrow_schema->GetFieldByName(field_name);
@@ -268,35 +267,41 @@ AbstractSplitRead::ApplySharedShreddingReaderIfNeeded(
}
std::shared_ptr<arrow::KeyValueMetadata> metadata =
std::const_pointer_cast<arrow::KeyValueMetadata>(file_field->metadata());
- if (!MapSharedShreddingUtils::HasShreddingMetadata(metadata)) {
- // not a map shared shredding field
+ bool is_shared_shredding_file =
MapSharedShreddingUtils::HasShreddingMetadata(metadata);
+ bool is_shared_shredding_map_access =
+ NestedProjectionUtils::IsMapSharedShreddingAccessField(read_field);
+ if (!is_shared_shredding_file && !is_shared_shredding_map_access) {
+ // Neither a shared-shredding file field nor a selected-key STRUCT
projection.
continue;
}
- // get meta
- PAIMON_ASSIGN_OR_RAISE(MapSharedShreddingFieldMeta meta,
-
MapSharedShreddingUtils::DeserializeMetadata(metadata));
- // get selected_keys
- PAIMON_ASSIGN_OR_RAISE(std::vector<std::string> selected_keys,
-
NestedProjectionUtils::GetMapSelectedKeys(read_field));
- if (selected_keys.empty()) {
- // select all keys
- selected_keys.reserve(meta.name_to_id.size());
- for (const auto& [key_name, _] : meta.name_to_id) {
- selected_keys.push_back(key_name);
+
+ std::unique_ptr<MapFieldReadPlan> field_read_plan;
+ if (is_shared_shredding_map_access) {
+ if (is_shared_shredding_file) {
+ PAIMON_ASSIGN_OR_RAISE(MapSharedShreddingFieldMeta meta,
+
MapSharedShreddingUtils::DeserializeMetadata(metadata));
+ PAIMON_ASSIGN_OR_RAISE(
+ field_read_plan,
+
MapFieldReadPlanFactory::CreateSharedSelectedKeysReadPlan(read_field, meta));
+ } else {
+ PAIMON_ASSIGN_OR_RAISE(field_read_plan,
+
MapFieldReadPlanFactory::CreateDefaultSelectedKeysReadPlan(
+ file_field, read_field));
}
+ } else {
+ PAIMON_ASSIGN_OR_RAISE(MapSharedShreddingFieldMeta meta,
+
MapSharedShreddingUtils::DeserializeMetadata(metadata));
+ PAIMON_ASSIGN_OR_RAISE(field_read_plan,
+
MapFieldReadPlanFactory::CreateMapReadPlan(read_field, meta));
}
- // get map type
- auto map_type =
arrow::internal::checked_pointer_cast<arrow::MapType>(read_field->type());
- shared_shredding_name_to_context.emplace(
- field_name,
- MapSharedShreddingFileReader::SharedShreddingContext(meta,
selected_keys, map_type));
+ field_read_plans.emplace(field_name, std::move(field_read_plan));
PAIMON_ASSIGN_OR_RAISE(int32_t field_id,
NestedProjectionUtils::GetPaimonFieldId(read_field));
handled_shared_shredding_field_ids.insert(field_id);
}
- if (!shared_shredding_name_to_context.empty()) {
+ if (!field_read_plans.empty()) {
file_reader = std::make_unique<MapSharedShreddingFileReader>(
- std::move(file_reader),
std::move(shared_shredding_name_to_context), pool_);
+ std::move(file_reader), std::move(field_read_plans), pool_);
}
return std::make_pair(std::move(file_reader),
std::move(handled_shared_shredding_field_ids));
}
diff --git a/src/paimon/core/operation/internal_read_context.cpp
b/src/paimon/core/operation/internal_read_context.cpp
index e8886b42..7c4e2ecd 100644
--- a/src/paimon/core/operation/internal_read_context.cpp
+++ b/src/paimon/core/operation/internal_read_context.cpp
@@ -31,6 +31,7 @@
#include "paimon/common/table/special_fields.h"
#include "paimon/common/types/data_field.h"
#include "paimon/common/utils/arrow/status_utils.h"
+#include "paimon/core/options/map_storage_layout.h"
#include "paimon/core/schema/arrow_schema_validator.h"
#include "paimon/core/utils/nested_projection_utils.h"
#include "paimon/status.h"
@@ -50,6 +51,31 @@ Result<std::shared_ptr<arrow::Field>>
InternalReadContext::AlignReadFieldWithTab
return table_field->WithType(read_field->type());
}
+ if (table_field->type()->id() == arrow::Type::MAP &&
+ NestedProjectionUtils::IsMapSharedShreddingAccessField(read_field)) {
+ auto table_map =
arrow::internal::checked_pointer_cast<arrow::MapType>(table_field->type());
+ if (table_map->key_type()->id() != arrow::Type::STRING) {
+ return Status::Invalid(fmt::format(
+ "Selected-key MAP pushdown only supports string MAP keys for
field '{}'",
+ table_field->name()));
+ }
+ PAIMON_RETURN_NOT_OK(
+
NestedProjectionUtils::ValidateMapSharedShreddingAccessField(read_field).status());
+ auto read_struct =
+
arrow::internal::checked_pointer_cast<arrow::StructType>(read_field->type());
+ const auto& selected_value_type = read_struct->field(0)->type();
+ if (!selected_value_type->Equals(table_map->item_type())) {
+ return Status::Invalid(fmt::format(
+ "Selected-key MAP pushdown does not support pruning MAP value
fields for "
+ "'{}': selected type {} vs MAP value type {}",
+ table_field->name(), selected_value_type->ToString(),
+ table_map->item_type()->ToString()));
+ }
+ auto aligned_field = table_field->WithType(read_field->type());
+ return DataField::MergeFieldMetadataByWhitelist(aligned_field,
read_field,
+
kReadMetadataWhitelist);
+ }
+
if (read_field->type()->id() != table_field->type()->id()) {
return Status::Invalid(fmt::format(
"Read schema field '{}' type {} does not match table field type
{}", read_field->name(),
@@ -198,6 +224,16 @@ Result<std::unique_ptr<InternalReadContext>>
InternalReadContext::Create(
}
PAIMON_ASSIGN_OR_RAISE(DataField table_field,
table_schema->GetField(read_field->name()));
+ if
(NestedProjectionUtils::IsMapSharedShreddingAccessField(read_field)) {
+ PAIMON_ASSIGN_OR_RAISE(MapStorageLayout layout,
+
core_options.GetMapStorageLayout(table_field.Name()));
+ if (layout != MapStorageLayout::SHARED_SHREDDING) {
+ return Status::Invalid(fmt::format(
+ "Selected-key MAP pushdown only supports top-level
shared-shredding MAP "
+ "field: {}",
+ table_field.Name()));
+ }
+ }
PAIMON_ASSIGN_OR_RAISE(
std::shared_ptr<arrow::Field> aligned_field,
AlignReadFieldWithTableFieldIds(read_field,
table_field.ArrowField()));
diff --git a/src/paimon/core/operation/internal_read_context_test.cpp
b/src/paimon/core/operation/internal_read_context_test.cpp
index 30ba77b7..28618857 100644
--- a/src/paimon/core/operation/internal_read_context_test.cpp
+++ b/src/paimon/core/operation/internal_read_context_test.cpp
@@ -25,6 +25,7 @@
#include "paimon/common/table/special_fields.h"
#include "paimon/common/types/data_field.h"
#include "paimon/core/schema/schema_manager.h"
+#include "paimon/data/shredding/map_shared_shredding_schema_utils.h"
#include "paimon/defs.h"
#include "paimon/fs/local/local_file_system.h"
#include "paimon/status.h"
@@ -303,4 +304,34 @@ TEST(InternalReadContext,
TestProjectedSchemaMetadataWhitelist) {
ASSERT_FALSE(custom_metadata_result.ok());
}
+TEST(InternalReadContext,
TestMapSharedShreddingAccessRequiresSharedShreddingLayout) {
+ auto map_field = arrow::field("tags", arrow::map(arrow::utf8(),
arrow::int64()));
+ ASSERT_OK_AND_ASSIGN(
+ std::unique_ptr<TableSchema> unique_table_schema,
+ TableSchema::Create(/*schema_id=*/0, arrow::schema({map_field}),
+ /*partition_keys=*/{}, /*primary_keys=*/{},
/*options=*/{}));
+ std::shared_ptr<TableSchema> table_schema = std::move(unique_table_schema);
+
+ auto c_map_field = std::make_unique<ArrowSchema>();
+ ASSERT_TRUE(arrow::ExportField(*map_field, c_map_field.get()).ok());
+ ASSERT_OK_AND_ASSIGN(std::unique_ptr<MapSharedShreddingAccessBuilder>
access_builder,
+
MapSharedShreddingAccessBuilder::Create(c_map_field.get()));
+ ASSERT_OK(access_builder->AddKey("a"));
+ ASSERT_OK_AND_ASSIGN(std::unique_ptr<ArrowSchema> c_access_field,
access_builder->Build());
+ auto imported_access_field = arrow::ImportField(c_access_field.get());
+ ASSERT_TRUE(imported_access_field.ok());
+ std::shared_ptr<arrow::Field> access_field =
imported_access_field.ValueOrDie();
+
+ auto c_read_schema = std::make_unique<ArrowSchema>();
+ ASSERT_TRUE(arrow::ExportSchema(*arrow::schema({access_field}),
c_read_schema.get()).ok());
+ ReadContextBuilder context_builder("/tmp/unused-table-path");
+ context_builder.SetReadSchema(std::move(c_read_schema));
+ ASSERT_OK_AND_ASSIGN(auto unique_read_context, context_builder.Finish());
+ std::shared_ptr<ReadContext> read_context = std::move(unique_read_context);
+
+ ASSERT_NOK_WITH_MSG(
+ InternalReadContext::Create(read_context, table_schema,
table_schema->Options()),
+ "Selected-key MAP pushdown only supports top-level shared-shredding
MAP field: tags");
+}
+
} // namespace paimon::test
diff --git a/src/paimon/core/utils/field_mapping.cpp
b/src/paimon/core/utils/field_mapping.cpp
index df3791e3..c9b70d47 100644
--- a/src/paimon/core/utils/field_mapping.cpp
+++ b/src/paimon/core/utils/field_mapping.cpp
@@ -108,9 +108,16 @@ Result<ExistFieldInfo>
FieldMappingBuilder::CreateExistFieldInfo(
// Recursively prune nested types in data_field to match
read_field's
// projection. For atomic types this is a no-op.
- PAIMON_ASSIGN_OR_RAISE(
- std::optional<std::shared_ptr<arrow::DataType>> pruned_type,
- NestedProjectionUtils::PruneDataType(read_field.Type(),
data_field.Type()));
+ std::optional<std::shared_ptr<arrow::DataType>> pruned_type;
+ if
(NestedProjectionUtils::IsMapSharedShreddingAccessField(read_field.ArrowField()))
{
+ PAIMON_ASSIGN_OR_RAISE(std::shared_ptr<arrow::DataType>
map_access_data_type,
+
NestedProjectionUtils::BuildMapSharedShreddingAccessDataType(
+ read_field.ArrowField(),
data_field.Type()));
+ pruned_type = std::move(map_access_data_type);
+ } else {
+ PAIMON_ASSIGN_OR_RAISE(pruned_type,
NestedProjectionUtils::PruneDataType(
+ read_field.Type(),
data_field.Type()));
+ }
if (!pruned_type.has_value()) {
// All sub-fields pruned away — treat as non-existent.
continue;
diff --git a/src/paimon/core/utils/nested_projection_utils.cpp
b/src/paimon/core/utils/nested_projection_utils.cpp
index 97c08e9a..3826ae81 100644
--- a/src/paimon/core/utils/nested_projection_utils.cpp
+++ b/src/paimon/core/utils/nested_projection_utils.cpp
@@ -446,79 +446,104 @@ Result<std::vector<std::string>>
NestedProjectionUtils::GetMapSelectedKeys(
return result;
}
-namespace {
+bool NestedProjectionUtils::IsMapSharedShreddingAccessField(
+ const std::shared_ptr<arrow::Field>& field) {
+ if (field->type()->id() != arrow::Type::STRUCT || !field->HasMetadata() ||
!field->metadata()) {
+ return false;
+ }
+ return field->metadata()->Contains(DataField::MAP_SELECTED_KEYS);
+}
-struct MapKeyAccessor {
- std::shared_ptr<arrow::StringArray> string_keys;
- std::shared_ptr<arrow::DictionaryArray> dict_keys;
- std::shared_ptr<arrow::StringArray> dict_values;
- std::shared_ptr<arrow::LargeStringArray> dict_large_values;
-};
+Result<std::vector<std::string>>
NestedProjectionUtils::ValidateMapSharedShreddingAccessField(
+ const std::shared_ptr<arrow::Field>& field) {
+ if (field->type()->id() != arrow::Type::STRUCT) {
+ return Status::Invalid(
+ fmt::format("selected-key MAP field {} is not a STRUCT",
field->name()));
+ }
+ auto struct_type =
arrow::internal::checked_pointer_cast<arrow::StructType>(field->type());
+ PAIMON_ASSIGN_OR_RAISE(std::vector<std::string> selected_keys,
GetMapSelectedKeys(field));
+ if (struct_type->num_fields() == 0 ||
+ selected_keys.size() !=
static_cast<size_t>(struct_type->num_fields())) {
+ return Status::Invalid(
+ fmt::format("selected-key metadata size {} does not match STRUCT
field count {} for {}",
+ selected_keys.size(), struct_type->num_fields(),
field->name()));
+ }
+ const auto& value_type = struct_type->field(0)->type();
+ for (int32_t i = 1; i < struct_type->num_fields(); ++i) {
+ if (!struct_type->field(i)->type()->Equals(value_type)) {
+ return Status::Invalid(fmt::format(
+ "selected-key MAP fields must have the same value type, but {}
and {} differ",
+ value_type->ToString(),
struct_type->field(i)->type()->ToString()));
+ }
+ }
+ return selected_keys;
+}
+
+Result<std::shared_ptr<arrow::DataType>>
+NestedProjectionUtils::BuildMapSharedShreddingAccessDataType(
+ const std::shared_ptr<arrow::Field>& read_field,
+ const std::shared_ptr<arrow::DataType>& data_type) {
+ if (!IsMapSharedShreddingAccessField(read_field)) {
+ return Status::Invalid(
+ fmt::format("field {} is not a selected-key MAP projection",
read_field->name()));
+ }
+ if (data_type->id() != arrow::Type::MAP) {
+ return Status::Invalid(
+ fmt::format("selected-key MAP projection {} requires MAP data
type, got {}",
+ read_field->name(), data_type->ToString()));
+ }
+
PAIMON_RETURN_NOT_OK(ValidateMapSharedShreddingAccessField(read_field).status());
+ auto read_struct =
arrow::internal::checked_pointer_cast<arrow::StructType>(read_field->type());
+ auto data_map =
arrow::internal::checked_pointer_cast<arrow::MapType>(data_type);
+ arrow::FieldVector data_children;
+ data_children.reserve(read_struct->num_fields());
+ for (const auto& read_child : read_struct->fields()) {
+ data_children.push_back(read_child->WithType(data_map->item_type()));
+ }
+ return arrow::struct_(std::move(data_children));
+}
-Result<MapKeyAccessor> BuildMapKeyAccessor(const
std::shared_ptr<arrow::Array>& key_array) {
- MapKeyAccessor accessor;
+Result<std::string_view> NestedProjectionUtils::GetMapKeyViewAt(
+ const std::shared_ptr<arrow::Array>& key_array, int64_t entry_idx) {
+ if (key_array->IsNull(entry_idx)) {
+ return Status::Invalid("selected-key MAP read found null MAP key at
entry " +
+ std::to_string(entry_idx));
+ }
if (key_array->type_id() == arrow::Type::STRING) {
- accessor.string_keys =
std::static_pointer_cast<arrow::StringArray>(key_array);
- return accessor;
+ return
arrow::internal::checked_pointer_cast<arrow::StringArray>(key_array)->GetView(
+ entry_idx);
}
if (key_array->type_id() == arrow::Type::DICTIONARY) {
- auto dict_type =
std::static_pointer_cast<arrow::DictionaryType>(key_array->type());
+ auto dict_type =
+
arrow::internal::checked_pointer_cast<arrow::DictionaryType>(key_array->type());
if (dict_type->value_type()->id() != arrow::Type::STRING &&
dict_type->value_type()->id() != arrow::Type::LARGE_STRING) {
return Status::Invalid(
- fmt::format("FilterMapArrayBySelectedKeys only supports string
keys or "
+ fmt::format("selected-key MAP read only supports string keys
or "
"dictionary<string|large_string> keys, got {}",
key_array->type()->ToString()));
}
- accessor.dict_keys =
std::static_pointer_cast<arrow::DictionaryArray>(key_array);
+ auto dict_keys =
arrow::internal::checked_pointer_cast<arrow::DictionaryArray>(key_array);
+ int64_t dict_idx = dict_keys->GetValueIndex(entry_idx);
+ const auto& dictionary = dict_keys->dictionary();
+ if (dictionary->IsNull(dict_idx)) {
+ return Status::Invalid(
+ "selected-key MAP read found null dictionary MAP key at
dictionary index " +
+ std::to_string(dict_idx));
+ }
if (dict_type->value_type()->id() == arrow::Type::STRING) {
- accessor.dict_values =
-
std::static_pointer_cast<arrow::StringArray>(accessor.dict_keys->dictionary());
- } else {
- accessor.dict_large_values =
-
std::static_pointer_cast<arrow::LargeStringArray>(accessor.dict_keys->dictionary());
+ return
arrow::internal::checked_pointer_cast<arrow::StringArray>(dictionary)
+ ->GetView(dict_idx);
}
- return accessor;
+ return
arrow::internal::checked_pointer_cast<arrow::LargeStringArray>(dictionary)
+ ->GetView(dict_idx);
}
return Status::Invalid(
- fmt::format("FilterMapArrayBySelectedKeys only supports string keys or
"
+ fmt::format("selected-key MAP read only supports string keys or "
"dictionary<string|large_string> keys, got {}",
key_array->type()->ToString()));
}
-Result<std::string_view> GetMapKeyViewAt(const MapKeyAccessor& accessor,
int64_t entry_idx) {
- if (accessor.string_keys) {
- if (accessor.string_keys->IsNull(entry_idx)) {
- return Status::Invalid("FilterMapArrayBySelectedKeys found null
map key at entry " +
- std::to_string(entry_idx));
- }
- return accessor.string_keys->GetView(entry_idx);
- }
-
- if (accessor.dict_keys->IsNull(entry_idx)) {
- return Status::Invalid("FilterMapArrayBySelectedKeys found null map
key at entry " +
- std::to_string(entry_idx));
- }
- int64_t dict_idx = accessor.dict_keys->GetValueIndex(entry_idx);
- if (accessor.dict_values) {
- if (accessor.dict_values->IsNull(dict_idx)) {
- return Status::Invalid(
- "FilterMapArrayBySelectedKeys found null dictionary map key at
dictionary index " +
- std::to_string(dict_idx));
- }
- return accessor.dict_values->GetView(dict_idx);
- }
-
- if (accessor.dict_large_values->IsNull(dict_idx)) {
- return Status::Invalid(
- "FilterMapArrayBySelectedKeys found null dictionary map key at
dictionary index " +
- std::to_string(dict_idx));
- }
- return accessor.dict_large_values->GetView(dict_idx);
-}
-
-} // namespace
-
Result<std::shared_ptr<arrow::Array>>
NestedProjectionUtils::FilterMapArrayBySelectedKeys(
const std::shared_ptr<arrow::Array>& array, const
std::vector<std::string>& selected_keys,
arrow::MemoryPool* pool) {
@@ -534,12 +559,11 @@ Result<std::shared_ptr<arrow::Array>>
NestedProjectionUtils::FilterMapArrayBySel
"FilterMapArrayBySelectedKeys requires map array, got {}",
array->type()->ToString()));
}
- auto map_array = std::static_pointer_cast<arrow::MapArray>(array);
- auto map_type = std::static_pointer_cast<arrow::MapType>(array->type());
+ auto map_array =
arrow::internal::checked_pointer_cast<arrow::MapArray>(array);
+ auto map_type =
arrow::internal::checked_pointer_cast<arrow::MapType>(array->type());
assert(map_array && map_type);
auto key_array = map_array->keys();
- PAIMON_ASSIGN_OR_RAISE(MapKeyAccessor key_accessor,
BuildMapKeyAccessor(key_array));
auto values_array = map_array->items();
int64_t num_maps = map_array->length();
@@ -575,7 +599,7 @@ Result<std::shared_ptr<arrow::Array>>
NestedProjectionUtils::FilterMapArrayBySel
for (const auto& selected_key : selected_keys) {
for (int64_t entry_idx = start; entry_idx < end; ++entry_idx) {
PAIMON_ASSIGN_OR_RAISE(std::string_view key_view,
- GetMapKeyViewAt(key_accessor,
entry_idx));
+ GetMapKeyViewAt(key_array, entry_idx));
if (key_view == selected_key) {
PAIMON_RETURN_NOT_OK_FROM_ARROW(key_builder->Append(
key_view.data(),
static_cast<int32_t>(key_view.size())));
diff --git a/src/paimon/core/utils/nested_projection_utils.h
b/src/paimon/core/utils/nested_projection_utils.h
index b0ab8fc2..59e7d95f 100644
--- a/src/paimon/core/utils/nested_projection_utils.h
+++ b/src/paimon/core/utils/nested_projection_utils.h
@@ -79,6 +79,27 @@ class PAIMON_EXPORT NestedProjectionUtils {
static Result<std::vector<std::string>> GetMapSelectedKeys(
const std::shared_ptr<arrow::Field>& field);
+ /// @return true when `field` is a selected-key MAP projection: a STRUCT
carrying
+ /// `paimon.map.selected-keys` metadata.
+ static bool IsMapSharedShreddingAccessField(const
std::shared_ptr<arrow::Field>& field);
+
+ /// Validates a selected-key MAP projection and returns its selected keys.
The field must be a
+ /// non-empty STRUCT, its metadata key count must match its child count,
and all children must
+ /// have the same value type.
+ static Result<std::vector<std::string>>
ValidateMapSharedShreddingAccessField(
+ const std::shared_ptr<arrow::Field>& field);
+
+ /// Rewrites a selected-key STRUCT projection to use the data file's
complete MAP value type
+ /// for every child before materialization. Cpp paimon does not support
schema evolution for
+ /// for field inside the MAP value.
+ static Result<std::shared_ptr<arrow::DataType>>
BuildMapSharedShreddingAccessDataType(
+ const std::shared_ptr<arrow::Field>& read_field,
+ const std::shared_ptr<arrow::DataType>& data_type);
+
+ /// Returns a string view for a MAP key stored as string or
dictionary<string|large_string>.
+ static Result<std::string_view> GetMapKeyViewAt(const
std::shared_ptr<arrow::Array>& key_array,
+ int64_t entry_idx);
+
/// Filter a MapArray so that only entries whose key is in `selected_keys`
are kept.
/// Supports string keys and dictionary<string|large_string> keys.
/// The output map entry order follows
diff --git a/src/paimon/core/utils/nested_projection_utils_test.cpp
b/src/paimon/core/utils/nested_projection_utils_test.cpp
index 7905b2ce..570a5321 100644
--- a/src/paimon/core/utils/nested_projection_utils_test.cpp
+++ b/src/paimon/core/utils/nested_projection_utils_test.cpp
@@ -547,6 +547,62 @@ TEST(NestedProjectionUtilsTest,
GetMapSelectedKeysDuplicateKey) {
"Duplicate selected key 'a'");
}
+// ============== MapSharedShreddingAccessField ==============
+
+TEST(NestedProjectionUtilsTest, IsMapSharedShreddingAccessField) {
+ auto metadata =
arrow::KeyValueMetadata::Make({DataField::MAP_SELECTED_KEYS}, {"a,b"});
+ auto access_type =
+ arrow::struct_({arrow::field("a", arrow::int64()), arrow::field("b",
arrow::int64())});
+
+ ASSERT_TRUE(NestedProjectionUtils::IsMapSharedShreddingAccessField(
+ arrow::field("tags", access_type, /*nullable=*/true, metadata)));
+ ASSERT_FALSE(
+
NestedProjectionUtils::IsMapSharedShreddingAccessField(arrow::field("tags",
access_type)));
+
ASSERT_FALSE(NestedProjectionUtils::IsMapSharedShreddingAccessField(arrow::field(
+ "tags", arrow::map(arrow::utf8(), arrow::int64()), /*nullable=*/true,
metadata)));
+}
+
+TEST(NestedProjectionUtilsTest, BuildMapSharedShreddingAccessDataType) {
+ auto read_type = arrow::struct_({
+ arrow::field("a", arrow::int64(), /*nullable=*/true),
+ arrow::field("b", arrow::int64(), /*nullable=*/true),
+ });
+ auto read_metadata =
arrow::KeyValueMetadata::Make({DataField::MAP_SELECTED_KEYS}, {"a,b"});
+ auto read_field = arrow::field("tags", read_type, /*nullable=*/true,
std::move(read_metadata));
+ auto data_value_type = arrow::int32();
+ auto data_type = arrow::map(arrow::utf8(), data_value_type);
+
+ ASSERT_OK_AND_ASSIGN(
+ std::shared_ptr<arrow::DataType> result,
+
NestedProjectionUtils::BuildMapSharedShreddingAccessDataType(read_field,
data_type));
+ auto result_struct =
arrow::internal::checked_pointer_cast<arrow::StructType>(result);
+ ASSERT_EQ(result_struct->num_fields(), 2);
+ ASSERT_EQ(result_struct->field(0)->name(), "a");
+ ASSERT_EQ(result_struct->field(1)->name(), "b");
+ ASSERT_TRUE(result_struct->field(0)->type()->Equals(data_value_type));
+ ASSERT_TRUE(result_struct->field(1)->type()->Equals(data_value_type));
+ ASSERT_TRUE(result_struct->field(0)->nullable());
+ ASSERT_TRUE(result_struct->field(1)->nullable());
+}
+
+TEST(NestedProjectionUtilsTest,
BuildMapSharedShreddingAccessDataTypeInvalidInput) {
+ auto access_metadata =
arrow::KeyValueMetadata::Make({DataField::MAP_SELECTED_KEYS}, {"a,b"});
+ auto access_field = arrow::field("tags", arrow::struct_({arrow::field("a",
arrow::int64())}),
+ /*nullable=*/true, access_metadata);
+
+ ASSERT_NOK_WITH_MSG(
+ NestedProjectionUtils::BuildMapSharedShreddingAccessDataType(
+ arrow::field("tags", arrow::struct_({arrow::field("a",
arrow::int64())})),
+ arrow::map(arrow::utf8(), arrow::int64())),
+ "is not a selected-key MAP projection");
+ ASSERT_NOK_WITH_MSG(
+
NestedProjectionUtils::BuildMapSharedShreddingAccessDataType(access_field,
arrow::int64()),
+ "requires MAP data type");
+
ASSERT_NOK_WITH_MSG(NestedProjectionUtils::BuildMapSharedShreddingAccessDataType(
+ access_field, arrow::map(arrow::utf8(),
arrow::int64())),
+ "metadata size 2 does not match STRUCT field count 1");
+}
+
// ============== FilterMapArrayBySelectedKeys ==============
class NestedProjectionUtilsMapArrayTest : public ::testing::Test {
diff --git a/test/inte/write_and_read_inte_test.cpp
b/test/inte/write_and_read_inte_test.cpp
index 9b8f9444..bdb0e2db 100644
--- a/test/inte/write_and_read_inte_test.cpp
+++ b/test/inte/write_and_read_inte_test.cpp
@@ -40,6 +40,7 @@
#include "paimon/core/io/data_file_meta.h"
#include "paimon/core/schema/schema_manager.h"
#include "paimon/core/table/source/data_split_impl.h"
+#include "paimon/data/shredding/map_shared_shredding_schema_utils.h"
#include "paimon/defs.h"
#include "paimon/file_store_commit.h"
#include "paimon/file_store_write.h"
@@ -167,6 +168,23 @@ class WriteAndReadInteTest
return std::make_shared<arrow::ChunkedArray>(expected)->Equals(actual);
}
+ Result<std::shared_ptr<arrow::Field>> BuildMapSharedShreddingAccessField(
+ const std::shared_ptr<arrow::Field>& map_field,
+ const std::vector<std::string>& selected_keys) const {
+ auto c_map_field = std::make_unique<ArrowSchema>();
+ PAIMON_RETURN_NOT_OK_FROM_ARROW(arrow::ExportField(*map_field,
c_map_field.get()));
+
PAIMON_ASSIGN_OR_RAISE(std::unique_ptr<MapSharedShreddingAccessBuilder>
access_builder,
+
MapSharedShreddingAccessBuilder::Create(c_map_field.get()));
+ for (const auto& selected_key : selected_keys) {
+ PAIMON_RETURN_NOT_OK(access_builder->AddKey(selected_key));
+ }
+ PAIMON_ASSIGN_OR_RAISE(std::unique_ptr<ArrowSchema> c_access_field,
+ access_builder->Build());
+ PAIMON_ASSIGN_OR_RAISE_FROM_ARROW(std::shared_ptr<arrow::Field>
access_field,
+
arrow::ImportField(c_access_field.get()));
+ return access_field;
+ }
+
Result<std::shared_ptr<Plan>> InnerScan(
const std::map<std::string, std::string>& options) const {
std::string table_path = PathUtil::JoinPath(test_dir_, "foo.db/bar");
@@ -2330,6 +2348,21 @@ TEST_P(WriteAndReadInteTest,
TestMapSharedShreddingReadAfterRenameColumn) {
[0, 2, [["c", 21]]]
])"));
ASSERT_TRUE(success);
+
+ ASSERT_OK_AND_ASSIGN(std::shared_ptr<arrow::Field> access_field,
+ BuildMapSharedShreddingAccessField(fields_v1[1],
{"b", "a"}));
+ auto read_schema = arrow::schema({arrow::field("id", arrow::int32()),
access_field});
+ expected_type = arrow::struct_({
+ arrow::field("_VALUE_KIND", arrow::int8()),
+ arrow::field("id", arrow::int32()),
+ access_field,
+ });
+ ASSERT_OK_AND_ASSIGN(success, ReadAndCheckWithReadSchema(options_v1,
read_schema, expected_type,
+ R"([
+ [0, 1, [12, 11]],
+ [0, 2, [null, null]]
+ ])"));
+ ASSERT_TRUE(success);
}
TEST_P(WriteAndReadInteTest, TestSharedShreddingWithSchemaEvolution) {
@@ -2419,6 +2452,27 @@ TEST_P(WriteAndReadInteTest,
TestSharedShreddingWithSchemaEvolution) {
[0, [["a", 32]], [["c", 52]], "new-2"]
])"));
ASSERT_TRUE(success);
+
+ ASSERT_OK_AND_ASSIGN(std::shared_ptr<arrow::Field> f0_access_field,
+ BuildMapSharedShreddingAccessField(fields_v1[0],
{"z", "a"}));
+ ASSERT_OK_AND_ASSIGN(std::shared_ptr<arrow::Field> f2_access_field,
+ BuildMapSharedShreddingAccessField(fields_v1[3],
{"x", "c"}));
+ auto read_schema =
+ arrow::schema({f0_access_field, f2_access_field, arrow::field("k2",
arrow::utf8())});
+ expected_type = arrow::struct_({
+ arrow::field("_VALUE_KIND", arrow::int8()),
+ f0_access_field,
+ f2_access_field,
+ arrow::field("k2", arrow::utf8()),
+ });
+ ASSERT_OK_AND_ASSIGN(success, ReadAndCheckWithReadSchema(options_v1,
read_schema, expected_type,
+ R"([
+ [0, [11, 10], null, "old-1"],
+ [0, [null, 12], null, "old-2"],
+ [0, [31, 30], [51, 50], "new-1"],
+ [0, [null, 32], [null, 52], "new-2"]
+ ])"));
+ ASSERT_TRUE(success);
}
// Verify storage-layout evolution: default->shared-shredding.
@@ -2480,6 +2534,23 @@ TEST_P(WriteAndReadInteTest,
TestMapStorageLayoutDefaultToSharedShredding) {
[0, 4, [["a", 40]]]
])"));
ASSERT_TRUE(success);
+
+ ASSERT_OK_AND_ASSIGN(std::shared_ptr<arrow::Field> access_field,
+ BuildMapSharedShreddingAccessField(fields[1], {"z",
"a"}));
+ auto read_schema = arrow::schema({arrow::field("id", arrow::int32()),
access_field});
+ auto expected_type = arrow::struct_({
+ arrow::field("_VALUE_KIND", arrow::int8()),
+ arrow::field("id", arrow::int32()),
+ access_field,
+ });
+ ASSERT_OK_AND_ASSIGN(success, ReadAndCheckWithReadSchema(options_v1,
read_schema, expected_type,
+ R"([
+ [0, 1, [11, 10]],
+ [0, 2, null],
+ [0, 3, [31, 30]],
+ [0, 4, [null, 40]]
+ ])"));
+ ASSERT_TRUE(success);
}
// Verify storage-layout evolution: shared-shredding->default.
@@ -2704,6 +2775,22 @@ TEST_P(WriteAndReadInteTest,
TestSharedShreddingWithStructValue) {
[0, 3, null]
])"));
ASSERT_TRUE(success);
+
+ ASSERT_OK_AND_ASSIGN(std::shared_ptr<arrow::Field> access_field,
+ BuildMapSharedShreddingAccessField(fields[1], {"a",
"z"}));
+ auto read_schema = arrow::schema({arrow::field("id", arrow::int32()),
access_field});
+ auto expected_type = arrow::struct_({
+ arrow::field("_VALUE_KIND", arrow::int8()),
+ arrow::field("id", arrow::int32()),
+ access_field,
+ });
+ ASSERT_OK_AND_ASSIGN(success, ReadAndCheckWithReadSchema(options,
read_schema, expected_type,
+ R"([
+ [0, 1, [["alice", 10], ["zoe", 11]]],
+ [0, 2, [["amy", null], null]],
+ [0, 3, null]
+ ])"));
+ ASSERT_TRUE(success);
}
TEST_P(WriteAndReadInteTest, TestMapSharedShreddingWithComplexValue) {
@@ -2798,6 +2885,26 @@ TEST_P(WriteAndReadInteTest,
TestMapSharedShreddingWithComplexValue) {
[0, 3, null]
])"));
ASSERT_TRUE(selected_success);
+
+ ASSERT_OK_AND_ASSIGN(std::shared_ptr<arrow::Field> access_field,
+ BuildMapSharedShreddingAccessField(fields[1], {"z",
"a"}));
+ read_schema = arrow::schema({arrow::field("id", arrow::int32()),
access_field});
+ expected_type = arrow::struct_({
+ arrow::field("_VALUE_KIND", arrow::int8()),
+ arrow::field("id", arrow::int32()),
+ access_field,
+ });
+ ASSERT_OK_AND_ASSIGN(bool access_success,
+ ReadAndCheckWithReadSchema(options, read_schema,
expected_type,
+ R"([
+ [0, 1, [
+ ["zeta", [9], [["iz", 90]]],
+ ["alpha", [1, 2], [["ia", 10], ["ib", 20]]]
+ ]],
+ [0, 2, [null, ["amy", null, [["ia", 30]]]]],
+ [0, 3, null]
+ ])"));
+ ASSERT_TRUE(access_success);
}
TEST_P(WriteAndReadInteTest,
TestMapSharedShreddingWithAllSupportedComplexValueTypes) {
@@ -2926,6 +3033,28 @@ TEST_P(WriteAndReadInteTest,
TestMapSharedShreddingWithAllSupportedComplexValueT
[0, 3, []]
])"));
ASSERT_TRUE(success);
+
+ ASSERT_OK_AND_ASSIGN(std::shared_ptr<arrow::Field> access_field,
+ BuildMapSharedShreddingAccessField(fields[1],
{"fixed-a"}));
+ auto read_schema = arrow::schema({arrow::field("id", arrow::int32()),
access_field});
+ auto expected_type = arrow::struct_({
+ arrow::field("_VALUE_KIND", arrow::int8()),
+ arrow::field("id", arrow::int32()),
+ access_field,
+ });
+ ASSERT_OK_AND_ASSIGN(success, ReadAndCheckWithReadSchema(options,
read_schema, expected_type,
+ R"([
+ [0, 1, [[
+ true, 1, 2, 3, 4, 5.5, 6.25, "str", "bin",
+ "12345678.90", "123456789012345678.12345", 19500,
+ "2023-11-14 22:13:20.123", "2023-11-14 22:13:20.123456789",
+ "2023-11-14 22:13:20.123", "2023-11-14 22:13:20.123456",
+ [7, null, 8], [["m1", 10], ["m2", null]], ["nested", 99]
+ ]]],
+ [0, 2, null],
+ [0, 3, [null]]
+ ])"));
+ ASSERT_TRUE(success);
}
TEST_P(WriteAndReadInteTest,
TestMapSharedShreddingStructValueSchemaEvolutionReadFails) {
@@ -3122,6 +3251,23 @@ TEST_P(WriteAndReadInteTest,
TestOrcDictionaryLazyDecodingWithSharedShredding) {
[0, 4, [["a", "red"]]]
])"));
ASSERT_TRUE(success);
+
+ ASSERT_OK_AND_ASSIGN(std::shared_ptr<arrow::Field> access_field,
+ BuildMapSharedShreddingAccessField(fields[1], {"z",
"a"}));
+ auto read_schema = arrow::schema({arrow::field("id", arrow::int32()),
access_field});
+ auto expected_type = arrow::struct_({
+ arrow::field("_VALUE_KIND", arrow::int8()),
+ arrow::field("id", arrow::int32()),
+ access_field,
+ });
+ ASSERT_OK_AND_ASSIGN(success, ReadAndCheckWithReadSchema(options_v1,
read_schema, expected_type,
+ R"([
+ [0, 1, ["blue", "red"]],
+ [0, 2, ["green", "red"]],
+ [0, 3, ["yellow", "red"]],
+ [0, 4, [null, "red"]]
+ ])"));
+ ASSERT_TRUE(success);
}
// Verify shared-shredding in the PK read path.
@@ -3177,6 +3323,22 @@ TEST_P(WriteAndReadInteTest, TestPkSharedShreddingMap) {
[0, 3, [["c", 30]]]
])"));
ASSERT_TRUE(success);
+
+ ASSERT_OK_AND_ASSIGN(std::shared_ptr<arrow::Field> access_field,
+ BuildMapSharedShreddingAccessField(fields[1], {"a",
"z"}));
+ auto read_schema = arrow::schema({arrow::field("pk", arrow::int32()),
access_field});
+ auto expected_type = arrow::struct_({
+ arrow::field("_VALUE_KIND", arrow::int8()),
+ arrow::field("pk", arrow::int32()),
+ access_field,
+ });
+ ASSERT_OK_AND_ASSIGN(success, ReadAndCheckWithReadSchema(options,
read_schema, expected_type,
+ R"([
+ [0, 1, [100, 101]],
+ [0, 2, [null, null]],
+ [0, 3, [null, null]]
+ ])"));
+ ASSERT_TRUE(success);
}
TEST_P(WriteAndReadInteTest, TestSharedShreddingPartialKeyRecallWithOverflow) {
@@ -3290,6 +3452,28 @@ TEST_P(WriteAndReadInteTest,
TestSharedShreddingPartialKeyRecallWithOverflow) {
])"));
ASSERT_TRUE(success);
}
+
+ // Sub-case 4: selected keys are exposed as STRUCT children instead of a
filtered MAP.
+ {
+ ASSERT_OK_AND_ASSIGN(
+ std::shared_ptr<arrow::Field> access_field,
+ BuildMapSharedShreddingAccessField(arrow::field("tags", map_type),
{"c", "a"}));
+
+ auto read_schema = arrow::schema({arrow::field("id", arrow::int32()),
access_field});
+ auto expected_type = arrow::struct_({
+ arrow::field("_VALUE_KIND", arrow::int8()),
+ arrow::field("id", arrow::int32()),
+ access_field,
+ });
+ ASSERT_OK_AND_ASSIGN(bool success,
+ ReadAndCheckWithReadSchema(options, read_schema,
expected_type,
+ R"([
+ [0, 1, [3, 1]],
+ [0, 2, [null, 10]],
+ [0, 3, null]
+ ])"));
+ ASSERT_TRUE(success);
+ }
}
TEST_P(WriteAndReadInteTest,
TestSharedShreddingPartialKeyRecallWithNullOrMissingKey) {
@@ -3381,6 +3565,26 @@ TEST_P(WriteAndReadInteTest,
TestSharedShreddingPartialKeyRecallWithNullOrMissin
])"));
ASSERT_TRUE(success);
}
+
+ // Sub-case 5: expose an existing and a never-written key as STRUCT
children.
+ {
+ ASSERT_OK_AND_ASSIGN(std::shared_ptr<arrow::Field> access_field,
+ BuildMapSharedShreddingAccessField(fields[1],
{"a", "nonexistent"}));
+ auto read_schema = arrow::schema({arrow::field("id", arrow::int32()),
access_field});
+ auto expected_type = arrow::struct_({
+ arrow::field("_VALUE_KIND", arrow::int8()),
+ arrow::field("id", arrow::int32()),
+ access_field,
+ });
+ ASSERT_OK_AND_ASSIGN(bool success,
+ ReadAndCheckWithReadSchema(options, read_schema,
expected_type,
+ R"([
+ [0, 1, null],
+ [0, 2, [null, null]],
+ [0, 3, [30, null]]
+ ])"));
+ ASSERT_TRUE(success);
+ }
}
TEST_P(WriteAndReadInteTest,
TestSharedShreddingPartialKeyRecallMultipleColumns) {
@@ -3470,6 +3674,29 @@ TEST_P(WriteAndReadInteTest,
TestSharedShreddingPartialKeyRecallMultipleColumns)
])"));
ASSERT_TRUE(success);
}
+
+ // Sub-case 3: expose selected keys from multiple MAP columns as
independent STRUCTs.
+ {
+ ASSERT_OK_AND_ASSIGN(std::shared_ptr<arrow::Field> tags_access_field,
+ BuildMapSharedShreddingAccessField(fields[1],
{"a", "b"}));
+ ASSERT_OK_AND_ASSIGN(std::shared_ptr<arrow::Field>
metrics_access_field,
+ BuildMapSharedShreddingAccessField(fields[2],
{"x"}));
+ auto read_schema = arrow::schema(
+ {arrow::field("id", arrow::int32()), tags_access_field,
metrics_access_field});
+ auto expected_type = arrow::struct_({
+ arrow::field("_VALUE_KIND", arrow::int8()),
+ arrow::field("id", arrow::int32()),
+ tags_access_field,
+ metrics_access_field,
+ });
+ ASSERT_OK_AND_ASSIGN(bool success,
+ ReadAndCheckWithReadSchema(options, read_schema,
expected_type,
+ R"([
+ [0, 1, [1, 2], [100]],
+ [0, 2, [10, null], [1000]]
+ ])"));
+ ASSERT_TRUE(success);
+ }
}
TEST_P(WriteAndReadInteTest,
TestMapStorageLayoutDefaultToSharedShreddingPartialKeyRecall) {
@@ -3543,6 +3770,23 @@ TEST_P(WriteAndReadInteTest,
TestMapStorageLayoutDefaultToSharedShreddingPartial
[0, 4, []]
])"));
ASSERT_TRUE(success);
+
+ ASSERT_OK_AND_ASSIGN(std::shared_ptr<arrow::Field> access_field,
+
BuildMapSharedShreddingAccessField(arrow::field("tags", map_type), {"a"}));
+ read_schema = arrow::schema({arrow::field("id", arrow::int32()),
access_field});
+ expected_type = arrow::struct_({
+ arrow::field("_VALUE_KIND", arrow::int8()),
+ arrow::field("id", arrow::int32()),
+ access_field,
+ });
+ ASSERT_OK_AND_ASSIGN(success, ReadAndCheckWithReadSchema(options_v1,
read_schema, expected_type,
+ R"([
+ [0, 1, [10]],
+ [0, 2, null],
+ [0, 3, [30]],
+ [0, 4, [null]]
+ ])"));
+ ASSERT_TRUE(success);
}
TEST_P(WriteAndReadInteTest,
TestMapStorageLayoutSharedShreddingToDefaultPartialKeyRecall) {
@@ -3620,6 +3864,24 @@ TEST_P(WriteAndReadInteTest,
TestMapStorageLayoutSharedShreddingToDefaultPartial
[0, 4, null]
])"));
ASSERT_TRUE(success);
+
+ ASSERT_OK_AND_ASSIGN(std::shared_ptr<arrow::Field> access_field,
+ BuildMapSharedShreddingAccessField(fields[1], {"a"}));
+ read_schema = arrow::schema({arrow::field("id", arrow::int32()),
access_field});
+ expected_type = arrow::struct_({
+ arrow::field("_VALUE_KIND", arrow::int8()),
+ arrow::field("id", arrow::int32()),
+ access_field,
+ });
+ ASSERT_NOK_WITH_MSG(ReadAndCheckWithReadSchema(options_v1, read_schema,
expected_type,
+ R"([
+ [0, 1, [10]],
+ [0, 2, [null]],
+ [0, 3, [30]],
+ [0, 4, null]
+ ])"),
+ "Selected-key MAP pushdown only supports top-level
shared-shredding MAP "
+ "field: tags");
}
TEST_P(WriteAndReadInteTest, TestSharedShreddingDuplicateSelectedKeys) {
@@ -3720,6 +3982,22 @@ TEST_P(WriteAndReadInteTest,
TestSharedShreddingAllNullMapColumn) {
[0, 3, null]
])"));
ASSERT_TRUE(success);
+
+ ASSERT_OK_AND_ASSIGN(std::shared_ptr<arrow::Field> access_field,
+ BuildMapSharedShreddingAccessField(fields[1], {"a"}));
+ auto read_schema = arrow::schema({arrow::field("id", arrow::int32()),
access_field});
+ auto expected_type = arrow::struct_({
+ arrow::field("_VALUE_KIND", arrow::int8()),
+ arrow::field("id", arrow::int32()),
+ access_field,
+ });
+ ASSERT_OK_AND_ASSIGN(success, ReadAndCheckWithReadSchema(options,
read_schema, expected_type,
+ R"([
+ [0, 1, null],
+ [0, 2, null],
+ [0, 3, null]
+ ])"));
+ ASSERT_TRUE(success);
}
INSTANTIATE_TEST_SUITE_P(FileFormatAndFileSystem, WriteAndReadInteTest,