@@ -27,7 +27,9 @@ use arrow::array::BooleanArray;
2727use arrow:: compute:: filter_record_batch;
2828use arrow:: datatypes:: { DataType , Field , FieldRef , Schema } ;
2929use arrow:: record_batch:: RecordBatch ;
30- use datafusion_common:: tree_node:: { Transformed , TransformedResult , TreeNode } ;
30+ use datafusion_common:: tree_node:: {
31+ Transformed , TransformedResult , TreeNode , TreeNodeRecursion ,
32+ } ;
3133use datafusion_common:: { internal_err, not_impl_err, Result , ScalarValue } ;
3234use datafusion_expr_common:: columnar_value:: ColumnarValue ;
3335use datafusion_expr_common:: interval_arithmetic:: Interval ;
@@ -345,6 +347,24 @@ pub trait PhysicalExpr: Send + Sync + Display + Debug + DynEq + DynHash {
345347 // This is a safe default behavior.
346348 Ok ( None )
347349 }
350+
351+ /// Returns the generation of this `PhysicalExpr` for snapshotting purposes.
352+ /// The generation is an arbitrary u64 that can be used to track changes
353+ /// in the state of the `PhysicalExpr` over time without having to do an exhaustive comparison.
354+ /// This is useful to avoid unecessary computation or serialization if there are no changes to the expression.
355+ /// In particular, dynamic expressions that may change over time; this allows cheap checks for changes.
356+ /// Static expressions that do not change over time should return 0, as does the default implementation.
357+ /// You should not call this method directly as it does not handle recursion.
358+ /// Instead use [`snapshot_generation`] to handle recursion and capture the
359+ /// full state of the `PhysicalExpr`.
360+ fn snapshot_generation ( & self ) -> u64 {
361+ // By default, we return 0 to indicate that this PhysicalExpr does not
362+ // have any dynamic references or state.
363+ // Since the recursive algorithm XORs the generations of all children the overall
364+ // generation will be 0 if no children have a non-zero generation, meaning that
365+ // static expressions will always return 0.
366+ 0
367+ }
348368}
349369
350370/// [`PhysicalExpr`] can't be constrained by [`Eq`] directly because it must remain object
@@ -538,16 +558,30 @@ pub fn snapshot_physical_expr(
538558 . data ( )
539559}
540560
541- /// Check if the given `PhysicalExpr` is dynamic.
542- /// See the documentation of [`PhysicalExpr::snapshot`] for more details.
543- pub fn is_dynamic_physical_expr ( expr : Arc < dyn PhysicalExpr > ) -> Result < bool > {
544- let mut is_dynamic = false ;
545- expr. transform_up ( |e| {
546- if e. snapshot ( ) ?. is_some ( ) {
547- is_dynamic = true ;
548- }
549- Ok ( Transformed :: no ( e) )
561+ /// Check the generation of this `PhysicalExpr`.
562+ /// Dynamic `PhysicalExpr`s may have a generation that is incremented
563+ /// every time the state of the `PhysicalExpr` changes.
564+ /// If the generation changes that means this `PhysicalExpr` or one of its children
565+ /// has changed since the last time it was evaluated.
566+ ///
567+ /// This algorithm will not produce collisions as long as the structure of the
568+ /// `PhysicalExpr` does not change and no `PhysicalExpr` decrements its own generation.
569+ pub fn snapshot_generation ( expr : & Arc < dyn PhysicalExpr > ) -> u64 {
570+ let mut generation = 0u64 ;
571+ expr. apply ( |e| {
572+ // Add the current generation of the `PhysicalExpr` to our global generation.
573+ generation = generation. wrapping_add ( e. snapshot_generation ( ) ) ;
574+ Ok ( TreeNodeRecursion :: Continue )
550575 } )
551- . data ( ) ?;
552- Ok ( is_dynamic)
576+ . expect ( "this traversal is infallible" ) ;
577+
578+ generation
579+ }
580+
581+ /// Check if the given `PhysicalExpr` is dynamic.
582+ /// Internally this calls [`snapshot_generation`] to check if the generation is non-zero,
583+ /// any dynamic `PhysicalExpr` should have a non-zero generation.
584+ pub fn is_dynamic_physical_expr ( expr : & Arc < dyn PhysicalExpr > ) -> bool {
585+ // If the generation is non-zero, then this `PhysicalExpr` is dynamic.
586+ snapshot_generation ( expr) != 0
553587}
0 commit comments