@@ -10,56 +10,50 @@ use vortex_array::scalar_fn::fns::is_null::IsNull;
1010use vortex_array:: scalar_fn:: fns:: list_length:: ListLength ;
1111use vortex_error:: VortexResult ;
1212
13- /// The deepest list child an expression needs, cheapest first.
13+ /// The deepest list child an expression needs, cheapest first, where I/O cost order is defined as
14+ /// `Validity < OffsetsAndValidity < All`.
1415///
15- /// Drives "fetch as little as possible": a projection/filter that only inspects the list's
16- /// null-ness needs the validity child; `list_length (root())` needs offsets plus validity; and
17- /// everything else needs the element values. The ordering `Validity < Offsets < Elements` lets us
18- /// take the max over the operands of a compound expression .
16+ /// For example:
17+ /// - `is_null (root())` only needs the validity child.
18+ /// - `list_length(root())` only needs the offsets and validity children.
19+ /// - `root()` needs elements, offsets, and validity children .
1920#[ derive( Debug , Clone , Copy , PartialEq , Eq , PartialOrd , Ord ) ]
20- pub ( super ) enum ExprClass {
21- /// Only the list's validity is needed (`is_null` / `is_not_null` of the list itself ).
21+ pub ( super ) enum ListChildrenNeeded {
22+ /// Only the validity child is needed (`is_null` / `is_not_null`).
2223 Validity ,
23- /// The list offsets are needed, but not the element values (`list_length` of the list itself ).
24- Offsets ,
25- /// The element values are needed (everything else) .
26- Elements ,
24+ /// The offsets and validity children are needed, but not the element values (`list_length`).
25+ OffsetsAndValidity ,
26+ /// All children are needed.
27+ All ,
2728}
2829
29- /// Classify `expr` by the deepest list child it touches, where `root()` is the list.
30- ///
31- /// The exact shapes `is_null(root())` / `is_not_null(root())` need only validity, and
32- /// `list_length(root())` needs offsets plus validity. Every other access to the list, including a
33- /// bare `root()`, falls through to [`ExprClass::Elements`], which is always correct.
34- pub ( super ) fn classify ( expr : & Expression ) -> ExprClass {
35- // `is_null(root())` / `is_not_null(root())` need only the list's own validity. Note this is
36- // the list's null-ness, not the validity of some derived value, so the child must be `root()`.
37- if ( expr. is :: < IsNull > ( ) || expr. is :: < IsNotNull > ( ) )
38- && expr. children ( ) . len ( ) == 1
39- && is_root ( expr. child ( 0 ) )
40- {
41- return ExprClass :: Validity ;
30+ /// The minimal list children needed to evaluate `expr`, where `root()` is a field with list dtype.
31+ pub ( super ) fn get_necessary_list_children ( expr : & Expression ) -> ListChildrenNeeded {
32+ if is_null_root ( expr) {
33+ return ListChildrenNeeded :: Validity ;
4234 }
4335
44- // `list_length(root())` only needs adjacent offset deltas. List validity is still needed
45- // because `list_length(NULL)` is NULL.
4636 if is_list_length_root ( expr) {
47- return ExprClass :: Offsets ;
37+ return ListChildrenNeeded :: OffsetsAndValidity ;
4838 }
4939
50- // A bare reference to the list needs its elements.
5140 if is_root ( expr) {
52- return ExprClass :: Elements ;
41+ return ListChildrenNeeded :: All ;
5342 }
5443
55- // Otherwise the requirement is the max over the operands. Operands that never touch the list
56- // (e.g. literals) contribute nothing, so an expression that never references `root()` is
57- // treated as the cheapest class.
44+ // Otherwise the requirement is the max over the operands. Childless expressions that never
45+ // touch the list, such as literals, fall back to the cheapest usable child.
5846 expr. children ( )
5947 . iter ( )
60- . map ( classify )
48+ . map ( get_necessary_list_children )
6149 . max ( )
62- . unwrap_or ( ExprClass :: Validity )
50+ . unwrap_or ( ListChildrenNeeded :: Validity )
51+ }
52+
53+ fn is_null_root ( expr : & Expression ) -> bool {
54+ ( expr. is :: < IsNull > ( ) || expr. is :: < IsNotNull > ( ) )
55+ && expr. children ( ) . len ( ) == 1
56+ && is_root ( expr. child ( 0 ) )
6357}
6458
6559fn is_list_length_root ( expr : & Expression ) -> bool {
@@ -119,36 +113,39 @@ mod tests {
119113
120114 use super :: * ;
121115
122- /// `classify ` keys off the deepest list child an expression touches; `Elements` is the
123- /// always-correct default for anything not specifically recognized.
116+ /// `get_necessary_list_children ` keys off the deepest list child an expression touches; `All`
117+ /// is the always-correct default for anything not specifically recognized.
124118 #[ rstest]
125119 // `is_null` / `is_not_null` of the list itself need only validity.
126- #[ case:: is_null( is_null( root( ) ) , ExprClass :: Validity ) ]
127- #[ case:: is_not_null( is_not_null( root( ) ) , ExprClass :: Validity ) ]
120+ #[ case:: is_null( is_null( root( ) ) , ListChildrenNeeded :: Validity ) ]
121+ #[ case:: is_not_null( is_not_null( root( ) ) , ListChildrenNeeded :: Validity ) ]
128122 // Compound over validity-only operands stays validity.
129- #[ case:: not_is_null( not( is_null( root( ) ) ) , ExprClass :: Validity ) ]
130- // A list-independent (constant) expression falls to the cheapest class .
131- #[ case:: constant( lit( 5 ) , ExprClass :: Validity ) ]
123+ #[ case:: not_is_null( not( is_null( root( ) ) ) , ListChildrenNeeded :: Validity ) ]
124+ // A list-independent (constant) expression falls to the cheapest usable child .
125+ #[ case:: constant( lit( 5 ) , ListChildrenNeeded :: Validity ) ]
132126 // `list_length(root())` needs offsets and validity, but not elements.
133- #[ case:: list_length( list_length( root( ) ) , ExprClass :: Offsets ) ]
127+ #[ case:: list_length( list_length( root( ) ) , ListChildrenNeeded :: OffsetsAndValidity ) ]
134128 // Compound over offsets-only operands stays offsets.
135- #[ case:: list_length_filter( gt( list_length( root( ) ) , lit( 1u64 ) ) , ExprClass :: Offsets ) ]
129+ #[ case:: list_length_filter(
130+ gt( list_length( root( ) ) , lit( 1u64 ) ) ,
131+ ListChildrenNeeded :: OffsetsAndValidity
132+ ) ]
136133 #[ case:: cast_list_length(
137134 cast(
138135 list_length( root( ) ) ,
139136 DType :: Primitive ( PType :: I64 , Nullability :: Nullable ) ,
140137 ) ,
141- ExprClass :: Offsets
138+ ListChildrenNeeded :: OffsetsAndValidity
142139 ) ]
143140 // A bare list reference needs the elements.
144- #[ case:: bare_root( root( ) , ExprClass :: Elements ) ]
141+ #[ case:: bare_root( root( ) , ListChildrenNeeded :: All ) ]
145142 // Any other fn over the list needs the elements.
146- #[ case:: not_root( not( root( ) ) , ExprClass :: Elements ) ]
143+ #[ case:: not_root( not( root( ) ) , ListChildrenNeeded :: All ) ]
147144 // `is_null` only short-circuits to validity when its argument is the list itself.
148- #[ case:: is_null_of_derived( is_null( not( root( ) ) ) , ExprClass :: Elements ) ]
145+ #[ case:: is_null_of_derived( is_null( not( root( ) ) ) , ListChildrenNeeded :: All ) ]
149146 // Max over operands: validity + elements => elements.
150- #[ case:: validity_and_elements( eq( is_null( root( ) ) , root( ) ) , ExprClass :: Elements ) ]
151- fn classify_expr_class ( #[ case] expr : Expression , #[ case] expected : ExprClass ) {
152- assert_eq ! ( classify ( & expr) , expected) ;
147+ #[ case:: validity_and_elements( eq( is_null( root( ) ) , root( ) ) , ListChildrenNeeded :: All ) ]
148+ fn classify_expr_class ( #[ case] expr : Expression , #[ case] expected : ListChildrenNeeded ) {
149+ assert_eq ! ( get_necessary_list_children ( & expr) , expected) ;
153150 }
154151}
0 commit comments