databendlabs/databend · error

Input plan must be Query, but it's

Error message

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

What it means

build_query in interpreter_copy_into_table (src/query/service/src/interpreters/interpreter_copy_into_table.rs:216) destructures the bound plan expecting Plan::Query to build a SelectInterpreter; any other variant hits unreachable!("Input plan must be Query, but it's {}"). COPY INTO <table> requires its source to be a query plan; a non-query plan here is an internal invariant violation.

Solutions

  1. Rewrite the COPY INTO source as an explicit SELECT: `COPY INTO target FROM (SELECT ... FROM @stage ...)`
  2. Upgrade to a version where the copy-into-table binder guarantees a Query plan or raises a clear user error
  3. If already using SELECT, file a bug with the exact SQL and Databend version
  4. As a code fix, replace unreachable! with a returned ErrorCode::Internal naming the unexpected variant

Example fix

// before
let (s_expr, metadata, bind_context, formatted_ast) = match plan {
    Plan::Query { s_expr, metadata, bind_context, formatted_ast, .. } => (...),
    v => unreachable!("Input plan must be Query, but it's {}", v),
};
// after
let (...) = match plan {
    Plan::Query { .. } => (...),
    v => return Err(ErrorCode::Internal(format!("Input plan must be Query, but it's {}", v))),
};
Defensive patterns

Strategy: validation

Validate before calling

-- ensure COPY INTO <table> source is an explicit SELECT
-- good: COPY INTO t FROM (SELECT $1, $2 FROM @stage)
-- avoid relying on implicit table/stage source binding

Type guard

if !matches!(plan, Plan::Query { .. }) {
    return Err(ErrorCode::BadArguments("COPY INTO table source must be a SELECT".into()));
}

Try / catch

match plan {
    Plan::Query { s_expr, metadata, bind_context, formatted_ast, .. } => build_query(...),
    other => return Err(ErrorCode::Internal(format!("Input plan must be Query, but it's {}", other))),
}

Prevention

When it happens

Trigger: Executing COPY INTO <table> FROM ... where the bound source plan is not Plan::Query — e.g. a stage/table source form the binder no longer wraps in a query plan, or a planner refactor emitting a different variant reached build_physical_plan -> build_query.

Common situations: COPY INTO a table from a stage with a source form that binds unexpectedly; statements copied from older Databend syntax after an upgrade changed binder output; internal regressions where Explain or DML wrapper plans leak into the copy path.

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/6af69ef70cc8d4b3. Report an issue: GitHub.

Appendix: source

Thrown at src/query/service/src/interpreters/interpreter_copy_into_table.rs:216

    /// Create a CopyInterpreter with context and [`CopyIntoTablePlan`].
    pub fn try_create(ctx: Arc<QueryContext>, plan: CopyIntoTablePlan) -> Result<Self> {
        Ok(CopyIntoTableInterpreter { ctx, plan })
    }

    #[async_backtrace::framed]
    async fn build_query(
        &self,
        query: &Plan,
    ) -> Result<(SelectInterpreter, Vec<UpdateStreamMetaReq>)> {
        let (s_expr, metadata, bind_context, formatted_ast) = match query {
            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(
            self.ctx.clone(),
            *(bind_context.clone()),
            *s_expr.clone(),
            metadata.clone(),
            formatted_ast.clone(),
            false,
        )?;

        Ok((select_interpreter, update_stream_meta))
    }

    #[async_backtrace::framed]
    pub async fn build_physical_plan(

View on GitHub (pinned to 288d84d76e)