|
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; |
7 | 13 | use vortex_array::EmptyMetadata; |
| 14 | +use vortex_array::IntoArray; |
| 15 | +use vortex_array::MaskFuture; |
| 16 | +use vortex_array::arrays::ChunkedArray; |
8 | 17 | use vortex_array::dtype::DType; |
9 | 18 | use vortex_array::expr::ExactBoundExpr; |
10 | 19 | use vortex_array::expr::label_bound_tree; |
| 20 | +use vortex_error::VortexExpect; |
11 | 21 | use vortex_error::VortexResult; |
12 | 22 | use vortex_error::vortex_bail; |
| 23 | +use vortex_error::vortex_ensure; |
| 24 | +use vortex_error::vortex_err; |
13 | 25 | use vortex_session::registry::CachedId; |
14 | 26 |
|
15 | 27 | use crate::layouts::row_idx::RowIdx as RowIdxFn; |
16 | 28 | use crate::plan::Eval; |
17 | 29 | use crate::plan::EvalPlan; |
18 | 30 | use crate::plan::Plan; |
| 31 | +use crate::plan::PlanArrayFuture; |
| 32 | +use crate::plan::PlanExecutionContext; |
19 | 33 | use crate::plan::PlanId; |
20 | 34 | use crate::plan::PlanParts; |
21 | 35 | use crate::plan::PlanRef; |
@@ -95,6 +109,56 @@ impl PlanVTable for Concat { |
95 | 109 | ConcatPlan::try_new(plan.dtype().clone(), children) |
96 | 110 | } |
97 | 111 |
|
| 112 | + fn execute( |
| 113 | + plan: &Plan<Self>, |
| 114 | + ctx: &PlanExecutionContext, |
| 115 | + row_range: &Range<u64>, |
| 116 | + mask: MaskFuture, |
| 117 | + ) -> VortexResult<PlanArrayFuture> { |
| 118 | + vortex_ensure!( |
| 119 | + row_range.start <= row_range.end && row_range.end <= plan.row_count(), |
| 120 | + "Concat row range {:?} is outside 0..{}", |
| 121 | + row_range, |
| 122 | + plan.row_count() |
| 123 | + ); |
| 124 | + vortex_ensure!( |
| 125 | + mask.len() == usize::try_from(row_range.end - row_range.start)?, |
| 126 | + "Concat mask length mismatch" |
| 127 | + ); |
| 128 | + if row_range.is_empty() { |
| 129 | + let empty = Canonical::empty(plan.dtype()).into_array(); |
| 130 | + return Ok(future::ready(Ok(empty)).boxed()); |
| 131 | + } |
| 132 | + |
| 133 | + let mut chunk_futures = Vec::new(); |
| 134 | + for (chunk, &chunk_offset) in plan.children().iter().zip(plan.row_offsets()) { |
| 135 | + let chunk_end = chunk_offset |
| 136 | + .checked_add(chunk.row_count()) |
| 137 | + .ok_or_else(|| vortex_err!("Chunk row offset overflow"))?; |
| 138 | + let start = row_range.start.max(chunk_offset); |
| 139 | + let end = row_range.end.min(chunk_end); |
| 140 | + if start < end { |
| 141 | + let child_range = start - chunk_offset..end - chunk_offset; |
| 142 | + let mask_range = usize::try_from(start - row_range.start)? |
| 143 | + ..usize::try_from(end - row_range.start)?; |
| 144 | + chunk_futures.push(chunk.execute(ctx, &child_range, mask.slice(mask_range))?); |
| 145 | + } |
| 146 | + } |
| 147 | + |
| 148 | + Ok(async move { |
| 149 | + let chunks: Vec<_> = FuturesOrdered::from_iter(chunk_futures) |
| 150 | + .try_collect() |
| 151 | + .await?; |
| 152 | + vortex_ensure!(!chunks.is_empty(), "Non-empty row range selected no chunks"); |
| 153 | + if chunks.len() == 1 { |
| 154 | + return Ok(chunks.into_iter().next().vortex_expect("one chunk")); |
| 155 | + } |
| 156 | + let dtype = chunks[0].dtype().clone(); |
| 157 | + Ok(ChunkedArray::try_new(chunks, dtype)?.into_array()) |
| 158 | + } |
| 159 | + .boxed()) |
| 160 | + } |
| 161 | + |
98 | 162 | fn child_name(_plan: &Plan<Self>, index: usize) -> Cow<'_, str> { |
99 | 163 | Cow::Owned(format!("chunks[{index}]")) |
100 | 164 | } |
|
0 commit comments