|
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; |
| 18 | +use vortex_error::VortexExpect; |
9 | 19 | use vortex_error::VortexResult; |
10 | 20 | use vortex_error::vortex_bail; |
| 21 | +use vortex_error::vortex_ensure; |
| 22 | +use vortex_error::vortex_err; |
11 | 23 | use vortex_session::registry::CachedId; |
12 | 24 |
|
13 | 25 | use crate::plan::Eval; |
14 | 26 | use crate::plan::EvalPlan; |
15 | 27 | use crate::plan::Plan; |
| 28 | +use crate::plan::PlanArrayFuture; |
16 | 29 | use crate::plan::PlanChildren; |
| 30 | +use crate::plan::PlanExecutionContext; |
17 | 31 | use crate::plan::PlanId; |
18 | 32 | use crate::plan::PlanParts; |
19 | 33 | use crate::plan::PlanRef; |
@@ -139,6 +153,62 @@ impl PlanVTable for Concat { |
139 | 153 | Ok(()) |
140 | 154 | } |
141 | 155 |
|
| 156 | + fn execute( |
| 157 | + plan: &Plan<Self>, |
| 158 | + ctx: &PlanExecutionContext, |
| 159 | + row_range: &Range<u64>, |
| 160 | + mask: MaskFuture, |
| 161 | + ) -> VortexResult<PlanArrayFuture> { |
| 162 | + vortex_ensure!( |
| 163 | + row_range.start <= row_range.end && row_range.end <= plan.row_count(), |
| 164 | + "Concat row range {:?} is outside 0..{}", |
| 165 | + row_range, |
| 166 | + plan.row_count() |
| 167 | + ); |
| 168 | + vortex_ensure!( |
| 169 | + mask.len() == usize::try_from(row_range.end - row_range.start)?, |
| 170 | + "Concat mask length mismatch" |
| 171 | + ); |
| 172 | + if row_range.is_empty() { |
| 173 | + let empty = Canonical::empty(plan.dtype()).into_array(); |
| 174 | + return Ok(future::ready(Ok(empty)).boxed()); |
| 175 | + } |
| 176 | + |
| 177 | + let mut chunk_futures = Vec::new(); |
| 178 | + for (chunk, &chunk_offset) in plan.children().iter().zip(plan.row_offsets()) { |
| 179 | + let chunk = chunk?; |
| 180 | + let chunk_end = chunk_offset |
| 181 | + .checked_add(chunk.row_count()) |
| 182 | + .ok_or_else(|| vortex_err!("Chunk row offset overflow"))?; |
| 183 | + let start = row_range.start.max(chunk_offset); |
| 184 | + let end = row_range.end.min(chunk_end); |
| 185 | + if start < end { |
| 186 | + let child_range = start - chunk_offset..end - chunk_offset; |
| 187 | + let mask_range = usize::try_from(start - row_range.start)? |
| 188 | + ..usize::try_from(end - row_range.start)?; |
| 189 | + let child_ctx = ctx.child_row_domain(chunk_offset)?; |
| 190 | + chunk_futures.push(chunk.execute( |
| 191 | + &child_ctx, |
| 192 | + &child_range, |
| 193 | + mask.slice(mask_range), |
| 194 | + )?); |
| 195 | + } |
| 196 | + } |
| 197 | + |
| 198 | + Ok(async move { |
| 199 | + let chunks: Vec<_> = FuturesOrdered::from_iter(chunk_futures) |
| 200 | + .try_collect() |
| 201 | + .await?; |
| 202 | + vortex_ensure!(!chunks.is_empty(), "Non-empty row range selected no chunks"); |
| 203 | + if chunks.len() == 1 { |
| 204 | + return Ok(chunks.into_iter().next().vortex_expect("one chunk")); |
| 205 | + } |
| 206 | + let dtype = chunks[0].dtype().clone(); |
| 207 | + Ok(ChunkedArray::try_new(chunks, dtype)?.into_array()) |
| 208 | + } |
| 209 | + .boxed()) |
| 210 | + } |
| 211 | + |
142 | 212 | fn child_name(_plan: &Plan<Self>, index: usize) -> Cow<'_, str> { |
143 | 213 | Cow::Owned(format!("chunks[{index}]")) |
144 | 214 | } |
|
0 commit comments