databendlabs/databend · error

internal error: entered unreachable code

Error message

internal error: entered unreachable code

What it means

Inside outer_join_to_anti_join, the null-tested equi-condition check matches on JoinType::Left and JoinType::Right only; any other join type (e.g. Full/Semi/Mark) inside the matched condition hits `unreachable!()`. The enclosing logic is supposed to only run this check on plain outer joins, so this panic means an unexpected join type reached the condition scan.

Solutions

  1. Confirm the rule's matcher only fires for Left/Right outer joins with a NULL-test filter on the non-preserved side.
  2. Inspect the S-expression right before the panic to see the actual join_type.
  3. Make the match defensive: return false for unexpected join types inside the closure instead of panicking.
  4. Re-run the failing query on a recent build to check whether a known optimizer fix already covers it.

Example fix

// before
_ => unreachable!(),
// after
_ => false,
Defensive patterns

Strategy: try-catch

Try / catch

// Catch at query execution layer and fall back to a rewritten query
match run(query) {
    Err(e) if is_internal_planner_error(&e) => run(rewrite_anti_join_manually(query)),
    r => r,
}

Prevention

When it happens

Trigger: A filter-over-outer-join pattern being converted to anti join where the join_type is no longer Left/Right at the point of the equi_conditions check — e.g. after another rule mutated join_type, or the rule's eligibility pre-check missed a join kind.

Common situations: Seen when optimizing queries like SELECT ... FROM a LEFT JOIN b ... WHERE b.x IS NULL after optimizer refactors; surfaces as an internal error panic during planning.

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

Appendix: source

Thrown at src/query/sql/src/planner/optimizer/optimizers/rule/join_rules/push_down_filter_join/outer_join_to_anti_join.rs:61

    let join_expr = s_expr.unary_child();
    let join = join_expr.plan().as_join().unwrap();
    let anti_join_type = match join.join_type {
        JoinType::Left => JoinType::LeftAnti,
        JoinType::Right => JoinType::RightAnti,
        _ => return Ok(None),
    };

    let Some(predicate_index) = filter.predicates.iter().position(|predicate| {
        let Some(null_tested_expr) = null_tested_expr(predicate) else {
            return false;
        };

        join.equi_conditions.iter().any(|condition| {
            !condition.is_null_equal
                && match join.join_type {
                    JoinType::Left => condition.right == *null_tested_expr,
                    JoinType::Right => condition.left == *null_tested_expr,
                    _ => unreachable!(),
                }
        })
    }) else {
        return Ok(None);
    };

    // An anti join outputs only its preserved side, while the original
    // outer-join/filter shape still exposes the other side as NULL. Recreate
    // those symbols so upper operators preserve their schema.
    let null_extended_prop = match join.join_type {
        JoinType::Left => join_expr.right_child().derive_relational_prop()?,
        JoinType::Right => join_expr.left_child().derive_relational_prop()?,
        _ => unreachable!(),
    };
    let metadata = metadata.read();
    let null_items = null_extended_prop
        .output_columns
        .iter()

View on GitHub (pinned to 288d84d76e)