|
25 | 25 | #include <utility> |
26 | 26 |
|
27 | 27 | #include "arrow/api.h" |
| 28 | +#include "arrow/ipc/json_simple.h" |
28 | 29 | #include "gtest/gtest.h" |
29 | 30 | #include "paimon/common/data/binary_row.h" |
30 | 31 | #include "paimon/common/data/data_define.h" |
31 | 32 | #include "paimon/core/io/data_file_meta.h" |
32 | 33 | #include "paimon/core/manifest/file_kind.h" |
33 | 34 | #include "paimon/core/manifest/file_source.h" |
34 | 35 | #include "paimon/core/manifest/manifest_entry.h" |
| 36 | +#include "paimon/core/manifest/manifest_entry_serializer.h" |
35 | 37 | #include "paimon/core/manifest/manifest_file_meta.h" |
36 | 38 | #include "paimon/core/stats/simple_stats.h" |
37 | 39 | #include "paimon/core/utils/file_store_path_factory.h" |
|
40 | 42 | #include "paimon/defs.h" |
41 | 43 | #include "paimon/format/file_format.h" |
42 | 44 | #include "paimon/format/file_format_factory.h" |
| 45 | +#include "paimon/format/format_writer.h" |
| 46 | +#include "paimon/format/writer_builder.h" |
43 | 47 | #include "paimon/fs/local/local_file_system.h" |
44 | 48 | #include "paimon/memory/memory_pool.h" |
45 | 49 | #include "paimon/testing/utils/binary_row_generator.h" |
@@ -104,30 +108,45 @@ class ManifestFileTest : public testing::Test { |
104 | 108 | const std::string& file_format_str, const std::string& root_path, |
105 | 109 | const std::string& file_name, const std::shared_ptr<MemoryPool>& pool, |
106 | 110 | const std::optional<int32_t>& bucket = std::nullopt) const { |
| 111 | + EXPECT_OK_AND_ASSIGN( |
| 112 | + std::vector<ManifestEntry> entries, |
| 113 | + TryReadManifestEntry(file_format_str, root_path, file_name, pool, bucket)); |
| 114 | + return entries; |
| 115 | + } |
| 116 | + |
| 117 | + Result<std::vector<ManifestEntry>> TryReadManifestEntry( |
| 118 | + const std::string& file_format_str, const std::string& root_path, |
| 119 | + const std::string& file_name, const std::shared_ptr<MemoryPool>& pool, |
| 120 | + const std::optional<int32_t>& bucket = std::nullopt, bool cache_enabled = false) const { |
107 | 121 | std::shared_ptr<FileSystem> file_system = std::make_shared<LocalFileSystem>(); |
108 | | - EXPECT_OK_AND_ASSIGN(std::shared_ptr<FileFormat> file_format, |
109 | | - FileFormatFactory::Get(file_format_str, {})); |
| 122 | + PAIMON_ASSIGN_OR_RAISE(std::shared_ptr<FileFormat> file_format, |
| 123 | + FileFormatFactory::Get(file_format_str, {})); |
110 | 124 | auto unused_schema = arrow::schema(arrow::FieldVector({arrow::field("f0", arrow::utf8())})); |
111 | | - EXPECT_OK_AND_ASSIGN(std::shared_ptr<FileStorePathFactory> path_factory, |
112 | | - FileStorePathFactory::Create( |
113 | | - root_path, unused_schema, /*partition_keys=*/{}, |
114 | | - /*default_part_value=*/"", file_format->Identifier(), |
115 | | - /*data_file_prefix=*/"data-", |
116 | | - /*legacy_partition_name_enabled=*/true, /*external_paths=*/{}, |
117 | | - /*global_index_external_path=*/std::nullopt, |
118 | | - /*index_file_in_data_file_dir=*/false, pool)); |
119 | | - EXPECT_OK_AND_ASSIGN(CoreOptions options, |
120 | | - CoreOptions::FromMap({{Options::FILE_FORMAT, "orc"}})); |
121 | | - EXPECT_OK_AND_ASSIGN( |
| 125 | + PAIMON_ASSIGN_OR_RAISE(std::shared_ptr<FileStorePathFactory> path_factory, |
| 126 | + FileStorePathFactory::Create( |
| 127 | + root_path, unused_schema, /*partition_keys=*/{}, |
| 128 | + /*default_part_value=*/"", file_format->Identifier(), |
| 129 | + /*data_file_prefix=*/"data-", |
| 130 | + /*legacy_partition_name_enabled=*/true, /*external_paths=*/{}, |
| 131 | + /*global_index_external_path=*/std::nullopt, |
| 132 | + /*index_file_in_data_file_dir=*/false, pool)); |
| 133 | + PAIMON_ASSIGN_OR_RAISE(CoreOptions options, |
| 134 | + CoreOptions::FromMap({{Options::FILE_FORMAT, "orc"}})); |
| 135 | + if (cache_enabled) { |
| 136 | + options.WithCache( |
| 137 | + std::make_shared<CountingRoutingCache>(CacheKind::MANIFEST, 64 * 1024 * 1024)); |
| 138 | + } |
| 139 | + PAIMON_ASSIGN_OR_RAISE( |
122 | 140 | std::unique_ptr<ManifestFile> manifest_file, |
123 | 141 | ManifestFile::Create(file_system, file_format, "zstd", path_factory, |
124 | 142 | /*target_file_size=*/1024, pool, options, unused_schema)); |
125 | 143 | std::vector<ManifestEntry> manifest_entries; |
126 | 144 | if (bucket) { |
127 | | - EXPECT_OK( |
| 145 | + PAIMON_RETURN_NOT_OK( |
128 | 146 | manifest_file->ReadBucketEntries(file_name, bucket.value(), &manifest_entries)); |
129 | 147 | } else { |
130 | | - EXPECT_OK(manifest_file->Read(file_name, /*filter=*/nullptr, &manifest_entries)); |
| 148 | + PAIMON_RETURN_NOT_OK( |
| 149 | + manifest_file->Read(file_name, /*filter=*/nullptr, &manifest_entries)); |
131 | 150 | } |
132 | 151 |
|
133 | 152 | return manifest_entries; |
@@ -373,45 +392,104 @@ TEST_F(ManifestFileTest, TestReadBucketEntriesSkipsDeserializingOtherBuckets) { |
373 | 392 | "manifest-3a44a0da-1008-463c-914e-28d271375e24-0", pool); |
374 | 393 | ASSERT_EQ(2, source_entries.size()); |
375 | 394 |
|
376 | | - auto test_dir = UniqueTestDirectory::Create(); |
377 | | - ASSERT_TRUE(test_dir); |
378 | | - std::shared_ptr<FileSystem> file_system = test_dir->GetFileSystem(); |
379 | | - ASSERT_OK(file_system->Mkdirs(FileStorePathFactory::ManifestPath(test_dir->Str()))); |
380 | | - ASSERT_OK_AND_ASSIGN(std::shared_ptr<FileFormat> file_format, |
381 | | - FileFormatFactory::Get("avro", {})); |
382 | | - auto unused_schema = arrow::schema(arrow::FieldVector({arrow::field("f0", arrow::utf8())})); |
383 | | - ASSERT_OK_AND_ASSIGN( |
384 | | - std::shared_ptr<FileStorePathFactory> path_factory, |
385 | | - FileStorePathFactory::Create(test_dir->Str(), unused_schema, /*partition_keys=*/{}, |
386 | | - /*default_part_value=*/"", file_format->Identifier(), |
387 | | - /*data_file_prefix=*/"data-", |
388 | | - /*legacy_partition_name_enabled=*/true, /*external_paths=*/{}, |
389 | | - /*global_index_external_path=*/std::nullopt, |
390 | | - /*index_file_in_data_file_dir=*/false, pool)); |
391 | | - ASSERT_OK_AND_ASSIGN(CoreOptions options, |
392 | | - CoreOptions::FromMap({{Options::FILE_FORMAT, "orc"}})); |
393 | | - ASSERT_OK_AND_ASSIGN( |
394 | | - std::unique_ptr<ManifestFile> manifest_file, |
395 | | - ManifestFile::Create(file_system, file_format, "zstd", path_factory, |
396 | | - /*target_file_size=*/1024, pool, options, unused_schema)); |
397 | | - |
398 | | - ManifestEntry invalid_other_bucket(FileKind(static_cast<int8_t>(2)), |
399 | | - source_entries[0].Partition(), /*bucket=*/1, |
400 | | - /*total_buckets=*/2, source_entries[0].File()); |
401 | | - ManifestEntry valid_target_bucket(FileKind::Add(), source_entries[1].Partition(), /*bucket=*/0, |
402 | | - /*total_buckets=*/2, source_entries[1].File()); |
403 | | - using WrittenFile = std::pair<std::string, int64_t>; |
404 | | - ASSERT_OK_AND_ASSIGN( |
405 | | - WrittenFile written_file, |
406 | | - manifest_file->WriteWithoutRolling({invalid_other_bucket, valid_target_bucket})); |
| 395 | + // A cache that rejects manifest keys must fall back to the single-pass reader. |
| 396 | + for (int32_t cache_mode : {0, 1, 2}) { |
| 397 | + SCOPED_TRACE(cache_mode); |
| 398 | + auto test_dir = UniqueTestDirectory::Create(); |
| 399 | + ASSERT_TRUE(test_dir); |
| 400 | + auto file_system = std::make_shared<CountingFileSystem>(); |
| 401 | + ASSERT_OK(file_system->Mkdirs(FileStorePathFactory::ManifestPath(test_dir->Str()))); |
| 402 | + ASSERT_OK_AND_ASSIGN(std::shared_ptr<FileFormat> file_format, |
| 403 | + FileFormatFactory::Get("avro", {})); |
| 404 | + auto unused_schema = arrow::schema(arrow::FieldVector({arrow::field("f0", arrow::utf8())})); |
| 405 | + ASSERT_OK_AND_ASSIGN(std::shared_ptr<FileStorePathFactory> path_factory, |
| 406 | + FileStorePathFactory::Create( |
| 407 | + test_dir->Str(), unused_schema, /*partition_keys=*/{}, |
| 408 | + /*default_part_value=*/"", file_format->Identifier(), |
| 409 | + /*data_file_prefix=*/"data-", |
| 410 | + /*legacy_partition_name_enabled=*/true, /*external_paths=*/{}, |
| 411 | + /*global_index_external_path=*/std::nullopt, |
| 412 | + /*index_file_in_data_file_dir=*/false, pool)); |
| 413 | + ASSERT_OK_AND_ASSIGN(CoreOptions options, |
| 414 | + CoreOptions::FromMap({{Options::FILE_FORMAT, "orc"}})); |
| 415 | + if (cache_mode != 0) { |
| 416 | + options.WithCache(std::make_shared<CountingRoutingCache>( |
| 417 | + cache_mode == 1 ? CacheKind::MANIFEST : CacheKind::DEFAULT, 64 * 1024 * 1024)); |
| 418 | + } |
| 419 | + ASSERT_OK_AND_ASSIGN( |
| 420 | + std::unique_ptr<ManifestFile> manifest_file, |
| 421 | + ManifestFile::Create(file_system, file_format, "null", path_factory, |
| 422 | + /*target_file_size=*/1024, pool, options, unused_schema)); |
407 | 423 |
|
408 | | - std::vector<ManifestEntry> all_entries; |
409 | | - ASSERT_NOK_WITH_MSG(manifest_file->Read(written_file.first, /*filter=*/nullptr, &all_entries), |
410 | | - "Unsupported byte value 2 for file kind."); |
| 424 | + ManifestEntry invalid_other_bucket(FileKind(static_cast<int8_t>(2)), |
| 425 | + source_entries[0].Partition(), /*bucket=*/1, |
| 426 | + /*total_buckets=*/2, source_entries[0].File()); |
| 427 | + ManifestEntry valid_target_bucket(FileKind::Add(), source_entries[1].Partition(), |
| 428 | + /*bucket=*/0, |
| 429 | + /*total_buckets=*/2, source_entries[1].File()); |
| 430 | + using WrittenFile = std::pair<std::string, int64_t>; |
| 431 | + ASSERT_OK_AND_ASSIGN( |
| 432 | + WrittenFile written_file, |
| 433 | + manifest_file->WriteWithoutRolling({invalid_other_bucket, valid_target_bucket})); |
| 434 | + |
| 435 | + // Exercise both a cold cache and reuse of the retained manifest bytes. |
| 436 | + for (int32_t read = 0; read < 2; ++read) { |
| 437 | + std::vector<ManifestEntry> bucket_entries; |
| 438 | + ASSERT_OK(manifest_file->ReadBucketEntries(written_file.first, /*bucket=*/0, |
| 439 | + &bucket_entries)); |
| 440 | + ASSERT_EQ(std::vector<ManifestEntry>({valid_target_bucket}), bucket_entries); |
| 441 | + ASSERT_EQ(cache_mode == 1 ? 1 : read + 1, file_system->open_count); |
| 442 | + } |
| 443 | + std::vector<ManifestEntry> missing_bucket_entries; |
| 444 | + ASSERT_OK(manifest_file->ReadBucketEntries(written_file.first, /*bucket=*/2, |
| 445 | + &missing_bucket_entries)); |
| 446 | + ASSERT_TRUE(missing_bucket_entries.empty()); |
| 447 | + std::vector<ManifestEntry> all_entries; |
| 448 | + ASSERT_NOK_WITH_MSG( |
| 449 | + manifest_file->Read(written_file.first, /*filter=*/nullptr, &all_entries), |
| 450 | + "Unsupported byte value 2 for file kind."); |
| 451 | + } |
| 452 | +} |
411 | 453 |
|
412 | | - std::vector<ManifestEntry> bucket_entries; |
413 | | - ASSERT_OK(manifest_file->ReadBucketEntries(written_file.first, /*bucket=*/0, &bucket_entries)); |
414 | | - ASSERT_EQ(std::vector<ManifestEntry>({valid_target_bucket}), bucket_entries); |
| 454 | +TEST_F(ManifestFileTest, TestBucketProbeValidatesVersionsOutsideSelectedBucket) { |
| 455 | + auto pool = GetDefaultPool(); |
| 456 | + ManifestEntrySerializer serializer(pool); |
| 457 | + auto type = |
| 458 | + arrow::struct_({arrow::field(serializer.GetDataType()->field(0)->name(), arrow::int32()), |
| 459 | + arrow::field(serializer.GetDataType()->field(3)->name(), arrow::int32())}); |
| 460 | + const std::vector<std::pair<std::string, std::string>> cases = { |
| 461 | + {R"([[999,1],[2,0]])", "Unsupported version: 999"}, |
| 462 | + {R"([[1,1],[2,0]])", "not compatible"}, |
| 463 | + {R"([[null,1],[2,0]])", "must not be null"}, |
| 464 | + {R"([[2,null],[2,0]])", "must not be null"}}; |
| 465 | + for (const auto& item : cases) { |
| 466 | + SCOPED_TRACE(item.first); |
| 467 | + auto dir = UniqueTestDirectory::Create(); |
| 468 | + ASSERT_TRUE(dir); |
| 469 | + auto fs = dir->GetFileSystem(); |
| 470 | + const std::string manifest_dir = FileStorePathFactory::ManifestPath(dir->Str()); |
| 471 | + ASSERT_OK(fs->Mkdirs(manifest_dir)); |
| 472 | + const std::string file_name = "manifest-invalid-version"; |
| 473 | + ASSERT_OK_AND_ASSIGN(std::shared_ptr<FileFormat> format, |
| 474 | + FileFormatFactory::Get("avro", {})); |
| 475 | + ArrowSchema c_schema; |
| 476 | + ASSERT_TRUE(arrow::ExportSchema(*arrow::schema(type->fields()), &c_schema).ok()); |
| 477 | + ASSERT_OK_AND_ASSIGN(std::shared_ptr<WriterBuilder> builder, |
| 478 | + format->CreateWriterBuilder(&c_schema, 2)); |
| 479 | + ASSERT_OK_AND_ASSIGN(std::shared_ptr<OutputStream> output, |
| 480 | + fs->Create(manifest_dir + "/" + file_name, false)); |
| 481 | + ASSERT_OK_AND_ASSIGN(std::unique_ptr<FormatWriter> writer, builder->Build(output, "null")); |
| 482 | + auto array = arrow::ipc::internal::json::ArrayFromJSON(type, item.first).ValueOrDie(); |
| 483 | + ArrowArray c_array; |
| 484 | + ASSERT_TRUE(arrow::ExportArray(*array, &c_array).ok()); |
| 485 | + ASSERT_OK(writer->AddBatch(&c_array)); |
| 486 | + ASSERT_OK(writer->Flush()); |
| 487 | + ASSERT_OK(writer->Finish()); |
| 488 | + ASSERT_OK(output->Close()); |
| 489 | + ASSERT_NOK_WITH_MSG(TryReadManifestEntry("avro", dir->Str(), file_name, pool, /*bucket=*/0, |
| 490 | + /*cache_enabled=*/true), |
| 491 | + item.second); |
| 492 | + } |
415 | 493 | } |
416 | 494 |
|
417 | 495 | TEST_F(ManifestFileTest, TestLegacyManifestFormatIsReadOnly) { |
@@ -539,6 +617,11 @@ TEST_F(ManifestFileTest, TestManifestFileCompatibleWithJavaPaimon09) { |
539 | 617 | ASSERT_EQ(expected_manifest_entries, |
540 | 618 | ReadManifestEntry("avro", paimon::test::GetDataDir() + "/avro", "avro_manifest_09", |
541 | 619 | pool, /*bucket=*/0)); |
| 620 | + ASSERT_OK_AND_ASSIGN(std::vector<ManifestEntry> cached_entries, |
| 621 | + TryReadManifestEntry("avro", paimon::test::GetDataDir() + "/avro", |
| 622 | + "avro_manifest_09", pool, /*bucket=*/0, |
| 623 | + /*cache_enabled=*/true)); |
| 624 | + ASSERT_EQ(expected_manifest_entries, cached_entries); |
542 | 625 | } |
543 | 626 |
|
544 | 627 | TEST_F(ManifestFileTest, TestManifestFileCompatibleWithJavaPaimon11) { |
@@ -578,6 +661,11 @@ TEST_F(ManifestFileTest, TestManifestFileCompatibleWithJavaPaimon11) { |
578 | 661 | ASSERT_EQ(expected_manifest_entries, |
579 | 662 | ReadManifestEntry("avro", paimon::test::GetDataDir() + "/avro", "avro_manifest_11", |
580 | 663 | pool, /*bucket=*/0)); |
| 664 | + ASSERT_OK_AND_ASSIGN(std::vector<ManifestEntry> cached_entries, |
| 665 | + TryReadManifestEntry("avro", paimon::test::GetDataDir() + "/avro", |
| 666 | + "avro_manifest_11", pool, /*bucket=*/0, |
| 667 | + /*cache_enabled=*/true)); |
| 668 | + ASSERT_EQ(expected_manifest_entries, cached_entries); |
581 | 669 | } |
582 | 670 |
|
583 | 671 | } // namespace paimon::test |
0 commit comments