|
28 | 28 | #include "gtest/gtest.h" |
29 | 29 | #include "paimon/common/data/shredding/map_shared_shredding_context.h" |
30 | 30 | #include "paimon/common/data/shredding/map_shared_shredding_utils.h" |
| 31 | +#include "paimon/common/data/shredding/map_shared_shredding_write_plan_factory.h" |
31 | 32 | #include "paimon/common/data/shredding/map_shredding_defs.h" |
32 | 33 | #include "paimon/core/core_options.h" |
33 | 34 | #include "paimon/memory/memory_pool.h" |
@@ -118,6 +119,77 @@ TEST_F(MapSharedShreddingBatchConverterTest, BasicConversion) { |
118 | 119 | ASSERT_EQ(expected_meta, converter->BuildFieldMeta("tags").value()); |
119 | 120 | } |
120 | 121 |
|
| 122 | +TEST_F(MapSharedShreddingBatchConverterTest, MapWithNullValue) { |
| 123 | + auto logical_schema = |
| 124 | + arrow::schema({arrow::field("metrics", arrow::map(arrow::utf8(), arrow::int64()))}); |
| 125 | + auto context = |
| 126 | + std::make_shared<MapSharedShreddingContext>(std::map<std::string, int32_t>{{"metrics", 2}}); |
| 127 | + ASSERT_OK_AND_ASSIGN(CoreOptions options, MakeCoreOptions({{"metrics", "plain"}})); |
| 128 | + ASSERT_OK_AND_ASSIGN(auto converter, MapSharedShreddingBatchConverter::Create( |
| 129 | + logical_schema, context, options, pool_)); |
| 130 | + auto logical_type = arrow::struct_(logical_schema->fields()); |
| 131 | + auto physical_type = arrow::struct_(converter->GetPhysicalSchema()->fields()); |
| 132 | + |
| 133 | + auto actual = |
| 134 | + RunConvert(logical_type, R"([[[["a", null], ["b", 20]]]])", physical_type, converter.get()); |
| 135 | + auto expected = ArrayFromJSON(physical_type, R"([[[[0, 1], null, 20, null]]])").ValueOrDie(); |
| 136 | + AssertArrayEquals(expected, actual); |
| 137 | + |
| 138 | + MapSharedShreddingFieldMeta expected_meta; |
| 139 | + expected_meta.name_to_id = {{"a", 0}, {"b", 1}}; |
| 140 | + expected_meta.field_to_columns = {{0, {0}}, {1, {1}}}; |
| 141 | + expected_meta.num_columns = 2; |
| 142 | + expected_meta.max_row_width = 2; |
| 143 | + ASSERT_EQ(expected_meta, converter->BuildFieldMeta("metrics").value()); |
| 144 | +} |
| 145 | + |
| 146 | +TEST_F(MapSharedShreddingBatchConverterTest, OverflowWhenExceedK) { |
| 147 | + auto logical_schema = |
| 148 | + arrow::schema({arrow::field("metrics", arrow::map(arrow::utf8(), arrow::int64()))}); |
| 149 | + auto context = |
| 150 | + std::make_shared<MapSharedShreddingContext>(std::map<std::string, int32_t>{{"metrics", 2}}); |
| 151 | + ASSERT_OK_AND_ASSIGN(CoreOptions options, MakeCoreOptions({{"metrics", "plain"}})); |
| 152 | + ASSERT_OK_AND_ASSIGN(auto converter, MapSharedShreddingBatchConverter::Create( |
| 153 | + logical_schema, context, options, pool_)); |
| 154 | + auto logical_type = arrow::struct_(logical_schema->fields()); |
| 155 | + auto physical_type = arrow::struct_(converter->GetPhysicalSchema()->fields()); |
| 156 | + |
| 157 | + auto actual = RunConvert(logical_type, R"([[[["a", 10], ["b", 20], ["c", 30]]]])", |
| 158 | + physical_type, converter.get()); |
| 159 | + auto expected = ArrayFromJSON(physical_type, R"([[[[0, 1], 10, 20, [[2, 30]]]]])").ValueOrDie(); |
| 160 | + AssertArrayEquals(expected, actual); |
| 161 | + |
| 162 | + MapSharedShreddingFieldMeta expected_meta; |
| 163 | + expected_meta.name_to_id = {{"a", 0}, {"b", 1}, {"c", 2}}; |
| 164 | + expected_meta.field_to_columns = {{0, {0}}, {1, {1}}}; |
| 165 | + expected_meta.overflow_field_set = {2}; |
| 166 | + expected_meta.num_columns = 2; |
| 167 | + expected_meta.max_row_width = 3; |
| 168 | + ASSERT_EQ(expected_meta, converter->BuildFieldMeta("metrics").value()); |
| 169 | +} |
| 170 | + |
| 171 | +TEST_F(MapSharedShreddingBatchConverterTest, EmptyAndNullMaps) { |
| 172 | + auto logical_schema = |
| 173 | + arrow::schema({arrow::field("metrics", arrow::map(arrow::utf8(), arrow::int64()))}); |
| 174 | + auto context = |
| 175 | + std::make_shared<MapSharedShreddingContext>(std::map<std::string, int32_t>{{"metrics", 2}}); |
| 176 | + ASSERT_OK_AND_ASSIGN(CoreOptions options, MakeCoreOptions({{"metrics", "plain"}})); |
| 177 | + ASSERT_OK_AND_ASSIGN(auto converter, MapSharedShreddingBatchConverter::Create( |
| 178 | + logical_schema, context, options, pool_)); |
| 179 | + auto logical_type = arrow::struct_(logical_schema->fields()); |
| 180 | + auto physical_type = arrow::struct_(converter->GetPhysicalSchema()->fields()); |
| 181 | + |
| 182 | + auto actual = RunConvert(logical_type, R"([[null], [[]]])", physical_type, converter.get()); |
| 183 | + auto expected = |
| 184 | + ArrayFromJSON(physical_type, R"([[null], [[[-1, -1], null, null, null]]])").ValueOrDie(); |
| 185 | + AssertArrayEquals(expected, actual); |
| 186 | + |
| 187 | + MapSharedShreddingFieldMeta expected_meta; |
| 188 | + expected_meta.num_columns = 2; |
| 189 | + expected_meta.max_row_width = 0; |
| 190 | + ASSERT_EQ(expected_meta, converter->BuildFieldMeta("metrics").value()); |
| 191 | +} |
| 192 | + |
121 | 193 | TEST_F(MapSharedShreddingBatchConverterTest, NestedValueStruct) { |
122 | 194 | // MAP<STRING, STRUCT<x:INT32, y:DOUBLE>>, K=2 |
123 | 195 | auto value_type = arrow::struct_({ |
@@ -408,13 +480,18 @@ TEST_F(MapSharedShreddingBatchConverterTest, BuildFieldMetaInvalidFieldName) { |
408 | 480 |
|
409 | 481 | // Valid case: "tags" exists |
410 | 482 | ASSERT_OK_AND_ASSIGN([[maybe_unused]] auto meta, converter->BuildFieldMeta("tags")); |
| 483 | + ASSERT_OK_AND_ASSIGN(int32_t max_row_width, converter->GetMaxRowWidth("tags")); |
| 484 | + ASSERT_EQ(0, max_row_width); |
411 | 485 |
|
412 | 486 | // Invalid case: "id" is not a shredding field |
413 | 487 | ASSERT_NOK_WITH_MSG(converter->BuildFieldMeta("id"), "cannot find field_name 'id'"); |
| 488 | + ASSERT_NOK_WITH_MSG(converter->GetMaxRowWidth("id"), "cannot find field_name 'id'"); |
414 | 489 |
|
415 | 490 | // Invalid case: nonexistent field name |
416 | 491 | ASSERT_NOK_WITH_MSG(converter->BuildFieldMeta("nonexistent"), |
417 | 492 | "cannot find field_name 'nonexistent'"); |
| 493 | + ASSERT_NOK_WITH_MSG(converter->GetMaxRowWidth("nonexistent"), |
| 494 | + "cannot find field_name 'nonexistent'"); |
418 | 495 | } |
419 | 496 |
|
420 | 497 | TEST_F(MapSharedShreddingBatchConverterTest, SequentialPlacementUsesSmallestColumn) { |
@@ -496,4 +573,57 @@ TEST_F(MapSharedShreddingBatchConverterTest, LruPlacementPreservesResidentColumn |
496 | 573 | ASSERT_EQ(expected_meta, converter->BuildFieldMeta("tags").value()); |
497 | 574 | } |
498 | 575 |
|
| 576 | +TEST_F(MapSharedShreddingBatchConverterTest, FactoryUsesMaxColumnCountForFirstFile) { |
| 577 | + auto logical_schema = |
| 578 | + arrow::schema({arrow::field("tags", arrow::map(arrow::utf8(), arrow::int32()))}); |
| 579 | + ASSERT_OK_AND_ASSIGN( |
| 580 | + CoreOptions options, |
| 581 | + CoreOptions::FromMap({{"fields.tags.map.storage-layout", "shared-shredding"}, |
| 582 | + {"fields.tags.map.shared-shredding.max-columns", "4"}})); |
| 583 | + ASSERT_OK_AND_ASSIGN( |
| 584 | + auto factory, MapSharedShreddingWritePlanFactory::Create(options, logical_schema, pool_)); |
| 585 | + |
| 586 | + ASSERT_TRUE(factory->ShouldCreateWritePlan()); |
| 587 | + ASSERT_FALSE(factory->ShouldInferWritePlan()); |
| 588 | + ASSERT_EQ(0, factory->InferBufferRowCount()); |
| 589 | + ASSERT_OK_AND_ASSIGN(auto converter, factory->CreateConverter("parquet", {})); |
| 590 | + ASSERT_OK_AND_ASSIGN(auto expected, MapSharedShreddingUtils::LogicalToPhysicalSchema( |
| 591 | + logical_schema, {{"tags", 4}})); |
| 592 | + ASSERT_TRUE(converter->GetPhysicalSchema()->Equals(*expected)); |
| 593 | +} |
| 594 | + |
| 595 | +TEST_F(MapSharedShreddingBatchConverterTest, FactoryUsesCompletedFileStatsForNextFile) { |
| 596 | + auto logical_schema = |
| 597 | + arrow::schema({arrow::field("tags", arrow::map(arrow::utf8(), arrow::int32()))}); |
| 598 | + ASSERT_OK_AND_ASSIGN( |
| 599 | + CoreOptions options, |
| 600 | + CoreOptions::FromMap({{"fields.tags.map.storage-layout", "shared-shredding"}, |
| 601 | + {"fields.tags.map.shared-shredding.max-columns", "8"}})); |
| 602 | + ASSERT_OK_AND_ASSIGN( |
| 603 | + auto factory, MapSharedShreddingWritePlanFactory::Create(options, logical_schema, pool_)); |
| 604 | + ASSERT_OK_AND_ASSIGN(auto first, factory->CreateConverter("parquet", {})); |
| 605 | + |
| 606 | + auto logical_type = arrow::struct_(logical_schema->fields()); |
| 607 | + auto first_physical_type = arrow::struct_(first->GetPhysicalSchema()->fields()); |
| 608 | + auto input = ArrayFromJSON(logical_type, R"([ |
| 609 | + [[["a", 1], ["b", 2]]], |
| 610 | + [[["c", 3], ["d", 4], ["e", 5]]] |
| 611 | + ])") |
| 612 | + .ValueOrDie(); |
| 613 | + ArrowArray c_input; |
| 614 | + ASSERT_TRUE(arrow::ExportArray(*input, &c_input).ok()); |
| 615 | + ASSERT_OK_AND_ASSIGN(auto c_output, first->Convert(&c_input)); |
| 616 | + auto first_output = arrow::ImportArray(c_output.get(), first_physical_type).ValueOrDie(); |
| 617 | + ASSERT_EQ(2, first_output->length()); |
| 618 | + ASSERT_OK(factory->OnFileCompleted(first)); |
| 619 | + |
| 620 | + ASSERT_OK_AND_ASSIGN(auto second, factory->CreateConverter("parquet", {})); |
| 621 | + ASSERT_OK_AND_ASSIGN(auto expected_first, MapSharedShreddingUtils::LogicalToPhysicalSchema( |
| 622 | + logical_schema, {{"tags", 8}})); |
| 623 | + ASSERT_OK_AND_ASSIGN(auto expected_second, MapSharedShreddingUtils::LogicalToPhysicalSchema( |
| 624 | + logical_schema, {{"tags", 3}})); |
| 625 | + ASSERT_TRUE(first->GetPhysicalSchema()->Equals(*expected_first)); |
| 626 | + ASSERT_TRUE(second->GetPhysicalSchema()->Equals(*expected_second)); |
| 627 | +} |
| 628 | + |
499 | 629 | } // namespace paimon |
0 commit comments