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
- Report the failing MERGE INTO query and plan to Databend maintainers — this is an internal invariant violation
- Upgrade to the newest Databend release to pick up fixes for Direct-strategy mutation handling
- Trace plan construction to find why a Direct-strategy plan entered the manipulate builder instead of the Direct fast path
- 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
- Route Direct-strategy mutation plans to their dedicated executor path before build_pipeline2
- Add exhaustive-match tests for MutationStrategy in every mutation plan builder
- Fail fast at plan construction if strategy and plan type are incompatible
- Prefer returning ErrorCode over unreachable!() for user-reachable states
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
- internal error: entered unreachable code
- internal error: entered unreachable code
- internal error: entered unreachable code
- internal error: entered unreachable code
- unexpected value type for grouping function
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)