@@ -19,32 +19,42 @@ use std::fmt;
1919use std:: hash:: { Hash , Hasher } ;
2020use std:: sync:: { Arc , Mutex } ;
2121
22- use datafusion_common:: { HashMap , Result , ScalarValue , internal_err} ;
22+ use datafusion_common:: { HashMap , Result , ScalarValue , TableReference , internal_err} ;
2323
2424/// Context used while converting a logical plan subtree into a physical plan.
2525///
2626/// Unlike [`ExecutionProps`](crate::execution_props::ExecutionProps), which
2727/// applies to the overall planning and execution of a query, this context can
28- /// differ between recursively planned subtrees. It currently carries the state
29- /// needed to create physical expressions for [`Expr::ScalarSubquery`] nodes
30- /// that read from a shared
31- /// [`ScalarSubqueryResults`] container.
28+ /// differ between recursively planned subtrees. It currently carries:
29+ ///
30+ /// * the state needed to create physical expressions for
31+ /// [`Expr::ScalarSubquery`] nodes that read from a shared
32+ /// [`ScalarSubqueryResults`] container, and
33+ /// * the qualifiers assigned to the [`Expr::LambdaVariable`]s that are in scope.
3234///
3335/// The physical planner builds this context from the set of uncorrelated scalar
3436/// subqueries it has scheduled for a subtree. It is then passed explicitly
3537/// through `create_physical_expr` so that function can find the slot index for
36- /// each [`Subquery`].
38+ /// each [`Subquery`]. While planning the body of a lambda,
39+ /// `create_physical_expr` extends the context with the lambda's parameters via
40+ /// [`Self::with_qualified_lambda_variables`].
3741///
3842/// An empty [`PhysicalPlanningContext`] (the [`Default`]) is what every
3943/// non-physical-planner caller passes; if such a caller encounters a scalar
4044/// subquery, `create_physical_expr` returns a `not_impl_err`.
4145///
4246/// [`Expr::ScalarSubquery`]: crate::Expr::ScalarSubquery
47+ /// [`Expr::LambdaVariable`]: crate::Expr::LambdaVariable
4348/// [`Subquery`]: crate::logical_plan::Subquery
4449#[ derive( Clone , Debug , Default ) ]
4550pub struct PhysicalPlanningContext {
46- indexes : HashMap < crate :: logical_plan:: Subquery , SubqueryIndex > ,
51+ // Behind an `Arc` because the context is cloned for each lambda body that
52+ // is planned, and the indexes are the same for the whole subtree.
53+ indexes : Arc < HashMap < crate :: logical_plan:: Subquery , SubqueryIndex > > ,
4754 results : ScalarSubqueryResults ,
55+ /// Maps each lambda variable name in scope to the qualifier generated for
56+ /// its lambda during physical planning.
57+ lambda_variable_qualifier : HashMap < String , TableReference > ,
4858}
4959
5060impl PhysicalPlanningContext {
@@ -55,7 +65,11 @@ impl PhysicalPlanningContext {
5565 indexes : HashMap < crate :: logical_plan:: Subquery , SubqueryIndex > ,
5666 results : ScalarSubqueryResults ,
5767 ) -> Self {
58- Self { indexes, results }
68+ Self {
69+ indexes : Arc :: new ( indexes) ,
70+ results,
71+ lambda_variable_qualifier : HashMap :: new ( ) ,
72+ }
5973 }
6074
6175 /// Returns the slot index assigned to `subquery`, if any.
@@ -70,6 +84,27 @@ impl PhysicalPlanningContext {
7084 pub fn results ( & self ) -> & ScalarSubqueryResults {
7185 & self . results
7286 }
87+
88+ /// Adds a mapping for each variable to the given qualifier. Existing
89+ /// variables with conflicting names get shadowed
90+ pub fn with_qualified_lambda_variables (
91+ mut self ,
92+ qualifier : & TableReference ,
93+ variables : & [ String ] ,
94+ ) -> Self {
95+ for var in variables {
96+ self . lambda_variable_qualifier
97+ . entry_ref ( var)
98+ . insert ( qualifier. clone ( ) ) ;
99+ }
100+
101+ self
102+ }
103+
104+ /// Returns the qualifier of the lambda variable `name`, if it is in scope.
105+ pub fn lambda_variable_qualifier ( & self , name : & str ) -> Option < & TableReference > {
106+ self . lambda_variable_qualifier . get ( name)
107+ }
73108}
74109
75110/// Index of a scalar subquery within a [`ScalarSubqueryResults`] container.
@@ -192,6 +227,20 @@ mod tests {
192227 Ok ( ( ) )
193228 }
194229
230+ #[ test]
231+ fn lambda_variables_shadow_outer_scope ( ) {
232+ let outer = TableReference :: bare ( "lambda_1" ) ;
233+ let inner = TableReference :: bare ( "lambda_2" ) ;
234+
235+ let ctx = PhysicalPlanningContext :: default ( )
236+ . with_qualified_lambda_variables ( & outer, & [ "x" . to_string ( ) , "y" . to_string ( ) ] )
237+ . with_qualified_lambda_variables ( & inner, & [ "y" . to_string ( ) ] ) ;
238+
239+ assert_eq ! ( ctx. lambda_variable_qualifier( "x" ) , Some ( & outer) ) ;
240+ assert_eq ! ( ctx. lambda_variable_qualifier( "y" ) , Some ( & inner) ) ;
241+ assert_eq ! ( ctx. lambda_variable_qualifier( "z" ) , None ) ;
242+ }
243+
195244 #[ test]
196245 fn scalar_subquery_results_clear ( ) -> Result < ( ) > {
197246 let results = ScalarSubqueryResults :: new ( 1 ) ;
0 commit comments