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

View file

@ -271,6 +271,7 @@ jobs:
--features any,mysql,macros,_unstable-all-types,runtime-${{ matrix.runtime }},tls-${{ matrix.tls }}
env:
DATABASE_URL: mysql://root:password@localhost:3306/sqlx?ssl-mode=disabled
RUSTFLAGS: --cfg mysql_${{ matrix.mysql }}
# MySQL 5.7 supports TLS but not TLSv1.3 as required by RusTLS.
- uses: actions-rs/cargo@v1
@ -282,6 +283,7 @@ jobs:
--features any,mysql,macros,_unstable-all-types,runtime-${{ matrix.runtime }},tls-${{ matrix.tls }}
env:
DATABASE_URL: mysql://root:password@localhost:3306/sqlx
RUSTFLAGS: --cfg mysql_${{ matrix.mysql }}
mariadb:
name: MariaDB
@ -322,3 +324,4 @@ jobs:
--features any,mysql,macros,_unstable-all-types,runtime-${{ matrix.runtime }},tls-${{ matrix.tls }}
env:
DATABASE_URL: mysql://root:password@localhost:3306/sqlx
RUSTFLAGS: --cfg mariadb_${{ matrix.mariadb }}

View file

@ -223,6 +223,11 @@ name = "sqlite-derives"
path = "tests/sqlite/derives.rs"
required-features = ["sqlite", "macros"]
[[test]]
name = "sqlite-error"
path = "tests/sqlite/error.rs"
required-features = ["sqlite"]
[[test]]
name = "sqlite-sqlcipher"
path = "tests/sqlite/sqlcipher.rs"
@ -262,6 +267,11 @@ name = "mysql-macros"
path = "tests/mysql/macros.rs"
required-features = ["mysql", "macros"]
[[test]]
name = "mysql-error"
path = "tests/mysql/error.rs"
required-features = ["mysql"]
[[test]]
name = "mysql-test-attr"
path = "tests/mysql/test-attr.rs"
@ -301,6 +311,11 @@ name = "postgres-derives"
path = "tests/postgres/derives.rs"
required-features = ["postgres", "macros"]
[[test]]
name = "postgres-error"
path = "tests/postgres/error.rs"
required-features = ["postgres"]
[[test]]
name = "postgres-test-attr"
path = "tests/postgres/test-attr.rs"

View file

