{"record":{"id":"c8f08c1dcaa46cdd","repo":"risingwavelabs/risingwave","slug":"expected-risingwave-plan-in-batchplanchoice-but-g","errorCode":null,"errorMessage":"Expected RisingWave plan in BatchPlanChoice, but got DataFusion plan","messagePattern":"Expected RisingWave plan in BatchPlanChoice, but got DataFusion plan","errorType":"error_code","errorClass":null,"httpStatus":null,"severity":"error","filePath":"src/frontend/src/handler/query.rs","lineNumber":65,"sourceCode":"    BatchPlanFragmenter, DistributedQueryStream, ExecutionContext, ExecutionContextRef,\n    LocalQueryExecution, LocalQueryStream,\n};\nuse crate::session::SessionImpl;\n\n/// Choice between running RisingWave's own batch executor (Rw) or a `DataFusion` (DF) logical plan.\npub enum BatchPlanChoice {\n    Rw(RwBatchQueryPlanResult),\n    #[cfg(feature = \"datafusion\")]\n    Df(DfBatchQueryPlanResult),\n}\n\nimpl BatchPlanChoice {\n    pub fn unwrap_rw(self) -> Result<RwBatchQueryPlanResult> {\n        match self {\n            BatchPlanChoice::Rw(result) => Ok(result),\n            #[cfg(feature = \"datafusion\")]\n            BatchPlanChoice::Df { .. } => {\n                risingwave_common::bail!(\n                    \"Expected RisingWave plan in BatchPlanChoice, but got DataFusion plan\"\n                )\n            }\n        }\n    }\n}\n\npub async fn handle_query(\n    handler_args: HandlerArgs,\n    stmt: Statement,\n    formats: Vec<Format>,\n) -> Result<RwPgResponse> {\n    let session = handler_args.session.clone();\n    let context = OptimizerContext::from_handler_args(handler_args);\n\n    #[cfg(feature = \"datafusion\")]\n    {\n        // We construct a future manually here to make sure this async function is `Send`.","sourceCodeStart":47,"sourceCodeEnd":83,"githubUrl":"https://github.com/risingwavelabs/risingwave/blob/6469eb736d691e8e9b8a419a57edd6429ca77417/src/frontend/src/handler/query.rs#L47-L83","documentation":"BatchPlanChoice is an enum that wraps either a RisingWave-native batch plan (Rw) or a DataFusion plan (Df). unwrap_rw() refuses to extract a DataFusion variant, since the caller requires a RisingWave plan to proceed. Throwing this error prevents accidentally treating a DataFusion plan as a native one.","triggerScenarios":"Calling unwrap_rw() on a BatchPlanChoice produced when the DataFusion feature is enabled and the query was planned by DataFusion (BatchPlanChoice::Df variant) instead of the native planner.","commonSituations":"Developers running with the `datafusion` feature flag enabled whose queries get routed to the DataFusion planner, then pass the plan to code paths (e.g. distributed batch execution) that only understand RisingWave plans.","solutions":["Disable the `datafusion` feature so queries are always planned natively by RisingWave.","Check the variant with matches!(choice, BatchPlanChoice::Rw(..)) before calling unwrap_rw().","Route DataFusion plans to a DataFusion-capable executor instead of the RisingWave batch path.","Add a match arm handling BatchPlanChoice::Df in the caller if both engines must be supported."],"exampleFix":"// before\nlet rw_plan = batch_plan.unwrap_rw()?;\n// after\nlet rw_plan = match batch_plan {\n    BatchPlanChoice::Rw(rw) => rw,\n    BatchPlanChoice::Df { .. } => return Err(anyhow!(\"query requires the RisingWave planner; rebuild without the datafusion feature\")),\n};","handlingStrategy":"type-guard","validationCode":"fn is_rw_plan(choice: &BatchPlanChoice) -> bool {\n    matches!(choice, BatchPlanChoice::Rw(_))\n}","typeGuard":"if let BatchPlanChoice::Rw(rw) = &choice { /* use rw */ }","tryCatchPattern":"match choice.unwrap_rw() {\n    Ok(rw) => run(rw),\n    Err(e) if e.to_string().contains(\"got DataFusion plan\") => run_df_fallback(),\n    Err(e) => return Err(e),\n}","preventionTips":["Gate unwrap_rw() calls behind non-datafusion builds or assert the variant first.","Centralize plan dispatch in one place instead of scattering unwrap_rw calls.","Run CI with the datafusion feature enabled to catch mixed-engine paths early."],"tags":["batch-planning","feature-flag","rust"],"backgroundTag":"type-mismatch","analyzedSha":"6469eb736d691e8e9b8a419a57edd6429ca77417","analyzedAt":"2026-09-11T21:06:21.487Z","contentChangedAt":"2026-09-11T21:06:21.487Z","schemaVersion":2},"datasetVersion":"2026-09-23T08:17:48.524Z"}