Introducing sqlx-error feature (#750)

* feat: Introducing feature "sqlx-error"

Purpose of this feature is to not convert errors given from sqlx into strings to ease further analysis of the error and react to it accordingly. This implementation uses a feature switch and an additional error kind to avoid interfering with existing implementations without this feature enabled.
See discussion https://github.com/SeaQL/sea-orm/discussions/709

* fix: Align feature "sqlx-error" with merged Migration error kind

Due to the merge, an other error kind had been introduced and the DbErr became Eq and Clone, however Eq cannot easily be derived from, so I went back to PartialEq, and since the sqlx error does not implement clone, this was converted into an Arc, to allow cloning of the new kind too.

* fix: Repairing failing jobs

Several jobs had failed as I missed to correct the return values of a few methods in transaction.rs and had a wrong understanding of map_err at that point.

* feat: realigning with latest changes in sea-orm, different approach

Instead of the former approach to introduce a new error kind, now the existing error types get extended, for now only Exec and Query, because these are the most relevant for the requirement context.
Afterwards it might still be possible to add some further detail information.
See discussion https://github.com/SeaQL/sea-orm/discussions/709

* Update src/driver/sqlx_mysql.rs

Integrating fixes done by @Sculas

Co-authored-by: Sculas <contact@sculas.xyz>

* Update src/driver/sqlx_postgres.rs

Integrating fixes done by @Sculas

Co-authored-by: Sculas <contact@sculas.xyz>

* Update src/driver/sqlx_sqlite.rs

Integrating fixes done by @Sculas

Co-authored-by: Sculas <contact@sculas.xyz>

* feat: reworking feature with thiserror

Following the latest suggestions I changed the implementation to utilize `thiserror`.
Now there are more error kinds to be able to see the different kinds directly in src/error.rs
To ensure the behaviour is as expected, I also introduce a further test, which checks for a uniqueness failure.

Co-authored-by: Sculas <contact@sculas.xyz>
This commit is contained in:
mohs8421 2022-08-28 05:13:51 +02:00 committed by GitHub
parent 13b53369e2
commit fe6c40dd75
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
16 changed files with 183 additions and 129 deletions

View File

@ -42,6 +42,7 @@ uuid = { version = "^1", features = ["serde", "v4"], optional = true }
ouroboros = "0.15"
url = "^2.2"
once_cell = "1.8"
thiserror = "^1"
[dev-dependencies]
smol = { version = "^1.2" }

View File

@ -26,10 +26,7 @@ macro_rules! impl_try_from_u64_err {
($newtype: ident) => {
impl sea_orm::TryFromU64 for $newtype {
fn try_from_u64(_n: u64) -> Result<Self, sea_orm::DbErr> {
Err(sea_orm::DbErr::Exec(format!(
"{} cannot be converted from u64",
stringify!($newtype)
)))
Err(sea_orm::ConvertFromU64(stringify!($newtype)))
}
}
};

View File

@ -1,5 +1,5 @@
use std::marker::PhantomData;
use sea_orm::entity::prelude::*;
use std::marker::PhantomData;
#[derive(Clone, Debug, PartialEq, DeriveEntityModel)]
#[sea_orm(table_name = "model")]
@ -31,10 +31,7 @@ impl<T> From<AccountId<T>> for Uuid {
impl<T> sea_orm::TryFromU64 for AccountId<T> {
fn try_from_u64(_n: u64) -> Result<Self, sea_orm::DbErr> {
Err(sea_orm::DbErr::Exec(format!(
"{} cannot be converted from u64",
stringify!(AccountId<T>)
)))
Err(sea_orm::ConvertFromU64(stringify!(AccountId<T>)))
}
}

View File

@ -102,7 +102,7 @@ impl MockDatabaseTrait for MockDatabase {
result: ExecResultHolder::Mock(std::mem::take(&mut self.exec_results[counter])),
})
} else {
Err(DbErr::Exec("`exec_results` buffer is empty.".to_owned()))
Err(DbErr::Query("`exec_results` buffer is empty.".to_owned()))
}
}

