|
20 | 20 |
|
21 | 21 | #include <algorithm> |
22 | 22 | #include <cstdint> |
| 23 | +#include <map> |
23 | 24 | #include <optional> |
24 | 25 | #include <string> |
25 | 26 | #include <utility> |
26 | 27 | #include <vector> |
27 | 28 |
|
| 29 | +#include "arrow/type.h" |
28 | 30 | #include "gtest/gtest.h" |
29 | 31 | #include "paimon/common/data/binary_row.h" |
30 | 32 | #include "paimon/common/data/binary_row_writer.h" |
31 | 33 | #include "paimon/common/io/cache/lru_cache.h" |
| 34 | +#include "paimon/core/bucket/default_bucket_function.h" |
32 | 35 | #include "paimon/core/manifest/manifest_entry.h" |
33 | 36 | #include "paimon/core/manifest/partition_entry.h" |
34 | 37 | #include "paimon/core/schema/schema_manager.h" |
|
37 | 40 | #include "paimon/core/table/source/abstract_table_scan.h" |
38 | 41 | #include "paimon/core/table/source/snapshot/snapshot_reader.h" |
39 | 42 | #include "paimon/defs.h" |
| 43 | +#include "paimon/executor.h" |
40 | 44 | #include "paimon/fs/local/local_file_system.h" |
41 | 45 | #include "paimon/memory/memory_pool.h" |
42 | 46 | #include "paimon/metrics.h" |
|
46 | 50 | #include "paimon/status.h" |
47 | 51 | #include "paimon/table/source/scan_metrics.h" |
48 | 52 | #include "paimon/table/source/table_scan.h" |
| 53 | +#include "paimon/testing/utils/binary_row_generator.h" |
49 | 54 | #include "paimon/testing/utils/testharness.h" |
50 | 55 | #include "paimon/testing/utils/timezone_guard.h" |
51 | 56 | namespace paimon::test { |
52 | 57 |
|
| 58 | +class AppendBucketPruningTest : public testing::Test { |
| 59 | + public: |
| 60 | + Result<std::unique_ptr<AppendOnlyFileStoreScan>> CreateScan( |
| 61 | + const std::shared_ptr<Predicate>& predicate, |
| 62 | + const std::optional<int32_t>& bucket = std::nullopt) const { |
| 63 | + auto arrow_schema = DataField::ConvertDataFieldsToArrowSchema( |
| 64 | + {DataField(0, arrow::field("rowkey", arrow::utf8())), |
| 65 | + DataField(1, arrow::field("value", arrow::int32()))}); |
| 66 | + PAIMON_ASSIGN_OR_RAISE(std::shared_ptr<TableSchema> schema, |
| 67 | + TableSchema::Create(schema_id_, arrow_schema, {}, {}, options_)); |
| 68 | + PAIMON_ASSIGN_OR_RAISE(CoreOptions options, CoreOptions::FromMap(options_)); |
| 69 | + auto filters = std::make_shared<ScanFilter>( |
| 70 | + predicate, std::vector<std::map<std::string, std::string>>(), bucket); |
| 71 | + return AppendOnlyFileStoreScan::Create(nullptr, schema_manager_, nullptr, nullptr, schema, |
| 72 | + arrow_schema, filters, options, |
| 73 | + GetGlobalDefaultExecutor(), pool_); |
| 74 | + } |
| 75 | + |
| 76 | + void CheckBuckets(const std::shared_ptr<Predicate>& predicate, |
| 77 | + const std::optional<int32_t>& expected_bucket, |
| 78 | + const std::optional<int32_t>& explicit_bucket = std::nullopt, |
| 79 | + int32_t total_buckets = kNumBuckets) const { |
| 80 | + ASSERT_OK_AND_ASSIGN(auto scan, CreateScan(predicate, explicit_bucket)); |
| 81 | + // Every file's stats match the lookup, so only bucket pruning can discard a file. |
| 82 | + SimpleStats stats = BinaryRowGenerator::GenerateStats( |
| 83 | + {std::string("a"), 0}, {std::string("z"), 100}, {0, 0}, pool_.get()); |
| 84 | + ASSERT_OK_AND_ASSIGN( |
| 85 | + auto file, |
| 86 | + DataFileMeta::ForAppend("data.parquet", 100, 10, stats, 0, 9, 0, std::nullopt, |
| 87 | + std::nullopt, std::nullopt, std::nullopt, std::nullopt)); |
| 88 | + for (int32_t bucket = 0; bucket < total_buckets; ++bucket) { |
| 89 | + ManifestEntry entry(FileKind::Add(), BinaryRow::EmptyRow(), bucket, total_buckets, |
| 90 | + file); |
| 91 | + ASSERT_OK_AND_ASSIGN(auto stats_scan, CreateScan(predicate, bucket)); |
| 92 | + ASSERT_OK_AND_ASSIGN(bool stats_match, stats_scan->FilterByStats(entry)); |
| 93 | + ASSERT_TRUE(stats_match); |
| 94 | + ASSERT_OK_AND_ASSIGN(bool keep, scan->FilterManifestEntry(entry)); |
| 95 | + ASSERT_EQ(keep, !expected_bucket.has_value() || bucket == expected_bucket.value()); |
| 96 | + } |
| 97 | + } |
| 98 | + |
| 99 | + std::shared_ptr<Predicate> KeyEquals() const { |
| 100 | + return PredicateBuilder::Equal(0, "rowkey", FieldType::STRING, |
| 101 | + Literal(FieldType::STRING, "key", 3)); |
| 102 | + } |
| 103 | + |
| 104 | + static constexpr int32_t kNumBuckets = 4; |
| 105 | + int64_t schema_id_ = 0; |
| 106 | + std::shared_ptr<SchemaManager> schema_manager_; |
| 107 | + std::shared_ptr<MemoryPool> pool_ = GetDefaultPool(); |
| 108 | + std::map<std::string, std::string> options_ = {{Options::BUCKET, "4"}, |
| 109 | + {Options::BUCKET_KEY, "rowkey"}}; |
| 110 | +}; |
| 111 | + |
| 112 | +TEST_F(AppendBucketPruningTest, PrunesStringKeyLookup) { |
| 113 | + BinaryRow key = BinaryRowGenerator::GenerateRow({std::string("key")}, pool_.get()); |
| 114 | + int32_t expected_bucket = DefaultBucketFunction().Bucket(key, kNumBuckets); |
| 115 | + CheckBuckets(KeyEquals(), expected_bucket); |
| 116 | +} |
| 117 | + |
| 118 | +TEST_F(AppendBucketPruningTest, PreservesExplicitBucketFilter) { |
| 119 | + BinaryRow key = BinaryRowGenerator::GenerateRow({std::string("key")}, pool_.get()); |
| 120 | + int32_t other_bucket = (DefaultBucketFunction().Bucket(key, kNumBuckets) + 1) % kNumBuckets; |
| 121 | + CheckBuckets(KeyEquals(), other_bucket, other_bucket); |
| 122 | +} |
| 123 | + |
| 124 | +TEST_F(AppendBucketPruningTest, DoesNotPruneWithoutCompleteEqualKeys) { |
| 125 | + CheckBuckets(nullptr, std::nullopt); |
| 126 | + CheckBuckets(PredicateBuilder::GreaterThan(0, "rowkey", FieldType::STRING, |
| 127 | + Literal(FieldType::STRING, "key", 3)), |
| 128 | + std::nullopt); |
| 129 | + options_[Options::BUCKET_KEY] = "rowkey,value"; |
| 130 | + CheckBuckets(KeyEquals(), std::nullopt); |
| 131 | +} |
| 132 | + |
| 133 | +TEST_F(AppendBucketPruningTest, DoesNotPruneBucketUnawareTable) { |
| 134 | + options_[Options::BUCKET] = "-1"; |
| 135 | + CheckBuckets(KeyEquals(), std::nullopt); |
| 136 | +} |
| 137 | + |
| 138 | +TEST_F(AppendBucketPruningTest, DoesNotPruneDifferentBucketCount) { |
| 139 | + CheckBuckets(KeyEquals(), std::nullopt, std::nullopt, 8); |
| 140 | +} |
| 141 | + |
| 142 | +TEST_F(AppendBucketPruningTest, DoesNotPruneDifferentSchema) { |
| 143 | + auto test_dir = UniqueTestDirectory::Create("local"); |
| 144 | + schema_manager_ = |
| 145 | + std::make_shared<SchemaManager>(std::make_shared<LocalFileSystem>(), test_dir->Str()); |
| 146 | + ASSERT_OK_AND_ASSIGN(auto scan, CreateScan(nullptr)); |
| 147 | + ASSERT_OK(schema_manager_->CreateTable(scan->schema_, {}, {}, options_)); |
| 148 | + schema_id_ = 1; |
| 149 | + CheckBuckets(KeyEquals(), std::nullopt); |
| 150 | +} |
| 151 | + |
53 | 152 | TEST(AppendOnlyFileStoreScanTest, TestReconstructPredicateWithNonCastedFields) { |
54 | 153 | std::string table_root = |
55 | 154 | paimon::test::GetDataDir() + |
|
0 commit comments