@ -160,6 +160,25 @@ pub fn mismatched_types<DB: Database, T: Type<DB>>(ty: &DB::TypeInfo) -> BoxDynE
.into()
}
/// The error kind.
///
/// This enum is to be used to identify frequent errors that can be handled by the program.
/// Although it currently only supports constraint violations, the type may grow in the future.
#[derive(Debug, PartialEq, Eq)]
#[non_exhaustive]
pub enum ErrorKind {
/// Unique/primary key constraint violation.
UniqueViolation,
/// Foreign key constraint violation.
ForeignKeyViolation,
/// Not-null constraint violation.
NotNullViolation,
/// Check constraint violation.
CheckViolation,
/// An unmapped error.
Other,
}
/// An error that was returned from the database.
pub trait DatabaseError: 'static + Send + Sync + StdError {
/// The primary, human-readable error message.
@ -192,6 +211,27 @@ pub trait DatabaseError: 'static + Send + Sync + StdError {
fn constraint(&self) -> Option<&str> {
None
}
/// Returns the kind of the error, if supported.
///
/// ### Note
/// Not all back-ends behave the same when reporting the error code.
fn kind(&self) -> ErrorKind;
/// Returns whether the error kind is a violation of a unique/primary key constraint.
fn is_unique_violation(&self) -> bool {
matches!(self.kind(), ErrorKind::UniqueViolation)
}
/// Returns whether the error kind is a violation of a foreign key.
fn is_foreign_key_violation(&self) -> bool {
matches!(self.kind(), ErrorKind::ForeignKeyViolation)
}
/// Returns whether the error kind is a violation of a check.
fn is_check_violation(&self) -> bool {
matches!(self.kind(), ErrorKind::CheckViolation)
}
}
impl dyn DatabaseError {

View file

@ -78,4 +78,80 @@ impl DatabaseError for MySqlDatabaseError {
fn into_error(self: Box<Self>) -> Box<dyn StdError + Send + Sync + 'static> {
self
}
fn kind(&self) -> ErrorKind {
match self.number() {
error_codes::ER_DUP_KEY
| error_codes::ER_DUP_ENTRY
| error_codes::ER_DUP_UNIQUE
| error_codes::ER_DUP_ENTRY_WITH_KEY_NAME
| error_codes::ER_DUP_UNKNOWN_IN_INDEX => ErrorKind::UniqueViolation,
error_codes::ER_NO_REFERENCED_ROW
| error_codes::ER_NO_REFERENCED_ROW_2
| error_codes::ER_ROW_IS_REFERENCED
| error_codes::ER_ROW_IS_REFERENCED_2
| error_codes::ER_FK_COLUMN_NOT_NULL
| error_codes::ER_FK_CANNOT_DELETE_PARENT => ErrorKind::ForeignKeyViolation,
error_codes::ER_BAD_NULL_ERROR | error_codes::ER_NO_DEFAULT_FOR_FIELD => {
ErrorKind::NotNullViolation
}
error_codes::ER_CHECK_CONSTRAINT_VIOLATED => ErrorKind::CheckViolation,
_ => ErrorKind::Other,
}
}
}
/// The MySQL server uses SQLSTATEs as a generic error category,
/// and returns a `error_code` instead within the error packet.
///
/// For reference: <https://dev.mysql.com/doc/mysql-errors/8.0/en/server-error-reference.html>.
pub(crate) mod error_codes {
/// Caused when a DDL operation creates duplicated keys.
pub const ER_DUP_KEY: u16 = 1022;
/// Caused when a DML operation tries create a duplicated entry for a key,
/// be it a unique or primary one.
pub const ER_DUP_ENTRY: u16 = 1062;
/// Similar to `ER_DUP_ENTRY`, but only present in NDB clusters.
///
/// See: <https://github.com/mysql/mysql-server/blob/fbdaa4def30d269bc4de5b85de61de34b11c0afc/mysql-test/suite/stress/include/ddl7.inc#L68>.
pub const ER_DUP_UNIQUE: u16 = 1169;
/// Similar to `ER_DUP_ENTRY`, but with a formatted string message.
///
/// See: <https://bugs.mysql.com/bug.php?id=46976>.
pub const ER_DUP_ENTRY_WITH_KEY_NAME: u16 = 1586;
/// Caused when a DDL operation to add a unique index fails,
/// because duplicate items were created by concurrent DML operations.
/// When this happens, the key is unknown, so the server can't use `ER_DUP_KEY`.
///
/// For example: an `INSERT` operation creates duplicate `name` fields when `ALTER`ing a table and making `name` unique.
pub const ER_DUP_UNKNOWN_IN_INDEX: u16 = 1859;
/// Caused when inserting an entry with a column with a value that does not reference a foreign row.
pub const ER_NO_REFERENCED_ROW: u16 = 1216;
/// Caused when deleting a row that is referenced in other tables.
pub const ER_ROW_IS_REFERENCED: u16 = 1217;
/// Caused when deleting a row that is referenced in other tables.
/// This differs from `ER_ROW_IS_REFERENCED` in that the error message contains the affected constraint.
pub const ER_ROW_IS_REFERENCED_2: u16 = 1451;
/// Caused when inserting an entry with a column with a value that does not reference a foreign row.
/// This differs from `ER_NO_REFERENCED_ROW` in that the error message contains the affected constraint.
pub const ER_NO_REFERENCED_ROW_2: u16 = 1452;
/// Caused when creating a FK with `ON DELETE SET NULL` or `ON UPDATE SET NULL` to a column that is `NOT NULL`, or vice-versa.
pub const ER_FK_COLUMN_NOT_NULL: u16 = 1830;
/// Removed in 5.7.3.
pub const ER_FK_CANNOT_DELETE_PARENT: u16 = 1834;
/// Caused when inserting a NULL value to a column marked as NOT NULL.
pub const ER_BAD_NULL_ERROR: u16 = 1048;
/// Caused when inserting a DEFAULT value to a column marked as NOT NULL, which also doesn't have a default value set.
pub const ER_NO_DEFAULT_FOR_FIELD: u16 = 1364;
/// Caused when a check constraint is violated.
///
/// Only available after 8.0.16.
pub const ER_CHECK_CONSTRAINT_VIOLATED: u16 = 3819;
}

