{"record":{"id":"9c68b16a9bcde322","repo":"databendlabs/databend","slug":"input-plan-must-be-query-but-it-s-9c68b1","errorCode":null,"errorMessage":"Input plan must be Query, but it's {}","messagePattern":"Input plan must be Query, but it's (.+?)","errorType":"panic","errorClass":null,"httpStatus":null,"severity":"error","filePath":"src/query/service/src/interpreters/interpreter_replace.rs","lineNumber":482,"sourceCode":"            meta: PhysicalPlanMeta::new(\"ReplaceAsyncSourcer\"),\n        }))\n    }\n\n    #[async_backtrace::framed]\n    async fn connect_query_plan_source<'a>(\n        &'a self,\n        ctx: Arc<QueryContext>,\n        query_plan: &Plan,\n    ) -> Result<ReplaceSourceCtx> {\n        let (s_expr, metadata, bind_context, formatted_ast) = match query_plan {\n            Plan::Query {\n                s_expr,\n                metadata,\n                bind_context,\n                formatted_ast,\n                ..\n            } => (s_expr, metadata, bind_context, formatted_ast),\n            v => unreachable!(\"Input plan must be Query, but it's {}\", v),\n        };\n\n        let update_stream_meta = dml_build_update_stream_req(self.ctx.clone()).await?;\n\n        let select_interpreter = SelectInterpreter::try_create(\n            ctx.clone(),\n            *(bind_context.clone()),\n            *s_expr.clone(),\n            metadata.clone(),\n            formatted_ast.clone(),\n            false,\n        )?;\n\n        let physical_plan = select_interpreter.build_physical_plan().await?;\n        let select_ctx = ReplaceSelectCtx {\n            select_column_bindings: bind_context.columns.clone(),\n            select_schema: query_plan.schema(),\n        };","sourceCodeStart":464,"sourceCodeEnd":500,"githubUrl":"https://github.com/databendlabs/databend/blob/288d84d76e20a2f8f7173bda9691eb6ece301aa9/src/query/service/src/interpreters/interpreter_replace.rs#L464-L500","documentation":"connect_query_plan_source destructures the input plan expecting it to be the Query variant; only then can it extract the SExpr/metadata/bind_context needed to build a SelectInterpreter. Any other Plan variant (e.g. an already-physical or DML plan) violates the assumption and panics via unreachable! with the actual variant.","triggerScenarios":"Running REPLACE INTO ... <source> where the logical plan of the source is not Plan::Query — e.g. the planner handed back a different plan node for the SELECT part.","commonSituations":"Planner rewrites or optimizer changes that substitute a non-Query plan for the REPLACE source; embedding REPLACE with an EXPLAIN-derived or cached plan object.","solutions":["Check the optimizer output for the REPLACE source query and confirm it remains Plan::Query.","Upgrade/patch the planner so REPLACE sources are kept as Query plans until interpretation.","Work around by using a simpler source form (plain SELECT) that reliably produces a Query plan."],"exampleFix":"// before\nv => unreachable!(\"Input plan must be Query, but it's {}\", v),\n// after\nv => Err(ErrorCode::Internal(format!(\n    \"REPLACE source must be a Query plan, got: {}\", v\n))),","handlingStrategy":"type-guard","validationCode":"// Verify the source plan variant before destructuring\nif !matches!(plan, Plan::Query { .. }) {\n    return Err(ErrorCode::Internal(\"REPLACE source is not a Query plan\".into()));\n}","typeGuard":"fn is_query_plan(p: &Plan) -> bool {\n    matches!(p, Plan::Query { .. })\n}","tryCatchPattern":null,"preventionTips":["Add planner tests asserting REPLACE sources stay Plan::Query","Handle new Plan variants in every destructuring site","Return structured Internal errors instead of unreachable!"],"tags":["rust","unreachable","plan","replace"],"backgroundTag":"internal-invariant-violation","analyzedSha":"288d84d76e20a2f8f7173bda9691eb6ece301aa9","analyzedAt":"2026-09-11T11:29:36.208Z","contentChangedAt":"2026-09-11T11:29:36.208Z","schemaVersion":2},"datasetVersion":"2026-09-16T04:17:20.429Z"}