View File

@ -238,6 +238,17 @@ impl DatabaseTransaction {
}
}
}
#[cfg(feature = "sqlx-dep")]
fn map_err_ignore_not_found<T: std::fmt::Debug>(
err: Result<Option<T>, sqlx::Error>,
) -> Result<Option<T>, DbErr> {
if let Err(sqlx::Error::RowNotFound) = err {
Ok(None)
} else {
err.map_err(|e| sqlx_error_to_query_err(e))
}
}
}
impl Drop for DatabaseTransaction {
@ -258,13 +269,14 @@ impl ConnectionTrait for DatabaseTransaction {
async fn execute(&self, stmt: Statement) -> Result<ExecResult, DbErr> {
debug_print!("{}", stmt);
let _res = match &mut *self.conn.lock().await {
match &mut *self.conn.lock().await {
#[cfg(feature = "sqlx-mysql")]
InnerConnection::MySql(conn) => {
let query = crate::driver::sqlx_mysql::sqlx_query(&stmt);
crate::metric::metric!(self.metric_callback, &stmt, {
query.execute(conn).await.map(Into::into)
})
.map_err(sqlx_error_to_exec_err)
}
#[cfg(feature = "sqlx-postgres")]
InnerConnection::Postgres(conn) => {
@ -272,6 +284,7 @@ impl ConnectionTrait for DatabaseTransaction {
crate::metric::metric!(self.metric_callback, &stmt, {
query.execute(conn).await.map(Into::into)
})
.map_err(sqlx_error_to_exec_err)
}
#[cfg(feature = "sqlx-sqlite")]
InnerConnection::Sqlite(conn) => {
@ -279,14 +292,13 @@ impl ConnectionTrait for DatabaseTransaction {
crate::metric::metric!(self.metric_callback, &stmt, {
query.execute(conn).await.map(Into::into)
})
.map_err(sqlx_error_to_exec_err)
}
#[cfg(feature = "mock")]
InnerConnection::Mock(conn) => return conn.execute(stmt),
#[allow(unreachable_patterns)]
_ => unreachable!(),
};
#[cfg(feature = "sqlx-dep")]
_res.map_err(sqlx_error_to_exec_err)
}
}
#[instrument(level = "trace")]
@ -294,32 +306,32 @@ impl ConnectionTrait for DatabaseTransaction {
async fn query_one(&self, stmt: Statement) -> Result<Option<QueryResult>, DbErr> {
debug_print!("{}", stmt);
let _res = match &mut *self.conn.lock().await {
match &mut *self.conn.lock().await {
#[cfg(feature = "sqlx-mysql")]
InnerConnection::MySql(conn) => {
let query = crate::driver::sqlx_mysql::sqlx_query(&stmt);
query.fetch_one(conn).await.map(|row| Some(row.into()))
Self::map_err_ignore_not_found(
query.fetch_one(conn).await.map(|row| Some(row.into())),
)
}
#[cfg(feature = "sqlx-postgres")]
InnerConnection::Postgres(conn) => {
let query = crate::driver::sqlx_postgres::sqlx_query(&stmt);
query.fetch_one(conn).await.map(|row| Some(row.into()))
Self::map_err_ignore_not_found(
query.fetch_one(conn).await.map(|row| Some(row.into())),
)
}
#[cfg(feature = "sqlx-sqlite")]
InnerConnection::Sqlite(conn) => {
let query = crate::driver::sqlx_sqlite::sqlx_query(&stmt);
query.fetch_one(conn).await.map(|row| Some(row.into()))
Self::map_err_ignore_not_found(
query.fetch_one(conn).await.map(|row| Some(row.into())),
)
}
#[cfg(feature = "mock")]
InnerConnection::Mock(conn) => return conn.query_one(stmt),
#[allow(unreachable_patterns)]
_ => unreachable!(),
};
#[cfg(feature = "sqlx-dep")]
if let Err(sqlx::Error::RowNotFound) = _res {
Ok(None)
} else {
_res.map_err(sqlx_error_to_query_err)
}
}
@ -328,7 +340,7 @@ impl ConnectionTrait for DatabaseTransaction {
async fn query_all(&self, stmt: Statement) -> Result<Vec<QueryResult>, DbErr> {
debug_print!("{}", stmt);
let _res = match &mut *self.conn.lock().await {
match &mut *self.conn.lock().await {
#[cfg(feature = "sqlx-mysql")]
InnerConnection::MySql(conn) => {
let query = crate::driver::sqlx_mysql::sqlx_query(&stmt);
@ -336,6 +348,7 @@ impl ConnectionTrait for DatabaseTransaction {
.fetch_all(conn)
.await
.map(|rows| rows.into_iter().map(|r| r.into()).collect())
.map_err(sqlx_error_to_query_err)
}
#[cfg(feature = "sqlx-postgres")]
InnerConnection::Postgres(conn) => {
@ -344,6 +357,7 @@ impl ConnectionTrait for DatabaseTransaction {
.fetch_all(conn)
.await
.map(|rows| rows.into_iter().map(|r| r.into()).collect())
.map_err(sqlx_error_to_query_err)
}
#[cfg(feature = "sqlx-sqlite")]
InnerConnection::Sqlite(conn) => {
@ -352,14 +366,13 @@ impl ConnectionTrait for DatabaseTransaction {
.fetch_all(conn)
.await
.map(|rows| rows.into_iter().map(|r| r.into()).collect())
.map_err(sqlx_error_to_query_err)
}
#[cfg(feature = "mock")]
InnerConnection::Mock(conn) => return conn.query_all(stmt),
#[allow(unreachable_patterns)]
_ => unreachable!(),
};
#[cfg(feature = "sqlx-dep")]
_res.map_err(sqlx_error_to_query_err)
}
}
}

View File

@ -2,15 +2,15 @@ use crate::DbErr;
/// Converts an [sqlx::error] execution error to a [DbErr]
pub fn sqlx_error_to_exec_err(err: sqlx::Error) -> DbErr {
DbErr::Exec(err.to_string())
DbErr::ExecSqlX(err)
}
/// Converts an [sqlx::error] query error to a [DbErr]
pub fn sqlx_error_to_query_err(err: sqlx::Error) -> DbErr {
DbErr::Query(err.to_string())
DbErr::QuerySqlX(err)
}
/// Converts an [sqlx::error] connection error to a [DbErr]
pub fn sqlx_error_to_conn_err(err: sqlx::Error) -> DbErr {
DbErr::Conn(err.to_string())
DbErr::ConnSqlX(err)
}

View File

@ -45,7 +45,7 @@ impl SqlxMySqlConnector {
let mut opt = options
.url
.parse::<MySqlConnectOptions>()
.map_err(|e| DbErr::Conn(e.to_string()))?;
.map_err(|e| DbErr::ConnSqlX(e))?;
use sqlx::ConnectOptions;
if !options.sqlx_logging {
opt.disable_statement_logging();
@ -89,9 +89,7 @@ impl SqlxMySqlPoolConnection {
}
})
} else {
Err(DbErr::Exec(
"Failed to acquire connection from pool.".to_owned(),
))
Err(DbErr::ConnFromPool)
}
}
@ -107,14 +105,12 @@ impl SqlxMySqlPoolConnection {
Ok(row) => Ok(Some(row.into())),
Err(err) => match err {
sqlx::Error::RowNotFound => Ok(None),
_ => Err(DbErr::Query(err.to_string())),
_ => Err(sqlx_error_to_query_err(err)),
},
}
})
} else {
Err(DbErr::Query(
"Failed to acquire connection from pool.".to_owned(),
))
Err(DbErr::ConnFromPool)
}
}
@ -132,9 +128,7 @@ impl SqlxMySqlPoolConnection {
}
})
} else {
Err(DbErr::Query(
"Failed to acquire connection from pool.".to_owned(),
))
Err(DbErr::ConnFromPool)
}
}
@ -150,9 +144,7 @@ impl SqlxMySqlPoolConnection {
self.metric_callback.clone(),
)))
} else {
Err(DbErr::Query(
"Failed to acquire connection from pool.".to_owned(),
))
Err(DbErr::ConnFromPool)
}
}
@ -162,9 +154,7 @@ impl SqlxMySqlPoolConnection {
if let Ok(conn) = self.pool.acquire().await {
DatabaseTransaction::new_mysql(conn, self.metric_callback.clone()).await
} else {
Err(DbErr::Query(
"Failed to acquire connection from pool.".to_owned(),
))
Err(DbErr::ConnFromPool)
}
}
@ -185,9 +175,7 @@ impl SqlxMySqlPoolConnection {
.map_err(|e| TransactionError::Connection(e))?;
transaction.run(callback).await
} else {
Err(TransactionError::Connection(DbErr::Query(
"Failed to acquire connection from pool.".to_owned(),
)))
Err(TransactionError::Connection(DbErr::ConnFromPool))
}
}

