1use std::borrow::Cow;
5use std::ops::Range;
6
7use vortex_array::EmptyMetadata;
8use vortex_array::dtype::DType;
9use vortex_array::dtype::FieldName;
10use vortex_array::dtype::FieldNames;
11use vortex_array::dtype::Nullability;
12use vortex_array::dtype::StructFields;
13use vortex_array::expr::BoundExpression;
14use vortex_array::expr::ExactBoundExpr;
15use vortex_array::expr::descendent_bound_annotations;
16use vortex_array::expr::make_bound_free_field_annotator;
17use vortex_array::expr::transform::partition_bound;
18use vortex_array::expr::traversal::NodeExt;
19use vortex_array::expr::traversal::Transformed;
20use vortex_array::expr::traversal::TraversalOrder;
21use vortex_array::scalar_fn::ScalarFnVTableExt;
22use vortex_array::scalar_fn::fns::get_item::GetItem;
23use vortex_array::scalar_fn::fns::pack::Pack as PackFn;
24use vortex_array::scalar_fn::fns::pack::PackOptions;
25use vortex_array::scalar_fn::fns::select::Select;
26use vortex_error::VortexExpect;
27use vortex_error::VortexResult;
28use vortex_error::vortex_bail;
29use vortex_error::vortex_ensure;
30use vortex_error::vortex_err;
31use vortex_mask::Mask;
32use vortex_session::registry::CachedId;
33
34use crate::plan::physical::Eval;
35use crate::plan::physical::EvalPlan;
36use crate::plan::physical::Plan;
37use crate::plan::physical::PlanChildren;
38use crate::plan::physical::PlanId;
39use crate::plan::physical::PlanParts;
40use crate::plan::physical::PlanRef;
41use crate::plan::physical::PlanVTable;
42use crate::plan::physical::operators::OnceSource;
43use crate::plan::physical::operators::PackSource;
44use crate::plan::physical::operators::WrapStage;
45use crate::plan::physical::operators::empty_struct;
46use crate::plan::physical::optimizer::PlanParentReduceRule;
47use crate::plan::physical::pipeline::Chain;
48use crate::plan::physical::pipeline::Compiler;
49use crate::plan::physical::pipeline::Reach;
50use crate::segments::SegmentId;
51
52#[derive(Clone, Debug)]
54pub struct Pack;
55
56#[derive(Clone, Debug)]
58pub struct PackData;
59
60pub type PackPlan = Plan<Pack>;
62
63impl PackPlan {
64 pub(crate) unsafe fn from_children_unchecked(
72 fields: StructFields,
73 nullability: Nullability,
74 row_count: u64,
75 children: PlanChildren,
76 ) -> Self {
77 PlanParts {
78 vtable: Pack,
79 dtype: DType::Struct(fields, nullability),
80 row_count,
81 children,
82 data: PackData,
83 }
84 .into_typed()
85 }
86
87 pub fn try_new(
93 fields: StructFields,
94 nullability: Nullability,
95 row_count: u64,
96 field_plans: Vec<PlanRef>,
97 validity: Option<PlanRef>,
98 ) -> VortexResult<Self> {
99 if field_plans.len() != fields.nfields() {
100 vortex_bail!(
101 "Pack expects {} field children but got {}",
102 fields.nfields(),
103 field_plans.len()
104 );
105 }
106 if validity.is_some() != (nullability == Nullability::Nullable) {
107 vortex_bail!("Pack validity child must be present exactly when the struct is nullable");
108 }
109
110 for (index, (field_dtype, field_plan)) in
111 fields.fields().zip(field_plans.iter()).enumerate()
112 {
113 validate_field_child(index, &field_dtype, row_count, field_plan)?;
114 }
115 if let Some(validity) = validity.as_ref() {
116 validate_validity_child(row_count, validity)?;
117 }
118
119 Ok(unsafe { Self::new_unchecked(fields, nullability, row_count, field_plans, validity) })
121 }
122
123 pub unsafe fn new_unchecked(
134 fields: StructFields,
135 nullability: Nullability,
136 row_count: u64,
137 field_plans: Vec<PlanRef>,
138 validity: Option<PlanRef>,
139 ) -> Self {
140 let mut children = field_plans;
141 children.extend(validity);
142 unsafe { Self::from_children_unchecked(fields, nullability, row_count, children.into()) }
144 }
145
146 pub fn fields(&self) -> &StructFields {
148 self.dtype()
149 .as_struct_fields_opt()
150 .vortex_expect("Pack dtype must be a struct")
151 }
152
153 pub fn nfields(&self) -> usize {
155 self.fields().nfields()
156 }
157
158 pub fn validity(&self) -> VortexResult<Option<PlanRef>> {
160 self.child(self.nfields())
161 }
162}
163
164impl PlanVTable for Pack {
165 type PlanData = PackData;
166 type Metadata = EmptyMetadata;
167
168 fn id(&self) -> PlanId {
169 static ID: CachedId = CachedId::new("vortex.plan.pack");
170 *ID
171 }
172
173 fn metadata(_plan: &Plan<Self>) -> Option<Self::Metadata> {
174 Some(EmptyMetadata)
176 }
177
178 fn with_children(
179 plan: &Plan<Self>,
180 children: &PlanChildren,
181 _data: &mut Self::PlanData,
182 ) -> VortexResult<()> {
183 let expected_children = plan.nfields() + usize::from(plan.dtype().is_nullable());
184 if children.len() != expected_children {
185 vortex_bail!(
186 "Pack expects {expected_children} children but got {}",
187 children.len()
188 );
189 }
190
191 for (index, field_dtype) in plan.fields().fields().enumerate() {
192 let child = children
193 .get(index)?
194 .ok_or_else(|| vortex_err!("Pack field child {index} is absent"))?;
195 validate_field_child(index, &field_dtype, plan.row_count(), &child)?;
196 }
197 if plan.dtype().is_nullable() {
198 let validity = children
199 .get(plan.nfields())?
200 .ok_or_else(|| vortex_err!("Pack validity child is absent"))?;
201 validate_validity_child(plan.row_count(), &validity)?;
202 }
203 Ok(())
204 }
205
206 fn child_name(plan: &Plan<Self>, index: usize) -> Cow<'_, str> {
207 assert!(
208 index < plan.children().len(),
209 "Pack child index out of bounds: {index} of {}",
210 plan.children().len()
211 );
212 if let Some(name) = plan.fields().field_name(index) {
213 return Cow::Borrowed(name.as_ref());
214 }
215 Cow::Borrowed("validity")
216 }
217
218 fn compile(
219 plan: &Plan<Self>,
220 rows: Range<u64>,
221 mask: &Mask,
222 compiler: &mut Compiler<'_>,
223 ) -> VortexResult<Option<Chain>> {
224 let count = plan.children().len();
225 if count == 0 {
226 let array = empty_struct(
228 plan.fields().clone(),
229 plan.dtype().nullability(),
230 mask.true_count(),
231 )?;
232 return Ok(Some(Chain::new(OnceSource::new(array))));
233 }
234 if mask.all_false() {
235 return Ok(None);
236 }
237 let mut chains = Vec::with_capacity(count);
238 for child in plan.children().iter_refs() {
239 chains.push(
240 compiler
241 .compile(child?, rows.clone(), mask)?
242 .ok_or_else(|| vortex_err!("A Pack field produced no rows"))?,
243 );
244 }
245 let nullable = plan.dtype().is_nullable();
246 if count == 1 && !nullable {
247 let chain = chains.remove(0);
249 return Ok(Some(chain.with(WrapStage::new(plan.fields().clone()))));
250 }
251 let source = PackSource::new(plan.fields().clone(), nullable, count);
252 Ok(Some(compiler.join(chains, source)))
253 }
254
255 fn reach(
256 plan: &Plan<Self>,
257 rows: Range<u64>,
258 at: &Reach,
259 visit: &mut dyn FnMut(SegmentId, Range<u64>),
260 ) -> VortexResult<()> {
261 for child in plan.children().iter_refs() {
262 child?.reach(rows.clone(), at, visit)?;
263 }
264 Ok(())
265 }
266}
267
268fn validate_field_child(
269 index: usize,
270 expected_dtype: &DType,
271 expected_row_count: u64,
272 child: &PlanRef,
273) -> VortexResult<()> {
274 if child.dtype() != expected_dtype {
275 vortex_bail!(
276 "Pack field child {index} has dtype {} but the field has dtype {expected_dtype}",
277 child.dtype()
278 );
279 }
280 if child.row_count() != expected_row_count {
281 vortex_bail!(
282 "Pack field child {index} has {} rows but the plan has {expected_row_count}",
283 child.row_count()
284 );
285 }
286 Ok(())
287}
288
289fn validate_validity_child(expected_row_count: u64, child: &PlanRef) -> VortexResult<()> {
290 let expected_dtype = DType::Bool(Nullability::NonNullable);
291 if child.dtype() != &expected_dtype {
292 vortex_bail!(
293 "Pack validity child has dtype {} but must have dtype {expected_dtype}",
294 child.dtype()
295 );
296 }
297 if child.row_count() != expected_row_count {
298 vortex_bail!(
299 "Pack validity child has {} rows but the plan has {expected_row_count}",
300 child.row_count()
301 );
302 }
303 Ok(())
304}
305
306impl PackPlan {
307 pub(crate) fn with_pruned_fields(
312 &self,
313 fields: Vec<(FieldName, PlanRef)>,
314 ) -> VortexResult<Self> {
315 vortex_ensure!(
316 !self.dtype().is_nullable(),
317 "Cannot prune fields from a nullable Pack"
318 );
319 let struct_fields = StructFields::from_iter(
320 fields
321 .iter()
322 .map(|(name, plan)| (name.clone(), plan.dtype().clone())),
323 );
324 let field_plans = fields.into_iter().map(|(_, plan)| plan).collect::<Vec<_>>();
325 PackPlan::try_new(
326 struct_fields,
327 Nullability::NonNullable,
328 self.row_count(),
329 field_plans,
330 None,
331 )
332 }
333}
334
335#[derive(Debug)]
337pub(crate) struct ExpressionPackRule;
338
339impl PlanParentReduceRule<Pack> for ExpressionPackRule {
340 type Parent = Eval;
341
342 fn reduce_parent(
343 &self,
344 child: &Plan<Pack>,
345 parent: &Plan<Eval>,
346 _child_idx: usize,
347 ) -> VortexResult<Option<PlanRef>> {
348 if child.dtype().is_nullable() {
349 return Ok(None);
350 }
351
352 let expression = parent.expression();
353 let fields = child.fields();
354 let referenced_fields =
355 descendent_bound_annotations(expression, make_bound_free_field_annotator(fields))
356 .get(&ExactBoundExpr(expression.clone()))
357 .vortex_expect("Bound expression missing free-field annotations")
358 .clone();
359 let expanded_root = expanded_struct_root(child.dtype(), fields)?;
360 let expanded = expand_struct_root(expression.clone(), &expanded_root, fields)?;
361 let partitioned =
362 partition_bound(expanded.clone(), make_bound_free_field_annotator(fields))?;
363
364 if partitioned.partition_names.is_empty() {
365 let selected_indices = fields
366 .names()
367 .iter()
368 .enumerate()
369 .filter_map(|(index, name)| referenced_fields.contains(name).then_some(index))
370 .collect::<Vec<_>>();
371 if selected_indices.len() == fields.nfields() {
372 return Ok(None);
373 }
374
375 let pruned_fields = selected_indices
376 .into_iter()
377 .map(|field_index| {
378 Ok((
379 field_name(fields, field_index)?,
380 field_plan(child, field_index)?,
381 ))
382 })
383 .collect::<VortexResult<Vec<_>>>()?;
384 let rewritten = child.with_pruned_fields(pruned_fields)?.into_plan();
385 return Ok(Some(
386 EvalPlan::try_new(expression.clone(), rewritten)?.into_plan(),
387 ));
388 }
389
390 if partitioned.partition_names.len() == 1 {
391 let name = partitioned
392 .partition_names
393 .get(0)
394 .ok_or_else(|| vortex_err!("Struct expression partition has no field"))?;
395 let index = fields.find(name).ok_or_else(|| {
396 vortex_err!("Struct expression references unknown field '{name}'")
397 })?;
398 let field = field_plan(child, index)?;
399 let lowered = step_into_struct_field(expanded, name, field.dtype().clone())?;
400 return Ok(Some(EvalPlan::try_new(lowered, field)?.into_plan()));
401 }
402
403 let residual = partitioned.root;
404 let mut collapsed = Vec::with_capacity(partitioned.partitions.len());
405 let mut field_expressions = vec![None; fields.nfields()];
406 for index in 0..partitioned.partitions.len() {
407 let name = &partitioned.partition_names[index];
408 let partition = &partitioned.partitions[index];
409 let field_index = fields.find(name).ok_or_else(|| {
410 vortex_err!("Struct expression references unknown field '{name}'")
411 })?;
412 let field = field_plan(child, field_index)?;
413 let lowered = if let Some(pack) = partition
414 .as_scalar()
415 .and_then(|scalar_fn| scalar_fn.as_opt::<PackFn>())
416 && partition.children().len() == 1
417 {
418 let value_name = pack
419 .names
420 .get(0)
421 .ok_or_else(|| vortex_err!("Struct expression partition pack is empty"))?;
422 collapsed.push((name.clone(), value_name.clone()));
423 partition.children()[0].clone()
424 } else {
425 partition.clone()
426 };
427 let lowered = step_into_struct_field(lowered, name, field.dtype().clone())?;
428 field_expressions[field_index] = Some(lowered);
429 }
430
431 let mut fields_changed = partitioned.partition_names.len() != fields.nfields();
432 let mut pruned_fields = Vec::with_capacity(partitioned.partition_names.len());
433 for (field_index, expression) in field_expressions.into_iter().enumerate() {
434 let Some(expression) = expression else {
435 continue;
436 };
437 let field = field_plan(child, field_index)?;
438 let field = if is_identity_expression(&expression, field.dtype())? {
439 field
440 } else {
441 fields_changed = true;
442 EvalPlan::try_new(expression, field)?.into_plan()
443 };
444 pruned_fields.push((field_name(fields, field_index)?, field));
445 }
446 let rewritten = if fields_changed {
447 child.with_pruned_fields(pruned_fields)?.into_plan()
448 } else {
449 child.to_plan()
450 };
451 let residual = rewrite_partition_root(residual, rewritten.dtype().clone(), &collapsed)?;
452
453 if !fields_changed && residual == *expression {
454 return Ok(None);
455 }
456
457 Ok(Some(EvalPlan::try_new(residual, rewritten)?.into_plan()))
458 }
459}
460
461pub(super) fn rewrite_partition_root(
470 expression: BoundExpression,
471 root_dtype: DType,
472 collapsed: &[(FieldName, FieldName)],
473) -> VortexResult<BoundExpression> {
474 Ok(expression
475 .transform_down(|node| {
476 if let Some(value_name) = node.as_opt::<GetItem>() {
477 let partition_access = &node.children()[0];
478 if let Some(partition_name) = partition_access.as_opt::<GetItem>()
479 && partition_access.children()[0].is_root()
480 && collapsed.iter().any(|(partition, value)| {
481 partition == partition_name && value == value_name
482 })
483 {
484 return Ok(Transformed {
485 value: BoundExpression::try_new(
486 GetItem.bind(partition_name.clone()),
487 [BoundExpression::new_root(root_dtype.clone())],
488 )?,
489 changed: true,
490 order: TraversalOrder::Skip,
491 });
492 }
493 }
494
495 if node.is_root() {
496 Ok(Transformed {
497 value: BoundExpression::new_root(root_dtype.clone()),
498 changed: true,
499 order: TraversalOrder::Skip,
500 })
501 } else {
502 Ok(Transformed::no(node))
503 }
504 })?
505 .into_inner())
506}
507
508fn field_name(fields: &StructFields, index: usize) -> VortexResult<FieldName> {
509 Ok(fields
510 .field_name(index)
511 .ok_or_else(|| vortex_err!("Struct field {index} has no name"))?
512 .clone())
513}
514
515fn field_plan(plan: &Plan<Pack>, index: usize) -> VortexResult<PlanRef> {
516 plan.child(index)?
517 .ok_or_else(|| vortex_err!("Struct field {index} has no plan"))
518}
519
520fn expanded_struct_root(
521 root_dtype: &DType,
522 fields: &StructFields,
523) -> VortexResult<BoundExpression> {
524 let root = BoundExpression::new_root(root_dtype.clone());
525 let children = fields
526 .names()
527 .iter()
528 .map(|name| BoundExpression::try_new(GetItem.bind(name.clone()), [root.clone()]))
529 .collect::<VortexResult<Vec<_>>>()?;
530 bound_pack(fields.names().clone(), children)
531}
532
533fn is_identity_expression(expression: &BoundExpression, input_dtype: &DType) -> VortexResult<bool> {
534 if expression.is_root() {
535 return Ok(expression.dtype() == input_dtype);
536 }
537 if input_dtype.is_nullable() {
538 return Ok(false);
539 }
540 let Some(fields) = input_dtype.as_struct_fields_opt() else {
541 return Ok(false);
542 };
543 Ok(expression == &expanded_struct_root(input_dtype, fields)?)
544}
545
546fn expand_struct_root(
547 expression: BoundExpression,
548 expanded_root: &BoundExpression,
549 fields: &StructFields,
550) -> VortexResult<BoundExpression> {
551 Ok(expression
552 .transform_down(|node| {
553 if node.is_root() {
554 return Ok(Transformed {
555 value: expanded_root.clone(),
556 changed: true,
557 order: TraversalOrder::Skip,
558 });
559 }
560
561 let Some(scalar_fn) = node.as_scalar() else {
562 return Ok(Transformed::no(node));
563 };
564 if !node
565 .children()
566 .first()
567 .is_some_and(BoundExpression::is_root)
568 {
569 return Ok(Transformed::no(node));
570 }
571
572 if let Some(field_name) = scalar_fn.as_opt::<GetItem>() {
573 let index = fields.find(field_name).ok_or_else(|| {
574 vortex_err!("Field {field_name} not found while expanding struct root")
575 })?;
576 return Ok(Transformed {
577 value: expanded_root.children()[index].clone(),
578 changed: true,
579 order: TraversalOrder::Skip,
580 });
581 }
582
583 if let Some(selection) = scalar_fn.as_opt::<Select>() {
584 let names = selection.normalize_to_included_fields(fields.names())?;
585 let children = names
586 .iter()
587 .map(|name| {
588 let index = fields
589 .find(name)
590 .vortex_expect("normalized selection fields must exist in the root");
591 expanded_root.children()[index].clone()
592 })
593 .collect();
594 return Ok(Transformed {
595 value: bound_pack(names, children)?,
596 changed: true,
597 order: TraversalOrder::Skip,
598 });
599 }
600
601 Ok(Transformed::no(node))
602 })?
603 .into_inner())
604}
605
606fn step_into_struct_field(
607 expression: BoundExpression,
608 field_name: &FieldName,
609 field_dtype: DType,
610) -> VortexResult<BoundExpression> {
611 Ok(expression
612 .transform_down(|node| {
613 let is_field_access = node
614 .as_scalar()
615 .and_then(|scalar_fn| scalar_fn.as_opt::<GetItem>())
616 .is_some_and(|name| name == field_name)
617 && node.children()[0].is_root();
618
619 if is_field_access {
620 Ok(Transformed {
621 value: BoundExpression::new_root(field_dtype.clone()),
622 changed: true,
623 order: TraversalOrder::Skip,
624 })
625 } else {
626 Ok(Transformed::no(node))
627 }
628 })?
629 .into_inner())
630}
631
632fn bound_pack(names: FieldNames, children: Vec<BoundExpression>) -> VortexResult<BoundExpression> {
633 BoundExpression::try_new(
634 PackFn.bind(PackOptions {
635 names,
636 nullability: Nullability::NonNullable,
637 }),
638 children,
639 )
640}