databendlabs/databend · error
internal error: entered unreachable code
Error message
internal error: entered unreachable code
What it means
This is a Rust `unreachable!()` panic inside `get_domain` in sort_spill.rs, which shrinks a spilled/merged block's columns to a 3-row domain for merge sorting. The code declares a 0-length column impossible in the spill-merge path and panics via `unreachable!()` instead of returning an error. It means the internal invariant 'every block entry column in a spilled batch has at least 1 row' was violated.
Solutions
- Ensure empty blocks are never spilled: filter out blocks with `block.is_empty()` / num_rows == 0 before writing to the spill location.
- Upgrade to a Databend version where the spill merger skips 0-row blocks in `get_domain`.
- If reproducible, capture the query plan and spilled-file metadata and file an issue with the query that triggered empty-column spill.
- As a workaround, reduce spill pressure (raise memory limit, reduce sort volume) so the merge path is not fed empty blocks.
Example fix
// before (sort_spill.rs, get_domain)
BlockEntry::Column(col) => match col.len() {
0 => unreachable!(),
1 | 2 => col.clone(),
...
}
// after
BlockEntry::Column(col) => match col.len() {
0 => return Ok(Column::full_default(0, /* col data type */)), // or skip this block entirely
1 | 2 => col.clone(),
...
} Defensive patterns
Strategy: validation
Validate before calling
// caller-side: never spill empty blocks into the sort merge streams
if block.num_rows() == 0 { return Ok(None); } // skip before passing to spill/Merger::new Prevention
- Filter 0-row blocks before spill and merge.
- Pin a Databend version with empty-block guards in sort_spill.
- Monitor for 'unreachable' panics in logs and attach query plans when reporting.
- Raise memory limits in tests so spill paths are exercised deliberately, not accidentally.
When it happens
Trigger: The sort spill merger builds block domains from spilled blocks and encounters a `BlockEntry::Column` whose column length is 0 — e.g. an empty block got spilled/serialized into the merge input streams, or a columnar entry with zero rows survived block filtering before `Merger::new`.
Common situations: Empty partition data being spilled during a large ORDER BY (spilling an empty block after filtering), a bug/edge case in block splitting where a 0-row block enters the merge, or corrupted/round-tripped spill files producing empty columns.
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
- unexpected value type for grouping function
- Unsupported unit
- internal error: entered unreachable code
- internal error: entered unreachable code
- internal error: entered unreachable code
AI-assisted analysis of databendlabs/databend@288d84d76e (2026-09-11).
Data as JSON: /api/errors/b87d34acf5cb8764.
Report an issue: GitHub.
Appendix: source
Thrown at src/query/pipeline/transforms/src/processors/transforms/sorts/sort_spill.rs:1077
pub type MemoryMerger<A> = Merger<A, DataBlockStream>;
pub fn create_memory_merger<A: SortAlgorithm>(
blocks: Vec<DataBlock>,
sort_row_offset: usize,
limit: Option<usize>,
batch_rows: usize,
) -> MemoryMerger<A> {
let streams = blocks
.into_iter()
.map(|data| DataBlockStream::new(data, sort_row_offset))
.collect();
Merger::<A, _>::new(streams, batch_rows, limit)
}
fn get_domain(entry: &BlockEntry) -> Column {
match entry {
BlockEntry::Column(col) => match col.len() {
0 => unreachable!(),
1 | 2 => col.clone(),
n => {
let mut bitmap = MutableBitmap::with_capacity(n);
bitmap.push(true);
bitmap.extend_constant(n - 2, false);
bitmap.push(true);
col.filter(&bitmap.freeze())
}
},
BlockEntry::Const(scalar, data_type, n) => match n {
0 => unreachable!(),
1 => BlockEntry::new_const_column(data_type.clone(), scalar.clone(), 1).to_column(),
_ => BlockEntry::new_const_column(data_type.clone(), scalar.clone(), 2).to_column(),
},
}
}
View on GitHub (pinned to 288d84d76e)