@@ -927,6 +927,60 @@ mod tests {
927927 Ok ( ( ) )
928928 }
929929
930+ /// EXPLAIN ANALYZE must not federate the Analyze wrapper — only the inner
931+ /// query should be federated. Otherwise the SQL Unparser fails because it
932+ /// cannot convert Analyze to SQL.
933+ #[ tokio:: test]
934+ async fn explain_analyze_not_federated ( ) -> Result < ( ) , DataFusionError > {
935+ let executor = TestExecutor {
936+ compute_context : "a" . into ( ) ,
937+ cannot_federate : None ,
938+ } ;
939+
940+ let table_ref = "test_table" . to_string ( ) ;
941+ let table = get_test_table_provider ( table_ref. clone ( ) , executor) ;
942+
943+ let state = crate :: default_session_state ( ) ;
944+ let ctx = SessionContext :: new_with_state ( state) ;
945+ ctx. register_table ( table_ref, table) . unwrap ( ) ;
946+
947+ // EXPLAIN ANALYZE wraps the query in LogicalPlan::Analyze.
948+ // The federation analyzer must NOT wrap the Analyze node itself.
949+ let plan = ctx
950+ . sql ( "EXPLAIN ANALYZE SELECT * FROM test_table" )
951+ . await ?
952+ . into_optimized_plan ( ) ?;
953+
954+ // The top-level node must be Analyze, not Federated.
955+ assert ! (
956+ matches!( plan, LogicalPlan :: Analyze ( _) ) ,
957+ "Expected Analyze at root, got: {}" ,
958+ plan. display_indent( )
959+ ) ;
960+
961+ // The inner plan should contain a Federated extension node.
962+ let mut found_federated = false ;
963+ plan. apply ( |node| {
964+ if let LogicalPlan :: Extension ( ext) = node {
965+ if ext. node . name ( ) == "Federated" {
966+ found_federated = true ;
967+ return Ok ( TreeNodeRecursion :: Stop ) ;
968+ }
969+ }
970+ Ok ( TreeNodeRecursion :: Continue )
971+ } ) ?;
972+ assert ! (
973+ found_federated,
974+ "Expected a Federated node inside the Analyze plan"
975+ ) ;
976+
977+ // Physical planning should succeed (this is where it used to fail).
978+ let physical_plan = ctx. state ( ) . create_physical_plan ( & plan) . await ?;
979+ assert_eq ! ( physical_plan. name( ) , "AnalyzeExec" ) ;
980+
981+ Ok ( ( ) )
982+ }
983+
930984 #[ tokio:: test]
931985 async fn sql_query_rewriter_hook_invoked_and_rewrites_sql ( ) -> Result < ( ) , DataFusionError > {
932986 let executor = TestExecutor {
0 commit comments