Skip to content

Commit 0944664

Browse files
committed
Add plan-native scan execution
Signed-off-by: Joe Isaacs <joe.isaacs@live.co.uk>
1 parent 0a5baa3 commit 0944664

22 files changed

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

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

Lines changed: 6 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -25,9 +25,10 @@ general logical query plan.
2525
Every rewrite must preserve the query result, including its dtype, row domain, row order, row
2626
identity, null behavior, and observable errors.
2727

28-
## Future execution
28+
## Execution
2929

30-
Plans currently stop at construction and optimization. A future PR will add a method for executing
31-
an optimized plan. That method will walk the physical plan, read the referenced layout data,
32-
evaluate its expressions, and return the result of the query. The execution API and return type
33-
will be defined as part of that integration rather than fixed by the planning IR today.
30+
Each plan node can execute a row range and selection mask. Leaf plans read their referenced
31+
segments, structural plans combine their children, and expression plans evaluate the remaining
32+
derived work. The separate `vortex-scan-v2` crate copies the existing scan orchestration around
33+
this API so the original `LayoutReader` scanner remains unchanged while the plan-native path is
34+
developed.

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/mod.rs

Lines changed: 17 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
66
mod children;
77
mod display;
8+
mod execution;
89
pub mod optimizer;
910
mod plans;
1011

@@ -19,6 +20,8 @@ pub use display::PlanSummaryExtractor;
1920
pub use display::PlanTreeContext;
2021
pub use display::PlanTreeDisplay;
2122
pub use display::PlanTreeExtractor;
23+
pub use execution::PlanArrayFuture;
24+
pub use execution::PlanExecutionContext;
2225
pub use plans::ChunkedPlan;
2326
pub use plans::DictPlan;
2427
pub use plans::ExpressionPlan;
@@ -64,6 +67,19 @@ pub trait Plan: Any + Send + Sync {
6467
/// domain.
6568
fn optimize(&self) -> VortexResult<PlanRef>;
6669

70+
/// Executes this plan for `row_range`, returning values selected by `mask`.
71+
///
72+
/// The row range is expressed in this plan's row domain. The returned array has one row for
73+
/// every true value in `mask`.
74+
fn execute(
75+
&self,
76+
_ctx: &PlanExecutionContext,
77+
_row_range: &std::ops::Range<u64>,
78+
_mask: vortex_array::MaskFuture,
79+
) -> VortexResult<PlanArrayFuture> {
80+
vortex_bail!("Plan execution is not implemented for '{}'", self.name())
81+
}
82+
6783
/// Returns the dtype produced by this plan.
6884
fn dtype(&self) -> &DType;
6985

@@ -86,7 +102,7 @@ pub trait Plan: Any + Send + Sync {
86102
}
87103
}
88104

89-
/// Constructs a physical plan without changing the layout or scan APIs.
105+
/// Constructs a physical plan for a stored layout tree.
90106
///
91107
/// Known layouts are represented by optimizer-visible plan nodes, which may defer constructing
92108
/// their children. Unsupported layout kinds return an error when their plan is requested.

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

Lines changed: 69 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,18 +2,31 @@
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;
13+
use vortex_array::IntoArray;
14+
use vortex_array::MaskFuture;
15+
use vortex_array::arrays::ChunkedArray;
716
use vortex_array::dtype::DType;
817
use vortex_array::expr::ExactBoundExpr;
918
use vortex_array::expr::label_bound_tree;
19+
use vortex_error::VortexExpect;
1020
use vortex_error::VortexResult;
21+
use vortex_error::vortex_ensure;
1122

1223
use crate::layouts::chunked::ChunkedLayout;
1324
use crate::layouts::row_idx::RowIdx;
1425
use crate::plan::ExpressionPlan;
1526
use crate::plan::LazyPlanChildren;
1627
use crate::plan::Plan;
28+
use crate::plan::PlanArrayFuture;
29+
use crate::plan::PlanExecutionContext;
1730
use crate::plan::PlanRef;
1831
use crate::plan::new_plan;
1932
use crate::plan::optimizer::PlanParentReduceRule;
@@ -60,6 +73,62 @@ impl Plan for ChunkedPlan {
6073
Ok(Arc::new(self.with_chunks(self.dtype.clone(), chunks)))
6174
}
6275

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+
63132
fn dtype(&self) -> &DType {
64133
&self.dtype
65134
}

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

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

