databendlabs/databend · error

plan in InsertInputSource::Stag must be CopyIntoTable

Error message

plan in InsertInputSource::Stag must be CopyIntoTable

What it means

connect_input_source in the REPLACE interpreter handles each InsertInputSource variant. When the source is InsertInputSource::Stage (with the storage-stage feature enabled), the embedded plan must specifically be a CopyIntoTable physical plan; any other plan type there violates the invariant and panics via unreachable!.

Solutions

  1. Inspect the REPLACE statement's stage source and confirm the planner emits CopyIntoTable for it.
  2. Upgrade to a build where the REPLACE planner produces CopyIntoTable for stage sources.
  3. Avoid REPLACE INTO ... FROM @stage; stage the data into a table first, then use REPLACE with a query source.

Example fix

// before
_ => unreachable!("plan in InsertInputSource::Stag must be CopyIntoTable"),
// after
other => Err(ErrorCode::Internal(format!(
    "REPLACE stage source expects CopyIntoTable plan, got {:?}", other
))),
Defensive patterns

Strategy: validation

Validate before calling

// Ensure REPLACE uses a query source, not a stage/stream source
let src = match &insert_plan.input_source {
    InsertInputSource::SelectPlan(_) => Ok(()),
    _ => Err("REPLACE only supports SELECT sources here"),
};

Prevention

When it happens

Trigger: Executing REPLACE ... with a stage-based input source where the physical plan stored in InsertInputSource::Stage is not CopyIntoTable — e.g. the planner produced a different stage-read plan for REPLACE.

Common situations: Building Databend with the 'storage-stage' feature and issuing REPLACE INTO ... FROM @stage; planner changes that wrap or alter the stage COPY plan before REPLACE execution.

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/8e7bf4884e784f8e. Report an issue: GitHub.

Appendix: source

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

                    let (physical_plan, _, _) = interpreter
                        .build_physical_plan(_table_info, &copy_plan, _table_meta_timestamps)
                        .await?;

                    // TODO optimization: if copy_plan.stage_table_info.files_to_copy is None, there should be a short-cut plan

                    *_purge_info = Some((
                        copy_plan.stage_table_info.files_to_copy.unwrap_or_default(),
                        copy_plan.stage_table_info.stage_info.clone(),
                        copy_plan.stage_table_info.copy_into_table_options.clone(),
                    ));
                    Ok(ReplaceSourceCtx {
                        root: physical_plan,
                        select_ctx: None,
                        update_stream_meta: vec![],
                        bind_context: None,
                    })
                }
                _ => unreachable!("plan in InsertInputSource::Stag must be CopyIntoTable"),
            },
            #[cfg(not(feature = "storage-stage"))]
            InsertInputSource::Stage(_) => Err(ErrorCode::Unimplemented(
                "Stage COPY source support is disabled, rebuild with cargo feature 'storage-stage'",
            )),
            InsertInputSource::StreamingLoad { .. } => {
                unreachable!("replace with streaming not supported yet")
            }
        }
    }

    fn connect_value_source(
        &self,
        schema: DataSchemaRef,
        source: &InsertValue,
    ) -> Result<PhysicalPlan> {
        Ok(PhysicalPlan::new(ReplaceAsyncSourcer {
            schema,

View on GitHub (pinned to 288d84d76e)