1use std::fmt::Display;
5use std::fmt::Formatter;
6use std::hash::Hash;
7
8use itertools::Itertools;
9use vortex_error::VortexExpect;
10use vortex_error::VortexResult;
11use vortex_error::vortex_ensure;
12use vortex_utils::aliases::hash_map::HashMap;
13
14use crate::dtype::DType;
15use crate::dtype::FieldName;
16use crate::dtype::FieldNames;
17use crate::dtype::Nullability;
18use crate::dtype::StructFields;
19use crate::expr::BoundExpression;
20use crate::expr::ExactBoundExpr;
21use crate::expr::analysis::Annotation;
22use crate::expr::analysis::AnnotationFn;
23use crate::expr::analysis::BoundAnnotations;
24use crate::expr::analysis::descendent_bound_annotations;
25use crate::expr::bound::get_item;
26use crate::expr::bound::pack;
27use crate::expr::traversal::NodeExt;
28use crate::expr::traversal::NodeRewriter;
29use crate::expr::traversal::Transformed;
30use crate::expr::traversal::TraversalOrder;
31
32pub fn partition_bound<A: AnnotationFn<BoundExpression>>(
45 expr: BoundExpression,
46 annotate_fn: A,
47) -> VortexResult<BoundPartitionedExpr<A::Annotation>>
48where
49 A::Annotation: Display,
50 FieldName: From<A::Annotation>,
51{
52 let annotations = descendent_bound_annotations(&expr, annotate_fn);
54 partition_bound_annotations(expr, annotations)
55}
56
57pub fn partition_bound_annotations<A>(
61 expr: BoundExpression,
62 annotations: BoundAnnotations<A>,
63) -> VortexResult<BoundPartitionedExpr<A>>
64where
65 A: Display + Clone + Eq + Hash,
66 FieldName: From<A>,
67{
68 let mut collector = PartitionCollector::<A>::new(&annotations);
69 expr.clone().rewrite(&mut collector)?;
70
71 let mut partitions = Vec::with_capacity(collector.sub_expressions.len());
72 let mut partition_annotations = Vec::with_capacity(collector.sub_expressions.len());
73
74 for (annotation, exprs) in collector.sub_expressions {
75 let names: FieldNames = exprs
77 .iter()
78 .enumerate()
79 .map(|(idx, _)| PartitionCollector::field_name(&annotation, idx))
80 .collect();
81 let expr = pack(names.into_iter().zip(exprs), Nullability::NonNullable);
82
83 partitions.push(expr);
84 partition_annotations.push(annotation);
85 }
86
87 let partition_names = partition_annotations
88 .iter()
89 .map(|id| FieldName::from(id.clone()))
90 .collect::<FieldNames>();
91 let root_scope = partition_root_dtype(&partition_names, &partitions);
92 let mut rewriter = PartitionRootRewriter::new(&annotations, root_scope);
93 let root = expr.rewrite(&mut rewriter)?.value;
94
95 Ok(BoundPartitionedExpr {
96 root,
97 partitions: partitions.into_boxed_slice(),
98 partition_names,
99 partition_annotations: partition_annotations.into_boxed_slice(),
100 })
101}
102
103#[derive(Debug)]
108pub struct BoundPartitionedExpr<A> {
109 pub root: BoundExpression,
111 pub partitions: Box<[BoundExpression]>,
113 pub partition_names: FieldNames,
115 pub partition_annotations: Box<[A]>,
117}
118
119impl<A: Display> Display for BoundPartitionedExpr<A> {
120 fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
121 write!(
122 f,
123 "root: {} {{{}}}",
124 self.root,
125 self.partition_names
126 .iter()
127 .zip(self.partitions.iter())
128 .map(|(name, partition)| format!("{name}: {partition}"))
129 .join(", ")
130 )
131 }
132}
133
134impl<A: Annotation> BoundPartitionedExpr<A>
135where
136 FieldName: From<A>,
137{
138 pub fn find_partition(&self, id: &A) -> Option<&BoundExpression> {
141 let id = FieldName::from(id.clone());
142 self.partition_names
143 .iter()
144 .position(|field| field == id)
145 .map(|idx| &self.partitions[idx])
146 }
147
148 pub fn replace_partitions(&mut self, partitions: Box<[BoundExpression]>) -> VortexResult<()> {
150 vortex_ensure!(
151 partitions.len() == self.partition_names.len(),
152 "Expected {} partitions, got {}",
153 self.partition_names.len(),
154 partitions.len()
155 );
156
157 let root_dtype = partition_root_dtype(&self.partition_names, &partitions);
158 let root = replace_root_dtype(self.root.clone(), root_dtype)?;
159 self.partitions = partitions;
160 self.root = root;
161 Ok(())
162 }
163}
164
165#[derive(Debug)]
166struct PartitionCollector<'a, A: Annotation> {
167 annotations: &'a BoundAnnotations<A>,
168 sub_expressions: HashMap<A, Vec<BoundExpression>>,
169}
170
171impl<'a, A: Annotation + Display> PartitionCollector<'a, A> {
172 fn new(annotations: &'a BoundAnnotations<A>) -> Self {
173 Self {
174 sub_expressions: HashMap::new(),
175 annotations,
176 }
177 }
178
179 fn field_name(annotation: &A, idx: usize) -> FieldName {
182 format!("{annotation}_{idx}").into()
183 }
184}
185
186impl<A: Annotation + Display> NodeRewriter for PartitionCollector<'_, A>
187where
188 FieldName: From<A>,
189{
190 type NodeTy = BoundExpression;
191
192 fn visit_down(&mut self, node: Self::NodeTy) -> VortexResult<Transformed<Self::NodeTy>> {
193 match self.annotations.get(&ExactBoundExpr(node.clone())) {
194 Some(annotations) if annotations.len() == 1 => {
196 let annotation = annotations
197 .iter()
198 .next()
199 .vortex_expect("expected one field");
200 let sub_exprs = self.sub_expressions.entry(annotation.clone()).or_default();
201 sub_exprs.push(node.clone());
202 Ok(Transformed {
203 value: node,
204 changed: false,
205 order: TraversalOrder::Skip,
206 })
207 }
208
209 _ => Ok(Transformed::no(node)),
211 }
212 }
213
214 fn visit_up(&mut self, node: Self::NodeTy) -> VortexResult<Transformed<Self::NodeTy>> {
215 Ok(Transformed::no(node))
216 }
217}
218
219struct PartitionRootRewriter<'a, A: Annotation> {
220 annotations: &'a BoundAnnotations<A>,
221 partition_offsets: HashMap<A, usize>,
222 root_dtype: DType,
223}
224
225impl<'a, A: Annotation> PartitionRootRewriter<'a, A> {
226 fn new(annotations: &'a BoundAnnotations<A>, root_dtype: DType) -> Self {
227 Self {
228 annotations,
229 partition_offsets: HashMap::new(),
230 root_dtype,
231 }
232 }
233}
234
235impl<A: Annotation + Display> NodeRewriter for PartitionRootRewriter<'_, A>
236where
237 FieldName: From<A>,
238{
239 type NodeTy = BoundExpression;
240
241 fn visit_down(&mut self, node: Self::NodeTy) -> VortexResult<Transformed<Self::NodeTy>> {
242 let Some(annotations) = self.annotations.get(&ExactBoundExpr(node.clone())) else {
243 return Ok(Transformed::no(node));
244 };
245 if annotations.len() != 1 {
246 return Ok(Transformed::no(node));
247 }
248
249 let annotation = annotations
250 .iter()
251 .next()
252 .vortex_expect("expected one annotation");
253 let offset = self
254 .partition_offsets
255 .entry(annotation.clone())
256 .or_default();
257 let field_name = PartitionCollector::field_name(annotation, *offset);
258 *offset += 1;
259
260 let partition = get_item(
261 FieldName::from(annotation.clone()),
262 BoundExpression::new_root(self.root_dtype.clone()),
263 );
264 let value = get_item(field_name, partition);
265
266 Ok(Transformed {
267 value,
268 changed: true,
269 order: TraversalOrder::Skip,
270 })
271 }
272}
273
274fn partition_root_dtype(names: &FieldNames, partitions: &[BoundExpression]) -> DType {
275 DType::Struct(
276 StructFields::new(
277 names.clone(),
278 partitions
279 .iter()
280 .map(|partition| partition.dtype().clone())
281 .collect(),
282 ),
283 Nullability::NonNullable,
284 )
285}
286
287fn replace_root_dtype(expr: BoundExpression, root_dtype: DType) -> VortexResult<BoundExpression> {
288 Ok(expr
289 .transform_down(|node| {
290 if node.is_root() {
291 Ok(Transformed {
292 value: BoundExpression::new_root(root_dtype.clone()),
293 changed: true,
294 order: TraversalOrder::Skip,
295 })
296 } else {
297 Ok(Transformed::no(node))
298 }
299 })?
300 .into_inner())
301}
302
303#[cfg(test)]
304mod tests {
305 use rstest::fixture;
306 use rstest::rstest;
307
308 use super::*;
309 use crate::dtype::DType;
310 use crate::dtype::Nullability::NonNullable;
311 use crate::dtype::Nullability::Nullable;
312 use crate::dtype::PType::I32;
313 use crate::dtype::StructFields;
314 use crate::expr::analysis::make_bound_free_field_annotator;
315 use crate::expr::checked_add;
316 use crate::expr::col;
317 use crate::expr::get_item;
318 use crate::expr::lit;
319 use crate::expr::merge;
320 use crate::expr::pack;
321 use crate::expr::root;
322
323 #[fixture]
324 fn dtype() -> DType {
325 DType::Struct(
326 StructFields::from_iter([
327 (
328 "a",
329 DType::Struct(
330 StructFields::from_iter([("x", I32.into()), ("y", DType::from(I32))]),
331 NonNullable,
332 ),
333 ),
334 ("b", I32.into()),
335 ("c", I32.into()),
336 ]),
337 NonNullable,
338 )
339 }
340
341 fn partition_by_field(
342 expr: BoundExpression,
343 dtype: &DType,
344 ) -> VortexResult<BoundPartitionedExpr<FieldName>> {
345 let fields = dtype.as_struct_fields_opt().unwrap();
346 partition_bound(expr, make_bound_free_field_annotator(fields))
347 }
348
349 #[rstest]
350 fn test_expr_top_level_ref(dtype: DType) {
351 let fields = dtype.as_struct_fields_opt().unwrap();
352
353 let expr = root();
354 let partitioned = partition_by_field(expr.bind(&dtype).unwrap(), &dtype).unwrap();
355
356 assert_eq!(partitioned.partitions.len(), 0);
358 assert_eq!(partitioned.root, root().bind(&dtype).unwrap());
359
360 let expr = pack(
362 fields
363 .names()
364 .iter()
365 .map(|name| (name.clone(), col(name.clone()))),
366 NonNullable,
367 );
368 let partitioned = partition_by_field(expr.bind(&dtype).unwrap(), &dtype).unwrap();
369
370 assert_eq!(partitioned.partitions.len(), fields.names().len());
371 }
372
373 #[rstest]
374 fn test_expr_top_level_ref_get_item_and_split(dtype: DType) {
375 let expr = get_item("y", get_item("a", root()));
376
377 let partitioned = partition_by_field(expr.bind(&dtype).unwrap(), &dtype).unwrap();
378 let root_dtype =
379 partition_root_dtype(&partitioned.partition_names, &partitioned.partitions);
380 assert_eq!(
381 partitioned.root,
382 get_item("a_0", get_item("a", root()))
383 .bind(&root_dtype)
384 .unwrap()
385 );
386 }
387
388 #[rstest]
389 fn test_expr_top_level_ref_get_item_and_split_pack(dtype: DType) {
390 let expr = pack(
391 [
392 ("x", get_item("x", get_item("a", root()))),
393 ("y", get_item("y", get_item("a", root()))),
394 ("c", get_item("c", root())),
395 ],
396 NonNullable,
397 );
398 let partitioned = partition_by_field(expr.bind(&dtype).unwrap(), &dtype).unwrap();
399
400 let split_a = partitioned.find_partition(&"a".into()).unwrap();
401 assert_eq!(
402 split_a,
403 &pack(
404 [
405 ("a_0", get_item("x", get_item("a", root()))),
406 ("a_1", get_item("y", get_item("a", root())))
407 ],
408 NonNullable
409 )
410 .bind(&dtype)
411 .unwrap()
412 );
413 }
414
415 #[rstest]
416 fn test_expr_top_level_ref_get_item_add(dtype: DType) {
417 let expr = checked_add(get_item("y", get_item("a", root())), lit(1));
418 let partitioned = partition_by_field(expr.bind(&dtype).unwrap(), &dtype).unwrap();
419
420 assert_eq!(partitioned.partitions.len(), 1);
422 }
423
424 #[rstest]
425 fn test_expr_top_level_ref_get_item_add_cannot_split(dtype: DType) {
426 let expr = checked_add(get_item("y", get_item("a", root())), get_item("b", root()));
427 let partitioned = partition_by_field(expr.bind(&dtype).unwrap(), &dtype).unwrap();
428
429 assert_eq!(partitioned.partitions.len(), 2);
431 }
432
433 #[rstest]
434 fn test_expr_merge(dtype: DType) {
435 let expr = merge([col("a"), pack([("b", col("b"))], NonNullable)]);
436
437 let partitioned = partition_by_field(expr.bind(&dtype).unwrap(), &dtype).unwrap();
438 let expected = merge([get_item("a_0", col("a")), get_item("b_0", col("b"))]);
439 let root_dtype =
440 partition_root_dtype(&partitioned.partition_names, &partitioned.partitions);
441 assert_eq!(
442 partitioned.root,
443 expected.bind(&root_dtype).unwrap(),
444 "{} {}",
445 partitioned.root,
446 expected
447 );
448
449 assert_eq!(partitioned.partitions.len(), 2);
450
451 let part_a = partitioned.find_partition(&"a".into()).unwrap();
452 let expected_a = pack([("a_0", col("a"))], NonNullable);
453 assert_eq!(
454 part_a,
455 &expected_a.bind(&dtype).unwrap(),
456 "{part_a} {expected_a}"
457 );
458
459 let part_b = partitioned.find_partition(&"b".into()).unwrap();
460 let expected_b = pack([("b_0", pack([("b", col("b"))], NonNullable))], NonNullable);
461 assert_eq!(
462 part_b,
463 &expected_b.bind(&dtype).unwrap(),
464 "{part_b} {expected_b}"
465 );
466 }
467
468 #[rstest]
469 fn replacing_partitions_refreshes_root_dtype(dtype: DType) -> VortexResult<()> {
470 let mut partitioned = partition_by_field(col("b").bind(&dtype)?, &dtype)?;
471 let field_dtype = DType::Primitive(I32, Nullable);
472 let replacement = pack([("b_0", root())], NonNullable).bind(&field_dtype)?;
473
474 partitioned.replace_partitions(vec![replacement].into_boxed_slice())?;
475
476 assert_eq!(partitioned.root.dtype(), &field_dtype);
477 Ok(())
478 }
479}