databendlabs/databend · error

internal error: entered unreachable code

Error message

internal error: entered unreachable code

What it means

A `unreachable!()` panic in `MutationManipulate::build_pipeline2`. The match maps each `MutationStrategy` to a (step, need_match, need_unmatch) tuple, and `Direct` is asserted impossible. Hitting this panic means a Direct-strategy mutation plan was dispatched to the matched/unmatched pipeline builder, violating the planner's assumption that Direct plans bypass this path entirely.

Solutions

  1. Report the failing MERGE INTO query and plan to Databend maintainers — this is an internal invariant violation
  2. Upgrade to the newest Databend release to pick up fixes for Direct-strategy mutation handling
  3. Trace plan construction to find why a Direct-strategy plan entered the manipulate builder instead of the Direct fast path
  4. Rewrite the MERGE INTO statement so the optimizer produces a MatchedOnly/MixedMatched/NotMatchedOnly strategy

Example fix

// before
MutationStrategy::MixedMatched => (2, true, true),
MutationStrategy::Direct => unreachable!(),
// after
MutationStrategy::MixedMatched => (2, true, true),
MutationStrategy::Direct => (0, false, false), // Direct strategy bypasses match/unmatch pipelines
Defensive patterns

Strategy: try-catch

Validate before calling

if matches!(plan.strategy, MutationStrategy::Direct) {
    return Err(ErrorCode::Internal("Direct strategy not supported in MutationManipulate"));
}

Type guard

fn is_direct_strategy(s: &MutationStrategy) -> bool { matches!(s, MutationStrategy::Direct) }

Try / catch

catch_unwind around build_pipeline2; map panic to ErrorCode::Internal with the query id attached

Prevention

When it happens

Trigger: Executing a MutationManipulate plan with `MutationStrategy::Direct`; the executor calls build_pipeline2 which only supports MatchedOnly, NotMatchedOnly, and MixedMatched strategies.

Common situations: MERGE INTO workloads after a planner/executor version skew; queries where the optimizer recently started classifying plans as Direct but the manipulate-stage builder predates that change.

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/13fe7c8af3859e14. Report an issue: GitHub.

Appendix: source

Thrown at src/query/service/src/physical_plans/physical_mutation_manipulate.rs:127

    // +----------------------+           |                       +---+                +-----------------------------+
    //                                    |                       |   | NotMatched     |                             +-+
    //                                    |                       +---+--------------->| MergeIntoNotMatchedProcessor| |
    //                                    +-----------------------+                    |                             +-+
    //                                                                                 +-----------------------------+
    // Note: here the output_port of MatchedSplitProcessor are arranged in the following order
    // (0) -> output_port_row_id
    // (1) -> output_port_updated

    // Outputs from MatchedSplitProcessor's output_port_updated and MergeIntoNotMatchedProcessor's output_port are merged and processed uniformly by the subsequent ResizeProcessor
    // receive matched data and not matched data parallelly.
    fn build_pipeline2(&self, builder: &mut PipelineBuilder) -> Result<()> {
        self.input.build_pipeline(builder)?;

        let (step, need_match, need_unmatch) = match self.strategy {
            MutationStrategy::MatchedOnly => (1, true, false),
            MutationStrategy::NotMatchedOnly => (1, false, true),
            MutationStrategy::MixedMatched => (2, true, true),
            MutationStrategy::Direct => unreachable!(),
        };

        let tbl = builder
            .ctx
            .build_table_by_table_info(&self.table_info, None)?;

        let input_schema = self.input.output_schema()?;
        let mut pipe_items = Vec::with_capacity(builder.main_pipeline.output_len());
        for _ in (0..builder.main_pipeline.output_len()).step_by(step) {
            if need_match {
                let matched_split_processor = MatchedSplitProcessor::create(
                    builder.ctx.clone(),
                    self.row_id_idx,
                    self.matched.clone(),
                    self.field_index_of_input_schema.clone(),
                    input_schema.clone(),
                    Arc::new(DataSchema::from(tbl.schema_with_stream())),
                    false,

View on GitHub (pinned to 288d84d76e)