databendlabs/databend · error

Input plan must be Query, but it's

Error message

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

What it means

The SET interpreter builds a sub-SELECT for assignment sources and destructures the input plan expecting the Query variant so it can construct a SelectInterpreter. If the plan is any other variant, the invariant is broken and the code panics via unreachable!, reporting the actual variant in the message.

Solutions

  1. Inspect the logical plan of the SET statement's right-hand side to see which non-Query variant is produced.
  2. Fix the planner so assignment sources are wrapped in Plan::Query, or add an arm handling the new variant.
  3. Upgrade to a version where SET source plans are guaranteed to be Query.

Example fix

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

Strategy: type-guard

Validate before calling

// Verify the SET source plan variant before destructuring
if !matches!(plan, Plan::Query { .. }) {
    return Err(ErrorCode::Internal("SET 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: Executing a SET/assignment statement whose value-producing sub-plan is not Plan::Query — e.g. an optimizer/planner change yields a different logical plan node for the assigned expression source.

Common situations: Internal planner regressions when new plan nodes are introduced; using SET ... = (SELECT ...) forms through code paths that reshape the sub-plan.

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/1c76cb0f5697376d. Report an issue: GitHub.

Appendix: source

Thrown at src/query/service/src/interpreters/interpreter_set.rs:196

    fn is_ddl(&self) -> bool {
        false
    }

    #[async_backtrace::framed]
    async fn execute2(&self) -> Result<PipelineBuildResult> {
        let scalars = match &self.set.values {
            SetScalarsOrQuery::VarValue(scalars) => scalars.clone(),
            SetScalarsOrQuery::Query(query) => {
                let (s_expr, metadata, bind_context, formatted_ast) = match query.as_ref() {
                    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 select_interpreter = SelectInterpreter::try_create(
                    self.ctx.clone(),
                    *(bind_context.clone()),
                    *s_expr.clone(),
                    metadata.clone(),
                    formatted_ast.clone(),
                    false,
                )?;

                let stream = select_interpreter
                    .execute_with_hooks(self.ctx.clone(), QueryFinishHooks::nested_with_hooks())
                    .await?;
                let datablocks: Vec<DataBlock> = stream.try_collect::<Vec<_>>().await?;
                let num_columns = bind_context.columns.len();
                if num_columns != self.set.idents.len() {
                    return Err(ErrorCode::BadArguments(format!(

View on GitHub (pinned to 288d84d76e)