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

  1. Ensure empty blocks are never spilled: filter out blocks with `block.is_empty()` / num_rows == 0 before writing to the spill location.
  2. Upgrade to a Databend version where the spill merger skips 0-row blocks in `get_domain`.
  3. If reproducible, capture the query plan and spilled-file metadata and file an issue with the query that triggered empty-column spill.
  4. 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

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


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)