diff --git a/.github/workflows/sqlx.yml b/.github/workflows/sqlx.yml index 35750698..115a7268 100644 --- a/.github/workflows/sqlx.yml +++ b/.github/workflows/sqlx.yml @@ -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 }} diff --git a/Cargo.toml b/Cargo.toml index d90ffd37..da437616 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -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" diff --git a/sqlx-core/src/error.rs b/sqlx-core/src/error.rs index a4283c10..bdca7e8a 100644 --- a/sqlx-core/src/error.rs +++ b/sqlx-core/src/error.rs @@ -160,6 +160,25 @@ pub fn mismatched_types>(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 { diff --git a/sqlx-mysql/src/error.rs b/sqlx-mysql/src/error.rs index 0717b49f..3ae40338 100644 --- a/sqlx-mysql/src/error.rs +++ b/sqlx-mysql/src/error.rs @@ -78,4 +78,80 @@ impl DatabaseError for MySqlDatabaseError { fn into_error(self: Box) -> Box { 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: . +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: . + pub const ER_DUP_UNIQUE: u16 = 1169; + /// Similar to `ER_DUP_ENTRY`, but with a formatted string message. + /// + /// See: . + 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; } diff --git a/sqlx-postgres/src/error.rs b/sqlx-postgres/src/error.rs index 931685a4..8de58d55 100644 --- a/sqlx-postgres/src/error.rs +++ b/sqlx-postgres/src/error.rs @@ -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: +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"; } diff --git a/sqlx-sqlite/src/error.rs b/sqlx-sqlite/src/error.rs index 570c3872..c00374fe 100644 --- a/sqlx-sqlite/src/error.rs +++ b/sqlx-sqlite/src/error.rs @@ -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) -> Box { 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, + } + } } diff --git a/tests/docker-compose.yml b/tests/docker-compose.yml index bd8ce462..f6d10830 100644 --- a/tests/docker-compose.yml +++ b/tests/docker-compose.yml @@ -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 diff --git a/tests/mysql/error.rs b/tests/mysql/error.rs new file mode 100644 index 00000000..bb174e5e --- /dev/null +++ b/tests/mysql/error.rs @@ -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::().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::().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::().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::().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(()) +} diff --git a/tests/mysql/setup.sql b/tests/mysql/setup.sql index 17b35143..733d90c1 100644 --- a/tests/mysql/setup.sql +++ b/tests/mysql/setup.sql @@ -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) +); diff --git a/tests/postgres/error.rs b/tests/postgres/error.rs new file mode 100644 index 00000000..d6f78140 --- /dev/null +++ b/tests/postgres/error.rs @@ -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::().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::().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::().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::().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(()) +} diff --git a/tests/postgres/setup.sql b/tests/postgres/setup.sql index 1a42d0b8..56edaf99 100644 --- a/tests/postgres/setup.sql +++ b/tests/postgres/setup.sql @@ -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, diff --git a/tests/sqlite/error.rs b/tests/sqlite/error.rs new file mode 100644 index 00000000..2bfa83c6 --- /dev/null +++ b/tests/sqlite/error.rs @@ -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::().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::().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::().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::().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(()) +} diff --git a/tests/sqlite/setup.sql b/tests/sqlite/setup.sql index 7b931ea3..8f7d77c4 100644 --- a/tests/sqlite/setup.sql +++ b/tests/sqlite/setup.sql @@ -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) +); diff --git a/tests/sqlite/sqlite.db b/tests/sqlite/sqlite.db index da8aac9f..6832b724 100644 Binary files a/tests/sqlite/sqlite.db and b/tests/sqlite/sqlite.db differ diff --git a/tests/x.py b/tests/x.py index 75791b53..1d03ed7a 100755 --- a/tests/x.py +++ b/tests/x.py @@ -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