Skip to content

Commit f5dcf6b

Browse files
authored
feat(shredding): support shared shredding file reader (alibaba#376)
1 parent 77aff80 commit f5dcf6b

7 files changed

Lines changed: 1343 additions & 11 deletions

src/paimon/CMakeLists.txt

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -142,6 +142,7 @@ set(PAIMON_COMMON_SRCS
142142
common/data/shredding/map_shared_shredding_context.cpp
143143
common/data/shredding/map_shared_shredding_batch_converter.cpp
144144
common/data/shredding/map_shared_shredding_column_allocator.cpp
145+
common/data/shredding/shared_shredding_file_reader.cpp
145146
common/utils/delta_varint_compressor.cpp
146147
common/utils/fields_comparator.cpp
147148
common/utils/path_util.cpp
@@ -541,6 +542,7 @@ if(PAIMON_BUILD_TESTS)
541542
common/data/shredding/map_shared_shredding_column_allocator_test.cpp
542543
common/data/shredding/map_shared_shredding_field_dict_test.cpp
543544
common/data/shredding/map_shared_shredding_context_test.cpp
545+
common/data/shredding/shared_shredding_file_reader_test.cpp
544546
STATIC_LINK_LIBS
545547
paimon_shared
546548
test_utils_static

src/paimon/common/data/shredding/map_shared_shredding_utils.cpp

Lines changed: 50 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -35,8 +35,23 @@
3535
#include "rapidjson/writer.h"
3636

3737
namespace paimon {
38-
// ---- Column detection ----
38+
Result<std::vector<int32_t>> MapSharedShreddingUtils::GetPhysicalColumnIndices(
39+
const MapSharedShreddingFieldMeta& meta, const std::string& name) {
40+
auto name_iter = meta.name_to_id.find(name);
41+
if (name_iter == meta.name_to_id.end()) {
42+
return Status::Invalid(
43+
fmt::format("cannot find field {} in map shared shredding meta", name));
44+
}
45+
auto id_iter = meta.field_to_columns.find(name_iter->second);
46+
if (id_iter == meta.field_to_columns.end()) {
47+
return Status::Invalid(
48+
fmt::format("cannot find field id {} in field_to_columns in map shared shredding meta",
49+
name_iter->second));
50+
}
51+
return id_iter->second;
52+
}
3953

54+
// ---- Column detection ----
4055
bool MapSharedShreddingUtils::IsShreddingKeyMap(
4156
const std::shared_ptr<arrow::DataType>& arrow_type) {
4257
if (arrow_type->id() != arrow::Type::MAP) {
@@ -75,24 +90,38 @@ Result<std::shared_ptr<MapSharedShreddingContext>> MapSharedShreddingUtils::Crea
7590
}
7691

7792
// ---- Schema conversion ----
93+
std::shared_ptr<arrow::DataType> MapSharedShreddingUtils::BuildSpecificPhysicalStructType(
94+
const std::shared_ptr<arrow::DataType>& value_type, const std::set<int32_t>& physical_col_ids,
95+
bool value_nullable, bool include_overflow) {
96+
std::vector<int32_t> sorted_cols(physical_col_ids.begin(), physical_col_ids.end());
97+
return InnerBuildSpecificPhysicalStructType(value_type, sorted_cols, value_nullable,
98+
include_overflow);
99+
}
78100

79101
std::shared_ptr<arrow::DataType> MapSharedShreddingUtils::BuildPhysicalStructType(
80102
const std::shared_ptr<arrow::DataType>& value_type, int32_t num_columns, bool value_nullable) {
81-
arrow::FieldVector struct_fields;
82-
struct_fields.reserve(num_columns + 2);
103+
std::vector<int32_t> sorted_cols(num_columns);
104+
std::iota(sorted_cols.begin(), sorted_cols.end(), 0);
105+
return InnerBuildSpecificPhysicalStructType(value_type, sorted_cols, value_nullable,
106+
/*include_overflow=*/true);
107+
}
83108

109+
std::shared_ptr<arrow::DataType> MapSharedShreddingUtils::InnerBuildSpecificPhysicalStructType(
110+
const std::shared_ptr<arrow::DataType>& value_type, const std::vector<int32_t>& sorted_cols,
111+
bool value_nullable, bool include_overflow) {
112+
arrow::FieldVector struct_fields;
113+
struct_fields.reserve(sorted_cols.size() + 2);
84114
struct_fields.push_back(
85115
arrow::field(MapSharedShreddingDefine::kFieldMapping, arrow::list(arrow::int32()), true));
86-
87-
for (int32_t i = 0; i < num_columns; ++i) {
88-
struct_fields.push_back(arrow::field(MapSharedShreddingDefine::PhysicalColumnName(i),
116+
for (const auto& col : sorted_cols) {
117+
struct_fields.push_back(arrow::field(MapSharedShreddingDefine::PhysicalColumnName(col),
89118
value_type, value_nullable));
90119
}
91-
92-
struct_fields.push_back(arrow::field(
93-
MapSharedShreddingDefine::kOverflow,
94-
arrow::map(arrow::int32(), arrow::field("value", value_type, value_nullable)), true));
95-
120+
if (include_overflow) {
121+
struct_fields.push_back(arrow::field(
122+
MapSharedShreddingDefine::kOverflow,
123+
arrow::map(arrow::int32(), arrow::field("value", value_type, value_nullable)), true));
124+
}
96125
return arrow::struct_(std::move(struct_fields));
97126
}
98127

@@ -400,6 +429,16 @@ bool MapSharedShreddingUtils::HasShreddingMetadata(
400429
return metadata->value(index) == MapShreddingDefine::kStorageLayoutSharedShredding;
401430
}
402431

432+
Result<bool> MapSharedShreddingUtils::IsOverflowField(const MapSharedShreddingFieldMeta& meta,
433+
const std::string& name) {
434+
auto name_iter = meta.name_to_id.find(name);
435+
if (name_iter == meta.name_to_id.end()) {
436+
return Status::Invalid(
437+
fmt::format("cannot find field {} in map shared shredding meta", name));
438+
}
439+
return meta.overflow_field_set.count(name_iter->second) > 0;
440+
}
441+
403442
std::function<Result<std::shared_ptr<arrow::Schema>>()>
404443
MapSharedShreddingUtils::BuildMetadataFinalizer(
405444
const std::shared_ptr<MapSharedShreddingBatchConverter>& converter,

src/paimon/common/data/shredding/map_shared_shredding_utils.h

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@
2020
#include <functional>
2121
#include <map>
2222
#include <memory>
23+
#include <set>
2324
#include <string>
2425
#include <vector>
2526

@@ -45,6 +46,13 @@ class MapSharedShreddingUtils {
4546
MapSharedShreddingUtils() = delete;
4647
~MapSharedShreddingUtils() = delete;
4748

49+
/// Returns the physical column indices for the given field name from the shredding meta.
50+
/// @param meta The shredding field meta parsed from file footer.
51+
/// @param name The field name to look up.
52+
/// @return Vector of physical column indices assigned to this field,
53+
/// or Status::Invalid if the field name or field id is not found.
54+
static Result<std::vector<int32_t>> GetPhysicalColumnIndices(
55+
const MapSharedShreddingFieldMeta& meta, const std::string& name);
4856
// ---- Column detection ----
4957

5058
/// Checks whether a given arrow field is MAP<STRING, T> (the type prerequisite for shredding).
@@ -80,6 +88,15 @@ class MapSharedShreddingUtils {
8088
const std::shared_ptr<arrow::Schema>& logical_schema,
8189
const std::map<std::string, int32_t>& field_to_num_columns);
8290

91+
/// Builds the physical Arrow type for one shredding MAP column with physical_col_ids.
92+
/// @param value_type The value type of the original MAP.
93+
/// @param physical_col_ids The set of physical column ids to include.
94+
/// @param value_nullable Whether the MAP's value field is nullable.
95+
/// @param include_overflow Whether to include __overflow column.
96+
static std::shared_ptr<arrow::DataType> BuildSpecificPhysicalStructType(
97+
const std::shared_ptr<arrow::DataType>& value_type,
98+
const std::set<int32_t>& physical_col_ids, bool value_nullable, bool include_overflow);
99+
83100
/// Builds field_to_num_columns map from DetectShreddingColumns result and CoreOptions.
84101
/// @param shredding_field_names Field names returned by DetectShreddingColumns.
85102
/// @param options CoreOptions containing per-column max-columns config.
@@ -107,6 +124,10 @@ class MapSharedShreddingUtils {
107124
/// Checks whether a KeyValueMetadata contains shredding MAP metadata.
108125
static bool HasShreddingMetadata(const std::shared_ptr<arrow::KeyValueMetadata>& metadata);
109126

127+
/// Checks whether a field in MapSharedShreddingFieldMeta is a overflow field.
128+
static Result<bool> IsOverflowField(const MapSharedShreddingFieldMeta& meta,
129+
const std::string& name);
130+
110131
// ---- Writer helpers ----
111132

112133
/// Builds a MetadataFinalizer that serializes shredding metadata into per-field
@@ -131,6 +152,15 @@ class MapSharedShreddingUtils {
131152
static std::shared_ptr<arrow::DataType> BuildPhysicalStructType(
132153
const std::shared_ptr<arrow::DataType>& value_type, int32_t num_columns,
133154
bool value_nullable);
155+
156+
/// Builds the physical Arrow type for one shredding MAP column with sorted_cols.
157+
/// @param value_type The value type of the original MAP.
158+
/// @param sorted_cols The vector of physical column ids to include.
159+
/// @param value_nullable Whether the MAP's value field is nullable.
160+
/// @param include_overflow Whether to include __overflow column.
161+
static std::shared_ptr<arrow::DataType> InnerBuildSpecificPhysicalStructType(
162+
const std::shared_ptr<arrow::DataType>& value_type, const std::vector<int32_t>& sorted_cols,
163+
bool value_nullable, bool include_overflow);
134164
};
135165

136166
} // namespace paimon

src/paimon/common/data/shredding/map_shared_shredding_utils_test.cpp

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

1717
#include "paimon/common/data/shredding/map_shared_shredding_utils.h"
1818

19+
#include <set>
20+
1921
#include "arrow/type.h"
2022
#include "arrow/util/key_value_metadata.h"
2123
#include "gtest/gtest.h"
@@ -162,6 +164,38 @@ TEST(MapSharedShreddingUtilsTest, LogicalToPhysicalSchemaNoShreddingColumns) {
162164
ASSERT_TRUE(physical_schema->Equals(schema));
163165
}
164166

167+
TEST(MapSharedShreddingUtilsTest, BuildSpecificPhysicalStructTypeWithOverflow) {
168+
auto actual = MapSharedShreddingUtils::BuildSpecificPhysicalStructType(
169+
arrow::int64(), /*physical_col_ids=*/{3, 1}, /*value_nullable=*/false,
170+
/*include_overflow=*/true);
171+
172+
auto expected = arrow::struct_({
173+
arrow::field("__field_mapping", arrow::list(arrow::int32()), true),
174+
arrow::field("__col_1", arrow::int64(), false),
175+
arrow::field("__col_3", arrow::int64(), false),
176+
arrow::field("__overflow",
177+
arrow::map(arrow::int32(), arrow::field("value", arrow::int64(), false)),
178+
true),
179+
});
180+
ASSERT_TRUE(actual->Equals(*expected)) << "Expected:\n"
181+
<< expected->ToString() << "\nActual:\n"
182+
<< actual->ToString();
183+
}
184+
185+
TEST(MapSharedShreddingUtilsTest, BuildSpecificPhysicalStructTypeWithoutOverflow) {
186+
auto actual = MapSharedShreddingUtils::BuildSpecificPhysicalStructType(
187+
arrow::utf8(), /*physical_col_ids=*/{3}, /*value_nullable=*/true,
188+
/*include_overflow=*/false);
189+
190+
auto expected = arrow::struct_({
191+
arrow::field("__field_mapping", arrow::list(arrow::int32()), true),
192+
arrow::field("__col_3", arrow::utf8(), true),
193+
});
194+
ASSERT_TRUE(actual->Equals(*expected)) << "Expected:\n"
195+
<< expected->ToString() << "\nActual:\n"
196+
<< actual->ToString();
197+
}
198+
165199
// ---- BuildColumnToNumColumns ----
166200

167201
TEST(MapSharedShreddingUtilsTest, BuildColumnToNumColumns) {
@@ -362,4 +396,94 @@ TEST(MapSharedShreddingUtilsTest, PhysicalColumnName) {
362396
ASSERT_EQ(MapSharedShreddingDefine::PhysicalColumnName(99), "__col_99");
363397
}
364398

399+
// ---- GetPhysicalColumnIndices ----
400+
401+
// Normal: single physical column per field
402+
TEST(MapSharedShreddingUtilsTest, GetPhysicalColumnIndicesSingleColumn) {
403+
MapSharedShreddingFieldMeta meta;
404+
meta.name_to_id = {{"age", 0}, {"name", 1}};
405+
meta.field_to_columns = {{0, {2}}, {1, {5}}};
406+
407+
ASSERT_OK_AND_ASSIGN(auto cols_age,
408+
MapSharedShreddingUtils::GetPhysicalColumnIndices(meta, "age"));
409+
ASSERT_EQ(cols_age, (std::vector<int32_t>{2}));
410+
411+
ASSERT_OK_AND_ASSIGN(auto cols_name,
412+
MapSharedShreddingUtils::GetPhysicalColumnIndices(meta, "name"));
413+
ASSERT_EQ(cols_name, (std::vector<int32_t>{5}));
414+
}
415+
416+
// Normal: multiple physical columns for one field
417+
TEST(MapSharedShreddingUtilsTest, GetPhysicalColumnIndicesMultipleColumns) {
418+
MapSharedShreddingFieldMeta meta;
419+
meta.name_to_id = {{"tags", 0}};
420+
meta.field_to_columns = {{0, {0, 3, 7}}};
421+
422+
ASSERT_OK_AND_ASSIGN(auto cols,
423+
MapSharedShreddingUtils::GetPhysicalColumnIndices(meta, "tags"));
424+
ASSERT_EQ(cols, (std::vector<int32_t>{0, 3, 7}));
425+
}
426+
427+
// Normal: many fields each mapping to different physical columns
428+
TEST(MapSharedShreddingUtilsTest, GetPhysicalColumnIndicesMultipleFields) {
429+
MapSharedShreddingFieldMeta meta;
430+
meta.name_to_id = {{"a", 0}, {"b", 1}, {"c", 2}};
431+
meta.field_to_columns = {{0, {0, 1}}, {1, {2, 3, 4}}, {2, {5}}};
432+
433+
ASSERT_OK_AND_ASSIGN(auto cols_a, MapSharedShreddingUtils::GetPhysicalColumnIndices(meta, "a"));
434+
ASSERT_EQ(cols_a, (std::vector<int32_t>{0, 1}));
435+
436+
ASSERT_OK_AND_ASSIGN(auto cols_b, MapSharedShreddingUtils::GetPhysicalColumnIndices(meta, "b"));
437+
ASSERT_EQ(cols_b, (std::vector<int32_t>{2, 3, 4}));
438+
439+
ASSERT_OK_AND_ASSIGN(auto cols_c, MapSharedShreddingUtils::GetPhysicalColumnIndices(meta, "c"));
440+
ASSERT_EQ(cols_c, (std::vector<int32_t>{5}));
441+
}
442+
443+
// Error: field name not found in name_to_id
444+
TEST(MapSharedShreddingUtilsTest, GetPhysicalColumnIndicesFieldNotFound) {
445+
MapSharedShreddingFieldMeta meta;
446+
meta.name_to_id = {{"age", 0}};
447+
meta.field_to_columns = {{0, {1}}};
448+
449+
ASSERT_NOK_WITH_MSG(MapSharedShreddingUtils::GetPhysicalColumnIndices(meta, "nonexistent"),
450+
"cannot find field nonexistent in map shared shredding meta");
451+
}
452+
453+
// Error: field name not found in empty meta
454+
TEST(MapSharedShreddingUtilsTest, GetPhysicalColumnIndicesEmptyMeta) {
455+
MapSharedShreddingFieldMeta meta;
456+
ASSERT_NOK_WITH_MSG(MapSharedShreddingUtils::GetPhysicalColumnIndices(meta, "any"),
457+
"cannot find field any in map shared shredding meta");
458+
}
459+
460+
// Error: field id exists in name_to_id but is missing from field_to_columns
461+
TEST(MapSharedShreddingUtilsTest, GetPhysicalColumnIndicesFieldIdMissingInFieldToColumns) {
462+
MapSharedShreddingFieldMeta meta;
463+
// "score" -> field_id 42, but field_to_columns has no entry for 42
464+
meta.name_to_id = {{"score", 42}};
465+
meta.field_to_columns = {};
466+
467+
ASSERT_NOK_WITH_MSG(MapSharedShreddingUtils::GetPhysicalColumnIndices(meta, "score"),
468+
"cannot find field id 42 in field_to_columns in map shared shredding meta");
469+
}
470+
471+
TEST(MapSharedShreddingUtilsTest, IsOverflowField) {
472+
MapSharedShreddingFieldMeta meta;
473+
meta.name_to_id = {{"a", 0}, {"b", 1}, {"c", 2}};
474+
meta.overflow_field_set = {0, 2};
475+
476+
ASSERT_OK_AND_ASSIGN(bool a_overflow, MapSharedShreddingUtils::IsOverflowField(meta, "a"));
477+
ASSERT_TRUE(a_overflow);
478+
479+
ASSERT_OK_AND_ASSIGN(bool b_overflow, MapSharedShreddingUtils::IsOverflowField(meta, "b"));
480+
ASSERT_FALSE(b_overflow);
481+
482+
ASSERT_OK_AND_ASSIGN(bool c_overflow, MapSharedShreddingUtils::IsOverflowField(meta, "c"));
483+
ASSERT_TRUE(c_overflow);
484+
485+
ASSERT_NOK_WITH_MSG(MapSharedShreddingUtils::IsOverflowField(meta, "missing"),
486+
"cannot find field missing in map shared shredding meta");
487+
}
488+
365489
} // namespace paimon::test

0 commit comments

Comments
 (0)