33
44use std:: fmt:: Display ;
55use std:: fmt:: Formatter ;
6+ use std:: hash:: Hash ;
67
78use itertools:: Itertools ;
89use vortex_error:: VortexExpect ;
@@ -19,13 +20,50 @@ use crate::expr::analysis::Annotation;
1920use crate :: expr:: analysis:: AnnotationFn ;
2021use crate :: expr:: analysis:: Annotations ;
2122use crate :: expr:: analysis:: descendent_annotations;
23+ use crate :: expr:: annotations;
2224use crate :: expr:: get_item;
25+ use crate :: expr:: is_root;
26+ use crate :: expr:: label_tree;
2327use crate :: expr:: pack;
2428use crate :: expr:: root;
2529use crate :: expr:: traversal:: NodeExt ;
2630use crate :: expr:: traversal:: NodeRewriter ;
2731use crate :: expr:: traversal:: Transformed ;
2832use crate :: expr:: traversal:: TraversalOrder ;
33+ use crate :: scalar_fn:: is_negative_cost;
34+
35+ /// Split expression into two parts:
36+ ///
37+ /// left is the outer part that we want to apply to array after canonicalizing.
38+ /// right is the optional inner part that we want to apply to array before
39+ /// canonicalizing.
40+ ///
41+ /// We want to push to array only if expression has a negative cost, is
42+ /// infallible and null-insensitive.
43+ pub fn split_expression_for_pushdown (
44+ expr : Expression ,
45+ dtype : & DType ,
46+ ) -> VortexResult < ( Expression , Option < Expression > ) > {
47+ let references_root = label_tree ( & expr, is_root, |acc, & child| acc | child) ;
48+ let annotations = annotations ( & expr, |expr| {
49+ let signature = expr. signature ( ) ;
50+ if !signature. is_fallible ( )
51+ && !signature. is_null_sensitive ( )
52+ && is_negative_cost ( expr. id ( ) )
53+ && references_root. get ( & expr) . copied ( ) . unwrap_or ( true )
54+ {
55+ vec ! [ "" ] // we want a single annotation so an empty literal is fine
56+ } else {
57+ vec ! [ ]
58+ }
59+ } ) ;
60+ let partition = partition_annotations ( expr. clone ( ) , dtype, annotations) ?;
61+ if partition. partitions . is_empty ( ) {
62+ Ok ( ( partition. root , None ) )
63+ } else {
64+ Ok ( ( partition. root , Some ( partition. partitions [ 0 ] . clone ( ) ) ) )
65+ }
66+ }
2967
3068/// Partition an expression into sub-expressions that are uniquely associated with an annotation.
3169/// A root expression is also returned that can be used to recombine the results of the partitions
@@ -49,11 +87,22 @@ where
4987{
5088 // Annotate each expression with the annotations that any of its descendent expressions have.
5189 let annotations = descendent_annotations ( & expr, annotate_fn) ;
90+ partition_annotations ( expr. clone ( ) , scope, annotations)
91+ }
5292
93+ pub fn partition_annotations < A > (
94+ expr : Expression ,
95+ scope : & DType ,
96+ annotations : Annotations < A > ,
97+ ) -> VortexResult < PartitionedExpr < A > >
98+ where
99+ A : Display + Clone + Eq + Hash ,
100+ FieldName : From < A > ,
101+ {
53102 // Now we split the original expression into sub-expressions based on the annotations, and
54103 // generate a root expression to re-assemble the results.
55- let mut splitter = StructFieldExpressionSplitter :: < A :: Annotation > :: new ( & annotations) ;
56- let root = expr. clone ( ) . rewrite ( & mut splitter) ?. value ;
104+ let mut splitter = StructFieldExpressionSplitter :: < A > :: new ( & annotations) ;
105+ let root = expr. rewrite ( & mut splitter) ?. value ;
57106
58107 let mut partitions = Vec :: with_capacity ( splitter. sub_expressions . len ( ) ) ;
59108 let mut partition_annotations = Vec :: with_capacity ( splitter. sub_expressions . len ( ) ) ;
@@ -205,10 +254,16 @@ where
205254mod tests {
206255 use rstest:: fixture;
207256 use rstest:: rstest;
257+ use vortex_array:: expr:: Expression ;
258+ use vortex_array:: expr:: byte_length;
259+ use vortex_array:: expr:: cast;
260+ use vortex_array:: expr:: like;
208261
262+ use super :: split_expression_for_pushdown;
209263 use super :: * ;
210264 use crate :: dtype:: DType ;
211265 use crate :: dtype:: Nullability :: NonNullable ;
266+ use crate :: dtype:: PType ;
212267 use crate :: dtype:: PType :: I32 ;
213268 use crate :: dtype:: StructFields ;
214269 use crate :: expr:: analysis:: make_free_field_annotator;
@@ -348,4 +403,57 @@ mod tests {
348403 let expected_b = pack ( [ ( "b_0" , pack ( [ ( "b" , col ( "b" ) ) ] , NonNullable ) ) ] , NonNullable ) ;
349404 assert_eq ! ( part_b, & expected_b, "{part_b} {expected_b}" ) ;
350405 }
406+
407+ fn pushed_inner ( exprs : impl IntoIterator < Item = Expression > ) -> Expression {
408+ pack (
409+ exprs
410+ . into_iter ( )
411+ . enumerate ( )
412+ . map ( |( idx, e) | ( format ! ( "_{idx}" ) , e) ) ,
413+ NonNullable ,
414+ )
415+ }
416+
417+ fn pushed_ref ( idx : usize ) -> Expression {
418+ get_item ( format ! ( "_{idx}" ) , get_item ( "" , root ( ) ) )
419+ }
420+
421+ #[ test]
422+ fn split_expr_root ( ) {
423+ let ( outer, inner) = split_expression_for_pushdown ( root ( ) , & DType :: Null ) . unwrap ( ) ;
424+ assert_eq ! ( outer, root( ) ) ;
425+ assert_eq ! ( inner, None ) ;
426+ }
427+
428+ #[ test]
429+ fn split_expr_partial_pushdown ( ) {
430+ // cast is fallible, thus not pushed
431+ let target = DType :: Primitive ( PType :: I64 , NonNullable ) ;
432+ let expr = cast ( byte_length ( root ( ) ) , target. clone ( ) ) ;
433+ let ( outer, inner) =
434+ split_expression_for_pushdown ( expr, & DType :: Utf8 ( false . into ( ) ) ) . unwrap ( ) ;
435+ // [0] = cast([1], dtype)
436+ // [1] = byte_length(root)
437+ assert_eq ! ( outer, cast( pushed_ref( 0 ) , target) ) ;
438+ assert_eq ! ( inner, Some ( pushed_inner( [ byte_length( root( ) ) ] ) ) ) ;
439+ }
440+
441+ #[ test]
442+ fn split_expr_full_pushdown ( ) {
443+ let expr = byte_length ( root ( ) ) ;
444+ let ( outer, inner) =
445+ split_expression_for_pushdown ( expr, & DType :: Utf8 ( false . into ( ) ) ) . unwrap ( ) ;
446+ assert_eq ! ( outer, pushed_ref( 0 ) ) ;
447+ assert_eq ! ( inner, Some ( pushed_inner( [ byte_length( root( ) ) ] ) ) ) ;
448+ }
449+
450+ #[ test]
451+ fn split_expr_no_pushdown ( ) {
452+ // like is fallible, thus not pushed. lit() does not reference root()
453+ let expr = like ( root ( ) , lit ( 1u64 ) ) ;
454+ let ( outer, inner) =
455+ split_expression_for_pushdown ( expr. clone ( ) , & DType :: Utf8 ( true . into ( ) ) ) . unwrap ( ) ;
456+ assert_eq ! ( outer, expr) ;
457+ assert_eq ! ( inner, None ) ;
458+ }
351459}
0 commit comments