2020#include " paimon/common/reader/late_materializing_file_batch_reader.h"
2121
2222#include < cassert>
23+ #include < limits>
2324#include < map>
2425#include < set>
2526#include < string>
4546namespace paimon {
4647
4748Result<std::unique_ptr<LateMaterializingFileBatchReader>> LateMaterializingFileBatchReader::Create (
48- std::unique_ptr<FileBatchReader> inner, const std::shared_ptr<arrow::MemoryPool>& arrow_pool) {
49+ std::unique_ptr<FileBatchReader> inner, const std::shared_ptr<arrow::MemoryPool>& arrow_pool,
50+ std::optional<ProbeValidation> validation) {
4951 if (arrow_pool == nullptr ) {
5052 return Status::Invalid (" arrow pool could not be nullptr." );
5153 }
5254 if (inner == nullptr ) {
5355 return Status::Invalid (" inner could not be nullptr." );
5456 }
57+ if (validation && (!validation->validate || validation->fields .empty ())) {
58+ return Status::Invalid (" probe validation requires fields and a callback" );
59+ }
5560 auto * prefetch_inner = dynamic_cast <PrefetchFileBatchReader*>(inner.get ());
5661 auto reader = std::unique_ptr<LateMaterializingFileBatchReader>(
5762 new LateMaterializingFileBatchReader (std::move (inner), prefetch_inner, arrow_pool));
63+ reader->validation_ = std::move (validation);
5864 return reader;
5965}
6066
6167Result<FileBatchReader::ReadBatch> LateMaterializingFileBatchReader::NextBatch () {
68+ if (state_ == kInit && validation_) {
69+ return Status::Invalid (" SetReadSchema is required before probe validation" );
70+ }
6271 if (state_ == kInit ) {
6372 // SetReadSchema has not been called: read with the file schema, matching the
6473 // FileBatchReader contract for schema-less reads.
6574 state_ = kNoLatMat ;
6675 }
6776 if (state_ == kProbing ) {
6877 PAIMON_RETURN_NOT_OK (ReadAndFilterProbeData ());
69- if (matched_bitmap_.IsEmpty ()) {
78+ if (!row_ids_fit_) {
79+ // Rebind to full batches and apply the residual predicate without a file bitmap.
80+ if (predicate_) {
81+ std::map<std::string, int32_t > name_to_idx;
82+ for (int32_t i = 0 ; i < full_schema_->num_fields (); ++i) {
83+ name_to_idx.emplace (full_schema_->field (i)->name (), i);
84+ }
85+ PAIMON_ASSIGN_OR_RAISE (
86+ std::shared_ptr<Predicate> bound,
87+ PredicateUtils::CreatePickedFieldFilter (predicate_, name_to_idx));
88+ probe_filter_ = std::dynamic_pointer_cast<PredicateFilter>(bound);
89+ }
90+ PAIMON_RETURN_NOT_OK (SetInnerReadSchema (full_schema_, nullptr , std::nullopt ));
91+ state_ = kFiltering ;
92+ } else if (matched_bitmap_.IsEmpty ()) {
7093 state_ = kEOF ;
7194 } else {
7295 // payload pass reads only the matched rows (matched_bitmap_ is non-empty here).
@@ -78,6 +101,8 @@ Result<FileBatchReader::ReadBatch> LateMaterializingFileBatchReader::NextBatch()
78101
79102 if (state_ == kNoLatMat ) {
80103 return inner_->NextBatch ();
104+ } else if (state_ == kFiltering ) {
105+ return ReadFilteredBatch ();
81106 } else if (state_ == kRunning ) {
82107 return ReadPayloadBatch ();
83108 } else if (state_ == kEOF ) {
@@ -90,8 +115,10 @@ Result<RoaringBitmap32> LateMaterializingFileBatchReader::FilterProbeBatch(
90115 const std::shared_ptr<arrow::Array>& array,
91116 const std::shared_ptr<PredicateFilter>& bound_filter) {
92117 // TODO(zhouhonfeng.zhf): use arrow::compute::Filter instead of PredicateFilter
93- PAIMON_ASSIGN_OR_RAISE (std::vector<char > results,
94- bound_filter->Test (*array, arrow_pool_.get ()));
118+ std::vector<char > results (array->length (), true );
119+ if (bound_filter) {
120+ PAIMON_ASSIGN_OR_RAISE (results, bound_filter->Test (*array, arrow_pool_.get ()));
121+ }
95122 if (results.size () != static_cast <size_t >(array->length ())) {
96123 return Status::Invalid (
97124 fmt::format (" predicate result size {} does not match probe batch length {}" ,
@@ -106,7 +133,12 @@ Result<RoaringBitmap32> LateMaterializingFileBatchReader::FilterProbeBatch(
106133 // map batch offset to file row id
107134 PAIMON_ASSIGN_OR_RAISE (uint64_t file_row,
108135 inner_->GetPreviousBatchFileRowId (static_cast <uint64_t >(i)));
109- if (selection_ && !selection_->Contains (static_cast <int32_t >(file_row))) {
136+ if (selection_ && (file_row > std::numeric_limits<uint32_t >::max () ||
137+ !selection_->Contains (static_cast <uint32_t >(file_row)))) {
138+ continue ;
139+ }
140+ if (file_row > std::numeric_limits<uint32_t >::max ()) {
141+ row_ids_fit_ = false ;
110142 continue ;
111143 }
112144 batch_matched.Add (static_cast <uint32_t >(i));
@@ -127,8 +159,20 @@ Status LateMaterializingFileBatchReader::ReadAndFilterProbeData() {
127159 auto & [c_array, c_schema] = batch;
128160 PAIMON_ASSIGN_OR_RAISE_FROM_ARROW (std::shared_ptr<arrow::Array> array,
129161 arrow::ImportArray (c_array.get (), c_schema.get ()));
162+ if (validation_) {
163+ if (!array || array->type_id () != arrow::Type::STRUCT ) {
164+ return Status::Invalid (" probe validation requires a struct array" );
165+ }
166+ PAIMON_RETURN_NOT_OK (
167+ validation_->validate (std::static_pointer_cast<arrow::StructArray>(array)));
168+ }
130169 PAIMON_ASSIGN_OR_RAISE (RoaringBitmap32 batch_matched,
131170 FilterProbeBatch (array, probe_filter_));
171+ if (!row_ids_fit_) {
172+ probe_arrays.clear ();
173+ matched_bitmap_ = RoaringBitmap32 ();
174+ continue ;
175+ }
132176 // Compact each probe batch down to its matched rows so probe_data_ aligns row-for-row
133177 // (ascending file order) with matched_bitmap_ and the later payload output.
134178 if (!batch_matched.IsEmpty ()) {
@@ -151,6 +195,53 @@ Status LateMaterializingFileBatchReader::ReadAndFilterProbeData() {
151195 return Status::OK ();
152196}
153197
198+ Result<FileBatchReader::ReadBatch> LateMaterializingFileBatchReader::ReadFilteredBatch () {
199+ while (true ) {
200+ PAIMON_ASSIGN_OR_RAISE (FileBatchReader::ReadBatch batch, inner_->NextBatch ());
201+ if (BatchReader::IsEofBatch (batch)) {
202+ state_ = kEOF ;
203+ return batch;
204+ }
205+ PAIMON_ASSIGN_OR_RAISE_FROM_ARROW (
206+ std::shared_ptr<arrow::Array> array,
207+ arrow::ImportArray (batch.first .get (), batch.second .get ()));
208+ std::vector<char > results (array->length (), true );
209+ if (probe_filter_) {
210+ PAIMON_ASSIGN_OR_RAISE (results, probe_filter_->Test (*array, arrow_pool_.get ()));
211+ }
212+ if (results.size () != static_cast <size_t >(array->length ())) {
213+ return Status::Invalid (" predicate result size does not match full batch length" );
214+ }
215+ RoaringBitmap32 selected;
216+ row_mapping_.clear ();
217+ for (int64_t i = 0 ; i < array->length (); ++i) {
218+ if (!results[i]) {
219+ continue ;
220+ }
221+ PAIMON_ASSIGN_OR_RAISE (uint64_t file_row, inner_->GetPreviousBatchFileRowId (i));
222+ if (selection_ && (file_row > std::numeric_limits<uint32_t >::max () ||
223+ !selection_->Contains (static_cast <uint32_t >(file_row)))) {
224+ continue ;
225+ }
226+ selected.Add (static_cast <uint32_t >(i));
227+ row_mapping_.push_back (file_row);
228+ }
229+ if (selected.IsEmpty ()) {
230+ continue ;
231+ }
232+ PAIMON_ASSIGN_OR_RAISE (arrow::ArrayVector slices,
233+ ReaderUtils::GenerateFilteredArrayVector (array, selected));
234+ PAIMON_ASSIGN_OR_RAISE_FROM_ARROW (std::shared_ptr<arrow::Array> filtered,
235+ arrow::Concatenate (slices, arrow_pool_.get ()));
236+ auto c_array = std::make_unique<ArrowArray>();
237+ auto c_schema = std::make_unique<ArrowSchema>();
238+ PAIMON_RETURN_NOT_OK_FROM_ARROW (
239+ arrow::ExportArray (*filtered, c_array.get (), c_schema.get ()));
240+ PAIMON_RETURN_NOT_OK (AddArrowArrayLifetime (c_array.get (), c_schema.get (), arrow_pool_));
241+ return std::make_pair (std::move (c_array), std::move (c_schema));
242+ }
243+ }
244+
154245Result<FileBatchReader::ReadBatch> LateMaterializingFileBatchReader::ReadPayloadBatch () {
155246 while (true ) {
156247 PAIMON_ASSIGN_OR_RAISE (FileBatchReader::ReadBatchWithBitmap batch_with_bitmap,
@@ -258,9 +349,19 @@ Status LateMaterializingFileBatchReader::SetReadSchema(
258349 PAIMON_ASSIGN_OR_RAISE_FROM_ARROW (full_schema_, arrow::ImportSchema (read_schema));
259350 predicate_ = predicate;
260351 selection_ = selection_bitmap;
261- if (predicate_ != nullptr ) {
352+ if (predicate_ != nullptr || validation_ ) {
262353 std::set<std::string> probe_names;
263- PAIMON_RETURN_NOT_OK (PredicateUtils::GetAllNames (predicate_, &probe_names));
354+ if (predicate_) {
355+ PAIMON_RETURN_NOT_OK (PredicateUtils::GetAllNames (predicate_, &probe_names));
356+ }
357+ if (validation_) {
358+ for (const auto & name : validation_->fields ) {
359+ if (!full_schema_->GetFieldByName (name)) {
360+ return Status::Invalid (" probe validation field missing: " , name);
361+ }
362+ probe_names.insert (name);
363+ }
364+ }
264365 arrow::FieldVector probe_fields;
265366 arrow::FieldVector payload_fields;
266367 for (const auto & field : full_schema_->fields ()) {
@@ -271,30 +372,36 @@ Status LateMaterializingFileBatchReader::SetReadSchema(
271372 }
272373 }
273374 // probing only pays off when the predicate fields are a strict subset of the read schema
274- if (!probe_fields.empty () && !payload_fields.empty ()) {
375+ if (!probe_fields.empty () && (!payload_fields.empty () || validation_)) {
376+ if (payload_fields.empty ()) {
377+ payload_fields = full_schema_->fields ();
378+ }
275379 probe_schema_ = arrow::schema (probe_fields, full_schema_->metadata ());
276380 payload_schema_ = arrow::schema (payload_fields, full_schema_->metadata ());
277- PAIMON_RETURN_NOT_OK (PredicateValidator::ValidatePredicateWithSchema (
278- *probe_schema_, predicate_, /* validate_field_idx=*/ false ));
279- std::map<std::string, int32_t > name_to_idx;
280- for (int32_t i = 0 ; i < probe_schema_->num_fields (); ++i) {
281- name_to_idx.emplace (probe_schema_->field (i)->name (), i);
282- }
283- PAIMON_ASSIGN_OR_RAISE (
284- std::shared_ptr<Predicate> bound_predicate,
285- PredicateUtils::CreatePickedFieldFilter (predicate_, name_to_idx));
286- probe_filter_ = std::dynamic_pointer_cast<PredicateFilter>(bound_predicate);
287- if (!probe_filter_) {
288- return Status::Invalid (" failed to bind predicate to probe schema" );
381+ if (predicate_) {
382+ PAIMON_RETURN_NOT_OK (PredicateValidator::ValidatePredicateWithSchema (
383+ *probe_schema_, predicate_, /* validate_field_idx=*/ false ));
384+ std::map<std::string, int32_t > name_to_idx;
385+ for (int32_t i = 0 ; i < probe_schema_->num_fields (); ++i) {
386+ name_to_idx.emplace (probe_schema_->field (i)->name (), i);
387+ }
388+ PAIMON_ASSIGN_OR_RAISE (
389+ std::shared_ptr<Predicate> bound_predicate,
390+ PredicateUtils::CreatePickedFieldFilter (predicate_, name_to_idx));
391+ probe_filter_ = std::dynamic_pointer_cast<PredicateFilter>(bound_predicate);
392+ if (!probe_filter_) {
393+ return Status::Invalid (" failed to bind predicate to probe schema" );
394+ }
289395 }
290396 }
291397 }
292398
293- if (predicate_ == nullptr || probe_schema_ == nullptr ) {
399+ if (probe_schema_ == nullptr ) {
294400 PAIMON_RETURN_NOT_OK (SetInnerReadSchema (full_schema_, predicate_, selection_));
295401 state_ = kNoLatMat ;
296402 } else {
297- PAIMON_RETURN_NOT_OK (SetInnerReadSchema (probe_schema_, predicate_, selection_));
403+ PAIMON_RETURN_NOT_OK (SetInnerReadSchema (probe_schema_, validation_ ? nullptr : predicate_,
404+ validation_ ? std::nullopt : selection_));
298405 state_ = kProbing ;
299406 }
300407 return Status::OK ();
@@ -319,6 +426,10 @@ Status LateMaterializingFileBatchReader::SeekToRow(uint64_t row_number) {
319426 PAIMON_ASSIGN_OR_RAISE (PrefetchFileBatchReader * prefetch_reader,
320427 GetPrefetchReaderOrRaise (" SeekToRow" ));
321428 PAIMON_RETURN_NOT_OK (prefetch_reader->SeekToRow (row_number));
429+ if (!row_ids_fit_) {
430+ state_ = kFiltering ;
431+ return Status::OK ();
432+ }
322433 if (state_ == kRunning || state_ == kEOF ) {
323434 if (matched_bitmap_.IsEmpty ()) {
324435 state_ = kEOF ;
@@ -350,6 +461,7 @@ Status LateMaterializingFileBatchReader::SetReadRanges(
350461
351462void LateMaterializingFileBatchReader::Reset () {
352463 state_ = kInit ;
464+ row_ids_fit_ = true ;
353465 matched_bitmap_ = RoaringBitmap32 ();
354466 probe_data_.reset ();
355467 probe_cursor_ = 0 ;
0 commit comments