View File

@ -45,7 +45,7 @@ impl SqlxPostgresConnector {
let mut opt = options
.url
.parse::<PgConnectOptions>()
.map_err(|e| DbErr::Conn(e.to_string()))?;
.map_err(|e| DbErr::ConnSqlX(e))?;
use sqlx::ConnectOptions;
if !options.sqlx_logging {
opt.disable_statement_logging();
@ -89,9 +89,7 @@ impl SqlxPostgresPoolConnection {
}
})
} else {
Err(DbErr::Exec(
"Failed to acquire connection from pool.".to_owned(),
))
Err(DbErr::ConnFromPool)
}
}
@ -107,14 +105,12 @@ impl SqlxPostgresPoolConnection {
Ok(row) => Ok(Some(row.into())),
Err(err) => match err {
sqlx::Error::RowNotFound => Ok(None),
_ => Err(DbErr::Query(err.to_string())),
_ => Err(sqlx_error_to_query_err(err)),
},
}
})
} else {
Err(DbErr::Query(
"Failed to acquire connection from pool.".to_owned(),
))
Err(DbErr::ConnFromPool)
}
}
@ -132,9 +128,7 @@ impl SqlxPostgresPoolConnection {
}
})
} else {
Err(DbErr::Query(
"Failed to acquire connection from pool.".to_owned(),
))
Err(DbErr::ConnFromPool)
}
}
@ -150,9 +144,7 @@ impl SqlxPostgresPoolConnection {
self.metric_callback.clone(),
)))
} else {
Err(DbErr::Query(
"Failed to acquire connection from pool.".to_owned(),
))
Err(DbErr::ConnFromPool)
}
}
@ -162,9 +154,7 @@ impl SqlxPostgresPoolConnection {
if let Ok(conn) = self.pool.acquire().await {
DatabaseTransaction::new_postgres(conn, self.metric_callback.clone()).await
} else {
Err(DbErr::Query(
"Failed to acquire connection from pool.".to_owned(),
))
Err(DbErr::ConnFromPool)
}
}
@ -185,9 +175,7 @@ impl SqlxPostgresPoolConnection {
.map_err(|e| TransactionError::Connection(e))?;
transaction.run(callback).await
} else {
Err(TransactionError::Connection(DbErr::Query(
"Failed to acquire connection from pool.".to_owned(),
)))
Err(TransactionError::Connection(DbErr::ConnFromPool))
}
}

