|
2 | 2 | // SPDX-FileCopyrightText: Copyright the Vortex contributors |
3 | 3 |
|
4 | 4 | use std::borrow::Cow; |
| 5 | +use std::future; |
| 6 | +use std::ops::Range; |
5 | 7 | use std::sync::Arc; |
6 | 8 |
|
| 9 | +use futures::FutureExt; |
| 10 | +use futures::TryStreamExt; |
| 11 | +use futures::stream::FuturesOrdered; |
| 12 | +use vortex_array::Canonical; |
| 13 | +use vortex_array::IntoArray; |
| 14 | +use vortex_array::MaskFuture; |
| 15 | +use vortex_array::arrays::ChunkedArray; |
7 | 16 | use vortex_array::dtype::DType; |
8 | 17 | use vortex_array::expr::ExactBoundExpr; |
9 | 18 | use vortex_array::expr::label_bound_tree; |
| 19 | +use vortex_error::VortexExpect; |
10 | 20 | use vortex_error::VortexResult; |
| 21 | +use vortex_error::vortex_ensure; |
11 | 22 |
|
12 | 23 | use crate::layouts::chunked::ChunkedLayout; |
13 | 24 | use crate::layouts::row_idx::RowIdx; |
14 | 25 | use crate::plan::ExpressionPlan; |
15 | 26 | use crate::plan::LazyPlanChildren; |
16 | 27 | use crate::plan::Plan; |
| 28 | +use crate::plan::PlanArrayFuture; |
| 29 | +use crate::plan::PlanExecutionContext; |
17 | 30 | use crate::plan::PlanRef; |
18 | 31 | use crate::plan::new_plan; |
19 | 32 | use crate::plan::optimizer::PlanParentReduceRule; |
@@ -60,6 +73,62 @@ impl Plan for ChunkedPlan { |
60 | 73 | Ok(Arc::new(self.with_chunks(self.dtype.clone(), chunks))) |
61 | 74 | } |
62 | 75 |
|
| 76 | + fn execute( |
| 77 | + &self, |
| 78 | + ctx: &PlanExecutionContext, |
| 79 | + row_range: &Range<u64>, |
| 80 | + mask: MaskFuture, |
| 81 | + ) -> VortexResult<PlanArrayFuture> { |
| 82 | + vortex_ensure!( |
| 83 | + row_range.start <= row_range.end && row_range.end <= self.row_count(), |
| 84 | + "Chunked plan row range {:?} is outside 0..{}", |
| 85 | + row_range, |
| 86 | + self.row_count() |
| 87 | + ); |
| 88 | + vortex_ensure!( |
| 89 | + mask.len() == usize::try_from(row_range.end - row_range.start)?, |
| 90 | + "Chunked plan mask length mismatch" |
| 91 | + ); |
| 92 | + if row_range.is_empty() { |
| 93 | + let empty = Canonical::empty(&self.dtype).into_array(); |
| 94 | + return Ok(future::ready(Ok(empty)).boxed()); |
| 95 | + } |
| 96 | + |
| 97 | + let mut chunk_futures = Vec::new(); |
| 98 | + let mut chunk_offset = 0_u64; |
| 99 | + for chunk_index in 0..self.chunks.len() { |
| 100 | + let chunk = self |
| 101 | + .chunks |
| 102 | + .get(chunk_index)? |
| 103 | + .ok_or_else(|| vortex_error::vortex_err!("Chunk {chunk_index} has no plan"))?; |
| 104 | + let chunk_end = chunk_offset |
| 105 | + .checked_add(chunk.row_count()) |
| 106 | + .ok_or_else(|| vortex_error::vortex_err!("Chunk row offset overflow"))?; |
| 107 | + let start = row_range.start.max(chunk_offset); |
| 108 | + let end = row_range.end.min(chunk_end); |
| 109 | + if start < end { |
| 110 | + let child_range = start - chunk_offset..end - chunk_offset; |
| 111 | + let mask_range = usize::try_from(start - row_range.start)? |
| 112 | + ..usize::try_from(end - row_range.start)?; |
| 113 | + chunk_futures.push(chunk.execute(ctx, &child_range, mask.slice(mask_range))?); |
| 114 | + } |
| 115 | + chunk_offset = chunk_end; |
| 116 | + } |
| 117 | + |
| 118 | + Ok(async move { |
| 119 | + let chunks: Vec<_> = FuturesOrdered::from_iter(chunk_futures) |
| 120 | + .try_collect() |
| 121 | + .await?; |
| 122 | + vortex_ensure!(!chunks.is_empty(), "Non-empty row range selected no chunks"); |
| 123 | + if chunks.len() == 1 { |
| 124 | + return Ok(chunks.into_iter().next().vortex_expect("one chunk")); |
| 125 | + } |
| 126 | + let dtype = chunks[0].dtype().clone(); |
| 127 | + Ok(ChunkedArray::try_new(chunks, dtype)?.into_array()) |
| 128 | + } |
| 129 | + .boxed()) |
| 130 | + } |
| 131 | + |
63 | 132 | fn dtype(&self) -> &DType { |
64 | 133 | &self.dtype |
65 | 134 | } |
|
0 commit comments