vortex_layout/plan/physical/plans/
zoned.rs1use std::borrow::Cow;
5use std::fmt;
6use std::ops::Range;
7use std::sync::Arc;
8
9use parking_lot::RwLock;
10use vortex_array::ArrayRef;
11use vortex_array::EmptyMetadata;
12use vortex_array::IntoArray;
13use vortex_array::VortexSessionExecute;
14use vortex_array::aggregate_fn::AggregateFnRef;
15use vortex_array::arrays::BoolArray;
16use vortex_array::arrays::ConstantArray;
17use vortex_array::arrays::StructArray;
18use vortex_array::dtype::DType;
19use vortex_array::expr::BoundExpression;
20use vortex_array::expr::traversal::NodeExt;
21use vortex_array::expr::traversal::Transformed;
22use vortex_array::expr::traversal::TraversalOrder;
23use vortex_array::scalar::Scalar;
24use vortex_array::scalar_fn::fns::dynamic::DynamicExprUpdates;
25use vortex_array::scalar_fn::fns::stat::StatFn;
26use vortex_array::validity::Validity;
27use vortex_buffer::BitBufferMut;
28use vortex_error::VortexExpect;
29use vortex_error::VortexResult;
30use vortex_error::vortex_bail;
31use vortex_error::vortex_err;
32use vortex_mask::Mask;
33use vortex_session::VortexSession;
34use vortex_session::registry::CachedId;
35
36use crate::layouts::zoned::zone_map::ZoneMap;
37use crate::plan::physical::Eval;
38use crate::plan::physical::Plan;
39use crate::plan::physical::PlanChildren;
40use crate::plan::physical::PlanId;
41use crate::plan::physical::PlanParts;
42use crate::plan::physical::PlanRef;
43use crate::plan::physical::PlanVTable;
44use crate::plan::physical::check_child_count;
45use crate::plan::physical::operators::OnceSource;
46use crate::plan::physical::operators::UNBOUNDED;
47use crate::plan::physical::operators::WholeSource;
48use crate::plan::physical::optimizer::PlanParentReduceRule;
49use crate::plan::physical::pipeline::Chain;
50use crate::plan::physical::pipeline::Compiler;
51use crate::plan::physical::pipeline::Reach;
52use crate::segments::SegmentId;
53
54const DATA: usize = 0;
55const ZONES: usize = 1;
56
57#[derive(Clone, Debug)]
58struct ZonedPruningState {
59 expression: BoundExpression,
60 column_dtype: DType,
62 pruned_zones: Arc<PrunedZones>,
64}
65
66pub(crate) struct PrunedZones {
72 updates: Option<DynamicExprUpdates>,
73 state: RwLock<Option<(ZoneMap, u64, Mask)>>,
75}
76
77impl PrunedZones {
78 fn new(expression: &BoundExpression) -> Self {
79 Self {
80 updates: DynamicExprUpdates::new(expression),
81 state: RwLock::new(None),
82 }
83 }
84
85 fn version(&self) -> u64 {
86 self.updates.as_ref().map_or(0, DynamicExprUpdates::version)
87 }
88
89 pub(crate) fn get(
92 &self,
93 expression: &BoundExpression,
94 session: &VortexSession,
95 ) -> VortexResult<Option<Mask>> {
96 let version = self.version();
97 {
98 let state = self.state.read();
99 match &*state {
100 None => return Ok(None),
101 Some((_, proven, pruned)) if *proven >= version => return Ok(Some(pruned.clone())),
102 Some(_) => {}
103 }
104 }
105 let mut state = self.state.write();
106 let Some((zones, proven, pruned)) = state.as_mut() else {
107 return Ok(None);
108 };
109 if *proven < version {
110 *pruned = zones.prune(expression, session)?;
111 *proven = version;
112 }
113 Ok(Some(pruned.clone()))
114 }
115
116 pub(crate) fn init(
118 &self,
119 zones: ZoneMap,
120 expression: &BoundExpression,
121 session: &VortexSession,
122 ) -> VortexResult<Mask> {
123 let version = self.version();
124 let pruned = zones.prune(expression, session)?;
125 let mut state = self.state.write();
126 if state.is_none() {
127 *state = Some((zones, version, pruned.clone()));
128 }
129 Ok(pruned)
130 }
131}
132
133impl fmt::Debug for PrunedZones {
134 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
135 f.debug_struct("PrunedZones")
136 .field("dynamic", &self.updates.is_some())
137 .field("read", &self.state.read().is_some())
138 .finish()
139 }
140}
141
142#[derive(Clone, Debug)]
144pub struct ZonedData {
145 zone_len: u64,
146 aggregate_fns: Arc<[AggregateFnRef]>,
148 pruning: Option<ZonedPruningState>,
149}
150
151#[derive(Clone, Debug)]
158pub struct Zoned;
159
160pub type ZonedPlan = Plan<Zoned>;
162
163impl ZonedPlan {
164 pub(crate) fn from_children(
165 dtype: DType,
166 row_count: u64,
167 children: PlanChildren,
168 zone_len: u64,
169 aggregate_fns: Arc<[AggregateFnRef]>,
170 ) -> Self {
171 PlanParts {
172 vtable: Zoned,
173 dtype,
174 row_count,
175 children,
176 data: ZonedData {
177 zone_len,
178 aggregate_fns,
179 pruning: None,
180 },
181 }
182 .into_typed()
183 }
184
185 pub fn new(
188 data: PlanRef,
189 zones: PlanRef,
190 zone_len: u64,
191 aggregate_fns: Arc<[AggregateFnRef]>,
192 ) -> Self {
193 let dtype = data.dtype().clone();
194 let row_count = data.row_count();
195 Self::from_children(
196 dtype,
197 row_count,
198 vec![data, zones].into(),
199 zone_len,
200 aggregate_fns,
201 )
202 }
203
204 pub fn zone_len(&self) -> u64 {
206 self.data().zone_len
207 }
208
209 pub fn aggregate_fns(&self) -> &Arc<[AggregateFnRef]> {
211 &self.data().aggregate_fns
212 }
213
214 pub fn pruning_column_dtype(&self) -> Option<&DType> {
216 self.data()
217 .pruning
218 .as_ref()
219 .map(|state| &state.column_dtype)
220 }
221
222 pub(crate) fn pruned_zones(&self) -> Option<&Arc<PrunedZones>> {
224 self.data()
225 .pruning
226 .as_ref()
227 .map(|state| &state.pruned_zones)
228 }
229
230 pub fn data_plan(&self) -> VortexResult<Option<PlanRef>> {
232 if self.is_pruning() {
233 return Ok(None);
234 }
235 self.child(DATA)
236 }
237
238 pub fn zones_plan(&self) -> VortexResult<PlanRef> {
240 let index = if self.is_pruning() { 0 } else { ZONES };
241 self.child_required(index)
242 }
243
244 pub fn is_pruning(&self) -> bool {
246 self.data().pruning.is_some()
247 }
248
249 pub fn pruning_expression(&self) -> Option<&BoundExpression> {
251 self.data().pruning.as_ref().map(|state| &state.expression)
252 }
253
254 fn with_pruning(&self, expression: BoundExpression) -> VortexResult<Option<Self>> {
255 if self.data().zone_len == 0 || self.is_pruning() {
256 return Ok(None);
257 }
258 let mut data = self.data().clone();
259 data.pruning = Some(ZonedPruningState {
260 expression: expression.clone(),
261 column_dtype: self.dtype().clone(),
262 pruned_zones: Arc::new(PrunedZones::new(&expression)),
263 });
264 Ok(Some(
265 PlanParts {
266 vtable: Zoned,
267 dtype: expression.dtype().clone(),
268 row_count: self.row_count(),
269 children: vec![self.zones_plan()?].into(),
270 data,
271 }
272 .into_typed(),
273 ))
274 }
275}
276
277impl PlanVTable for Zoned {
278 type PlanData = ZonedData;
279 type Metadata = EmptyMetadata;
280
281 fn id(&self) -> PlanId {
282 static ID: CachedId = CachedId::new("vortex.plan.zoned");
283 *ID
284 }
285
286 fn fmt(plan: &Plan<Self>, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
287 if let Some(expression) = plan.pruning_expression() {
288 write!(formatter, " prune={expression}")?;
289 }
290 Ok(())
291 }
292
293 fn metadata(_plan: &Plan<Self>) -> Option<Self::Metadata> {
294 None
295 }
296
297 fn with_children(
298 plan: &Plan<Self>,
299 children: &PlanChildren,
300 data: &mut Self::PlanData,
301 ) -> VortexResult<()> {
302 if plan.is_pruning() {
303 check_child_count("Zoned pruning", children, 1)?;
304 if let Some(state) = &mut data.pruning {
306 state.pruned_zones = Arc::new(PrunedZones::new(&state.expression));
307 }
308 return Ok(());
309 }
310
311 check_child_count("Zoned", children, 2)?;
312 let data = children
313 .get(DATA)?
314 .ok_or_else(|| vortex_error::vortex_err!("Zoned data child is absent"))?;
315 if data.dtype() != plan.dtype() || data.row_count() != plan.row_count() {
316 vortex_error::vortex_bail!("Zoned data child shape does not match the plan output");
317 }
318 Ok(())
319 }
320
321 fn child_name(plan: &Plan<Self>, index: usize) -> Cow<'_, str> {
322 if plan.is_pruning() {
323 return if index == 0 {
324 Cow::Borrowed("zones")
325 } else {
326 Cow::Owned(format!("child[{index}]"))
327 };
328 }
329 match index {
330 DATA => Cow::Borrowed("data"),
331 ZONES => Cow::Borrowed("zones"),
332 _ => Cow::Owned(format!("child[{index}]")),
333 }
334 }
335
336 fn compile(
340 plan: &Plan<Self>,
341 rows: Range<u64>,
342 mask: &Mask,
343 compiler: &mut Compiler<'_>,
344 ) -> VortexResult<Option<Chain>> {
345 if !plan.is_pruning() {
346 let Some(data) = plan.data_plan()? else {
347 vortex_bail!("Zoned plan has no data child");
348 };
349 return compiler.compile(&data, rows, mask);
350 }
351 if mask.all_false() {
352 return Ok(None);
353 }
354 let expression = plan
355 .pruning_expression()
356 .ok_or_else(|| vortex_err!("Zoned pruning plan has no expression"))?;
357 if let Some(pruned) = pruned_zones(plan)?.get(expression, compiler.session())? {
358 return Ok(Some(Chain::new(OnceSource::new(expand_zones(
359 plan, &rows, mask, &pruned,
360 )?))));
361 }
362 let zones = plan.zones_plan()?;
363 let count = usize::try_from(zones.row_count())?;
364 let Some(chain) = compiler.compile(&zones, 0..zones.row_count(), &Mask::new_true(count))?
365 else {
366 vortex_bail!("Zoned pruning plan has an empty zone table");
367 };
368 let zoned = plan.clone();
369 let mask = mask.clone();
370 let source = WholeSource::new(zones.dtype().clone(), move |table, cx| {
371 let pruned = prune_zones(&zoned, cx.session(), table)?;
372 expand_zones(&zoned, &rows, &mask, &pruned)
373 });
374 Ok(Some(compiler.join_with(vec![chain], |_| UNBOUNDED, source)))
375 }
376
377 fn reach(
378 plan: &Plan<Self>,
379 rows: Range<u64>,
380 at: &Reach,
381 visit: &mut dyn FnMut(SegmentId, Range<u64>),
382 ) -> VortexResult<()> {
383 if !plan.is_pruning() {
384 return match plan.data_plan()? {
385 Some(data) => data.reach(rows, at, visit),
386 None => Ok(()),
387 };
388 }
389 let zones = plan.zones_plan()?;
390 zones.reach(0..zones.row_count(), &at.fixed(&rows), visit)
391 }
392}
393
394fn pruned_zones(plan: &ZonedPlan) -> VortexResult<&Arc<PrunedZones>> {
396 plan.pruned_zones()
397 .ok_or_else(|| vortex_err!("Zone pruning needs a pruning plan"))
398}
399
400fn prune_zones(plan: &ZonedPlan, session: &VortexSession, table: ArrayRef) -> VortexResult<Mask> {
403 let table = table.execute::<StructArray>(&mut session.create_execution_ctx())?;
404 let (Some(expression), Some(column_dtype)) =
405 (plan.pruning_expression(), plan.pruning_column_dtype())
406 else {
407 vortex_bail!("Zone pruning needs a pruning plan");
408 };
409 let zone_map = ZoneMap::try_new(
410 column_dtype.clone(),
411 table,
412 Arc::clone(plan.aggregate_fns()),
413 plan.zone_len(),
414 plan.row_count(),
415 )?;
416 pruned_zones(plan)?.init(zone_map, expression, session)
417}
418
419fn expand_zones(
424 plan: &ZonedPlan,
425 rows: &Range<u64>,
426 mask: &Mask,
427 pruned: &Mask,
428) -> VortexResult<ArrayRef> {
429 let zone_len = plan.zone_len();
430 let start_zone = usize::try_from(rows.start / zone_len)?;
431 let end_zone = usize::try_from(rows.end.div_ceil(zone_len))?;
432 let selected_zones = pruned.slice(start_zone..end_zone);
433 if selected_zones.all_true() || selected_zones.all_false() {
434 let scalar = Scalar::from(selected_zones.all_true()).cast(plan.dtype())?;
435 return Ok(ConstantArray::new(scalar, mask.true_count()).into_array());
436 }
437 let mut bits = BitBufferMut::with_capacity(mask.len());
438 let mut row = rows.start;
439 while row < rows.end {
440 let zone = usize::try_from(row / zone_len).vortex_expect("zone index must fit in usize");
441 let zone_end = ((row / zone_len) + 1) * zone_len;
442 let end = zone_end.min(rows.end);
443 bits.append_n(
444 pruned.value(zone),
445 usize::try_from(end - row).vortex_expect("zone rows must fit in usize"),
446 );
447 row = end;
448 }
449 let validity = if plan.dtype().is_nullable() {
450 Validity::AllValid
451 } else {
452 Validity::NonNullable
453 };
454 let array = BoolArray::try_new(bits.freeze(), validity)?.into_array();
455 if mask.all_true() {
456 return Ok(array);
457 }
458 array.filter(mask.clone())
459}
460
461#[derive(Debug)]
463pub(crate) struct ExpressionZonedRule;
464
465impl PlanParentReduceRule<Zoned> for ExpressionZonedRule {
466 type Parent = Eval;
467
468 fn reduce_parent(
469 &self,
470 child: &ZonedPlan,
471 parent: &Plan<Eval>,
472 _child_idx: usize,
473 ) -> VortexResult<Option<PlanRef>> {
474 let mut contains_stat = false;
475 let mut contains_root = false;
476 parent.expression().clone().transform_down(|expression| {
477 if expression
478 .as_scalar()
479 .is_some_and(|scalar_fn| scalar_fn.is::<StatFn>())
480 {
481 contains_stat = true;
482 return Ok(Transformed {
483 value: expression,
484 order: TraversalOrder::Skip,
485 changed: false,
486 });
487 }
488 contains_root |= expression.is_root();
489 Ok(Transformed::no(expression))
490 })?;
491 if !parent.dtype().is_boolean() || !contains_stat || contains_root {
492 return Ok(None);
493 }
494
495 Ok(child
496 .with_pruning(parent.expression().clone())?
497 .map(Plan::into_plan))
498 }
499}