databendlabs/databend · error

plan in InsertInputSource::Stag must be CopyIntoTable

Error message

plan in InsertInputSource::Stag must be CopyIntoTable

What it means

During EXPLAIN of an INSERT, format_insert_source expects the plan stored in InsertInputSource::Stag to be a CopyIntoTable plan (it renders the stage child tree); any other plan shape hits `unreachable!("plan in InsertInputSource::Stag must be CopyIntoTable")`. The binder is the only producer of this variant, so the panic indicates the binder attached a non-copy-into plan to a stage input.

Solutions

  1. Inspect the binder code that constructs InsertInputSource::Stag and confirm it always wraps a CopyIntoTable plan.
  2. Add the actual plan debug output to the unreachable! message for faster diagnosis.
  3. If other plan shapes are legitimate, match them explicitly and format accordingly instead of panicking.
  4. Check recent changes to insert/stage binding; use git blame on the producer of the variant.

Example fix

// before
_ => unreachable!("plan in InsertInputSource::Stag must be CopyIntoTable"),
// after
other => FormatTreeNode::with_children(format!("{plan_name} ({other:?}):"), vec![]).format_pretty(),
Defensive patterns

Strategy: try-catch

Try / catch

// Skip EXPLAIN of stage inserts if the version is affected; execute directly
match try_explain(stmt) {
    Err(e) if e.contains("Stag must be CopyIntoTable") => exec_insert(stmt),
    r => r,
}

Prevention

When it happens

Trigger: Running EXPLAIN (or EXPLAIN VERBOSE) on an INSERT ... INTO ... FROM @stage statement whose bound stage input plan is not CopyIntoTable — e.g. binder changes produce a different plan wrapper for stage sources.

Common situations: Hit by users explaining data-loading statements (COPY-style INSERT from a named stage or files) after binder/planner refactors; shows up only on the EXPLAIN path, not 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/b832762d86121bdd. Report an issue: GitHub.

Appendix: source

Thrown at src/query/sql/src/planner/plans/insert.rs:269

                    FormatTreeNode::new(format!("from_stage_attachment: {from_stage_attachment}")),
                    FormatTreeNode::new(format!(
                        "required_values_schema: [{required_values_schema}]"
                    )),
                    FormatTreeNode::new(format!(
                        "required_source_schema: [{required_source_schema}]"
                    )),
                    FormatTreeNode::new(format!("write_mode: {write_mode}")),
                    FormatTreeNode::new(format!("validation_mode: {validation_mode}")),
                    FormatTreeNode::new(format!("stage_table_info: {stage_table_info}")),
                    FormatTreeNode::new(format!("enable_distributed: {enable_distributed}")),
                ];
                children.extend(stage_node);
                Ok(
                    FormatTreeNode::with_children(format!("{plan_name} (stage):"), children)
                        .format_pretty()?,
                )
            }
            _ => unreachable!("plan in InsertInputSource::Stag must be CopyIntoTable"),
        },
    }
}

impl std::fmt::Debug for Insert {
    fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
        f.debug_struct("Insert")
            .field("catalog", &self.catalog)
            .field("database", &self.database)
            .field("table", &self.table)
            .field("branch", &self.branch)
            .field("schema", &self.schema)
            .field("overwrite", &self.overwrite)
            .finish()
    }
}

View on GitHub (pinned to 288d84d76e)