Skip to content

Commit 21eb936

Browse files
committed
Add plan-native scan execution
Signed-off-by: Joe Isaacs <joe.isaacs@live.co.uk>
1 parent 483f117 commit 21eb936

27 files changed

Lines changed: 2024 additions & 9 deletions

File tree

Cargo.lock

Lines changed: 20 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Cargo.toml

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@ members = [
2323
"vortex-btrblocks",
2424
"vortex-layout",
2525
"vortex-scan",
26+
"vortex-scan-v2",
2627
"vortex-file",
2728
"vortex-ipc",
2829
"vortex",
@@ -325,6 +326,7 @@ vortex-proto = { version = "0.1.0", path = "./vortex-proto", default-features =
325326
vortex-row = { version = "0.1.0", path = "./vortex-row", default-features = false }
326327
vortex-runend = { version = "0.1.0", path = "./encodings/runend", default-features = false }
327328
vortex-scan = { version = "0.1.0", path = "./vortex-scan", default-features = false }
329+
vortex-scan-v2 = { version = "0.1.0", path = "./vortex-scan-v2", default-features = false }
328330
vortex-sequence = { version = "0.1.0", path = "encodings/sequence", default-features = false }
329331
vortex-session = { version = "0.1.0", path = "./vortex-session", default-features = false }
330332
vortex-sparse = { version = "0.1.0", path = "./encodings/sparse", default-features = false }

docs/developer-guide/internals/scan-planning.md

Lines changed: 10 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,7 @@ on demand.
1818
| `Take` | index `values` by `codes` |
1919
| `ListPack` | assemble a list from elements and offsets, plus optional validity |
2020
| `Eval` | apply an expression to its child |
21-
| `RowIdx` | offset row numbers into the file's row domain |
21+
| `RowIdx` | generate row numbers for the current execution row domain |
2222

2323
Naming operators for what they compute is what lets one rule cover every case. `Concat` of
2424
`Concat` flattens on shape alone, and `Take` over `SegmentScan` is the dictionary pushdown,
@@ -55,9 +55,14 @@ ID, dtype, row count, and lazy children. Only the unsized tail containing the vt
5555
already serialize their metadata; the ones holding a read context or a bound expression return
5656
`None` until those codecs exist.
5757

58+
## Execution
59+
60+
Each operator executes over a row range and selection mask. `SegmentScan` reads its segment,
61+
structural operators combine their children, and `Eval` applies the remaining derived work.
62+
`vortex-scan-v2` copies the existing scan orchestration around this API, so the original
63+
`LayoutReader` scanner is untouched while the plan-native path is developed.
64+
5865
## Future work
5966

60-
Plans currently stop at construction and optimization. Still to come: a plan registry and foreign
61-
operator placeholder so third-party operators survive a round trip, a serialization envelope, and
62-
an execution stage that walks an optimized plan, reads the referenced segments, and returns the
63-
query result.
67+
Still to come: a plan registry and foreign operator placeholder so third-party operators survive
68+
a round trip, and a serialization envelope.

vortex-layout/src/layouts/row_idx/mod.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -297,7 +297,7 @@ fn row_idx_dtype() -> DType {
297297
}
298298

299299
// Returns a SequenceArray representing the row indices for the given row range,
300-
fn idx_array(row_offset: u64, row_range: &Range<u64>) -> SequenceArray {
300+
pub(crate) fn idx_array(row_offset: u64, row_range: &Range<u64>) -> SequenceArray {
301301
Sequence::try_new(
302302
PValue::U64(row_offset + row_range.start),
303303
PValue::U64(1),
Lines changed: 67 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,67 @@
1+
// SPDX-License-Identifier: Apache-2.0
2+
// SPDX-FileCopyrightText: Copyright the Vortex contributors
3+
4+
use std::sync::Arc;
5+
6+
use futures::future::BoxFuture;
7+
use vortex_array::ArrayRef;
8+
use vortex_error::VortexResult;
9+
use vortex_session::VortexSession;
10+
11+
use crate::segments::SegmentSource;
12+
13+
/// Future resolving to the array produced by a physical plan.
14+
pub type PlanArrayFuture = BoxFuture<'static, VortexResult<ArrayRef>>;
15+
16+
/// Runtime dependencies shared by every node in a plan execution.
17+
#[derive(Clone)]
18+
pub struct PlanExecutionContext {
19+
segment_source: Arc<dyn SegmentSource>,
20+
session: VortexSession,
21+
row_offset: u64,
22+
}
23+
24+
impl PlanExecutionContext {
25+
/// Creates an execution context over a segment source and Vortex session.
26+
pub fn new(segment_source: Arc<dyn SegmentSource>, session: VortexSession) -> Self {
27+
Self {
28+
segment_source,
29+
session,
30+
row_offset: 0,
31+
}
32+
}
33+
34+
/// Sets the global row index of the first row in the root plan's row domain.
35+
pub fn with_row_offset(mut self, row_offset: u64) -> Self {
36+
self.row_offset = row_offset;
37+
self
38+
}
39+
40+
/// Returns the global row index of the first row in the current plan's row domain.
41+
pub fn row_offset(&self) -> u64 {
42+
self.row_offset
43+
}
44+
45+
/// Derives the execution context for a child whose row domain starts within this one.
46+
pub(crate) fn child_row_domain(&self, relative_row_offset: u64) -> VortexResult<Self> {
47+
let row_offset = self
48+
.row_offset
49+
.checked_add(relative_row_offset)
50+
.ok_or_else(|| vortex_error::vortex_err!("Plan row-domain offset overflow"))?;
51+
Ok(Self {
52+
segment_source: Arc::clone(&self.segment_source),
53+
session: self.session.clone(),
54+
row_offset,
55+
})
56+
}
57+
58+
/// Returns the segment source used to satisfy leaf reads.
59+
pub fn segment_source(&self) -> &Arc<dyn SegmentSource> {
60+
&self.segment_source
61+
}
62+
63+
/// Returns the Vortex session used for array decoding and expression execution.
64+
pub fn session(&self) -> &VortexSession {
65+
&self.session
66+
}
67+
}

vortex-layout/src/plan/lower.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -126,6 +126,7 @@ fn lower_dict(layout: &DictLayout) -> VortexResult<TakePlan> {
126126
TakePlan::from_children_unchecked(
127127
layout.dtype().clone(),
128128
layout.row_count(),
129+
layout.has_all_values_referenced(),
129130
lazy_children(layout.to_layout(), vec![1, 0]),
130131
)
131132
})

vortex-layout/src/plan/mod.rs

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@
99
1010
mod children;
1111
mod display;
12+
mod execution;
1213
mod lower;
1314
mod optimize;
1415
pub mod optimizer;
@@ -22,6 +23,8 @@ pub use display::PlanSummaryExtractor;
2223
pub use display::PlanTreeContext;
2324
pub use display::PlanTreeDisplay;
2425
pub use display::PlanTreeExtractor;
26+
pub use execution::PlanArrayFuture;
27+
pub use execution::PlanExecutionContext;
2528
pub use lower::lower;
2629
pub use optimize::optimize;
2730
pub use plans::Concat;
@@ -43,6 +46,7 @@ pub use plans::SegmentScan;
4346
pub use plans::SegmentScanData;
4447
pub use plans::SegmentScanPlan;
4548
pub use plans::Take;
49+
pub use plans::TakeData;
4650
pub use plans::TakePlan;
4751
pub use plans::Zoned;
4852
pub use plans::ZonedPlan;

vortex-layout/src/plan/plans/concat.rs

Lines changed: 70 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,18 +2,32 @@
22
// SPDX-FileCopyrightText: Copyright the Vortex contributors
33

44
use std::borrow::Cow;
5+
use std::future;
6+
use std::ops::Range;
57
use std::sync::Arc;
68

9+
use futures::FutureExt;
10+
use futures::TryStreamExt;
11+
use futures::stream::FuturesOrdered;
12+
use vortex_array::Canonical;
713
use vortex_array::EmptyMetadata;
14+
use vortex_array::IntoArray;
15+
use vortex_array::MaskFuture;
16+
use vortex_array::arrays::ChunkedArray;
817
use vortex_array::dtype::DType;
18+
use vortex_error::VortexExpect;
919
use vortex_error::VortexResult;
1020
use vortex_error::vortex_bail;
21+
use vortex_error::vortex_ensure;
22+
use vortex_error::vortex_err;
1123
use vortex_session::registry::CachedId;
1224

1325
use crate::plan::Eval;
1426
use crate::plan::EvalPlan;
1527
use crate::plan::Plan;
28+
use crate::plan::PlanArrayFuture;
1629
use crate::plan::PlanChildren;
30+
use crate::plan::PlanExecutionContext;
1731
use crate::plan::PlanId;
1832
use crate::plan::PlanParts;
1933
use crate::plan::PlanRef;
@@ -139,6 +153,62 @@ impl PlanVTable for Concat {
139153
Ok(())
140154
}
141155

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+
142212
fn child_name(_plan: &Plan<Self>, index: usize) -> Cow<'_, str> {
143213
Cow::Owned(format!("chunks[{index}]"))
144214
}

vortex-layout/src/plan/plans/eval.rs

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,15 +3,20 @@
33

44
use std::borrow::Cow;
55
use std::fmt;
6+
use std::ops::Range;
67

8+
use futures::FutureExt;
79
use vortex_array::EmptyMetadata;
10+
use vortex_array::MaskFuture;
811
use vortex_array::expr::BoundExpression;
912
use vortex_error::VortexResult;
1013
use vortex_error::vortex_bail;
1114
use vortex_session::registry::CachedId;
1215

1316
use crate::plan::Plan;
17+
use crate::plan::PlanArrayFuture;
1418
use crate::plan::PlanChildren;
19+
use crate::plan::PlanExecutionContext;
1520
use crate::plan::PlanId;
1621
use crate::plan::PlanParts;
1722
use crate::plan::PlanRef;
@@ -106,6 +111,17 @@ impl PlanVTable for Eval {
106111
Ok(())
107112
}
108113

114+
fn execute(
115+
plan: &Plan<Self>,
116+
ctx: &PlanExecutionContext,
117+
row_range: &Range<u64>,
118+
mask: MaskFuture,
119+
) -> VortexResult<PlanArrayFuture> {
120+
let child = plan.child_plan()?.execute(ctx, row_range, mask)?;
121+
let expression = plan.expression().clone();
122+
Ok(async move { child.await?.apply_bound(&expression) }.boxed())
123+
}
124+
109125
fn child_name(_plan: &Plan<Self>, index: usize) -> Cow<'_, str> {
110126
if index == 0 {
111127
Cow::Borrowed("child")

0 commit comments

Comments
 (0)