|
| 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/core/operation/commit/sequence_snapshot_properties.h" |
| 18 | + |
| 19 | +#include <cstdint> |
| 20 | +#include <map> |
| 21 | +#include <optional> |
| 22 | +#include <string> |
| 23 | +#include <vector> |
| 24 | + |
| 25 | +#include "gtest/gtest.h" |
| 26 | +#include "paimon/common/data/binary_row.h" |
| 27 | +#include "paimon/core/io/data_file_meta.h" |
| 28 | +#include "paimon/core/manifest/file_kind.h" |
| 29 | +#include "paimon/core/manifest/manifest_entry.h" |
| 30 | +#include "paimon/core/snapshot.h" |
| 31 | +#include "paimon/core/stats/simple_stats.h" |
| 32 | +#include "paimon/data/timestamp.h" |
| 33 | +#include "paimon/testing/utils/testharness.h" |
| 34 | + |
| 35 | +namespace paimon::test { |
| 36 | + |
| 37 | +class SequenceSnapshotPropertiesTest : public testing::Test { |
| 38 | + protected: |
| 39 | + Snapshot MakeSnapshot( |
| 40 | + const std::optional<std::map<std::string, std::string>>& properties) const { |
| 41 | + return Snapshot( |
| 42 | + /*id=*/1, |
| 43 | + /*schema_id=*/1, |
| 44 | + /*base_manifest_list=*/"base-manifest-list", |
| 45 | + /*base_manifest_list_size=*/std::nullopt, |
| 46 | + /*delta_manifest_list=*/"delta-manifest-list", |
| 47 | + /*delta_manifest_list_size=*/std::nullopt, |
| 48 | + /*changelog_manifest_list=*/std::nullopt, |
| 49 | + /*changelog_manifest_list_size=*/std::nullopt, |
| 50 | + /*index_manifest=*/std::nullopt, |
| 51 | + /*commit_user=*/"test-user", |
| 52 | + /*commit_identifier=*/1, Snapshot::CommitKind::Append(), |
| 53 | + /*time_millis=*/0, |
| 54 | + /*total_record_count=*/0, |
| 55 | + /*delta_record_count=*/0, |
| 56 | + /*changelog_record_count=*/std::nullopt, |
| 57 | + /*watermark=*/std::nullopt, |
| 58 | + /*statistics=*/std::nullopt, properties, |
| 59 | + /*next_row_id=*/std::nullopt); |
| 60 | + } |
| 61 | + |
| 62 | + std::shared_ptr<DataFileMeta> CreateDataFileMeta(int64_t max_sequence_number) const { |
| 63 | + return std::make_shared<DataFileMeta>( |
| 64 | + "data-file", 1024, 8, DataFileMeta::EmptyMinKey(), DataFileMeta::EmptyMaxKey(), |
| 65 | + SimpleStats::EmptyStats(), SimpleStats::EmptyStats(), /*min_seq_no=*/0, |
| 66 | + /*max_seq_no=*/max_sequence_number, |
| 67 | + /*schema_id=*/1, /*level=*/0, |
| 68 | + /*extra_files=*/std::vector<std::optional<std::string>>(), |
| 69 | + /*creation_time=*/Timestamp(0, 0), |
| 70 | + /*delete_row_count=*/std::nullopt, |
| 71 | + /*embedded_index=*/nullptr, /*file_source=*/std::nullopt, |
| 72 | + /*external_path=*/std::nullopt, |
| 73 | + /*value_stats_cols=*/std::nullopt, /*first_row_id=*/std::nullopt, |
| 74 | + /*write_cols=*/std::nullopt); |
| 75 | + } |
| 76 | + |
| 77 | + ManifestEntry CreateEntry(const FileKind& kind, int64_t max_sequence_number) const { |
| 78 | + return ManifestEntry(kind, BinaryRow(0), /*bucket=*/0, /*total_buckets=*/1, |
| 79 | + CreateDataFileMeta(max_sequence_number)); |
| 80 | + } |
| 81 | +}; |
| 82 | + |
| 83 | +TEST_F(SequenceSnapshotPropertiesTest, MaxSequenceNumberEmptySnapshot) { |
| 84 | + ASSERT_OK_AND_ASSIGN(std::optional<int64_t> result, |
| 85 | + SequenceSnapshotProperties::MaxSequenceNumber(std::nullopt)); |
| 86 | + EXPECT_FALSE(result.has_value()); |
| 87 | +} |
| 88 | + |
| 89 | +TEST_F(SequenceSnapshotPropertiesTest, MaxSequenceNumberSnapshotWithoutProperties) { |
| 90 | + ASSERT_OK_AND_ASSIGN(std::optional<int64_t> result, |
| 91 | + SequenceSnapshotProperties::MaxSequenceNumber(MakeSnapshot(std::nullopt))); |
| 92 | + EXPECT_FALSE(result.has_value()); |
| 93 | +} |
| 94 | + |
| 95 | +TEST_F(SequenceSnapshotPropertiesTest, MaxSequenceNumberKeyMissing) { |
| 96 | + std::map<std::string, std::string> properties{{"other-key", "42"}}; |
| 97 | + ASSERT_OK_AND_ASSIGN(std::optional<int64_t> result, |
| 98 | + SequenceSnapshotProperties::MaxSequenceNumber(MakeSnapshot(properties))); |
| 99 | + EXPECT_FALSE(result.has_value()); |
| 100 | +} |
| 101 | + |
| 102 | +TEST_F(SequenceSnapshotPropertiesTest, MaxSequenceNumberValid) { |
| 103 | + std::map<std::string, std::string> properties{ |
| 104 | + {SequenceSnapshotProperties::kMaxSequenceNumberKey, "123"}}; |
| 105 | + ASSERT_OK_AND_ASSIGN(std::optional<int64_t> result, |
| 106 | + SequenceSnapshotProperties::MaxSequenceNumber(MakeSnapshot(properties))); |
| 107 | + ASSERT_TRUE(result.has_value()); |
| 108 | + EXPECT_EQ(123, result.value()); |
| 109 | +} |
| 110 | + |
| 111 | +TEST_F(SequenceSnapshotPropertiesTest, MaxSequenceNumberTrailingCharacters) { |
| 112 | + std::map<std::string, std::string> properties{ |
| 113 | + {SequenceSnapshotProperties::kMaxSequenceNumberKey, "123abc"}}; |
| 114 | + ASSERT_NOK_WITH_MSG(SequenceSnapshotProperties::MaxSequenceNumber(MakeSnapshot(properties)), |
| 115 | + "trailing characters are not allowed"); |
| 116 | +} |
| 117 | + |
| 118 | +TEST_F(SequenceSnapshotPropertiesTest, MaxSequenceNumberNotANumber) { |
| 119 | + std::map<std::string, std::string> properties{ |
| 120 | + {SequenceSnapshotProperties::kMaxSequenceNumberKey, "not-a-number"}}; |
| 121 | + ASSERT_NOK_WITH_MSG(SequenceSnapshotProperties::MaxSequenceNumber(MakeSnapshot(properties)), |
| 122 | + "Invalid"); |
| 123 | +} |
| 124 | + |
| 125 | +TEST_F(SequenceSnapshotPropertiesTest, MaxSequenceNumberFromFilesEmpty) { |
| 126 | + EXPECT_FALSE(SequenceSnapshotProperties::MaxSequenceNumberFromFiles({}).has_value()); |
| 127 | +} |
| 128 | + |
| 129 | +TEST_F(SequenceSnapshotPropertiesTest, MaxSequenceNumberFromFilesOnlyDelete) { |
| 130 | + std::vector<ManifestEntry> files{CreateEntry(FileKind::Delete(), 100)}; |
| 131 | + EXPECT_FALSE(SequenceSnapshotProperties::MaxSequenceNumberFromFiles(files).has_value()); |
| 132 | +} |
| 133 | + |
| 134 | +TEST_F(SequenceSnapshotPropertiesTest, MaxSequenceNumberFromFilesSkipsDelete) { |
| 135 | + std::vector<ManifestEntry> files{CreateEntry(FileKind::Add(), 10), |
| 136 | + CreateEntry(FileKind::Delete(), 999), |
| 137 | + CreateEntry(FileKind::Add(), 42)}; |
| 138 | + std::optional<int64_t> result = SequenceSnapshotProperties::MaxSequenceNumberFromFiles(files); |
| 139 | + ASSERT_TRUE(result.has_value()); |
| 140 | + EXPECT_EQ(42, result.value()); |
| 141 | +} |
| 142 | + |
| 143 | +TEST_F(SequenceSnapshotPropertiesTest, MergeMaxSequenceNumberNoInput) { |
| 144 | + std::map<std::string, std::string> properties{{"existing", "value"}}; |
| 145 | + std::map<std::string, std::string> merged = SequenceSnapshotProperties::MergeMaxSequenceNumber( |
| 146 | + properties, /*latest_max_sequence_number=*/std::nullopt, /*delta_files=*/{}); |
| 147 | + EXPECT_EQ(properties, merged); |
| 148 | + EXPECT_EQ(0u, merged.count(SequenceSnapshotProperties::kMaxSequenceNumberKey)); |
| 149 | +} |
| 150 | + |
| 151 | +TEST_F(SequenceSnapshotPropertiesTest, MergeMaxSequenceNumberLatestOnly) { |
| 152 | + std::map<std::string, std::string> merged = SequenceSnapshotProperties::MergeMaxSequenceNumber( |
| 153 | + /*properties=*/{}, /*latest_max_sequence_number=*/50, /*delta_files=*/{}); |
| 154 | + EXPECT_EQ("50", merged.at(SequenceSnapshotProperties::kMaxSequenceNumberKey)); |
| 155 | +} |
| 156 | + |
| 157 | +TEST_F(SequenceSnapshotPropertiesTest, MergeMaxSequenceNumberDeltaOnly) { |
| 158 | + std::vector<ManifestEntry> delta_files{CreateEntry(FileKind::Add(), 77)}; |
| 159 | + std::map<std::string, std::string> merged = SequenceSnapshotProperties::MergeMaxSequenceNumber( |
| 160 | + /*properties=*/{}, /*latest_max_sequence_number=*/std::nullopt, delta_files); |
| 161 | + EXPECT_EQ("77", merged.at(SequenceSnapshotProperties::kMaxSequenceNumberKey)); |
| 162 | +} |
| 163 | + |
| 164 | +TEST_F(SequenceSnapshotPropertiesTest, MergeMaxSequenceNumberTakesMaximum) { |
| 165 | + std::vector<ManifestEntry> delta_files{CreateEntry(FileKind::Add(), 30)}; |
| 166 | + std::map<std::string, std::string> merged = SequenceSnapshotProperties::MergeMaxSequenceNumber( |
| 167 | + /*properties=*/{}, /*latest_max_sequence_number=*/90, delta_files); |
| 168 | + EXPECT_EQ("90", merged.at(SequenceSnapshotProperties::kMaxSequenceNumberKey)); |
| 169 | + |
| 170 | + std::vector<ManifestEntry> larger_delta{CreateEntry(FileKind::Add(), 150)}; |
| 171 | + std::map<std::string, std::string> merged2 = SequenceSnapshotProperties::MergeMaxSequenceNumber( |
| 172 | + /*properties=*/{}, /*latest_max_sequence_number=*/90, larger_delta); |
| 173 | + EXPECT_EQ("150", merged2.at(SequenceSnapshotProperties::kMaxSequenceNumberKey)); |
| 174 | +} |
| 175 | + |
| 176 | +} // namespace paimon::test |
0 commit comments