databendlabs/databend · error

_ => unreachable!()

Error message

_ => unreachable!()

What it means

final_scan dispatches on JoinType to run the appropriate final-scan (outer/anti/semi/mark) pass. The catch-all arm panics, so a JoinType that is not one of the explicitly handled variants reaching final_scan is an internal bug: only right-side/full/mark joins should require a final scan, and any other type here means the caller dispatched final scan for a join that doesn't need one.

Solutions

  1. Add a match arm for the offending JoinType in final_scan, or fix the caller's condition that decides final scan is needed
  2. Enumerate all JoinType variants and confirm which legitimately need final scan
  3. Check recent commits adding join types for missed match arms
  4. Reduce the failing query to its join type and file an issue with EXPLAIN output

Example fix

// before
_ => unreachable!(),
// after
JoinType::LeftSemi | JoinType::LeftAnti => Ok(vec![]), // no right-side final scan needed
other => Err(ErrorCode::Internal(format!("unexpected join type in final_scan: {:?}", other))),
Defensive patterns

Strategy: validation

Validate before calling

// Only invoke final_scan for join types that need a right-side scan
fn needs_final_scan(jt: &JoinType) -> bool {
    matches!(jt, JoinType::Right | JoinType::RightAny | JoinType::RightSingle
        | JoinType::Full | JoinType::RightSemi | JoinType::RightAnti | JoinType::LeftMark)
}
if !needs_final_scan(&join_type) { skip_final_scan(); }

Type guard

fn needs_final_scan(jt: &JoinType) -> bool {
    matches!(jt, JoinType::Right | JoinType::RightAny | JoinType::RightSingle
        | JoinType::Full | JoinType::RightSemi | JoinType::RightAnti | JoinType::LeftMark)
}

Prevention

When it happens

Trigger: final_scan is called with task data for a join whose JoinType is Left, Inner, LeftSemi, LeftAnti, LeftAny, LeftSingle, etc. — i.e. the driver invoked final scan when no right-side scan was required, or a new JoinType was added without updating this match.

Common situations: Adding a new JoinType to the planner without updating hash_join_probe_state's final_scan; bugs in the condition deciding whether a final scan is needed (should_probe_final_scan-like checks).

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

Appendix: source

Thrown at src/query/service/src/pipelines/processors/transforms/hash_join/hash_join_probe_state.rs:437

        let tasks = (0..task_num).collect_vec();
        *self.final_scan_tasks.write() = tasks.into();
        Ok(())
    }

    pub fn final_scan_task(&self) -> Option<usize> {
        let mut tasks = self.final_scan_tasks.write();
        tasks.pop_front()
    }

    pub fn final_scan(&self, task: usize, state: &mut ProbeState) -> Result<Vec<DataBlock>> {
        match &self.hash_join_state.hash_join_desc.join_type {
            JoinType::Right | JoinType::RightAny | JoinType::RightSingle | JoinType::Full => {
                self.right_and_full_outer_scan(task, state)
            }
            JoinType::RightSemi => self.right_semi_outer_scan(task, state),
            JoinType::RightAnti => self.right_anti_outer_scan(task, state),
            JoinType::LeftMark => self.left_mark_scan(task, state),
            _ => unreachable!(),
        }
    }

    pub fn right_and_full_outer_scan(
        &self,
        task: usize,
        probe_state: &mut ProbeState,
    ) -> Result<Vec<DataBlock>> {
        check_interrupt()?;

        // Probe states.
        let max_block_size = probe_state.max_block_size;
        let mutable_indexes = &mut probe_state.mutable_indexes;
        let build_indexes = &mut mutable_indexes.build_indexes;
        let mut projected_probe_fields = vec![];
        for (i, field) in self.probe_schema.fields().iter().enumerate() {
            if self.probe_projections.contains(&i) {
                projected_probe_fields.push(field.clone());

View on GitHub (pinned to 288d84d76e)