transact-rs/sqlx · error

downcast to wrong DatabaseError type; original error: {e}

Error message

downcast to wrong DatabaseError type; original error: {e}

What it means

DatabaseError::downcast() (boxed variant, sqlx-core/src/error.rs:299) panics when `Box<dyn DatabaseError>` cannot be converted into the requested concrete error type E. It is the owning counterpart of downcast_ref; a failed downcast means the error came from a different database backend than assumed. The panic message includes the original error for diagnosis.

Source

Thrown at sqlx-core/src/error.rs:299

    /// Panics if the database error type is not `E`. This is a deliberate contrast from
    /// `Error::downcast_ref` which returns `Option<&E>`. In normal usage, you should know the
    /// specific error type. In other cases, use `try_downcast_ref`.
    pub fn downcast_ref<E: DatabaseError>(&self) -> &E {
        self.try_downcast_ref().unwrap_or_else(|| {
            panic!("downcast to wrong DatabaseError type; original error: {self}")
        })
    }

    /// Downcast this generic database error to a specific database error type.
    ///
    /// # Panics
    ///
    /// Panics if the database error type is not `E`. This is a deliberate contrast from
    /// `Error::downcast` which returns `Option<E>`. In normal usage, you should know the
    /// specific error type. In other cases, use `try_downcast`.
    pub fn downcast<E: DatabaseError>(self: Box<Self>) -> Box<E> {
        self.try_downcast()
            .unwrap_or_else(|e| panic!("downcast to wrong DatabaseError type; original error: {e}"))
    }

    /// Downcast a reference to this generic database error to a specific
    /// database error type.
    #[inline]
    pub fn try_downcast_ref<E: DatabaseError>(&self) -> Option<&E> {
        self.as_error().downcast_ref()
    }

    /// Downcast this generic database error to a specific database error type.
    #[inline]
    pub fn try_downcast<E: DatabaseError>(self: Box<Self>) -> Result<Box<E>, Box<Self>> {
        if self.as_error().is::<E>() {
            Ok(self.into_error().downcast().unwrap())
        } else {
            Err(self)
        }
    }

View on GitHub (pinned to 03af8bcc57)

Solutions

  1. Switch to `try_downcast::<E>()` which returns Option/Result and handle the mismatch gracefully.
  2. Track the backend alongside stored errors (enum wrapper) so the correct concrete type is requested.
  3. Restructure to keep backend-specific error handling adjacent to the backend-specific pool that produced the error.

Example fix

// before
let mysql_err = boxed_err.downcast::<MySqlDatabaseError>();

// after
match boxed_err.try_downcast::<MySqlDatabaseError>() {
    Some(e) => handle_mysql(e),
    None => log::warn!("non-MySQL db error: {boxed_err}"),
}
Defensive patterns

Strategy: type-guard

Type guard

fn try_owned_downcast(db_err: Box<dyn sqlx::error::DatabaseError>) -> Option<Box<sqlx::mysql::MySqlDatabaseError>> {
    db_err.try_downcast::<sqlx::mysql::MySqlDatabaseError>()
}

Try / catch

// Use the fallible variant:
let Ok(mysql_err) = boxed_err.try_downcast::<MySqlDatabaseError>() else {
    return handle_other(boxed_err);
};

Prevention

When it happens

Trigger: `err.downcast::<MySqlDatabaseError>()` on a boxed error originating from Postgres/SQLite, typically inside a shared helper that takes `Box<dyn DatabaseError>` or stores errors from mixed pools, then unwraps to one specific backend type.

Common situations: Middleware or retry layers that persist and later re-downcast database errors assuming a fixed backend; test suites where a different database feature is enabled than in production code paths; migrating an app from one database to another without updating downcast targets.

Related errors


AI-assisted analysis of transact-rs/sqlx@03af8bcc57 (2026-09-03). Data as JSON: /api/errors/cea0702da9fd6516. Report an issue: GitHub.