Skip to content

Commit 1770fee

Browse files
authored
feat(shredding): support shared-shredding map write (alibaba#364)
1 parent 7acda1d commit 1770fee

62 files changed

Lines changed: 3749 additions & 270 deletions

File tree

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

include/paimon/format/format_writer.h

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,8 +16,11 @@
1616

1717
#pragma once
1818

19+
#include <map>
1920
#include <memory>
21+
#include <string>
2022

23+
#include "paimon/status.h"
2124
#include "paimon/type_fwd.h"
2225

2326
struct ArrowArray;
@@ -66,6 +69,10 @@ class PAIMON_EXPORT FormatWriter {
6669
/// Get metrics of the writer
6770
/// @return The accumulated writer metrics to current state.
6871
virtual std::shared_ptr<Metrics> GetWriterMetrics() const = 0;
72+
73+
/// Adds metadata to the file footer. Values are encoded by each format writer
74+
/// before being persisted. Must be called before Finish().
75+
virtual Status AddMetadata(const std::map<std::string, std::string>& metadata) = 0;
6976
};
7077

7178
} // namespace paimon

src/paimon/CMakeLists.txt

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -139,6 +139,9 @@ set(PAIMON_COMMON_SRCS
139139
common/utils/crc32c.cpp
140140
common/utils/decimal_utils.cpp
141141
common/data/shredding/map_shared_shredding_utils.cpp
142+
common/data/shredding/map_shared_shredding_context.cpp
143+
common/data/shredding/map_shared_shredding_batch_converter.cpp
144+
common/data/shredding/map_shared_shredding_column_allocator.cpp
142145
common/utils/delta_varint_compressor.cpp
143146
common/utils/fields_comparator.cpp
144147
common/utils/path_util.cpp
@@ -534,6 +537,10 @@ if(PAIMON_BUILD_TESTS)
534537
common/utils/threadsafe_queue_test.cpp
535538
common/utils/generic_lru_cache_test.cpp
536539
common/data/shredding/map_shared_shredding_utils_test.cpp
540+
common/data/shredding/map_shared_shredding_batch_converter_test.cpp
541+
common/data/shredding/map_shared_shredding_column_allocator_test.cpp
542+
common/data/shredding/map_shared_shredding_field_dict_test.cpp
543+
common/data/shredding/map_shared_shredding_context_test.cpp
537544
STATIC_LINK_LIBS
538545
paimon_shared
539546
test_utils_static
Lines changed: 314 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,314 @@
1+
/*
2+
* Copyright 2026-present Alibaba Inc.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
#include "paimon/common/data/shredding/map_shared_shredding_batch_converter.h"
18+
19+
#include <algorithm>
20+
#include <string>
21+
#include <utility>
22+
23+
#include "arrow/array.h"
24+
#include "arrow/builder.h"
25+
#include "arrow/c/bridge.h"
26+
#include "arrow/type.h"
27+
#include "fmt/format.h"
28+
#include "paimon/common/data/shredding/map_shared_shredding_context.h"
29+
#include "paimon/common/data/shredding/map_shared_shredding_utils.h"
30+
#include "paimon/common/data/shredding/map_shredding_defs.h"
31+
#include "paimon/common/utils/arrow/mem_utils.h"
32+
#include "paimon/common/utils/arrow/status_utils.h"
33+
namespace paimon {
34+
/// Checks that a dynamic_cast result is not null, returning Status::Invalid on failure.
35+
#define PAIMON_CHECK_NOT_NULL(ptr, msg) \
36+
do { \
37+
if (PAIMON_UNLIKELY((ptr) == nullptr)) { \
38+
return Status::Invalid(msg); \
39+
} \
40+
} while (false)
41+
42+
Result<MapSharedShreddingBatchConverter::ConverterBundle>
43+
MapSharedShreddingBatchConverter::CreateConverter(
44+
const std::shared_ptr<arrow::Schema>& logical_schema,
45+
const std::shared_ptr<MapSharedShreddingContext>& context,
46+
const std::shared_ptr<MemoryPool>& pool) {
47+
ConverterBundle bundle;
48+
if (!context) {
49+
return bundle;
50+
}
51+
52+
std::map<std::string, int32_t> field_to_k = context->ComputeNextK();
53+
PAIMON_ASSIGN_OR_RAISE(bundle.physical_schema, MapSharedShreddingUtils::LogicalToPhysicalSchema(
54+
logical_schema, field_to_k));
55+
bundle.converter = std::make_shared<MapSharedShreddingBatchConverter>(
56+
logical_schema, bundle.physical_schema, field_to_k, pool);
57+
return bundle;
58+
}
59+
60+
MapSharedShreddingBatchConverter::MapSharedShreddingBatchConverter(
61+
const std::shared_ptr<arrow::Schema>& logical_schema,
62+
const std::shared_ptr<arrow::Schema>& physical_schema,
63+
const std::map<std::string, int32_t>& field_to_num_columns,
64+
const std::shared_ptr<MemoryPool>& pool)
65+
: logical_schema_(logical_schema),
66+
physical_schema_(physical_schema),
67+
pool_(GetArrowPool(pool)) {
68+
// Iterate in schema field order (not map order) so that shredding_field_names_
69+
// matches the order in which shredding columns appear in the schema.
70+
// This is critical for the sequential matching logic in Convert().
71+
for (int32_t i = 0; i < logical_schema->num_fields(); ++i) {
72+
const std::string& name = logical_schema->field(i)->name();
73+
auto it = field_to_num_columns.find(name);
74+
if (it != field_to_num_columns.end()) {
75+
contexts_.emplace_back(name, it->second);
76+
shredding_field_names_.push_back(name);
77+
}
78+
}
79+
}
80+
81+
Result<std::unique_ptr<ArrowArray>> MapSharedShreddingBatchConverter::Convert(
82+
ArrowArray* logical_batch) {
83+
std::shared_ptr<arrow::DataType> logical_type = arrow::struct_(logical_schema_->fields());
84+
PAIMON_ASSIGN_OR_RAISE_FROM_ARROW(std::shared_ptr<arrow::Array> logical_array,
85+
arrow::ImportArray(logical_batch, logical_type));
86+
auto logical_struct = std::dynamic_pointer_cast<arrow::StructArray>(logical_array);
87+
PAIMON_CHECK_NOT_NULL(logical_struct,
88+
"MapSharedShreddingBatchConverter: input is not a StructArray");
89+
90+
int32_t num_fields = logical_schema_->num_fields();
91+
arrow::ArrayVector physical_columns;
92+
physical_columns.reserve(num_fields);
93+
size_t context_idx = 0;
94+
for (int32_t col = 0; col < num_fields; ++col) {
95+
auto column = logical_struct->field(col);
96+
const std::string& field_name = logical_schema_->field(col)->name();
97+
if (context_idx < shredding_field_names_.size() &&
98+
shredding_field_names_[context_idx] == field_name) {
99+
auto physical_struct_type = physical_schema_->field(col)->type();
100+
PAIMON_ASSIGN_OR_RAISE(
101+
std::shared_ptr<arrow::Array> physical_column,
102+
ConvertOneColumn(column, physical_struct_type, &contexts_[context_idx]));
103+
physical_columns.push_back(std::move(physical_column));
104+
++context_idx;
105+
} else {
106+
physical_columns.push_back(column);
107+
}
108+
}
109+
110+
PAIMON_ASSIGN_OR_RAISE_FROM_ARROW(
111+
std::shared_ptr<arrow::Array> physical_struct,
112+
arrow::StructArray::Make(physical_columns, physical_schema_->field_names()));
113+
114+
std::unique_ptr<ArrowArray> result = std::make_unique<ArrowArray>();
115+
PAIMON_RETURN_NOT_OK_FROM_ARROW(arrow::ExportArray(*physical_struct, result.get()));
116+
return result;
117+
}
118+
119+
Result<std::shared_ptr<arrow::Array>> MapSharedShreddingBatchConverter::ConvertOneColumn(
120+
const std::shared_ptr<arrow::Array>& map_column,
121+
const std::shared_ptr<arrow::DataType>& physical_struct_type, ColumnContext* context) const {
122+
auto map_array = std::dynamic_pointer_cast<arrow::MapArray>(map_column);
123+
PAIMON_CHECK_NOT_NULL(map_array, "MapSharedShreddingBatchConverter: column is not a MapArray");
124+
125+
int64_t num_rows = map_array->length();
126+
int32_t num_cols = context->num_columns;
127+
128+
auto keys_array = std::dynamic_pointer_cast<arrow::StringArray>(map_array->keys());
129+
PAIMON_CHECK_NOT_NULL(keys_array,
130+
"MapSharedShreddingBatchConverter: MAP keys are not StringArray");
131+
auto values_array = map_array->items();
132+
133+
// Create StructBuilder from physical struct type — it owns all child builders.
134+
PAIMON_ASSIGN_OR_RAISE_FROM_ARROW(std::unique_ptr<arrow::ArrayBuilder> struct_builder_base,
135+
arrow::MakeBuilder(physical_struct_type, pool_.get()));
136+
auto* struct_builder = dynamic_cast<arrow::StructBuilder*>(struct_builder_base.get());
137+
PAIMON_CHECK_NOT_NULL(struct_builder,
138+
"MapSharedShreddingBatchConverter: failed to create StructBuilder");
139+
PAIMON_RETURN_NOT_OK_FROM_ARROW(struct_builder->Reserve(num_rows));
140+
141+
// Extract child builders: [field_mapping, col_0..K-1, overflow]
142+
auto* field_mapping_builder =
143+
dynamic_cast<arrow::ListBuilder*>(struct_builder->field_builder(0));
144+
PAIMON_CHECK_NOT_NULL(field_mapping_builder,
145+
"MapSharedShreddingBatchConverter: field_mapping is not a ListBuilder");
146+
auto* field_mapping_value_builder =
147+
dynamic_cast<arrow::Int32Builder*>(field_mapping_builder->value_builder());
148+
PAIMON_CHECK_NOT_NULL(
149+
field_mapping_value_builder,
150+
"MapSharedShreddingBatchConverter: field_mapping value is not Int32Builder");
151+
PAIMON_RETURN_NOT_OK_FROM_ARROW(field_mapping_builder->Reserve(num_rows));
152+
PAIMON_RETURN_NOT_OK_FROM_ARROW(field_mapping_value_builder->Reserve(num_rows * num_cols));
153+
154+
std::vector<arrow::ArrayBuilder*> col_builders_raw;
155+
col_builders_raw.reserve(num_cols);
156+
for (int32_t c = 0; c < num_cols; ++c) {
157+
arrow::ArrayBuilder* col_builder = struct_builder->field_builder(1 + c);
158+
PAIMON_CHECK_NOT_NULL(col_builder, "MapSharedShreddingBatchConverter: col builder is null");
159+
PAIMON_RETURN_NOT_OK_FROM_ARROW(col_builder->Reserve(num_rows));
160+
col_builders_raw.push_back(col_builder);
161+
}
162+
163+
int32_t overflow_field_idx = 1 + num_cols;
164+
auto* overflow_builder =
165+
dynamic_cast<arrow::MapBuilder*>(struct_builder->field_builder(overflow_field_idx));
166+
PAIMON_CHECK_NOT_NULL(overflow_builder,
167+
"MapSharedShreddingBatchConverter: overflow is not a MapBuilder");
168+
auto* overflow_key_builder =
169+
dynamic_cast<arrow::Int32Builder*>(overflow_builder->key_builder());
170+
PAIMON_CHECK_NOT_NULL(overflow_key_builder,
171+
"MapSharedShreddingBatchConverter: overflow key is not Int32Builder");
172+
arrow::ArrayBuilder* overflow_value_builder = overflow_builder->item_builder();
173+
PAIMON_CHECK_NOT_NULL(overflow_value_builder,
174+
"MapSharedShreddingBatchConverter: overflow value builder is null");
175+
PAIMON_RETURN_NOT_OK_FROM_ARROW(overflow_builder->Reserve(num_rows));
176+
177+
// Process each row
178+
for (int64_t row = 0; row < num_rows; ++row) {
179+
if (map_array->IsNull(row)) {
180+
// StructBuilder::AppendNull() auto-appends empty values to all children.
181+
PAIMON_RETURN_NOT_OK_FROM_ARROW(struct_builder->AppendNull());
182+
continue;
183+
}
184+
185+
PAIMON_RETURN_NOT_OK_FROM_ARROW(struct_builder->Append());
186+
187+
int64_t start = map_array->value_offset(row);
188+
int64_t length = map_array->value_length(row);
189+
190+
// Extract field ids and build lookup map
191+
std::vector<int32_t> field_ids;
192+
std::unordered_map<int32_t, int64_t> field_id_to_value_index;
193+
ExtractRowFields(keys_array, start, length, &context->dict, &field_ids,
194+
&field_id_to_value_index);
195+
196+
// Allocate columns
197+
RowAllocation allocation = context->allocator.AllocateRow(field_ids);
198+
199+
// Fill sub-columns
200+
PAIMON_RETURN_NOT_OK(AppendFieldMapping(allocation, num_cols, field_mapping_builder,
201+
field_mapping_value_builder));
202+
PAIMON_RETURN_NOT_OK(AppendColumnValues(values_array, allocation, field_id_to_value_index,
203+
num_cols, col_builders_raw));
204+
PAIMON_RETURN_NOT_OK(AppendOverflow(values_array, allocation, field_id_to_value_index,
205+
overflow_builder, overflow_key_builder,
206+
overflow_value_builder));
207+
}
208+
209+
// Finalize
210+
std::shared_ptr<arrow::StructArray> result;
211+
PAIMON_RETURN_NOT_OK_FROM_ARROW(struct_builder->Finish(&result));
212+
return result;
213+
}
214+
215+
void MapSharedShreddingBatchConverter::ExtractRowFields(
216+
const std::shared_ptr<arrow::StringArray>& keys_array, int64_t start, int64_t length,
217+
MapSharedShreddingFieldDict* dict, std::vector<int32_t>* field_ids_out,
218+
std::unordered_map<int32_t, int64_t>* field_id_to_value_index_out) const {
219+
field_ids_out->clear();
220+
field_ids_out->reserve(length);
221+
field_id_to_value_index_out->clear();
222+
field_id_to_value_index_out->reserve(length);
223+
for (int64_t j = 0; j < length; ++j) {
224+
std::string key_str = keys_array->GetString(start + j);
225+
int32_t field_id = dict->GetOrAssign(key_str);
226+
field_ids_out->push_back(field_id);
227+
(*field_id_to_value_index_out)[field_id] = start + j;
228+
}
229+
}
230+
231+
Status MapSharedShreddingBatchConverter::AppendFieldMapping(
232+
const RowAllocation& allocation, int32_t num_cols, arrow::ListBuilder* list_builder,
233+
arrow::Int32Builder* value_builder) const {
234+
PAIMON_RETURN_NOT_OK_FROM_ARROW(list_builder->Append());
235+
for (int32_t c = 0; c < num_cols; ++c) {
236+
PAIMON_RETURN_NOT_OK_FROM_ARROW(value_builder->Append(allocation.col_to_field[c]));
237+
}
238+
return Status::OK();
239+
}
240+
241+
Status MapSharedShreddingBatchConverter::AppendColumnValues(
242+
const std::shared_ptr<arrow::Array>& values_array, const RowAllocation& allocation,
243+
const std::unordered_map<int32_t, int64_t>& field_id_to_value_index, int32_t num_cols,
244+
const std::vector<arrow::ArrayBuilder*>& col_builders) const {
245+
for (int32_t c = 0; c < num_cols; ++c) {
246+
int32_t assigned_field_id = allocation.col_to_field[c];
247+
if (assigned_field_id == -1) {
248+
PAIMON_RETURN_NOT_OK_FROM_ARROW(col_builders[c]->AppendNull());
249+
} else {
250+
auto it = field_id_to_value_index.find(assigned_field_id);
251+
if (PAIMON_UNLIKELY(it == field_id_to_value_index.end())) {
252+
return Status::Invalid(
253+
fmt::format("MapSharedShreddingBatchConverter: field_id {} assigned to col {} "
254+
"but not found in current row",
255+
assigned_field_id, c));
256+
}
257+
PAIMON_RETURN_NOT_OK_FROM_ARROW(
258+
col_builders[c]->AppendArraySlice(*values_array->data(), it->second, 1));
259+
}
260+
}
261+
return Status::OK();
262+
}
263+
264+
Status MapSharedShreddingBatchConverter::AppendOverflow(
265+
const std::shared_ptr<arrow::Array>& values_array, const RowAllocation& allocation,
266+
const std::unordered_map<int32_t, int64_t>& field_id_to_value_index,
267+
arrow::MapBuilder* overflow_builder, arrow::Int32Builder* overflow_key_builder,
268+
arrow::ArrayBuilder* overflow_value_builder) const {
269+
if (allocation.overflow_fields.empty()) {
270+
PAIMON_RETURN_NOT_OK_FROM_ARROW(overflow_builder->AppendNull());
271+
return Status::OK();
272+
}
273+
274+
PAIMON_RETURN_NOT_OK_FROM_ARROW(overflow_builder->Append());
275+
for (int32_t overflow_field_id : allocation.overflow_fields) {
276+
auto it = field_id_to_value_index.find(overflow_field_id);
277+
if (PAIMON_UNLIKELY(it == field_id_to_value_index.end())) {
278+
return Status::Invalid(fmt::format(
279+
"MapSharedShreddingBatchConverter: overflow field_id {} not found in current row",
280+
overflow_field_id));
281+
}
282+
PAIMON_RETURN_NOT_OK_FROM_ARROW(overflow_key_builder->Append(overflow_field_id));
283+
PAIMON_RETURN_NOT_OK_FROM_ARROW(
284+
overflow_value_builder->AppendArraySlice(*values_array->data(), it->second, 1));
285+
}
286+
return Status::OK();
287+
}
288+
289+
Result<MapSharedShreddingFieldMeta> MapSharedShreddingBatchConverter::BuildFieldMeta(
290+
const std::string& field_name) const {
291+
for (const auto& context : contexts_) {
292+
if (context.field_name == field_name) {
293+
MapSharedShreddingFieldMeta meta;
294+
meta.name_to_id = context.dict.GetNameToId();
295+
// Convert set<int32_t> -> vector<int32_t> for field_to_columns
296+
for (const auto& [field_id, col_set] : context.allocator.GetFieldToColumns()) {
297+
meta.field_to_columns[field_id] =
298+
std::vector<int32_t>(col_set.begin(), col_set.end());
299+
}
300+
meta.overflow_field_set = context.allocator.GetOverflowFieldSet();
301+
meta.num_columns = context.allocator.GetNumColumns();
302+
meta.max_row_width = context.allocator.GetMaxRowWidth();
303+
return meta;
304+
}
305+
}
306+
return Status::Invalid(fmt::format(
307+
"cannot find field_name '{}' in MapSharedShreddingBatchConverter contexts", field_name));
308+
}
309+
310+
const std::vector<std::string>& MapSharedShreddingBatchConverter::GetShreddingColumnNames() const {
311+
return shredding_field_names_;
312+
}
313+
314+
} // namespace paimon

0 commit comments

Comments
 (0)