1use crate::DecodeResult;
19use crate::arrow::arrow_reader::{
20 ParquetRecordBatchReader, RowGroupPlan, RowGroupSelection, RowSelection,
21};
22use crate::arrow::push_decoder::reader_builder::{
23 RowBudget, RowGroupBuildResult, RowGroupReaderBuilder, RowGroupReaderBuilderParts,
24};
25use crate::errors::ParquetError;
26use crate::file::metadata::ParquetMetaData;
27use arrow_schema::SchemaRef;
28use bytes::Bytes;
29use std::collections::VecDeque;
30use std::ops::Range;
31use std::sync::Arc;
32
33#[derive(Debug)]
35enum QueuedRowGroupDecision {
36 Read(NextRowGroup),
38 Skip { remaining_budget: RowBudget },
40}
41
42#[derive(Debug)]
44struct NextRowGroup {
45 row_group_idx: usize,
46 row_count: usize,
47 selection: Option<RowSelection>,
49 budget: RowBudget,
51}
52
53#[derive(Debug, Clone)]
56enum QueuedRowGroups {
57 Global {
59 row_groups: VecDeque<usize>,
60 selection: Option<RowSelection>,
61 },
62 PerRowGroup(VecDeque<RowGroupSelection>),
64}
65
66impl QueuedRowGroups {
67 fn try_new(
69 parquet_metadata: &ParquetMetaData,
70 row_group_plan: RowGroupPlan,
71 ) -> Result<Self, ParquetError> {
72 match row_group_plan {
73 RowGroupPlan::Global {
74 row_groups,
75 selection,
76 } => Ok(Self::Global {
77 row_groups: row_groups
78 .unwrap_or_else(|| (0..parquet_metadata.num_row_groups()).collect())
79 .into(),
80 selection,
81 }),
82 RowGroupPlan::PerRowGroup(row_groups) => {
83 for row_group in &row_groups {
84 let row_count =
85 parquet_metadata.row_group_num_rows(row_group.row_group_index)?;
86 if let Some(selection) = &row_group.selection {
87 let selection_rows = selection.total_row_count();
88 if selection_rows > row_count {
89 return Err(ParquetError::General(format!(
90 "Row selection for row group {} contains {selection_rows} rows, but the row group has {row_count}",
91 row_group.row_group_index
92 )));
93 }
94 }
95 }
96 Ok(Self::PerRowGroup(row_groups.into()))
97 }
98 RowGroupPlan::Conflicting => Err(RowGroupPlan::conflict_error()),
99 }
100 }
101
102 fn into_plan(self) -> RowGroupPlan {
104 match self {
105 Self::Global {
106 row_groups,
107 selection,
108 } => RowGroupPlan::Global {
109 row_groups: Some(Vec::from(row_groups)),
110 selection,
111 },
112 Self::PerRowGroup(row_groups) => RowGroupPlan::PerRowGroup(Vec::from(row_groups)),
113 }
114 }
115
116 fn front(&self) -> Option<usize> {
117 match self {
118 Self::Global { row_groups, .. } => row_groups.front().copied(),
119 Self::PerRowGroup(row_groups) => row_groups
120 .front()
121 .map(|row_group| row_group.row_group_index),
122 }
123 }
124
125 fn len(&self) -> usize {
126 match self {
127 Self::Global { row_groups, .. } => row_groups.len(),
128 Self::PerRowGroup(row_groups) => row_groups.len(),
129 }
130 }
131
132 fn clear(&mut self) {
133 match self {
134 Self::Global {
135 row_groups,
136 selection,
137 } => {
138 row_groups.clear();
139 *selection = None;
140 }
141 Self::PerRowGroup(row_groups) => row_groups.clear(),
142 }
143 }
144
145 fn global_selection_is_exhausted(&self) -> bool {
148 matches!(
149 self,
150 Self::Global {
151 selection: Some(selection),
152 ..
153 } if selection.row_count() == 0
154 )
155 }
156
157 fn pop_front_selection(&mut self, row_count: usize) -> Option<RowSelection> {
159 match self {
160 Self::Global {
161 row_groups,
162 selection,
163 } => {
164 let popped = row_groups.pop_front();
165 debug_assert!(popped.is_some(), "front row group checked before pop");
166 selection
167 .as_mut()
168 .map(|selection| selection.split_off(row_count))
169 }
170 Self::PerRowGroup(row_groups) => {
171 row_groups
172 .pop_front()
173 .expect("front row group checked before pop")
174 .selection
175 }
176 }
177 }
178}
179
180#[derive(Debug, Clone)]
181struct RowGroupFrontier {
182 parquet_metadata: Arc<ParquetMetaData>,
184 queued: QueuedRowGroups,
186 budget: RowBudget,
188 has_predicates: bool,
191}
192
193impl RowGroupFrontier {
194 fn new(
195 parquet_metadata: Arc<ParquetMetaData>,
196 row_group_plan: RowGroupPlan,
197 budget: RowBudget,
198 has_predicates: bool,
199 ) -> Result<Self, ParquetError> {
200 let queued = QueuedRowGroups::try_new(&parquet_metadata, row_group_plan)?;
201
202 Ok(Self {
203 parquet_metadata,
204 queued,
205 budget,
206 has_predicates,
207 })
208 }
209
210 fn update_budget_after_row_group(&mut self, budget: RowBudget) {
211 self.budget = budget;
212 }
213
214 fn peek_next_row_group(&self) -> Result<Option<usize>, ParquetError> {
225 Ok(self
226 .clone()
227 .next_readable_row_group()?
228 .map(|next_row_group| next_row_group.row_group_idx))
229 }
230
231 fn clear_remaining(&mut self) {
232 self.queued.clear();
233 }
234
235 fn plan_selected_row_group(
241 &self,
242 next_row_group: NextRowGroup,
243 selected_rows: usize,
244 ) -> QueuedRowGroupDecision {
245 if self.has_predicates {
246 return QueuedRowGroupDecision::Read(next_row_group);
247 }
248
249 let rows_after_budget = self.budget.rows_after(selected_rows);
250 if rows_after_budget != 0 {
251 return QueuedRowGroupDecision::Read(next_row_group);
252 }
253
254 QueuedRowGroupDecision::Skip {
255 remaining_budget: self.budget.advance(selected_rows, rows_after_budget),
256 }
257 }
258
259 fn next_readable_row_group(&mut self) -> Result<Option<NextRowGroup>, ParquetError> {
261 loop {
262 let Some(row_group_idx) = self.queued.front() else {
263 return Ok(None);
264 };
265 if self.budget.is_exhausted() || self.queued.global_selection_is_exhausted() {
269 self.clear_remaining();
270 return Ok(None);
271 }
272
273 let row_count = self.parquet_metadata.row_group_num_rows(row_group_idx)?;
274 let selection = self.queued.pop_front_selection(row_count);
275 let (selection, selected_rows) = match selection {
276 Some(selection) => {
277 let selected_rows = selection.row_count();
278 if selected_rows == 0 {
279 continue;
280 }
281 (
283 (selected_rows != row_count).then_some(selection),
284 selected_rows,
285 )
286 }
287 None => (None, row_count),
288 };
289
290 let next_row_group = NextRowGroup {
291 row_group_idx,
292 row_count,
293 selection,
294 budget: self.budget,
295 };
296
297 match self.plan_selected_row_group(next_row_group, selected_rows) {
298 QueuedRowGroupDecision::Read(next_row_group) => {
299 return Ok(Some(next_row_group));
300 }
301 QueuedRowGroupDecision::Skip { remaining_budget } => {
302 self.budget = remaining_budget;
303 }
304 }
305 }
306 }
307}
308
309#[derive(Debug)]
315pub(crate) struct RemainingRowGroups {
316 schema: SchemaRef,
320
321 frontier: RowGroupFrontier,
323
324 row_group_reader_builder: RowGroupReaderBuilder,
326}
327
328#[derive(Debug)]
332pub(crate) struct RemainingRowGroupsParts {
333 pub schema: SchemaRef,
335 pub metadata: Arc<ParquetMetaData>,
337 pub row_group_plan: RowGroupPlan,
339 pub offset: Option<usize>,
341 pub limit: Option<usize>,
343 pub reader_builder: RowGroupReaderBuilderParts,
345}
346
347impl RemainingRowGroups {
348 pub fn new(
349 schema: SchemaRef,
350 parquet_metadata: Arc<ParquetMetaData>,
351 row_group_plan: RowGroupPlan,
352 budget: RowBudget,
353 has_predicates: bool,
354 row_group_reader_builder: RowGroupReaderBuilder,
355 ) -> Result<Self, ParquetError> {
356 Ok(Self {
357 schema,
358 frontier: RowGroupFrontier::new(
359 parquet_metadata,
360 row_group_plan,
361 budget,
362 has_predicates,
363 )?,
364 row_group_reader_builder,
365 })
366 }
367
368 pub(crate) fn into_parts(self) -> RemainingRowGroupsParts {
374 let Self {
375 schema,
376 frontier,
377 row_group_reader_builder,
378 } = self;
379 let RowGroupFrontier {
381 parquet_metadata,
382 queued,
383 budget,
384 has_predicates: _,
385 } = frontier;
386 let row_group_plan = queued.into_plan();
387 RemainingRowGroupsParts {
388 schema,
389 metadata: parquet_metadata,
390 row_group_plan,
391 offset: budget.offset(),
392 limit: budget.limit(),
393 reader_builder: row_group_reader_builder.into_parts(),
394 }
395 }
396
397 pub fn push_data(
399 &mut self,
400 ranges: Vec<Range<u64>>,
401 buffers: Vec<Bytes>,
402 ) -> Result<(), ParquetError> {
403 self.row_group_reader_builder.push_data(ranges, buffers)
404 }
405
406 pub fn buffered_bytes(&self) -> u64 {
408 self.row_group_reader_builder.buffered_bytes()
409 }
410
411 pub fn clear_all_ranges(&mut self) {
413 self.row_group_reader_builder.clear_all_ranges();
414 }
415
416 pub fn is_at_row_group_boundary(&self) -> bool {
419 self.row_group_reader_builder.is_finished()
420 }
421
422 pub fn row_groups_remaining(&self) -> usize {
425 self.frontier.queued.len()
426 }
427
428 pub fn peek_next_row_group(&self) -> Result<Option<usize>, ParquetError> {
443 if self.row_group_reader_builder.has_active_row_group() {
444 return Ok(None);
445 }
446 self.frontier.peek_next_row_group()
447 }
448
449 pub fn try_next_reader(
453 &mut self,
454 ) -> Result<DecodeResult<ParquetRecordBatchReader>, ParquetError> {
455 loop {
456 if !self.row_group_reader_builder.has_active_row_group() {
457 match self.frontier.next_readable_row_group()? {
461 Some(NextRowGroup {
462 row_group_idx,
463 row_count,
464 selection,
465 budget,
466 }) => {
467 self.row_group_reader_builder.next_row_group(
468 row_group_idx,
469 row_count,
470 selection,
471 budget,
472 )?;
473 }
474 None => return Ok(DecodeResult::Finished),
475 }
476 }
477
478 match self.row_group_reader_builder.try_build()? {
479 RowGroupBuildResult::Finished { remaining_budget } => {
480 self.frontier
481 .update_budget_after_row_group(remaining_budget);
482 }
484 RowGroupBuildResult::NeedsData(ranges) => {
485 return Ok(DecodeResult::NeedsData(ranges));
487 }
488 RowGroupBuildResult::Data {
489 batch_reader,
490 remaining_budget,
491 } => {
492 self.frontier
493 .update_budget_after_row_group(remaining_budget);
494 return Ok(DecodeResult::Data(batch_reader));
496 }
497 }
498 }
499 }
500}
501
502#[cfg(test)]
503mod tests {
504 use super::*;
505 use crate::arrow::arrow_reader::RowSelector;
506 use crate::arrow::push_decoder::test::test_file_parquet_metadata;
507
508 fn global_plan(
509 row_groups: Option<Vec<usize>>,
510 selection: Option<RowSelection>,
511 ) -> RowGroupPlan {
512 RowGroupPlan::Global {
513 row_groups,
514 selection,
515 }
516 }
517
518 #[test]
519 fn queued_row_groups_encapsulates_plan_transitions() {
520 let metadata = test_file_parquet_metadata();
521
522 let mut all_row_groups =
523 QueuedRowGroups::try_new(&metadata, global_plan(None, None)).unwrap();
524 assert_eq!(all_row_groups.len(), 2);
525 assert_eq!(all_row_groups.front(), Some(0));
526 assert!(!all_row_groups.global_selection_is_exhausted());
527 assert!(all_row_groups.pop_front_selection(200).is_none());
528 assert_eq!(all_row_groups.front(), Some(1));
529 all_row_groups.clear();
530 assert_eq!(all_row_groups.len(), 0);
531 assert!(matches!(
532 all_row_groups.into_plan(),
533 RowGroupPlan::Global {
534 row_groups: Some(row_groups),
535 selection: None,
536 } if row_groups.is_empty()
537 ));
538
539 let global_selection = RowSelection::from(vec![
540 RowSelector::skip(10),
541 RowSelector::select(5),
542 RowSelector::skip(185),
543 RowSelector::select(200),
544 ]);
545 let mut global = QueuedRowGroups::try_new(
546 &metadata,
547 global_plan(Some(vec![0, 1]), Some(global_selection)),
548 )
549 .unwrap();
550 let first = global.pop_front_selection(200).unwrap();
551 assert_eq!(first.row_count(), 5);
552 assert!(!global.global_selection_is_exhausted());
553 assert!(matches!(
554 global.into_plan(),
555 RowGroupPlan::Global {
556 row_groups: Some(row_groups),
557 selection: Some(selection),
558 } if row_groups == vec![1] && selection.row_count() == 200
559 ));
560
561 let local_selection =
562 RowSelection::from(vec![RowSelector::skip(5), RowSelector::select(3)]);
563 let mut local = QueuedRowGroups::try_new(
564 &metadata,
565 RowGroupPlan::PerRowGroup(vec![
566 RowGroupSelection::new(1, Some(local_selection)),
567 RowGroupSelection::new(0, None),
568 ]),
569 )
570 .unwrap();
571 assert_eq!(local.front(), Some(1));
572 assert_eq!(local.pop_front_selection(200).unwrap().row_count(), 3);
573 assert!(!local.global_selection_is_exhausted());
574 assert!(matches!(
575 local.into_plan(),
576 RowGroupPlan::PerRowGroup(row_groups)
577 if row_groups == vec![RowGroupSelection::new(0, None)]
578 ));
579
580 let exhausted = QueuedRowGroups::try_new(
581 &metadata,
582 global_plan(
583 Some(vec![0]),
584 Some(RowSelection::from(vec![RowSelector::skip(200)])),
585 ),
586 )
587 .unwrap();
588 assert!(exhausted.global_selection_is_exhausted());
589 }
590
591 #[test]
592 fn frontier_handles_global_and_local_exhaustion() {
593 let metadata = test_file_parquet_metadata();
594 let budget = RowBudget::new(None, None);
595
596 let mut global = RowGroupFrontier::new(
597 Arc::clone(&metadata),
598 global_plan(
599 Some(vec![0, 1]),
600 Some(RowSelection::from(vec![RowSelector::skip(400)])),
601 ),
602 budget,
603 false,
604 )
605 .unwrap();
606 assert!(global.next_readable_row_group().unwrap().is_none());
607 assert_eq!(global.queued.len(), 0);
608
609 let mut local = RowGroupFrontier::new(
610 Arc::clone(&metadata),
611 RowGroupPlan::PerRowGroup(vec![
612 RowGroupSelection::new(0, Some(RowSelection::from(vec![RowSelector::skip(200)]))),
613 RowGroupSelection::new(1, None),
614 ]),
615 budget,
616 false,
617 )
618 .unwrap();
619 let next = local.next_readable_row_group().unwrap().unwrap();
620 assert_eq!(next.row_group_idx, 1);
621 assert_eq!(next.row_count, 200);
622 assert!(next.selection.is_none());
623
624 let mut exhausted_budget = RowGroupFrontier::new(
625 metadata,
626 RowGroupPlan::PerRowGroup(vec![RowGroupSelection::new(0, None)]),
627 RowBudget::new(None, Some(0)),
628 false,
629 )
630 .unwrap();
631 assert!(
632 exhausted_budget
633 .next_readable_row_group()
634 .unwrap()
635 .is_none()
636 );
637 assert_eq!(exhausted_budget.queued.len(), 0);
638 }
639
640 #[test]
641 fn frontier_reports_invalid_global_row_group_while_peeking() {
642 let metadata = test_file_parquet_metadata();
643 let frontier = RowGroupFrontier::new(
644 metadata,
645 global_plan(Some(vec![2]), None),
646 RowBudget::new(None, None),
647 false,
648 )
649 .unwrap();
650
651 let error = frontier.peek_next_row_group().unwrap_err();
652 assert!(
653 error
654 .to_string()
655 .contains("Row group index 2 out of bounds for file with 2 row groups")
656 );
657 }
658
659 #[test]
660 fn metadata_row_count_overflow_is_reported() {
661 let metadata = test_file_parquet_metadata();
662 let mut builder = metadata.as_ref().clone().into_builder();
663 let mut row_groups = builder.take_row_groups();
664 let negative_row_group = row_groups
665 .remove(0)
666 .into_builder()
667 .set_num_rows(-1)
668 .build()
669 .unwrap();
670 let metadata = builder.set_row_groups(vec![negative_row_group]).build();
671
672 let error = metadata.row_group_num_rows(0).unwrap_err();
673 assert!(error.to_string().contains("Row count overflow"));
674 }
675}