feat: better database errors (#2109)

* feat(core): create error kind enum

* feat(core): add error kind for postgres

* feat(core): add error kind for sqlite

* feat(core): add error kind for mysql

* test(postgres): add error tests

* test(sqlite): add error tests

* test(mysql): add error tests

* fix(tests): fix tests rebasing

* refac(errors): add `ErrorKind::Other` variant
This commit is contained in:
Luiz Carvalho
2023-02-08 18:23:33 -03:00
committed by Austin Bonander
parent 771ab80a62
commit c09532864d
15 changed files with 451 additions and 67 deletions

72
tests/sqlite/error.rs Normal file
View File

@@ -0,0 +1,72 @@
use sqlx::{error::ErrorKind, query, sqlite::Sqlite, Connection, Executor, Transaction};
use sqlx_test::new;
#[sqlx_macros::test]
async fn it_fails_with_unique_violation() -> anyhow::Result<()> {
let mut conn = new::<Sqlite>().await?;
let mut tx = conn.begin().await?;
let res: Result<_, sqlx::Error> = sqlx::query("INSERT INTO tweet VALUES (1, 'Foo', true, 1);")
.execute(&mut *tx)
.await;
let err = res.unwrap_err();
let err = err.into_database_error().unwrap();
assert_eq!(err.kind(), ErrorKind::UniqueViolation);
Ok(())
}
#[sqlx_macros::test]
async fn it_fails_with_foreign_key_violation() -> anyhow::Result<()> {
let mut conn = new::<Sqlite>().await?;
let mut tx = conn.begin().await?;
let res: Result<_, sqlx::Error> =
sqlx::query("INSERT INTO tweet_reply (id, tweet_id, text) VALUES (2, 2, 'Reply!');")
.execute(&mut *tx)
.await;
let err = res.unwrap_err();
let err = err.into_database_error().unwrap();
assert_eq!(err.kind(), ErrorKind::ForeignKeyViolation);
Ok(())
}
#[sqlx_macros::test]
async fn it_fails_with_not_null_violation() -> anyhow::Result<()> {
let mut conn = new::<Sqlite>().await?;
let mut tx = conn.begin().await?;
let res: Result<_, sqlx::Error> = sqlx::query("INSERT INTO tweet (text) VALUES (null);")
.execute(&mut *tx)
.await;
let err = res.unwrap_err();
let err = err.into_database_error().unwrap();
assert_eq!(err.kind(), ErrorKind::NotNullViolation);
Ok(())
}
#[sqlx_macros::test]
async fn it_fails_with_check_violation() -> anyhow::Result<()> {
let mut conn = new::<Sqlite>().await?;
let mut tx = conn.begin().await?;
let res: Result<_, sqlx::Error> =
sqlx::query("INSERT INTO products VALUES (1, 'Product 1', 0);")
.execute(&mut *tx)
.await;
let err = res.unwrap_err();
let err = err.into_database_error().unwrap();
assert_eq!(err.kind(), ErrorKind::CheckViolation);
Ok(())
}

View File

@@ -8,6 +8,16 @@ CREATE TABLE tweet (
INSERT INTO tweet(id, text, owner_id)
VALUES (1, '#sqlx is pretty cool!', 1);
--
CREATE TABLE tweet_reply (
id BIGINT NOT NULL PRIMARY KEY,
tweet_id BIGINT NOT NULL,
text TEXT NOT NULL,
owner_id BIGINT,
CONSTRAINT tweet_id_fk FOREIGN KEY (tweet_id) REFERENCES tweet(id)
);
INSERT INTO tweet_reply(id, tweet_id, text, owner_id)
VALUES (1, 1, 'Yeah! #sqlx is indeed pretty cool!', 1);
--
CREATE TABLE accounts (
id INTEGER NOT NULL PRIMARY KEY,
name TEXT NOT NULL,
@@ -18,3 +28,10 @@ VALUES (1, 'Herp Derpinson', 1);
CREATE VIEW accounts_view as
SELECT *
FROM accounts;
--
CREATE TABLE products (
product_no INTEGER,
name TEXT,
price NUMERIC,
CONSTRAINT price_greater_than_zero CHECK (price > 0)
);

Binary file not shown.