View File

@ -46,7 +46,7 @@ impl SqlxSqliteConnector {
let mut opt = options
.url
.parse::<SqliteConnectOptions>()
.map_err(|e| DbErr::Conn(e.to_string()))?;
.map_err(|e| DbErr::ConnSqlX(e))?;
if options.sqlcipher_key.is_some() {
opt = opt.pragma("key", options.sqlcipher_key.clone().unwrap());
}
@ -96,9 +96,7 @@ impl SqlxSqlitePoolConnection {
}
})
} else {
Err(DbErr::Exec(
"Failed to acquire connection from pool.".to_owned(),
))
Err(DbErr::ConnFromPool)
}
}
@ -114,14 +112,12 @@ impl SqlxSqlitePoolConnection {
Ok(row) => Ok(Some(row.into())),
Err(err) => match err {
sqlx::Error::RowNotFound => Ok(None),
_ => Err(DbErr::Query(err.to_string())),
_ => Err(sqlx_error_to_query_err(err)),
},
}
})
} else {
Err(DbErr::Query(
"Failed to acquire connection from pool.".to_owned(),
))
Err(DbErr::ConnFromPool)
}
}
@ -139,9 +135,7 @@ impl SqlxSqlitePoolConnection {
}
})
} else {
Err(DbErr::Query(
"Failed to acquire connection from pool.".to_owned(),
))
Err(DbErr::ConnFromPool)
}
}
@ -157,9 +151,7 @@ impl SqlxSqlitePoolConnection {
self.metric_callback.clone(),
)))
} else {
Err(DbErr::Query(
"Failed to acquire connection from pool.".to_owned(),
))
Err(DbErr::ConnFromPool)
}
}
@ -169,9 +161,7 @@ impl SqlxSqlitePoolConnection {
if let Ok(conn) = self.pool.acquire().await {
DatabaseTransaction::new_sqlite(conn, self.metric_callback.clone()).await
} else {
Err(DbErr::Query(
"Failed to acquire connection from pool.".to_owned(),
))
Err(DbErr::ConnFromPool)
}
}
@ -192,9 +182,7 @@ impl SqlxSqlitePoolConnection {
.map_err(|e| TransactionError::Connection(e))?;
transaction.run(callback).await
} else {
Err(TransactionError::Connection(DbErr::Query(
"Failed to acquire connection from pool.".to_owned(),
)))
Err(TransactionError::Connection(DbErr::ConnFromPool))
}
}

