2626#include " arrow/c/abi.h"
2727#include " arrow/c/bridge.h"
2828#include " paimon/common/data/columnar/columnar_row.h"
29+ #include " paimon/common/predicate/predicate_validator.h"
2930#include " paimon/common/reader/late_materializing_file_batch_reader.h"
3031#include " paimon/common/utils/arrow/status_utils.h"
32+ #include " paimon/common/utils/checked_cast.h"
3133#include " paimon/core/io/rolling_file_writer.h"
3234#include " paimon/core/manifest/manifest_entry.h"
3335#include " paimon/core/manifest/manifest_entry_serializer.h"
@@ -51,6 +53,12 @@ class Schema;
5153namespace paimon {
5254class MemoryPool ;
5355
56+ namespace {
57+ constexpr int32_t kVersionFieldIndex = 0 ;
58+ constexpr int32_t kBucketFieldIndex = 3 ;
59+ constexpr int32_t kTotalBucketsFieldIndex = 4 ;
60+ } // namespace
61+
5462ManifestFile::ManifestFile (const std::shared_ptr<FileSystem>& file_system,
5563 const std::shared_ptr<ReaderBuilder>& reader_builder,
5664 const std::shared_ptr<WriterBuilder>& writer_builder,
@@ -93,10 +101,10 @@ Result<std::unique_ptr<ManifestFile>> ManifestFile::Create(
93101}
94102
95103Status ManifestFile::ReadBucketEntries (const std::string& file_name, int32_t bucket,
104+ const std::optional<int32_t >& expected_total_buckets,
96105 std::optional<int64_t > file_size,
97- std::vector<ManifestEntry>* entries,
98- const std::optional<int32_t >& expected_total_buckets) const {
99- // Readers without an in-memory selective probe still filter aligned Arrow columns
106+ std::vector<ManifestEntry>* entries) const {
107+ // Readers without precise bitmap selection still filter aligned Arrow columns
100108 // before constructing ManifestEntry and DataFileMeta objects.
101109 return ReadArrowBatches (
102110 file_name, file_size,
@@ -105,49 +113,41 @@ Status ManifestFile::ReadBucketEntries(const std::string& file_name, int32_t buc
105113 ColumnarRow row (batch->fields (), pool_, /* row_id=*/ 0 );
106114 for (int64_t i = 0 ; i < batch->length (); ++i) {
107115 row.SetRowId (i);
116+ PAIMON_RETURN_NOT_OK (
117+ ManifestEntrySerializer::ValidateVersion (row.GetInt (kVersionFieldIndex )));
108118 // Different or unknown bucket counts must reach the compatibility checks.
109119 const bool historical_layout =
110- expected_total_buckets && (row.IsNullAt (3 ) || row.IsNullAt (4 ) ||
111- row.GetInt (4 ) != expected_total_buckets.value ());
120+ expected_total_buckets &&
121+ (row.IsNullAt (kBucketFieldIndex ) || row.IsNullAt (kTotalBucketsFieldIndex ) ||
122+ row.GetInt (kTotalBucketsFieldIndex ) != expected_total_buckets.value ());
112123 if (!historical_layout && ManifestEntrySerializer::GetBucket (row) != bucket) {
113124 continue ;
114125 }
115- // Only validate entries retained by the bucket selector. FromRow checks the
116- // serialization version before decoding the remaining metadata.
117126 PAIMON_ASSIGN_OR_RAISE (ManifestEntry entry, serializer_->FromRow (row));
118127 entries->push_back (std::move (entry));
119128 }
120129 return Status::OK ();
121130 },
122131 [this , bucket, expected_total_buckets](std::unique_ptr<FileBatchReader>* reader) {
123- return PrepareBucketRead (reader, bucket, expected_total_buckets);
132+ return PrepareBucketRead (bucket, expected_total_buckets, reader );
124133 });
125134}
126135
127- Status ManifestFile::PrepareBucketRead (std::unique_ptr<FileBatchReader>* reader, int32_t bucket,
128- const std::optional<int32_t >& expected_total_buckets) const {
136+ Status ManifestFile::PrepareBucketRead (int32_t bucket,
137+ const std::optional<int32_t >& expected_total_buckets,
138+ std::unique_ptr<FileBatchReader>* reader) const {
129139 if (!(*reader)->SupportPreciseBitmapSelection ()) {
130140 return Status::OK ();
131141 }
132142 PAIMON_ASSIGN_OR_RAISE (std::unique_ptr<ArrowSchema> c_schema, (*reader)->GetFileSchema ());
133143 PAIMON_ASSIGN_OR_RAISE_FROM_ARROW (std::shared_ptr<arrow::Schema> file_schema,
134144 arrow::ImportSchema (c_schema.get ()));
135145 const auto & target_type = serializer_->GetDataType ();
136- const std::string& bucket_name = target_type->field (3 )->name ();
137- auto bucket_field = file_schema->GetFieldByName (bucket_name);
138- if (!bucket_field || bucket_field->type ()->id () != arrow::Type::INT32 ) {
139- return Status::OK ();
140- }
141- if (expected_total_buckets) {
142- auto total_field = file_schema->GetFieldByName (" _TOTAL_BUCKETS" );
143- if (!total_field || total_field->type ()->id () != arrow::Type::INT32 ) {
144- return Status::OK ();
145- }
146- }
146+ const std::string& bucket_name = target_type->field (kBucketFieldIndex )->name ();
147147 std::shared_ptr<Predicate> selector = PredicateBuilder::Equal (
148148 file_schema->GetFieldIndex (bucket_name), bucket_name, FieldType::INT , Literal (bucket));
149149 if (expected_total_buckets) {
150- const std::string total_name = " _TOTAL_BUCKETS " ;
150+ const std::string& total_name = target_type-> field ( kTotalBucketsFieldIndex )-> name () ;
151151 const int32_t total_index = file_schema->GetFieldIndex (total_name);
152152 PAIMON_ASSIGN_OR_RAISE (
153153 selector,
@@ -156,6 +156,24 @@ Status ManifestFile::PrepareBucketRead(std::unique_ptr<FileBatchReader>* reader,
156156 PredicateBuilder::NotEqual (total_index, total_name, FieldType::INT ,
157157 Literal (expected_total_buckets.value ()))}));
158158 }
159+ // Retain unsupported versions regardless of bucket so the consumer validates every version
160+ // before bucket filtering, including when the probe would otherwise select no entries.
161+ const std::string& version_name = target_type->field (kVersionFieldIndex )->name ();
162+ const int32_t version_index = file_schema->GetFieldIndex (version_name);
163+ PAIMON_ASSIGN_OR_RAISE (
164+ selector,
165+ PredicateBuilder::Or (
166+ {selector, PredicateBuilder::IsNull (version_index, version_name, FieldType::INT ),
167+ PredicateBuilder::NotEqual (
168+ version_index, version_name, FieldType::INT ,
169+ Literal (
170+ checked_cast<ManifestEntrySerializer*>(serializer_.get ())->GetVersion ()))}));
171+ if (!PredicateValidator::ValidatePredicateWithSchema (*file_schema, selector,
172+ /* validate_field_idx=*/ true )
173+ .ok ()) {
174+ // An incompatible projection must fall back to ordinary schema evolution.
175+ return Status::OK ();
176+ }
159177 PAIMON_ASSIGN_OR_RAISE (
160178 std::unique_ptr<LateMaterializingFileBatchReader> selective_reader,
161179 LateMaterializingFileBatchReader::Create (std::move (*reader), arrow_pool_));
0 commit comments