Skip to content

Commit 7efb9db

Browse files
committed
Add plan-native scan execution
Signed-off-by: Joe Isaacs <joe.isaacs@live.co.uk>
1 parent 5f41c68 commit 7efb9db

28 files changed

Lines changed: 2234 additions & 139 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",
@@ -323,6 +324,7 @@ vortex-proto = { version = "0.1.0", path = "./vortex-proto", default-features =
323324
vortex-row = { version = "0.1.0", path = "./vortex-row", default-features = false }
324325
vortex-runend = { version = "0.1.0", path = "./encodings/runend", default-features = false }
325326
vortex-scan = { version = "0.1.0", path = "./vortex-scan", default-features = false }
327+
vortex-scan-v2 = { version = "0.1.0", path = "./vortex-scan-v2", default-features = false }
326328
vortex-sequence = { version = "0.1.0", path = "encodings/sequence", default-features = false }
327329
vortex-session = { version = "0.1.0", path = "./vortex-session", default-features = false }
328330
vortex-sparse = { version = "0.1.0", path = "./encodings/sparse", default-features = false }

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

Lines changed: 9 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -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: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
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+
}
22+
23+
impl PlanExecutionContext {
24+
/// Creates an execution context over a segment source and Vortex session.
25+
pub fn new(segment_source: Arc<dyn SegmentSource>, session: VortexSession) -> Self {
26+
Self {
27+
segment_source,
28+
session,
29+
}
30+
}
31+
32+
/// Returns the segment source used to satisfy leaf reads.
33+
pub fn segment_source(&self) -> &Arc<dyn SegmentSource> {
34+
&self.segment_source
35+
}
36+
37+
/// Returns the Vortex session used for array decoding and expression execution.
38+
pub fn session(&self) -> &VortexSession {
39+
&self.session
40+
}
41+
}

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: 6 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;
@@ -44,9 +47,12 @@ pub use plans::SegmentScan;
4447
pub use plans::SegmentScanData;
4548
pub use plans::SegmentScanPlan;
4649
pub use plans::Take;
50+
pub use plans::TakeData;
4751
pub use plans::TakePlan;
4852
pub use plans::Zoned;
4953
pub use plans::ZonedPlan;
54+
pub use plans::plan_row_idx_expression;
55+
pub use plans::row_idx_dtype;
5056
pub use typed::DynPlan;
5157
pub use typed::Plan;
5258
pub use typed::PlanParts;

vortex-layout/src/plan/optimizer/mod.rs

Lines changed: 0 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -19,12 +19,10 @@ use super::Concat;
1919
use super::Eval;
2020
use super::Pack;
2121
use super::PlanRef;
22-
use super::RowIdx;
2322
use super::Take;
2423
use super::plans::EvalIdentityRule;
2524
use super::plans::ExpressionConcatRule;
2625
use super::plans::ExpressionPackRule;
27-
use super::plans::ExpressionRowIdxRule;
2826
use super::plans::ExpressionTakeRule;
2927

3028
static EVAL_IDENTITY_RULE: PlanReduceRuleAdapter<Eval, EvalIdentityRule> =
@@ -36,15 +34,12 @@ static EXPRESSION_CONCAT_RULE: PlanParentReduceRuleAdapter<Concat, ExpressionCon
3634
PlanParentReduceRuleAdapter::new(ExpressionConcatRule);
3735
static EXPRESSION_TAKE_RULE: PlanParentReduceRuleAdapter<Take, ExpressionTakeRule> =
3836
PlanParentReduceRuleAdapter::new(ExpressionTakeRule);
39-
static EXPRESSION_ROW_IDX_RULE: PlanParentReduceRuleAdapter<RowIdx, ExpressionRowIdxRule> =
40-
PlanParentReduceRuleAdapter::new(ExpressionRowIdxRule);
4137
static EXPRESSION_PACK_RULE: PlanParentReduceRuleAdapter<Pack, ExpressionPackRule> =
4238
PlanParentReduceRuleAdapter::new(ExpressionPackRule);
4339

4440
static PARENT_RULES: PlanParentRuleSet = PlanParentRuleSet::new(&[
4541
&EXPRESSION_CONCAT_RULE,
4642
&EXPRESSION_TAKE_RULE,
47-
&EXPRESSION_ROW_IDX_RULE,
4843
&EXPRESSION_PACK_RULE,
4944
]);
5045

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

Lines changed: 65 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -2,21 +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;
9-
use vortex_array::expr::ExactBoundExpr;
10-
use vortex_array::expr::label_bound_tree;
18+
use vortex_error::VortexExpect;
1119
use vortex_error::VortexResult;
1220
use vortex_error::vortex_bail;
21+
use vortex_error::vortex_ensure;
22+
use vortex_error::vortex_err;
1323
use vortex_session::registry::CachedId;
1424

15-
use crate::layouts::row_idx::RowIdx as RowIdxFn;
1625
use crate::plan::Eval;
1726
use crate::plan::EvalPlan;
1827
use crate::plan::Plan;
28+
use crate::plan::PlanArrayFuture;
1929
use crate::plan::PlanChildren;
30+
use crate::plan::PlanExecutionContext;
2031
use crate::plan::PlanId;
2132
use crate::plan::PlanParts;
2233
use crate::plan::PlanRef;
@@ -142,6 +153,57 @@ impl PlanVTable for Concat {
142153
Ok(())
143154
}
144155

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+
chunk_futures.push(chunk.execute(ctx, &child_range, mask.slice(mask_range))?);
190+
}
191+
}
192+
193+
Ok(async move {
194+
let chunks: Vec<_> = FuturesOrdered::from_iter(chunk_futures)
195+
.try_collect()
196+
.await?;
197+
vortex_ensure!(!chunks.is_empty(), "Non-empty row range selected no chunks");
198+
if chunks.len() == 1 {
199+
return Ok(chunks.into_iter().next().vortex_expect("one chunk"));
200+
}
201+
let dtype = chunks[0].dtype().clone();
202+
Ok(ChunkedArray::try_new(chunks, dtype)?.into_array())
203+
}
204+
.boxed())
205+
}
206+
145207
fn child_name(_plan: &Plan<Self>, index: usize) -> Cow<'_, str> {
146208
Cow::Owned(format!("chunks[{index}]"))
147209
}
@@ -161,23 +223,6 @@ impl PlanParentReduceRule<Concat> for ExpressionConcatRule {
161223
_child_idx: usize,
162224
) -> VortexResult<Option<PlanRef>> {
163225
let expression = parent.expression();
164-
// Row-index expressions are relative to the whole row domain, so they cannot be evaluated
165-
// chunk by chunk.
166-
let references_row_idx = label_bound_tree(
167-
expression,
168-
|node| {
169-
node.as_scalar()
170-
.is_some_and(|scalar_fn| scalar_fn.is::<RowIdxFn>())
171-
},
172-
|acc, &child| acc | child,
173-
)
174-
.get(&ExactBoundExpr(expression.clone()))
175-
.copied()
176-
.unwrap_or(false);
177-
if references_row_idx {
178-
return Ok(None);
179-
}
180-
181226
let chunks = child
182227
.children()
183228
.iter()

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)