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
- Add a match arm for the offending JoinType in final_scan, or fix the caller's condition that decides final scan is needed
- Enumerate all JoinType variants and confirm which legitimately need final scan
- Check recent commits adding join types for missed match arms
- 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 adding a JoinType, grep for exhaustive matches on JoinType and update all of them
- Derive the final-scan requirement from the JoinType enum itself rather than ad-hoc caller checks
- Add exhaustiveness tests enumerating every JoinType through the probe pipeline
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
- _ => unreachable!()
- _ => unreachable!()
- HashJoinHashTable::NestedLoop(_) => unreachable!()
- HashJoinHashTable::NestedLoop(_) => unreachable!()
- _ => unreachable!()
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)