Skip to content

Commit ed64b51

Browse files
committed
fix(manifest): address selective decoding review feedback
Preserve manifest version validation and reuse historical schema checks. Simplify Avro bitmap selection and block index state while keeping the batch loop in NextBatch. Skip single-bucket probes, support uncached selective decoding, update integration coverage, and remove unnecessary test changes.
1 parent 2c1c417 commit ed64b51

12 files changed

Lines changed: 675 additions & 304 deletions

‎src/paimon/core/manifest/manifest_file.cpp‎

Lines changed: 40 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -26,8 +26,10 @@
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;
5153
namespace paimon {
5254
class 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+
5462
ManifestFile::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

95103
Status 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_));

‎src/paimon/core/manifest/manifest_file.h‎

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -67,15 +67,15 @@ class ManifestFile : public ObjectsFile<ManifestEntry> {
6767
/// different or unknown bucket counts for the existing compatibility checks.
6868
/// For inferred buckets, the caller must first verify that all historical bucket schemas
6969
/// covered by the manifest are compatible with the scan's bucket selector.
70-
/// Serialization versions are validated only for the retained entries.
70+
/// Serialization versions are validated for all entries before bucket filtering.
7171
Status ReadBucketEntries(const std::string& file_name, int32_t bucket,
72+
const std::optional<int32_t>& expected_total_buckets,
7273
std::optional<int64_t> file_size,
73-
std::vector<ManifestEntry>* entries,
74-
const std::optional<int32_t>& expected_total_buckets) const;
74+
std::vector<ManifestEntry>* entries) const;
7575

7676
private:
77-
Status PrepareBucketRead(std::unique_ptr<FileBatchReader>* reader, int32_t bucket,
78-
const std::optional<int32_t>& expected_total_buckets) const;
77+
Status PrepareBucketRead(int32_t bucket, const std::optional<int32_t>& expected_total_buckets,
78+
std::unique_ptr<FileBatchReader>* reader) const;
7979

8080
ManifestFile(const std::shared_ptr<FileSystem>& file_system,
8181
const std::shared_ptr<ReaderBuilder>& reader_builder,

0 commit comments

Comments
 (0)