22// SPDX-FileCopyrightText: Copyright the Vortex contributors
33
44//! Bind abstract `vortex.stat` expressions to a concrete stats representation.
5+ //!
6+ //! Stats rewrite rules describe pruning in terms of `vortex.stat(input, aggregate_fn)` placeholders
7+ //! so the rewrite is independent of where statistics are stored. Binding is the later pass that
8+ //! replaces those placeholders with the representation used by a caller: zone-map field references,
9+ //! file-level stat literals, or typed nulls for missing stats.
510
611use vortex_error:: VortexResult ;
712
@@ -20,6 +25,14 @@ pub trait StatBinder {
2025 /// The dtype scope used to type-check expressions before stats are bound.
2126 fn scope ( & self ) -> & DType ;
2227
28+ /// The dtype scope used after stats have been bound.
29+ ///
30+ /// Binders that rewrite stats to a different root expression, such as a
31+ /// stats-table row, should return that post-binding root dtype.
32+ fn bound_scope ( & self ) -> DType {
33+ self . scope ( ) . clone ( )
34+ }
35+
2336 /// Bind `stat(input)` to a concrete expression.
2437 ///
2538 /// Returning `Ok(None)` marks the stat as unavailable. [`bind_stats`] will
@@ -52,10 +65,9 @@ pub trait StatBinder {
5265 /// Expression to use when a stat is unavailable.
5366 ///
5467 /// The default is a nullable null literal, which preserves three-valued
55- /// pruning semantics for stats-table execution. Catalog-like binders can
56- /// return `Ok(None)` to reject expressions that require unavailable stats.
57- fn missing_stat ( & mut self , dtype : DType ) -> VortexResult < Option < Expression > > {
58- Ok ( Some ( null_expr ( dtype) ) )
68+ /// pruning semantics for stats-table execution.
69+ fn missing_stat ( & mut self , dtype : DType ) -> VortexResult < Expression > {
70+ Ok ( null_expr ( dtype) )
5971 }
6072}
6173
@@ -64,10 +76,7 @@ pub trait StatBinder {
6476/// The predicate is usually the output of a stats rewrite rule. Rewrite rules
6577/// are responsible for expressing stat semantics; binding maps aggregate-backed
6678/// stat requests to the concrete stats representation supported by the binder.
67- pub fn bind_stats (
68- predicate : Expression ,
69- binder : & mut impl StatBinder ,
70- ) -> VortexResult < Option < Expression > > {
79+ pub fn bind_stats ( predicate : Expression , binder : & mut impl StatBinder ) -> VortexResult < Expression > {
7180 let scope = binder. scope ( ) . clone ( ) ;
7281 let lowered = predicate
7382 . transform_down ( |expr| {
@@ -79,18 +88,13 @@ pub fn bind_stats(
7988 Some ( bound) => Ok ( Transformed :: yes ( bound) ) ,
8089 None => {
8190 let dtype = expr. return_dtype ( & scope) ?;
82- match binder. missing_stat ( dtype. clone ( ) ) ? {
83- Some ( missing) => Ok ( Transformed :: yes ( missing) ) ,
84- None => Ok ( Transformed :: yes ( null_expr ( dtype) ) ) ,
85- }
91+ Ok ( Transformed :: yes ( binder. missing_stat ( dtype) ?) )
8692 }
8793 }
8894 } ) ?
8995 . into_inner ( ) ;
9096
91- #[ expect( deprecated) ]
92- let lowered = lowered. simplify_untyped ( ) ?;
93- Ok ( Some ( lowered) )
97+ lowered. optimize_recursive ( & binder. bound_scope ( ) )
9498}
9599
96100fn bind_stat_fn (
@@ -100,6 +104,7 @@ fn bind_stat_fn(
100104) -> VortexResult < Option < Expression > > {
101105 let options = expr. as_ :: < StatFn > ( ) ;
102106 let aggregate_fn = options. aggregate_fn ( ) ;
107+ // `StatFn` has exactly one child: the expression the aggregate statistic is computed over.
103108 let input = expr. child ( 0 ) ;
104109
105110 let stat_dtype = expr. return_dtype ( scope) ?;
0 commit comments