@@ -22,6 +22,7 @@ use crate::filter_pushdown::{
2222} ;
2323pub use crate :: metrics:: Metric ;
2424pub use crate :: ordering:: InputOrderMode ;
25+ use crate :: recursive_query:: RecursiveQueryExec ;
2526use crate :: sort_pushdown:: SortOrderPushdownResult ;
2627pub use crate :: stream:: EmptyRecordBatchStream ;
2728
@@ -31,12 +32,15 @@ pub use datafusion_common::utils::project_schema;
3132pub use datafusion_common:: { ColumnStatistics , Statistics , internal_err} ;
3233pub use datafusion_execution:: { RecordBatchStream , SendableRecordBatchStream } ;
3334pub use datafusion_expr:: { Accumulator , ColumnarValue } ;
35+ use datafusion_physical_expr:: expressions:: DynamicFilterPhysicalExpr ;
3436pub use datafusion_physical_expr:: window:: WindowExpr ;
3537pub use datafusion_physical_expr:: {
3638 Distribution , Partitioning , PhysicalExpr , expressions,
3739} ;
40+ use parking_lot:: Mutex ;
3841
3942use std:: any:: Any ;
43+ use std:: cell:: RefCell ;
4044use std:: fmt:: Debug ;
4145use std:: sync:: Arc ;
4246
@@ -680,6 +684,12 @@ pub trait ExecutionPlan: Debug + DisplayAs + Send + Sync {
680684 /// in order to wire up the working table used during recursive-CTE execution.
681685 /// Similar patterns can be followed by custom nodes that need late-bound
682686 /// dependencies or shared state.
687+ ///
688+ /// Also, this method is used to update dynamic filters in the plan when
689+ /// its state is re-set. So if the node supports dynamic filtering it must
690+ /// implement this method to be reusable. Please, check [`reset_plan_states`]
691+ /// for the details.
692+ ///
683693 fn with_new_state (
684694 & self ,
685695 _state : Arc < dyn Any + Send + Sync > ,
@@ -722,6 +732,11 @@ pub trait ExecutionPlan: Debug + DisplayAs + Send + Sync {
722732 ) -> Option < Arc < dyn ExecutionPlan > > {
723733 None
724734 }
735+
736+ /// Returns dynamic filters owned by the plan.
737+ fn dynamic_filters ( & self ) -> Vec < Arc < DynamicFilterPhysicalExpr > > {
738+ vec ! [ ]
739+ }
725740}
726741
727742/// [`ExecutionPlan`] Invariant Level
@@ -1398,6 +1413,43 @@ pub fn check_not_null_constraints(
13981413 Ok ( batch)
13991414}
14001415
1416+ /// Unions dynamic filters collected from plan nodes during state re-set bypass.
1417+ /// Passed into [`ExecutionPlan::with_new_state`] to actualize node filters.
1418+ #[ derive( Default ) ]
1419+ pub struct DynamicFilters {
1420+ filters : Mutex < Vec < Arc < DynamicFilterPhysicalExpr > > > ,
1421+ }
1422+
1423+ impl DynamicFilters {
1424+ fn extend ( & self , iter : impl Iterator < Item = Arc < DynamicFilterPhysicalExpr > > ) {
1425+ self . filters . lock ( ) . extend ( iter) ;
1426+ }
1427+
1428+ /// Lookup for the filter with the same origin as a passed filter `expr`.
1429+ fn filter_with_same_origin_as (
1430+ & self ,
1431+ expr : & DynamicFilterPhysicalExpr ,
1432+ ) -> Result < Arc < DynamicFilterPhysicalExpr > > {
1433+ for filter in self . filters . lock ( ) . iter ( ) {
1434+ if filter. has_same_origin ( expr) {
1435+ return Ok ( Arc :: clone ( filter) ) ;
1436+ }
1437+ }
1438+ internal_err ! ( "updated dynamic filter is not found" )
1439+ }
1440+
1441+ /// Update each dynamic filter sub-expression to an actual version.
1442+ ///
1443+ /// * If dynamic filter with the same origin is not found, then an error is returned.
1444+ /// * If `expr` does not contain dynamic filters and there are no updates, then `Ok(None)` is returned.
1445+ pub fn actualize_dynamic_filter (
1446+ & self ,
1447+ expr : & DynamicFilterPhysicalExpr ,
1448+ ) -> Result < Option < Arc < DynamicFilterPhysicalExpr > > > {
1449+ todo ! ( )
1450+ }
1451+ }
1452+
14011453/// Make plan ready to be re-executed returning its clone with state reset for all nodes.
14021454///
14031455/// Some plans will change their internal states after execution, making them unable to be executed again.
@@ -1407,18 +1459,61 @@ pub fn check_not_null_constraints(
14071459/// However, if the data of the left table is derived from the work table, it will become outdated
14081460/// as the work table changes. When the next iteration executes this plan again, we must clear the left table.
14091461///
1410- /// # Limitations
1462+ /// # Dynamic filters
1463+ ///
1464+ /// Dynamic filters are re-set during state re-set bypass and must be updated in nodes which poll them.
1465+ /// To be able to do it the method [`ExecutionPlan::with_new_state`] should be implemented for the node
1466+ /// that supports dynamic filtering. The node should down-cast input state into [`DynamicFilters`] and
1467+ /// lookup for the filter with the same origin as it has to acquire an actual filter version.
14111468///
1412- /// While this function enables plan reuse, it does not allow the same plan to be executed if it (OR):
1469+ /// # Work table
14131470///
1414- /// * uses dynamic filters,
1415- /// * represents a recursive query.
1471+ /// Work table is re-set during state re-set bypass and must be updated in nodes which write into it.
1472+ /// To be able to do it the method [`ExecutionPlan::with_new_state`] should be implemented for the node
1473+ /// that uses work table.
14161474///
14171475pub fn reset_plan_states ( plan : Arc < dyn ExecutionPlan > ) -> Result < Arc < dyn ExecutionPlan > > {
1418- plan. transform_up ( |plan| {
1419- let new_plan = Arc :: clone ( & plan) . reset_state ( ) ?;
1420- Ok ( Transformed :: yes ( new_plan) )
1421- } )
1476+ let dynamic_filters = Arc :: new ( DynamicFilters :: default ( ) ) ;
1477+ let work_tables = RefCell :: new ( vec ! [ ] ) ;
1478+
1479+ plan. transform_down_up (
1480+ // Collect dynamic filters from nodes.
1481+ |plan| {
1482+ // Note: here we re-set state prior to children state is re-set to be able
1483+ // to collect and push new versions of the dynamic filters/work table.
1484+ // Children actually will be replacedo on the way up from the bottom.
1485+ let mut new_plan = Arc :: clone ( & plan) . reset_state ( ) ?;
1486+
1487+ // Try to inject updated dynamic filters.
1488+ if let Some ( plan) = new_plan. with_new_state ( Arc :: clone ( & dynamic_filters) as _ )
1489+ {
1490+ new_plan = plan;
1491+ }
1492+
1493+ // Try to inject updated work table.
1494+ if let Some ( work_table) = work_tables. borrow_mut ( ) . last ( )
1495+ && let Some ( plan) = new_plan. with_new_state ( Arc :: clone ( work_table) )
1496+ {
1497+ new_plan = plan;
1498+ }
1499+
1500+ dynamic_filters. extend ( new_plan. dynamic_filters ( ) . into_iter ( ) ) ;
1501+ if let Some ( plan) = new_plan. as_any ( ) . downcast_ref :: < RecursiveQueryExec > ( ) {
1502+ work_tables
1503+ . borrow_mut ( )
1504+ . push ( Arc :: clone ( plan. work_table ( ) ) as _ )
1505+ }
1506+
1507+ Ok ( Transformed :: yes ( new_plan) )
1508+ } ,
1509+ |plan| {
1510+ if plan. as_any ( ) . is :: < RecursiveQueryExec > ( ) {
1511+ work_tables. borrow_mut ( ) . pop ( ) ;
1512+ }
1513+ // Here we must return [`Transformed::yes`] to actually replace children.
1514+ Ok ( Transformed :: yes ( plan) )
1515+ } ,
1516+ )
14221517 . data ( )
14231518}
14241519
0 commit comments