databendlabs/databend · error

internal error: entered unreachable code

Error message

internal error: entered unreachable code

What it means

access_system_history (src/query/service/src/interpreters/access/privilege_access.rs:319) matches on the (catalog_name, db_name, stage_name) tuple and only handles two shapes: (Some, Some, None) for the sensitive history database and (None, None, Some) for the sensitive history stage. Any other combination — including all-None or mixed Some/Some/Some — falls into `_ => unreachable!()`, panicking the query node with 'internal error: entered unreachable code'.

Solutions

  1. Identify which statement triggered validation with the unhandled identifier combination (check query log) and rework the statement to reference either a history database table or the history stage, not both/neither
  2. Upgrade to a Databend version where access_system_history returns PermissionDenied for unhandled shapes instead of panicking
  3. As a code fix, replace the `_ => unreachable!()` arm with `Err(ErrorCode::PermissionDenied(...))` or `Ok(())` so unexpected shapes degrade gracefully
  4. File a bug with the exact SQL statement and Databend version

Example fix

// before
_ => unreachable!(),
// after
_ => Err(ErrorCode::PermissionDenied(
    "Permission Denied: unhandled object shape for sensitive system history resource".to_string(),
)),
Defensive patterns

Strategy: validation

Validate before calling

-- only reference history objects in the supported shapes:
--   a table under <catalog>.system_history, or the history stage alone
SELECT * FROM default.system_history.log_tbl;
-- avoid mixed references like a db+stage in one grant

Type guard

let shape = (catalog_name.is_some(), db_name.is_some(), stage_name.is_some());
if !matches!(shape, (true, true, false) | (false, false, true)) {
    // route away from access_system_history
}

Try / catch

// callers (validate_*_access) should pre-check identifier arity
if (catalog.is_some() || db.is_some()) && stage.is_some() {
    return Err(ErrorCode::BadArguments("cannot mix database and stage identifiers".into()));
}

Prevention

When it happens

Trigger: Any privilege validation call that reaches access_system_history with an argument shape outside the two handled patterns: e.g. validating access with both db_name and stage_name set, with catalog only (db_name None), or with all three None. Reached through validate_db_access, validate_table_access, validate_table_index_access, validate_drop_table_index_access, or validate_stage_access.

Common situations: A user queries or mutates a history/log table or stage through a code path that passes an unexpected combination of object identifiers (e.g. a fully-qualified name including both database and stage, or a validation hook invoked with None identifiers); common after upgrades where new object kinds route through the shared validation entry points.

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

Appendix: source

Thrown at src/query/service/src/interpreters/access/privilege_access.rs:319

                        if current_role.name == BUILTIN_ROLE_ACCOUNT_ADMIN {
                            Ok(())
                        } else {
                            Err(ErrorCode::PermissionDenied(format!(
                                "Permission Denied: Operation '{:?}' on stage {sensitive_system_stage} is not allowed",
                                privilege
                            )))
                        }
                    } else {
                        Err(ErrorCode::PermissionDenied(format!(
                            "Permission Denied: Operation '{:?}' on stage {sensitive_system_stage} is not allowed",
                            privilege
                        )))
                    }
                } else {
                    Ok(())
                };
            }
            _ => unreachable!(),
        }

        Ok(())
    }

    async fn get_role_names_and_ownerships(
        &self,
        tenant: &Tenant,
    ) -> Result<(Vec<String>, Vec<SeqV<OwnershipInfo>>)> {
        let roles = self.ctx.get_all_effective_roles().await?;
        let roles_name = roles
            .iter()
            .map(|role| role.name.to_string())
            .collect::<Vec<_>>();

        if roles_name
            .iter()
            .any(|role_name| role_name == BUILTIN_ROLE_ACCOUNT_ADMIN)

View on GitHub (pinned to 288d84d76e)