Skip to content

Commit 7b37778

Browse files
authored
Merge pull request #66 from spiceai/viktor/fix-explain-analyze
Fix Explain Analyze
2 parents 09379a9 + 69e2d95 commit 7b37778

2 files changed

Lines changed: 64 additions & 1 deletion

File tree

datafusion-federation/src/analyzer/mod.rs

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -238,7 +238,16 @@ impl FederationAnalyzerRule {
238238

239239
// If all sources are federated to the same provider
240240
if let ScanResult::Distinct(provider) = sole_provider {
241-
match (is_root, provider.analyzer(plan)) {
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+
};
250+
match (is_root, provider_analyzer) {
242251
(false, Some(_)) => {
243252
// The largest sub-plan is higher up.
244253
return Ok((None, ScanResult::Distinct(provider)));

datafusion-federation/src/sql/mod.rs

Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -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

Comments
 (0)