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