44
use std::borrow::Cow;
5+
use std::ops::Range;
56
use std::sync::Arc;
67

8+
use futures::FutureExt;
9+
use futures::try_join;
10+
use vortex_array::IntoArray;
11+
use vortex_array::MaskFuture;
12+
use vortex_array::arrays::DictArray;
713
use vortex_array::expr::ExactBoundExpr;
814
use vortex_array::expr::label_bound_tree;
15+
use vortex_array::optimizer::ArrayOptimizer;
916
use vortex_error::VortexResult;
1017
use vortex_error::vortex_bail;
1118

1219
use crate::layouts::dict::DictLayout;
1320
use crate::plan::ExpressionPlan;
1421
use crate::plan::Plan;
22+
use crate::plan::PlanArrayFuture;
23+
use crate::plan::PlanExecutionContext;
1524
use crate::plan::PlanRef;
1625
use crate::plan::new_plan;
1726
use crate::plan::optimizer::PlanParentReduceRule;
@@ -67,6 +76,35 @@ impl Plan for DictPlan {
6776
Ok(Arc::new(self.with_children(codes, values)))
6877
}
6978

79+
fn execute(
80+
&self,
81+
ctx: &PlanExecutionContext,
82+
row_range: &Range<u64>,
83+
mask: MaskFuture,
84+
) -> VortexResult<PlanArrayFuture> {
85+
let codes = self.codes.execute(ctx, row_range, mask)?;
86+
let values_len = usize::try_from(self.values.row_count())?;
87+
let values = self.values.execute(
88+
ctx,
89+
&(0..self.values.row_count()),
90+
MaskFuture::new_true(values_len),
91+
)?;
92+
let all_values_referenced = self.layout.has_all_values_referenced();
93+
94+
Ok(async move {
95+
let (codes, values) = try_join!(codes, values)?;
96+
// SAFETY: DictLayout validation guarantees integer codes and matching child dtypes.
97+
let dictionary = unsafe {
98+
DictArray::new_unchecked(codes, values)
99+
.set_all_values_referenced(all_values_referenced)
100+
}
101+
.into_array()
102+
.optimize()?;
103+
Ok(dictionary)
104+
}
105+
.boxed())
106+
}
107+
70108
fn dtype(&self) -> &vortex_array::dtype::DType {
71109
&self.dtype
72110
}

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

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

44
use std::any::TypeId;
55
use std::borrow::Cow;
6+
use std::ops::Range;
67
use std::sync::Arc;
78

9+
use futures::FutureExt;
10+
use vortex_array::MaskFuture;
811
use vortex_array::dtype::DType;
912
use vortex_array::dtype::FieldName;
1013
use vortex_array::expr::BoundExpression;
@@ -17,6 +20,8 @@ use vortex_error::VortexResult;
1720
use vortex_error::vortex_bail;
1821

1922
use crate::plan::Plan;
23+
use crate::plan::PlanArrayFuture;
24+
use crate::plan::PlanExecutionContext;
2025
use crate::plan::PlanRef;
2126
use crate::plan::optimizer::reduce_parent;
2227

@@ -100,6 +105,17 @@ impl Plan for ExpressionPlan {
100105
self.optimize_top_down(None)
101106
}
102107

108+
fn execute(
109+
&self,
110+
ctx: &PlanExecutionContext,
111+
row_range: &Range<u64>,
112+
mask: MaskFuture,
113+
) -> VortexResult<PlanArrayFuture> {
114+
let child = self.child.execute(ctx, row_range, mask)?;
115+
let expression = self.expression.clone();
116+
Ok(async move { child.await?.apply_bound(&expression) }.boxed())
117+
}
118+
103119
fn dtype(&self) -> &DType {
104120
self.expression.dtype()
105121
}

0 commit comments

Comments
 (0)