View file

@ -203,4 +203,26 @@ impl DatabaseError for PgDatabaseError {
fn constraint(&self) -> Option<&str> {
self.constraint()
}
fn kind(&self) -> ErrorKind {
match self.code() {
error_codes::UNIQUE_VIOLATION => ErrorKind::UniqueViolation,
error_codes::FOREIGN_KEY_VIOLATION => ErrorKind::ForeignKeyViolation,
error_codes::NOT_NULL_VIOLATION => ErrorKind::NotNullViolation,
error_codes::CHECK_VIOLATION => ErrorKind::CheckViolation,
_ => ErrorKind::Other,
}
}
}
/// For reference: <https://www.postgresql.org/docs/current/errcodes-appendix.html>
pub(crate) mod error_codes {
/// Caused when a unique or primary key is violated.
pub const UNIQUE_VIOLATION: &str = "23505";
/// Caused when a foreign key is violated.
pub const FOREIGN_KEY_VIOLATION: &str = "23503";
/// Caused when a column marked as NOT NULL received a null value.
pub const NOT_NULL_VIOLATION: &str = "23502";
/// Caused when a check constraint is violated.
pub const CHECK_VIOLATION: &str = "23514";
}

View file

@ -4,7 +4,11 @@ use std::fmt::{self, Display, Formatter};
use std::os::raw::c_int;
use std::{borrow::Cow, str::from_utf8_unchecked};
use libsqlite3_sys::{sqlite3, sqlite3_errmsg, sqlite3_extended_errcode};
use libsqlite3_sys::{
sqlite3, sqlite3_errmsg, sqlite3_extended_errcode, SQLITE_CONSTRAINT_CHECK,
SQLITE_CONSTRAINT_FOREIGNKEY, SQLITE_CONSTRAINT_NOTNULL, SQLITE_CONSTRAINT_PRIMARYKEY,
SQLITE_CONSTRAINT_UNIQUE,
};
pub(crate) use sqlx_core::error::*;
@ -82,4 +86,14 @@ impl DatabaseError for SqliteError {
fn into_error(self: Box<Self>) -> Box<dyn StdError + Send + Sync + 'static> {
self
}
fn kind(&self) -> ErrorKind {
match self.code {
SQLITE_CONSTRAINT_UNIQUE | SQLITE_CONSTRAINT_PRIMARYKEY => ErrorKind::UniqueViolation,
SQLITE_CONSTRAINT_FOREIGNKEY => ErrorKind::ForeignKeyViolation,
SQLITE_CONSTRAINT_NOTNULL => ErrorKind::NotNullViolation,
SQLITE_CONSTRAINT_CHECK => ErrorKind::CheckViolation,
_ => ErrorKind::Other,
}
}
}

View file

@ -173,44 +173,3 @@ services:
- "./postgres/setup.sql:/docker-entrypoint-initdb.d/setup.sql"
command: >
-c ssl=on -c ssl_cert_file=/var/lib/postgresql/server.crt -c ssl_key_file=/var/lib/postgresql/server.key
#
# Microsoft SQL Server (MSSQL)
# https://hub.docker.com/_/microsoft-mssql-server
#
mssql_2019:
build:
context: .
dockerfile: mssql/Dockerfile
args:
VERSION: 2019-latest
ports:
- 1433
environment:
ACCEPT_EULA: "Y"
SA_PASSWORD: Password123!
mssql_2017:
build:
context: .
dockerfile: mssql/mssql-2017.dockerfile
args:
VERSION: 2017-latest
ports:
- 1433
environment:
ACCEPT_EULA: "Y"
SA_PASSWORD: Password123!
#
# IBM Db2
# https://hub.docker.com/r/ibmcom/db2
#
db2_11:
image: ibmcom/db2:11.5.1.0-CN1
privileged: true
environment:
LICENSE: accept
DB2INST1_PASSWORD: password
DBNAME: sqlx

