diff --git a/sqlx-core/src/error.rs b/sqlx-core/src/error.rs index bc20d6062f..7ad3638e11 100644 --- a/sqlx-core/src/error.rs +++ b/sqlx-core/src/error.rs @@ -158,6 +158,15 @@ pub trait DatabaseError: 'static + Send + Sync + StdError { #[doc(hidden)] fn into_error(self: Box) -> Box; + + /// Returns the name of the constraint that triggered the error, if applicable. + /// If the error was caused by a conflict of a unique index, this will be the index name. + /// + /// ### Note + /// Currently only populated by the Postgres driver. + fn constraint(&self) -> Option<&str> { + None + } } impl dyn DatabaseError { diff --git a/sqlx-core/src/postgres/error.rs b/sqlx-core/src/postgres/error.rs index b3343d0a48..4c528b78fb 100644 --- a/sqlx-core/src/postgres/error.rs +++ b/sqlx-core/src/postgres/error.rs @@ -184,4 +184,8 @@ impl DatabaseError for PgDatabaseError { fn into_error(self: Box) -> Box { self } + + fn constraint(&self) -> Option<&str> { + self.constraint() + } } diff --git a/tests/postgres/postgres.rs b/tests/postgres/postgres.rs index 1cbf9e6c91..e8845bb669 100644 --- a/tests/postgres/postgres.rs +++ b/tests/postgres/postgres.rs @@ -80,6 +80,42 @@ async fn it_can_inspect_errors() -> anyhow::Result<()> { assert_eq!(err.code(), "42703"); assert_eq!(err.position(), Some(PgErrorPosition::Original(8))); assert_eq!(err.routine(), Some("errorMissingColumn")); + assert_eq!(err.constraint(), None); + + Ok(()) +} + +#[sqlx_macros::test] +async fn it_can_inspect_constraint_errors() -> anyhow::Result<()> { + let mut conn = new::().await?; + + let res: Result<_, sqlx::Error> = + sqlx::query("INSERT INTO products VALUES (1, 'Product 1', 0);") + .execute(&mut conn) + .await; + let err = res.unwrap_err(); + + // can also do [as_database_error] or use `match ..` + let err = err.into_database_error().unwrap(); + + assert_eq!( + err.message(), + "new row for relation \"products\" violates check constraint \"products_price_check\"" + ); + assert_eq!(err.code().as_deref(), Some("23514")); + + // can also do [downcast_ref] + let err: Box = err.downcast(); + + assert_eq!(err.severity(), PgSeverity::Error); + assert_eq!( + err.message(), + "new row for relation \"products\" violates check constraint \"products_price_check\"" + ); + assert_eq!(err.code(), "23514"); + assert_eq!(err.position(), None); + assert_eq!(err.routine(), Some("ExecConstraints")); + assert_eq!(err.constraint(), Some("products_price_check")); Ok(()) } diff --git a/tests/postgres/setup.sql b/tests/postgres/setup.sql index 1037448750..9818d139ba 100644 --- a/tests/postgres/setup.sql +++ b/tests/postgres/setup.sql @@ -23,3 +23,9 @@ CREATE TYPE float_range AS RANGE subtype = float8, subtype_diff = float8mi ); + +CREATE TABLE products ( + product_no INTEGER, + name TEXT, + price NUMERIC CHECK (price > 0) +);