Added enum for SQLx Error and basic functions for it (#1707)
* Added enum for SQL Error and basic functions for it * fmt * Restructured code to allow multiple database system simultaneously * updated code error and moved Eq to derive by compiler * added ForeignKeyViolation implementation * fmt * added type param * changed type param and used unwrap_or_default instead of unwrap * fmt * refactor code and update documentation * updated some error code and fixed formatting * create SQL Err test for UniqueConstraintViolation * fmt * updated error codes for mysql errors * added test for ForeignKeyConstraintViolation and removed unused imports/codes * fmt * updated doc and error message * added static str in SqlErr * changed to add error message into SqlErr as String * updated test file to check database type through connection * fmt * updated test for SqlErr to match the error type only * Removed comment and fixed grammar mistake * fmt * Update src/error.rs * Refactoring --------- Co-authored-by: Chris Tsang <chris.2y3@outlook.com> Co-authored-by: Billy Chan <ccw.billy.123@gmail.com>
This commit is contained in:
parent
c477f67190
commit
b37e7655f8
90
src/error.rs
90
src/error.rs
@ -139,3 +139,93 @@ where
|
|||||||
{
|
{
|
||||||
DbErr::Json(s.to_string())
|
DbErr::Json(s.to_string())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// An error from unsuccessful SQL query
|
||||||
|
#[derive(Error, Debug, Clone, PartialEq, Eq)]
|
||||||
|
#[non_exhaustive]
|
||||||
|
pub enum SqlErr {
|
||||||
|
/// Error for duplicate record in unique field or primary key field
|
||||||
|
#[error("Unique Constraint Violated: {0}")]
|
||||||
|
UniqueConstraintViolation(String),
|
||||||
|
/// Error for Foreign key constraint
|
||||||
|
#[error("Foreign Key Constraint Violated: {0}")]
|
||||||
|
ForeignKeyConstraintViolation(String),
|
||||||
|
}
|
||||||
|
|
||||||
|
#[allow(dead_code)]
|
||||||
|
impl DbErr {
|
||||||
|
/// Convert generic DbErr by sqlx to SqlErr, return none if the error is not any type of SqlErr
|
||||||
|
pub fn sql_err(&self) -> Option<SqlErr> {
|
||||||
|
#[cfg(any(
|
||||||
|
feature = "sqlx-mysql",
|
||||||
|
feature = "sqlx-postgres",
|
||||||
|
feature = "sqlx-sqlite"
|
||||||
|
))]
|
||||||
|
{
|
||||||
|
use std::ops::Deref;
|
||||||
|
if let DbErr::Exec(RuntimeErr::SqlxError(sqlx::Error::Database(e)))
|
||||||
|
| DbErr::Query(RuntimeErr::SqlxError(sqlx::Error::Database(e))) = self
|
||||||
|
{
|
||||||
|
let error_code = e.code().unwrap_or_default();
|
||||||
|
let _error_code_expanded = error_code.deref();
|
||||||
|
#[cfg(feature = "sqlx-mysql")]
|
||||||
|
if e.try_downcast_ref::<sqlx::mysql::MySqlDatabaseError>()
|
||||||
|
.is_some()
|
||||||
|
{
|
||||||
|
let error_number = e
|
||||||
|
.try_downcast_ref::<sqlx::mysql::MySqlDatabaseError>()?
|
||||||
|
.number();
|
||||||
|
match error_number {
|
||||||
|
// 1022 Can't write; duplicate key in table '%s'
|
||||||
|
// 1062 Duplicate entry '%s' for key %d
|
||||||
|
// 1169 Can't write, because of unique constraint, to table '%s'
|
||||||
|
// 1586 Duplicate entry '%s' for key '%s'
|
||||||
|
1022 | 1062 | 1169 | 1586 => {
|
||||||
|
return Some(SqlErr::UniqueConstraintViolation(e.message().into()))
|
||||||
|
}
|
||||||
|
// 1216 Cannot add or update a child row: a foreign key constraint fails
|
||||||
|
// 1217 Cannot delete or update a parent row: a foreign key constraint fails
|
||||||
|
// 1451 Cannot delete or update a parent row: a foreign key constraint fails (%s)
|
||||||
|
// 1452 Cannot add or update a child row: a foreign key constraint fails (%s)
|
||||||
|
// 1557 Upholding foreign key constraints for table '%s', entry '%s', key %d would lead to a duplicate entry
|
||||||
|
// 1761 Foreign key constraint for table '%s', record '%s' would lead to a duplicate entry in table '%s', key '%s'
|
||||||
|
// 1762 Foreign key constraint for table '%s', record '%s' would lead to a duplicate entry in a child table
|
||||||
|
1216 | 1217 | 1451 | 1452 | 1557 | 1761 | 1762 => {
|
||||||
|
return Some(SqlErr::ForeignKeyConstraintViolation(e.message().into()))
|
||||||
|
}
|
||||||
|
_ => return None,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
#[cfg(feature = "sqlx-postgres")]
|
||||||
|
if e.try_downcast_ref::<sqlx::postgres::PgDatabaseError>()
|
||||||
|
.is_some()
|
||||||
|
{
|
||||||
|
match _error_code_expanded {
|
||||||
|
"23505" => {
|
||||||
|
return Some(SqlErr::UniqueConstraintViolation(e.message().into()))
|
||||||
|
}
|
||||||
|
"23503" => {
|
||||||
|
return Some(SqlErr::ForeignKeyConstraintViolation(e.message().into()))
|
||||||
|
}
|
||||||
|
_ => return None,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
#[cfg(feature = "sqlx-sqlite")]
|
||||||
|
if e.try_downcast_ref::<sqlx::sqlite::SqliteError>().is_some() {
|
||||||
|
match _error_code_expanded {
|
||||||
|
// error code 1555 refers to the primary key's unique constraint violation
|
||||||
|
// error code 2067 refers to the UNIQUE unique constraint violation
|
||||||
|
"1555" | "2067" => {
|
||||||
|
return Some(SqlErr::UniqueConstraintViolation(e.message().into()))
|
||||||
|
}
|
||||||
|
"787" => {
|
||||||
|
return Some(SqlErr::ForeignKeyConstraintViolation(e.message().into()))
|
||||||
|
}
|
||||||
|
_ => return None,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
None
|
||||||
|
}
|
||||||
|
}
|
||||||
|
67
tests/sql_err_tests.rs
Normal file
67
tests/sql_err_tests.rs
Normal file
@ -0,0 +1,67 @@
|
|||||||
|
pub mod common;
|
||||||
|
pub use common::{bakery_chain::*, setup::*, TestContext};
|
||||||
|
use rust_decimal_macros::dec;
|
||||||
|
pub use sea_orm::{
|
||||||
|
entity::*, error::DbErr, error::SqlErr, tests_cfg, DatabaseConnection, DbBackend, EntityName,
|
||||||
|
ExecResult,
|
||||||
|
};
|
||||||
|
use uuid::Uuid;
|
||||||
|
|
||||||
|
#[sea_orm_macros::test]
|
||||||
|
#[cfg(any(
|
||||||
|
feature = "sqlx-mysql",
|
||||||
|
feature = "sqlx-sqlite",
|
||||||
|
feature = "sqlx-postgres"
|
||||||
|
))]
|
||||||
|
async fn main() {
|
||||||
|
let ctx = TestContext::new("bakery_chain_sql_err_tests").await;
|
||||||
|
create_tables(&ctx.db).await.unwrap();
|
||||||
|
test_error(&ctx.db).await;
|
||||||
|
ctx.delete().await;
|
||||||
|
}
|
||||||
|
|
||||||
|
pub async fn test_error(db: &DatabaseConnection) {
|
||||||
|
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");
|
||||||
|
|
||||||
|
assert!(matches!(
|
||||||
|
error.sql_err(),
|
||||||
|
Some(SqlErr::UniqueConstraintViolation(_))
|
||||||
|
));
|
||||||
|
|
||||||
|
let fk_cake = cake::ActiveModel {
|
||||||
|
name: Set("fk error Cake".to_owned()),
|
||||||
|
price: Set(dec!(10.25)),
|
||||||
|
gluten_free: Set(false),
|
||||||
|
serial: Set(Uuid::new_v4()),
|
||||||
|
bakery_id: Set(Some(1000)),
|
||||||
|
..Default::default()
|
||||||
|
};
|
||||||
|
|
||||||
|
let fk_error = fk_cake
|
||||||
|
.insert(db)
|
||||||
|
.await
|
||||||
|
.expect_err("create foreign key should fail with non-primary key");
|
||||||
|
|
||||||
|
assert!(matches!(
|
||||||
|
fk_error.sql_err(),
|
||||||
|
Some(SqlErr::ForeignKeyConstraintViolation(_))
|
||||||
|
));
|
||||||
|
}
|
Loading…
x
Reference in New Issue
Block a user