@@ -46,8 +46,9 @@ impl AnalyzerRule for FederationAnalyzerRule {
4646
4747 // Find all federation providers for TableReferences that appear in the plan, to resolve OuterRefColumns
4848 let providers = get_plan_provider_recursively ( & plan) ?;
49+ let explain_context = Self :: explain_context_template ( & plan) ;
4950
50- match self . analyze_plan_recursively ( & plan, true , config, & providers) ? {
51+ match self . analyze_plan_recursively ( & plan, true , config, & providers, explain_context ) ? {
5152 ( Some ( optimized_plan) , _) => Ok ( optimized_plan) ,
5253 ( None , _) => Ok ( plan) ,
5354 }
@@ -85,6 +86,27 @@ impl FederationAnalyzerRule {
8586 self
8687 }
8788
89+ fn explain_context_template ( plan : & LogicalPlan ) -> Option < LogicalPlan > {
90+ match plan {
91+ LogicalPlan :: Explain ( _) | LogicalPlan :: Analyze ( _) => Some ( plan. clone ( ) ) ,
92+ _ => None ,
93+ }
94+ }
95+
96+ fn wrap_federated_plan (
97+ plan : LogicalPlan ,
98+ explain_context : Option < & LogicalPlan > ,
99+ ) -> Result < LogicalPlan > {
100+ if matches ! ( plan, LogicalPlan :: Explain ( _) | LogicalPlan :: Analyze ( _) ) {
101+ return Ok ( plan) ;
102+ }
103+
104+ match explain_context {
105+ Some ( wrapper) => wrapper. with_new_exprs ( wrapper. expressions ( ) , vec ! [ plan] ) ,
106+ None => Ok ( plan) ,
107+ }
108+ }
109+
88110 /// Scans a plan to see if it belongs to a single [`FederationProvider`].
89111 fn scan_plan_recursively (
90112 & self ,
@@ -184,7 +206,9 @@ impl FederationAnalyzerRule {
184206 is_root : bool ,
185207 config : & ConfigOptions ,
186208 providers : & HashMap < TableReference , Arc < dyn FederationProvider > > ,
209+ explain_context : Option < LogicalPlan > ,
187210 ) -> Result < ( Option < LogicalPlan > , ScanResult ) > {
211+ let explain_context = explain_context. or_else ( || Self :: explain_context_template ( plan) ) ;
188212 let mut sole_provider: ScanResult = ScanResult :: None ;
189213
190214 if let LogicalPlan :: Extension ( Extension { ref node } ) = plan {
@@ -217,7 +241,9 @@ impl FederationAnalyzerRule {
217241 // Recursively analyze inputs
218242 let input_results = inputs
219243 . iter ( )
220- . map ( |i| self . analyze_plan_recursively ( i, false , config, providers) )
244+ . map ( |i| {
245+ self . analyze_plan_recursively ( i, false , config, providers, explain_context. clone ( ) )
246+ } )
221247 . collect :: < Result < Vec < _ > > > ( ) ?;
222248
223249 // Aggregate the input providers
@@ -238,23 +264,25 @@ impl FederationAnalyzerRule {
238264
239265 // If all sources are federated to the same provider
240266 if let ScanResult :: Distinct ( provider) = sole_provider {
241- // Analyze plans (EXPLAIN ANALYZE) cannot be converted to SQL by the
242- // Unparser, so they must not be federated as a whole. Only the inner
243- // query should be federated; DataFusion's AnalyzeExec will handle
244- // executing it and collecting metrics.
245- let provider_analyzer = if matches ! ( plan, LogicalPlan :: Analyze ( _) ) {
246- None
247- } else {
248- provider. analyzer ( plan)
249- } ;
267+ // Explain and Analyze wrappers stay in the DataFusion plan so their
268+ // physical operators can still run. The corresponding directive is
269+ // injected into the federated subquery instead.
270+ let federated_plan = Self :: wrap_federated_plan ( plan. clone ( ) , explain_context. as_ref ( ) ) ?;
271+ let provider_analyzer =
272+ if matches ! ( plan, LogicalPlan :: Analyze ( _) | LogicalPlan :: Explain ( _) ) {
273+ None
274+ } else {
275+ provider. analyzer ( & federated_plan)
276+ } ;
250277 match ( is_root, provider_analyzer) {
251278 ( false , Some ( _) ) => {
252279 // The largest sub-plan is higher up.
253280 return Ok ( ( None , ScanResult :: Distinct ( provider) ) ) ;
254281 }
255282 ( true , Some ( FederationAnalyzerForLogicalPlan :: With ( analyzer) ) ) => {
256283 // If this is the root plan node; federate the entire plan
257- let optimized = analyzer. execute_and_check ( plan. clone ( ) , config, |_, _| { } ) ?;
284+ let optimized =
285+ analyzer. execute_and_check ( federated_plan, config, |_, _| { } ) ?;
258286 return Ok ( ( Some ( optimized) , ScanResult :: None ) ) ;
259287 }
260288 ( _, None | Some ( FederationAnalyzerForLogicalPlan :: Unable ) ) => {
@@ -291,22 +319,26 @@ impl FederationAnalyzerRule {
291319 return Ok ( original_input) ;
292320 } ;
293321
322+ let federated_input = Self :: wrap_federated_plan (
323+ wrap_projection ( original_input. clone ( ) ) ?,
324+ explain_context. as_ref ( ) ,
325+ ) ?;
326+
294327 let Some ( FederationAnalyzerForLogicalPlan :: With ( analyzer) ) =
295- provider. analyzer ( & original_input )
328+ provider. analyzer ( & federated_input )
296329 else {
297330 // Either provider has no analyzer, or cannot federate [`LogicalPlan`].
298331 return Ok ( original_input) ;
299332 } ;
300333
301334 // Replace the input with the federated counterpart
302- let wrapped = wrap_projection ( original_input) ?;
303- analyzer. execute_and_check ( wrapped, config, |_, _| { } )
335+ analyzer. execute_and_check ( federated_input, config, |_, _| { } )
304336 } )
305337 . collect :: < Result < Vec < _ > > > ( ) ?;
306338
307339 // Optimize expressions if needed
308340 let new_expressions = if optimize_expressions {
309- self . analyze_plan_exprs ( plan, config, providers) ?
341+ self . analyze_plan_exprs ( plan, config, providers, explain_context ) ?
310342 } else {
311343 plan. expressions ( )
312344 } ;
@@ -324,13 +356,14 @@ impl FederationAnalyzerRule {
324356 plan : & LogicalPlan ,
325357 config : & ConfigOptions ,
326358 providers : & HashMap < TableReference , Arc < dyn FederationProvider > > ,
359+ explain_context : Option < LogicalPlan > ,
327360 ) -> Result < Vec < Expr > > {
328361 plan. expressions ( )
329362 . iter ( )
330363 . map ( |expr| {
331- let transformed = expr
332- . clone ( )
333- . transform ( & |e| self . analyze_expr_recursively ( e , config , providers ) ) ?;
364+ let transformed = expr. clone ( ) . transform ( & |e| {
365+ self . analyze_expr_recursively ( e , config , providers , explain_context . clone ( ) )
366+ } ) ?;
334367 Ok ( transformed. data )
335368 } )
336369 . collect :: < Result < Vec < _ > > > ( )
@@ -343,12 +376,18 @@ impl FederationAnalyzerRule {
343376 expr : Expr ,
344377 _config : & ConfigOptions ,
345378 providers : & HashMap < TableReference , Arc < dyn FederationProvider > > ,
379+ explain_context : Option < LogicalPlan > ,
346380 ) -> Result < Transformed < Expr > > {
347381 match expr {
348382 Expr :: ScalarSubquery ( ref subquery) => {
349383 // Analyze as root to force federating the sub-query
350- let ( new_subquery, _) =
351- self . analyze_plan_recursively ( & subquery. subquery , true , _config, providers) ?;
384+ let ( new_subquery, _) = self . analyze_plan_recursively (
385+ & subquery. subquery ,
386+ true ,
387+ _config,
388+ providers,
389+ explain_context. clone ( ) ,
390+ ) ?;
352391 let Some ( new_subquery) = new_subquery else {
353392 return Ok ( Transformed :: no ( expr) ) ;
354393 } ;
@@ -382,6 +421,7 @@ impl FederationAnalyzerRule {
382421 true ,
383422 _config,
384423 providers,
424+ explain_context,
385425 ) ?;
386426 let Some ( new_subquery) = new_subquery else {
387427 return Ok ( Transformed :: no ( expr) ) ;
0 commit comments