databendlabs/databend · error

internal error: entered unreachable code

Error message

internal error: entered unreachable code

What it means

This is a Rust `unreachable!()` panic in `build_pipeline2` of `PhysicalMutation`. The code asserts that `MutationStrategy::Direct` never reaches this match arm when computing `serialize_len` (the output port count after optionally removing the row-id port). Hitting it means the mutation plan was constructed with a `Direct` strategy but the pipeline-build path still ran this branch — an internal planner invariant violation, not user input error.

Solutions

  1. Capture the failing query's EXPLAIN/plan output and file a Databend bug with the MERGE INTO statement and plan, since this is an internal invariant violation
  2. Check the Databend version and upgrade to the latest release where Direct-strategy handling may have been fixed
  3. Inspect how the mutation plan was created (which code path chose MutationStrategy::Direct) and ensure Direct plans take a different pipeline-build path
  4. As a workaround, rewrite the MERGE INTO so the optimizer classifies it as MatchedOnly/MixedMatched/NotMatchedOnly instead of Direct

Example fix

// before
MutationStrategy::Direct => unreachable!(),
// after
MutationStrategy::Direct => 0, // Direct strategy does not serialize matched/not-matched rows
Defensive patterns

Strategy: try-catch

Validate before calling

// Rust: callers cannot inspect strategy directly; guard at API boundary
if matches!(plan.strategy(), MutationStrategy::Direct) {
    return Err(ErrorCode::Internal("Direct strategy not supported by this build path"));
}

Type guard

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

Try / catch

// Catch the panic at the query-execution boundary
let result = std::panic::catch_unwind(AssertUnwindSafe(|| builder.build_pipeline2(...)));
match result { Ok(r) => r, Err(_) => Err(ErrorCode::Internal("mutation pipeline build panicked")) }

Prevention

When it happens

Trigger: Executing a MERGE INTO physical mutation plan whose `strategy` is `MutationStrategy::Direct` while `build_pipeline2` executes the `serialize_len` computation; i.e., a Direct-strategy plan was routed through a build path that expects NotMatchedOnly/MixedMatched/MatchedOnly.

Common situations: Running Databend MERGE INTO queries after planner changes that allow Direct strategy to reach this builder; custom or patched plans mixing strategy kinds; version upgrades where strategy classification changed but pipeline construction did not.

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/70d30b1beac6ad58. Report an issue: GitHub.

Appendix: source

Thrown at src/query/service/src/physical_plans/physical_mutation.rs:186

        let table = FuseTable::try_from_table(tbl.as_ref())?;
        let block_thresholds = table.get_block_thresholds();

        let input_schema = DataSchema::from(table.schema_with_stream()).into();
        let cluster_stats_gen =
            table.get_cluster_stats_gen(builder.ctx.clone(), 0, block_thresholds, input_schema)?;

        let max_threads = builder.settings.get_max_threads()? as usize;

        // For row_id port, create rowid_aggregate_mutator
        // For matched data port and unmatched port, do serialize
        let serialize_len = match self.strategy {
            MutationStrategy::NotMatchedOnly => builder.main_pipeline.output_len(),
            MutationStrategy::MixedMatched | MutationStrategy::MatchedOnly => {
                // remove row id port
                builder.main_pipeline.output_len() - 1
            }
            MutationStrategy::Direct => unreachable!(),
        };

        // 1. Fill default and computed columns
        builder.build_fill_columns_in_merge_into(
            tbl.clone(),
            serialize_len,
            self.need_match,
            self.unmatched.clone(),
        )?;

        // 2. Add cluster‘s blocksort if it's a cluster table
        builder.build_compact_and_cluster_sort_in_merge_into(
            table,
            self.need_match,
            serialize_len,
            block_thresholds,
        )?;

View on GitHub (pinned to 288d84d76e)