databendlabs/databend · error

Input plan must be Query, but it's

Error message

Input plan must be Query, but it's {}

What it means

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.

Solutions

  1. Check the optimizer output for the REPLACE source query and confirm it remains Plan::Query.
  2. Upgrade/patch the planner so REPLACE sources are kept as Query plans until interpretation.
  3. Work around by using a simpler source form (plain SELECT) that reliably produces a Query plan.

Example fix

// before
v => unreachable!("Input plan must be Query, but it's {}", v),
// after
v => Err(ErrorCode::Internal(format!(
    "REPLACE source must be a Query plan, got: {}", v
))),
Defensive patterns

Strategy: type-guard

Validate before calling

// Verify the source plan variant before destructuring
if !matches!(plan, Plan::Query { .. }) {
    return Err(ErrorCode::Internal("REPLACE source is not a Query plan".into()));
}

Type guard

fn is_query_plan(p: &Plan) -> bool {
    matches!(p, Plan::Query { .. })
}

Prevention

When it happens

Trigger: 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.

Common situations: 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.

Understand the failure class

Background: "This is a bug, please report it": internal invariant violations, unreachable panics, and SNH errors explained — this error's family across 47 libraries.

Related errors


AI-assisted analysis of databendlabs/databend@288d84d76e (2026-09-11). Data as JSON: /api/errors/9c68b16a9bcde322. Report an issue: GitHub.

Appendix: source

Thrown at src/query/service/src/interpreters/interpreter_replace.rs:482

            meta: PhysicalPlanMeta::new("ReplaceAsyncSourcer"),
        }))
    }

    #[async_backtrace::framed]
    async fn connect_query_plan_source<'a>(
        &'a self,
        ctx: Arc<QueryContext>,
        query_plan: &Plan,
    ) -> Result<ReplaceSourceCtx> {
        let (s_expr, metadata, bind_context, formatted_ast) = match query_plan {
            Plan::Query {
                s_expr,
                metadata,
                bind_context,
                formatted_ast,
                ..
            } => (s_expr, metadata, bind_context, formatted_ast),
            v => unreachable!("Input plan must be Query, but it's {}", v),
        };

        let update_stream_meta = dml_build_update_stream_req(self.ctx.clone()).await?;

        let select_interpreter = SelectInterpreter::try_create(
            ctx.clone(),
            *(bind_context.clone()),
            *s_expr.clone(),
            metadata.clone(),
            formatted_ast.clone(),
            false,
        )?;

        let physical_plan = select_interpreter.build_physical_plan().await?;
        let select_ctx = ReplaceSelectCtx {
            select_column_bindings: bind_context.columns.clone(),
            select_schema: query_plan.schema(),
        };

View on GitHub (pinned to 288d84d76e)