View File

@ -1,38 +1,64 @@
#[cfg(feature = "sqlx-dep")]
use sqlx::error::Error as SqlxError;
use thiserror::Error;
/// An error from unsuccessful database operations
#[derive(Debug, PartialEq, Eq, Clone)]
#[derive(Error, Debug)]
pub enum DbErr {
/// This error happens, when a pool was not able to create a connection
#[error("Failed to acquire connection from pool.")]
ConnFromPool,
/// Error in case of invalid type conversion attempts
#[error("fail to convert '{0}' into '{1}'")]
CannotConvertInto(String, String),
/// Error in case of invalid type conversion from an u64
#[error("{0} cannot be converted from u64")]
ConvertFromU64(String),
/// After an insert statement it was impossible to retrieve the last_insert_id
#[error("Fail to unpack last_insert_id")]
InsertCouldNotUnpackInsertId,
/// When updating, a model should know it's primary key to check
/// if the record has been correctly updated, otherwise this error will occur
#[error("Fail to get primary key from model")]
UpdateCouldNotGetPrimaryKey,
/// There was a problem with the database connection
#[error("Connection Error: {0}")]
Conn(String),
/// There was a problem with the database connection from sqlx
#[cfg(feature = "sqlx-dep")]
#[error("Connection Error: {0}")]
ConnSqlX(#[source] SqlxError),
/// An operation did not execute successfully
Exec(String),
#[cfg(feature = "sqlx-dep")]
#[error("Execution Error: {0}")]
ExecSqlX(#[source] SqlxError),
/// An error occurred while performing a query, with more details from sqlx
#[cfg(feature = "sqlx-dep")]
#[error("Query Error: {0}")]
QuerySqlX(#[source] SqlxError),
/// An error occurred while performing a query
#[error("Query Error: {0}")]
Query(String),
/// The record was not found in the database
#[error("RecordNotFound Error: {0}")]
RecordNotFound(String),
/// A custom error
#[error("Custom Error: {0}")]
Custom(String),
/// Error occurred while parsing value as target type
#[error("Type Error: {0}")]
Type(String),
/// Error occurred while parsing json value as target type
#[error("Json Error: {0}")]
Json(String),
/// A migration error
#[error("Migration Error: {0}")]
Migration(String),
}
impl std::error::Error for DbErr {}
impl std::fmt::Display for DbErr {
fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
match self {
Self::Conn(s) => write!(f, "Connection Error: {}", s),
Self::Exec(s) => write!(f, "Execution Error: {}", s),
Self::Query(s) => write!(f, "Query Error: {}", s),
Self::RecordNotFound(s) => write!(f, "RecordNotFound Error: {}", s),
Self::Custom(s) => write!(f, "Custom Error: {}", s),
Self::Type(s) => write!(f, "Type Error: {}", s),
Self::Json(s) => write!(f, "Json Error: {}", s),
Self::Migration(s) => write!(f, "Migration Error: {}", s),
}
impl PartialEq for DbErr {
fn eq(&self, other: &Self) -> bool {
self.to_string() == other.to_string()
}
}

View File

@ -130,7 +130,7 @@ where
Some(value_tuple) => FromValueTuple::from_value_tuple(value_tuple),
None => match last_insert_id_opt {
Some(last_insert_id) => last_insert_id,
None => return Err(DbErr::Exec("Fail to unpack last_insert_id".to_owned())),
None => return Err(DbErr::InsertCouldNotUnpackInsertId),
},
};
Ok(InsertResult { last_insert_id })
@ -176,6 +176,8 @@ where
};
match found {
Some(model) => Ok(model),
None => Err(DbErr::Exec("Failed to find inserted item".to_owned())),
None => Err(DbErr::RecordNotFound(
"Failed to find inserted item".to_owned(),
)),
}
}

View File

@ -379,8 +379,9 @@ impl TryGetable for Decimal {
use rust_decimal::prelude::FromPrimitive;
match val {
Some(v) => Decimal::from_f64(v).ok_or_else(|| {
TryGetError::DbErr(DbErr::Query(
"Failed to convert f64 into Decimal".to_owned(),
TryGetError::DbErr(DbErr::CannotConvertInto(
"f64".to_owned(),
"Decimal".to_owned(),
))
}),
None => Err(TryGetError::Null(column)),
@ -708,10 +709,7 @@ macro_rules! try_from_u64_err {
( $type: ty ) => {
impl TryFromU64 for $type {
fn try_from_u64(_: u64) -> Result<Self, DbErr> {
Err(DbErr::Exec(format!(
"{} cannot be converted from u64",
stringify!($type)
)))
Err(DbErr::ConvertFromU64(stringify!($type).to_string()))
}
}
};
@ -722,10 +720,7 @@ macro_rules! try_from_u64_err {
$( $gen_type: TryFromU64, )*
{
fn try_from_u64(_: u64) -> Result<Self, DbErr> {
Err(DbErr::Exec(format!(
"{} cannot be converted from u64",
stringify!(($($gen_type,)*))
)))
Err(DbErr::ConvertFromU64(stringify!($($gen_type,)*).to_string()))
}
}
};
@ -744,11 +739,7 @@ macro_rules! try_from_u64_numeric {
fn try_from_u64(n: u64) -> Result<Self, DbErr> {
use std::convert::TryInto;
n.try_into().map_err(|_| {
DbErr::Exec(format!(
"fail to convert '{}' into '{}'",
n,
stringify!($type)
))
DbErr::CannotConvertInto(n.to_string(), stringify!($type).to_string())
})
}
}
@ -828,9 +819,11 @@ mod tests {
#[test]
fn from_try_get_error() {
// TryGetError::DbErr
let expected = DbErr::Query("expected error message".to_owned());
let try_get_error = TryGetError::DbErr(expected.clone());
assert_eq!(DbErr::from(try_get_error), expected);
let try_get_error = TryGetError::DbErr(DbErr::Query("expected error message".to_owned()));
assert_eq!(
DbErr::from(try_get_error),
DbErr::Query("expected error message".to_owned())
);
// TryGetError::Null
let try_get_error = TryGetError::Null("column".to_owned());

View File

@ -122,7 +122,7 @@ where
Updater::new(query).check_record_exists().exec(db).await?;
let primary_key_value = match model.get_primary_key_value() {
Some(val) => FromValueTuple::from_value_tuple(val),
None => return Err(DbErr::Exec("Fail to get primary key from model".to_owned())),
None => return Err(DbErr::UpdateCouldNotGetPrimaryKey),
};
let found = <A::Entity as EntityTrait>::find_by_id(primary_key_value)
.one(db)
@ -130,7 +130,9 @@ where
// If we cannot select the updated row from db by the cached primary key
match found {
Some(model) => Ok(model),
None => Err(DbErr::Exec("Failed to find inserted item".to_owned())),
None => Err(DbErr::RecordNotFound(
"Failed to find inserted item".to_owned(),
)),
}
}
}

56
tests/crud/error.rs Normal file
View File

@ -0,0 +1,56 @@
pub use super::*;
use rust_decimal_macros::dec;
use sea_orm::DbErr;
#[cfg(any(
feature = "sqlx-mysql",
feature = "sqlx-sqlite",
feature = "sqlx-postgres"
))]
use sqlx::Error;
use uuid::Uuid;
pub async fn test_cake_error_sqlx(db: &DbConn) {
let mud_cake = cake::ActiveModel {
name: Set("Moldy Cake".to_owned()),
price: Set(dec!(10.25)),
gluten_free: Set(false),
serial: Set(Uuid::new_v4()),
bakery_id: Set(None),
..Default::default()
};
let cake = mud_cake.save(db).await.expect("could not insert cake");
// if compiling without sqlx, this assignment will complain,
// but the whole test is useless in that case anyway.
#[allow(unused_variables)]
let error: DbErr = cake
.into_active_model()
.insert(db)
.await
.expect_err("inserting should fail due to duplicate primary key");
#[cfg(any(feature = "sqlx-mysql", feature = "sqlx-sqlite"))]
match error {
DbErr::ExecSqlX(sqlx_error) => match sqlx_error {
Error::Database(e) => {
#[cfg(feature = "sqlx-mysql")]
assert_eq!(e.code().unwrap(), "23000");
#[cfg(feature = "sqlx-sqlite")]
assert_eq!(e.code().unwrap(), "1555");
}
_ => panic!("Unexpected sqlx-error kind"),
},
_ => panic!("Unexpected Error kind"),
}
#[cfg(feature = "sqlx-postgres")]
match error {
DbErr::QuerySqlX(sqlx_error) => match sqlx_error {
Error::Database(e) => {
assert_eq!(e.code().unwrap(), "23505");
}
_ => panic!("Unexpected sqlx-error kind"),
},
_ => panic!("Unexpected Error kind"),
}
}

View File

@ -3,6 +3,7 @@ pub mod create_cake;
pub mod create_lineitem;
pub mod create_order;
pub mod deletes;
pub mod error;
pub mod updates;
pub use create_baker::*;
@ -10,6 +11,7 @@ pub use create_cake::*;
pub use create_lineitem::*;
pub use create_order::*;
pub use deletes::*;
pub use error::*;
pub use updates::*;
pub use super::common::bakery_chain::*;

View File

@ -35,5 +35,6 @@ pub async fn create_entities(db: &DatabaseConnection) {
test_update_deleted_customer(db).await;
test_delete_cake(db).await;
test_cake_error_sqlx(db).await;
test_delete_bakery(db).await;
}