77
tests/mysql/error.rs Normal file
View file

@ -0,0 +1,77 @@
use sqlx::{error::ErrorKind, mysql::MySql, Connection};
use sqlx_test::new;
#[sqlx_macros::test]
async fn it_fails_with_unique_violation() -> anyhow::Result<()> {
let mut conn = new::<MySql>().await?;
let mut tx = conn.begin().await?;
sqlx::query("INSERT INTO tweet(id, text, owner_id) VALUES (1, 'Foo', 1)")
.execute(&mut *tx)
.await?;
let res: Result<_, sqlx::Error> = sqlx::query("INSERT INTO tweet VALUES (1, NOW(), 'Foo', 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::<MySql>().await?;
let mut tx = conn.begin().await?;
let res: Result<_, sqlx::Error> =
sqlx::query("INSERT INTO tweet_reply (tweet_id, text) VALUES (1, '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::<MySql>().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(())
}
#[cfg(mysql_8)]
#[sqlx_macros::test]
async fn it_fails_with_check_violation() -> anyhow::Result<()> {
let mut conn = new::<MySql>().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

@ -6,3 +6,19 @@ CREATE TABLE tweet
text TEXT NOT NULL,
owner_id BIGINT
);
CREATE TABLE tweet_reply
(
id BIGINT PRIMARY KEY AUTO_INCREMENT,
tweet_id BIGINT NOT NULL,
created_at TIMESTAMP NOT NULL DEFAULT NOW(),
text TEXT NOT NULL,
owner_id BIGINT,
CONSTRAINT tweet_id_fk FOREIGN KEY (tweet_id) REFERENCES tweet(id)
);
CREATE TABLE products (
product_no INTEGER,
name TEXT,
price NUMERIC CHECK (price > 0)
);

76
tests/postgres/error.rs Normal file
View file

@ -0,0 +1,76 @@
use sqlx::{error::ErrorKind, postgres::Postgres, Connection};
use sqlx_test::new;
#[sqlx_macros::test]
async fn it_fails_with_unique_violation() -> anyhow::Result<()> {
let mut conn = new::<Postgres>().await?;
let mut tx = conn.begin().await?;
sqlx::query("INSERT INTO tweet(id, text, owner_id) VALUES (1, 'Foo', 1);")
.execute(&mut *tx)
.await?;
let res: Result<_, sqlx::Error> = sqlx::query("INSERT INTO tweet VALUES (1, NOW(), 'Foo', 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::<Postgres>().await?;
let mut tx = conn.begin().await?;
let res: Result<_, sqlx::Error> =
sqlx::query("INSERT INTO tweet_reply (tweet_id, text) VALUES (1, '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::<Postgres>().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::<Postgres>().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

@ -21,6 +21,15 @@ CREATE TABLE tweet
owner_id BIGINT
);
CREATE TABLE tweet_reply
(
id BIGSERIAL PRIMARY KEY,
tweet_id BIGINT NOT NULL REFERENCES tweet(id),
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
text TEXT NOT NULL,
owner_id BIGINT
);
CREATE TYPE float_range AS RANGE
(
subtype = float8,

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.

View file

@ -127,10 +127,10 @@ for path in glob(os.path.join(os.path.dirname(__file__), "target/**/*.gc*"), rec
# check
#
for runtime in ["async-std", "tokio", "actix"]:
for tls in ["native-tls", "rustls"]:
for runtime in ["async-std", "tokio"]:
for tls in ["native-tls", "rustls", "none"]:
run(
f"cargo c --no-default-features --features all-databases,all-types,offline,macros,runtime-{runtime}-{tls}",
f"cargo c --no-default-features --features all-databases,_unstable-all-types,macros,runtime-{runtime},tls-{tls}",
comment="check with async-std",
tag=f"check_{runtime}_{tls}"
)
@ -139,10 +139,10 @@ for runtime in ["async-std", "tokio", "actix"]:
# unit test
#
for runtime in ["async-std", "tokio", "actix"]:
for tls in ["native-tls", "rustls"]:
for runtime in ["async-std", "tokio"]:
for tls in ["native-tls", "rustls", "none"]:
run(
f"cargo test --no-default-features --manifest-path sqlx-core/Cargo.toml --features all-databases,all-types,runtime-{runtime}-{tls}",
f"cargo test --no-default-features --manifest-path sqlx-core/Cargo.toml --features json,offline,migrate,_rt-{runtime},_tls-{tls}",
comment="unit test core",
tag=f"unit_{runtime}_{tls}"
)
@ -151,15 +151,15 @@ for runtime in ["async-std", "tokio", "actix"]:
# integration tests
#
for runtime in ["async-std", "tokio", "actix"]:
for tls in ["native-tls", "rustls"]:
for runtime in ["async-std", "tokio"]:
for tls in ["native-tls", "rustls", "none"]:
#
# sqlite
#
run(
f"cargo test --no-default-features --features macros,offline,any,all-types,sqlite,runtime-{runtime}-{tls}",
f"cargo test --no-default-features --features macros,any,_unstable-all-types,sqlite,runtime-{runtime},tls-{tls}",
comment=f"test sqlite",
service="sqlite",
tag=f"sqlite" if runtime == "async-std" else f"sqlite_{runtime}",
@ -171,7 +171,7 @@ for runtime in ["async-std", "tokio", "actix"]:
for version in ["14", "13", "12", "11", "10"]:
run(
f"cargo test --no-default-features --features macros,offline,any,all-types,postgres,runtime-{runtime}-{tls}",
f"cargo test --no-default-features --features macros,any,unstable-all-types,postgres,runtime-{runtime},tls-{tls}",
comment=f"test postgres {version}",
service=f"postgres_{version}",
tag=f"postgres_{version}" if runtime == "async-std" else f"postgres_{version}_{runtime}",
@ -180,7 +180,7 @@ for runtime in ["async-std", "tokio", "actix"]:
## +ssl
for version in ["14", "13", "12", "11", "10"]:
run(
f"cargo test --no-default-features --features macros,offline,any,all-types,postgres,runtime-{runtime}-{tls}",
f"cargo test --no-default-features --features macros,any,_unstable-all-types,postgres,runtime-{runtime},tls-{tls}",
comment=f"test postgres {version} ssl",
database_url_args="sslmode=verify-ca&sslrootcert=.%2Ftests%2Fcerts%2Fca.crt",
service=f"postgres_{version}",
@ -193,7 +193,7 @@ for runtime in ["async-std", "tokio", "actix"]:
for version in ["8", "5_7"]:
run(
f"cargo test --no-default-features --features macros,offline,any,all-types,mysql,runtime-{runtime}-{tls}",
f"cargo test --no-default-features --features macros,any,_unstable-all-types,mysql,runtime-{runtime},tls-{tls}",
comment=f"test mysql {version}",
service=f"mysql_{version}",
tag=f"mysql_{version}" if runtime == "async-std" else f"mysql_{version}_{runtime}",
@ -205,23 +205,11 @@ for runtime in ["async-std", "tokio", "actix"]:
for version in ["10_6", "10_5", "10_4", "10_3", "10_2"]:
run(
f"cargo test --no-default-features --features macros,offline,any,all-types,mysql,runtime-{runtime}-{tls}",
f"cargo test --no-default-features --features macros,any,_unstable-all-types,mysql,runtime-{runtime},tls-{tls}",
comment=f"test mariadb {version}",
service=f"mariadb_{version}",
tag=f"mariadb_{version}" if runtime == "async-std" else f"mariadb_{version}_{runtime}",
)
#
# mssql
#
for version in ["2019", "2017"]:
run(
f"cargo test --no-default-features --features macros,offline,any,all-types,mssql,runtime-{runtime}-{tls}",
comment=f"test mssql {version}",
service=f"mssql_{version}",
tag=f"mssql_{version}" if runtime == "async-std" else f"mssql_{version}_{runtime}",
)
# TODO: Use [grcov] if available
# ~/.cargo/bin/grcov tests/.cache/target/debug -s sqlx-core/ -t html --llvm --branch -o ./target/debug/coverage