diff --git a/.gitignore b/.gitignore index 49cd284b..fea344bf 100644 --- a/.gitignore +++ b/.gitignore @@ -9,6 +9,4 @@ target/ # Environment .env - -tests/fixtures/*.sqlite-shm -tests/fixtures/*.sqlite-wal +!tests/.env diff --git a/Cargo.lock b/Cargo.lock index e6c71af1..99ca9c0a 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2042,6 +2042,7 @@ dependencies = [ "serde_json", "sqlx-core", "sqlx-macros", + "sqlx-rt", "sqlx-test", "time 0.2.16", "tokio 0.2.21", diff --git a/Cargo.toml b/Cargo.toml index 98e1c108..0c1a15f8 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -78,67 +78,65 @@ async-std = { version = "1.5.0", features = [ "attributes" ] } tokio = { version = "0.2.13", features = [ "full" ] } dotenv = "0.15.0" trybuild = "1.0.24" +sqlx-rt = { path = "./sqlx-rt" } sqlx-test = { path = "./sqlx-test" } paste = "0.1.7" serde = { version = "1.0", features = [ "derive" ] } serde_json = "1.0.48" -[[test]] -name = "postgres-macros" -required-features = [ "postgres", "macros" ] - -[[test]] -name = "mysql-macros" -required-features = [ "mysql", "macros" ] +# +# SQLite +# [[test]] name = "sqlite" +path = "tests/sqlite/sqlite.rs" required-features = [ "sqlite" ] -[[test]] -name = "sqlite-macros" -required-features = [ "sqlite", "macros" ] - -[[test]] -name = "sqlite-raw" -required-features = [ "sqlite" ] - -[[test]] -name = "sqlite-derives" -required-features = [ "sqlite", "macros" ] - [[test]] name = "sqlite-types" +path = "tests/sqlite/types.rs" required-features = [ "sqlite" ] +[[test]] +name = "sqlite-describe" +path = "tests/sqlite/describe.rs" +required-features = [ "sqlite" ] + +# +# MySQL +# + [[test]] name = "mysql" +path = "tests/mysql/mysql.rs" required-features = [ "mysql" ] [[test]] -name = "mysql-raw" +name = "mysql-types" +path = "tests/mysql/types.rs" required-features = [ "mysql" ] [[test]] -name = "mysql-derives" -required-features = [ "mysql", "macros" ] +name = "mysql-describe" +path = "tests/mysql/describe.rs" +required-features = [ "mysql" ] + +# +# PostgreSQL +# [[test]] name = "postgres" -required-features = [ "postgres" ] - -[[test]] -name = "postgres-raw" +path = "tests/postgres/postgres.rs" required-features = [ "postgres" ] [[test]] name = "postgres-types" +path = "tests/postgres/types.rs" required-features = [ "postgres" ] [[test]] -name = "postgres-derives" -required-features = [ "postgres", "macros" ] - -[[test]] -name = "mysql-types" -required-features = [ "mysql" ] +name = "postgres-describe" +path = "tests/postgres/describe.rs" +required-features = [ "postgres" ] diff --git a/sqlx-core/src/mysql/io/buf_mut.rs b/sqlx-core/src/mysql/io/buf_mut.rs index 5b59c856..95bf04f3 100644 --- a/sqlx-core/src/mysql/io/buf_mut.rs +++ b/sqlx-core/src/mysql/io/buf_mut.rs @@ -117,14 +117,6 @@ fn test_encodes_string_lenenc() { assert_eq!(&buf[..], b"\x0Drandom_string"); } -#[test] -fn test_encodes_string_null() { - let mut buf = Vec::with_capacity(1024); - buf.put_str_nul("random_string"); - - assert_eq!(&buf[..], b"random_string\0"); -} - #[test] fn test_encodes_byte_lenenc() { let mut buf = Vec::with_capacity(1024); diff --git a/sqlx-core/src/mysql/options.rs b/sqlx-core/src/mysql/options.rs index aeed5ec2..90ece06a 100644 --- a/sqlx-core/src/mysql/options.rs +++ b/sqlx-core/src/mysql/options.rs @@ -75,8 +75,9 @@ impl FromStr for MySqlSslMode { /// # use sqlx_core::connection::Connect; /// # use sqlx_core::mysql::{MySqlConnectOptions, MySqlConnection, MySqlSslMode}; /// # -/// # #[sqlx_rt::main] -/// # async fn main() -> Result<(), Error> { +/// # fn main() { +/// # #[cfg(feature = "runtime-async-std")] +/// # sqlx_rt::async_std::task::block_on(async move { /// // URI connection string /// let conn = MySqlConnection::connect("mysql://root:password@localhost/db").await?; /// @@ -87,7 +88,7 @@ impl FromStr for MySqlSslMode { /// .password("password") /// .database("db") /// ).await?; -/// # Ok(()) +/// # }).unwrap(); /// # } /// ``` #[derive(Debug, Clone)] diff --git a/sqlx-core/src/pool/mod.rs b/sqlx-core/src/pool/mod.rs index 10da0858..6117a171 100644 --- a/sqlx-core/src/pool/mod.rs +++ b/sqlx-core/src/pool/mod.rs @@ -160,8 +160,8 @@ fn assert_pool_traits() { fn assert_send_sync() {} fn assert_clone() {} - fn assert_pool() { - assert_send_sync::>(); - assert_clone::>(); + fn assert_pool() { + assert_send_sync::>(); + assert_clone::>(); } } diff --git a/sqlx-core/src/postgres/message/notification.rs b/sqlx-core/src/postgres/message/notification.rs index eb12952d..207b8f30 100644 --- a/sqlx-core/src/postgres/message/notification.rs +++ b/sqlx-core/src/postgres/message/notification.rs @@ -29,9 +29,9 @@ impl Decode<'_> for Notification { fn test_decode_notification_response() { const NOTIFICATION_RESPONSE: &[u8] = b"\x34\x20\x10\x02TEST-CHANNEL\0THIS IS A TEST\0"; - let message = Notification::read(NOTIFICATION_RESPONSE).unwrap(); + let message = Notification::decode(Bytes::from(NOTIFICATION_RESPONSE)).unwrap(); assert_eq!(message.process_id, 0x34201002); - assert_eq!(&*message.channel, b"TEST-CHANNEL"[..]); - assert_eq!(&*message.payload, b"THIS IS A TEST"[..]); + assert_eq!(&*message.channel, &b"TEST-CHANNEL"[..]); + assert_eq!(&*message.payload, &b"THIS IS A TEST"[..]); } diff --git a/sqlx-core/src/postgres/options.rs b/sqlx-core/src/postgres/options.rs index 0503bdaa..e31bab80 100644 --- a/sqlx-core/src/postgres/options.rs +++ b/sqlx-core/src/postgres/options.rs @@ -88,8 +88,9 @@ impl FromStr for PgSslMode { /// # use sqlx_core::connection::Connect; /// # use sqlx_core::postgres::{PgConnectOptions, PgConnection, PgSslMode}; /// # -/// # #[sqlx_rt::main] -/// # async fn main() -> Result<(), Error> { +/// # fn main() { +/// # #[cfg(feature = "runtime-async-std")] +/// # sqlx_rt::async_std::task::block_on(async move { /// // URI connection string /// let conn = PgConnection::connect("postgres://localhost/mydb").await?; /// @@ -101,7 +102,7 @@ impl FromStr for PgSslMode { /// .password("secret-password") /// .ssl_mode(PgSslMode::Require) /// ).await?; -/// # Ok(()) +/// # }).unwrap(); /// # } /// ``` #[derive(Debug, Clone)] diff --git a/sqlx-core/src/query.rs b/sqlx-core/src/query.rs index 93f4fd1a..b3e01dcb 100644 --- a/sqlx-core/src/query.rs +++ b/sqlx-core/src/query.rs @@ -308,7 +308,7 @@ where { Query { database: PhantomData, - arguments: Default::default(), + arguments: Some(Default::default()), query: sql, } } diff --git a/sqlx-core/src/sqlite/statement/worker.rs b/sqlx-core/src/sqlite/statement/worker.rs index f1eac278..41b69dbf 100644 --- a/sqlx-core/src/sqlite/statement/worker.rs +++ b/sqlx-core/src/sqlite/statement/worker.rs @@ -1,22 +1,31 @@ -use std::ptr::null_mut; -use std::sync::atomic::{spin_loop_hint, AtomicI32, AtomicPtr, Ordering}; -use std::sync::Arc; -use std::thread::{self, park, spawn, JoinHandle}; - use either::Either; -use libsqlite3_sys::{sqlite3_step, sqlite3_stmt, SQLITE_DONE, SQLITE_ROW}; -use sqlx_rt::yield_now; +use libsqlite3_sys::{sqlite3_step, SQLITE_DONE, SQLITE_ROW}; use crate::error::Error; use crate::sqlite::statement::StatementHandle; +#[cfg(not(feature = "runtime-tokio"))] +use { + libsqlite3_sys::sqlite3_stmt, + sqlx_rt::yield_now, + std::ptr::null_mut, + std::sync::atomic::{spin_loop_hint, AtomicI32, AtomicPtr, Ordering}, + std::sync::Arc, + std::thread::{self, park, spawn, JoinHandle}, +}; + // For async-std and actix, the worker maintains a dedicated thread for each SQLite connection // All invocations of [sqlite3_step] are run on this thread // For tokio, the worker is a thin wrapper around an invocation to [block_in_place] +#[cfg(not(feature = "runtime-tokio"))] const STATE_CLOSE: i32 = -1; + +#[cfg(not(feature = "runtime-tokio"))] const STATE_READY: i32 = 0; + +#[cfg(not(feature = "runtime-tokio"))] const STATE_INITIAL: i32 = 1; #[cfg(not(feature = "runtime-tokio"))] @@ -156,7 +165,7 @@ impl StatementWorker { pub(crate) async fn step(&self, statement: &StatementHandle) -> Result, Error> { let statement = *statement; - let status = sqlx_rt::blocking!({ unsafe { sqlite3_step(statement.0.as_ptr()) } }); + let status = sqlx_rt::blocking!(unsafe { sqlite3_step(statement.0.as_ptr()) }); match status { // a row was found diff --git a/sqlx-macros/src/lib.rs b/sqlx-macros/src/lib.rs index a303e565..d967b4bf 100644 --- a/sqlx-macros/src/lib.rs +++ b/sqlx-macros/src/lib.rs @@ -79,3 +79,51 @@ pub fn derive_from_row(input: TokenStream) -> TokenStream { Err(e) => e.to_compile_error().into(), } } + +#[doc(hidden)] +#[proc_macro_attribute] +pub fn test(_attr: TokenStream, input: TokenStream) -> TokenStream { + let input = syn::parse_macro_input!(input as syn::ItemFn); + + let ret = &input.sig.output; + let name = &input.sig.ident; + let body = &input.block; + let attrs = &input.attrs; + + let result = if cfg!(feature = "runtime-tokio") { + quote! { + #[test] + #(#attrs)* + fn #name() #ret { + sqlx_rt::tokio::runtime::Builder::new() + .threaded_scheduler() + .enable_io() + .enable_time() + .build() + .unwrap() + .block_on(async { #body }) + } + } + } else if cfg!(feature = "runtime-async-std") { + quote! { + #[test] + #(#attrs)* + fn #name() #ret { + sqlx_rt::async_std::task::block_on(async { #body }) + } + } + } else if cfg!(feature = "runtime-actix") { + quote! { + #[test] + #(#attrs)* + fn #name() #ret { + sqlx_rt::actix_rt::System::new("sqlx-test") + .block_on(async { #body }) + } + } + } else { + panic!("one of 'runtime-actix', 'runtime-async-std' or 'runtime-tokio' features must be enabled"); + }; + + result.into() +} diff --git a/sqlx-rt/src/lib.rs b/sqlx-rt/src/lib.rs index 8c9dd777..835f08eb 100644 --- a/sqlx-rt/src/lib.rs +++ b/sqlx-rt/src/lib.rs @@ -28,7 +28,7 @@ pub use native_tls; ))] pub use tokio::{ self, fs, io::AsyncRead, io::AsyncReadExt, io::AsyncWrite, io::AsyncWriteExt, net::TcpStream, - task::yield_now, time::delay_for as sleep, time::timeout, + task::spawn, task::yield_now, time::delay_for as sleep, time::timeout, }; #[cfg(all( diff --git a/sqlx-test/src/lib.rs b/sqlx-test/src/lib.rs index 03978df0..ee11fd2f 100644 --- a/sqlx-test/src/lib.rs +++ b/sqlx-test/src/lib.rs @@ -1,4 +1,5 @@ -use sqlx::{Connect, Database}; +use sqlx::{database::Database, Connect}; +use std::env; fn setup_if_needed() { let _ = dotenv::dotenv(); @@ -13,42 +14,81 @@ where { setup_if_needed(); - Ok(DB::Connection::connect(dotenv::var("DATABASE_URL")?).await?) + Ok(DB::Connection::connect(&env::var("DATABASE_URL")?).await?) } // Test type encoding and decoding #[macro_export] macro_rules! test_type { - ($name:ident($db:ident, $ty:ty, $sql:literal, $($text:literal == $value:expr),+ $(,)?)) => { - $crate::test_prepared_type!($name($db, $ty, $sql, $($text == $value),+)); - $crate::test_unprepared_type!($name($db, $ty, $($text == $value),+)); + ($name:ident<$ty:ty>($db:ident, $sql:literal, $($text:literal == $value:expr),+ $(,)?)) => { + $crate::__test_prepared_type!($name<$ty>($db, $sql, $($text == $value),+)); + $crate::test_unprepared_type!($name<$ty>($db, $($text == $value),+)); }; - ($name:ident($db:ident, $ty:ty, $($text:literal == $value:expr),+ $(,)?)) => { - $crate::test_prepared_type!($name($db, $ty, $($text == $value),+)); - $crate::test_unprepared_type!($name($db, $ty, $($text == $value),+)); + ($name:ident<$ty:ty>($db:ident, $($text:literal == $value:expr),+ $(,)?)) => { + paste::item! { + $crate::__test_prepared_type!($name<$ty>($db, $crate::[< $db _query_for_test_prepared_type >]!(), $($text == $value),+)); + $crate::test_unprepared_type!($name<$ty>($db, $($text == $value),+)); + } + }; + + ($name:ident($db:ident, $($text:literal == $value:expr),+ $(,)?)) => { + $crate::test_type!($name<$name>($db, $($text == $value),+)); + }; +} + +// Test type decoding only +#[macro_export] +macro_rules! test_decode_type { + ($name:ident<$ty:ty>($db:ident, $($text:literal == $value:expr),+ $(,)?)) => { + $crate::__test_prepared_decode_type!($name<$ty>($db, $($text == $value),+)); + $crate::test_unprepared_type!($name<$ty>($db, $($text == $value),+)); + }; + + ($name:ident($db:ident, $($text:literal == $value:expr),+ $(,)?)) => { + $crate::test_decode_type!($name<$name>($db, $($text == $value),+)); + }; +} + +// Test type encoding and decoding +#[macro_export] +macro_rules! test_prepared_type { + ($name:ident<$ty:ty>($db:ident, $sql:literal, $($text:literal == $value:expr),+ $(,)?)) => { + $crate::__test_prepared_type!($name<$ty>($db, $sql, $($text == $value),+)); + }; + + ($name:ident<$ty:ty>($db:ident, $($text:literal == $value:expr),+ $(,)?)) => { + paste::item! { + $crate::__test_prepared_type!($name<$ty>($db, $crate::[< $db _query_for_test_prepared_type >]!(), $($text == $value),+)); + } + }; + + ($name:ident($db:ident, $($text:literal == $value:expr),+ $(,)?)) => { + $crate::__test_prepared_type!($name<$name>($db, $($text == $value),+)); }; } // Test type decoding for the simple (unprepared) query API #[macro_export] macro_rules! test_unprepared_type { - ($name:ident($db:ident, $ty:ty, $($text:literal == $value:expr),+ $(,)?)) => { + ($name:ident<$ty:ty>($db:ident, $($text:literal == $value:expr),+ $(,)?)) => { paste::item! { - #[cfg_attr(feature = "runtime-async-std", async_std::test)] - #[cfg_attr(feature = "runtime-tokio", tokio::test)] + #[sqlx_macros::test] async fn [< test_unprepared_type_ $name >] () -> anyhow::Result<()> { use sqlx::prelude::*; + use futures::TryStreamExt; let mut conn = sqlx_test::new::<$db>().await?; $( - let query = format!("SELECT {} as _1", $text); - let mut cursor = conn.fetch(&*query); - let row = cursor.next().await?.unwrap(); - let rec = row.try_get::<$ty, _>("_1")?; + let query = format!("SELECT {}", $text); + let mut s = conn.fetch(&*query); + let row = s.try_next().await?.unwrap(); + let rec = row.try_get::<$ty, _>(0)?; assert!($value == rec); + + drop(s); )+ Ok(()) @@ -57,95 +97,76 @@ macro_rules! test_unprepared_type { } } -// TODO: This macro is cursed. Needs a good re-factor. -// Test type encoding and decoding for the prepared query API +// Test type decoding only for the prepared query API #[macro_export] -macro_rules! test_prepared_type { - ($name:ident($db:ident, $ty:ty, $sql:literal, $($text:literal == $value:expr),+ $(,)?)) => { +macro_rules! __test_prepared_decode_type { + ($name:ident<$ty:ty>($db:ident, $($text:literal == $value:expr),+ $(,)?)) => { paste::item! { - #[cfg_attr(feature = "runtime-async-std", async_std::test)] - #[cfg_attr(feature = "runtime-tokio", tokio::test)] - async fn [< test_prepared_type_ $name >] () -> anyhow::Result<()> { - use sqlx::prelude::*; + #[sqlx_macros::test] + async fn [< test_prepared_decode_type_ $name >] () -> anyhow::Result<()> { + use sqlx::Row; let mut conn = sqlx_test::new::<$db>().await?; $( - let query = format!($sql, $text); + let query = format!("SELECT {}", $text); - let rec: (bool, Option, $ty, $ty) = sqlx::query_as(&query) - .bind($value) - .bind($value) - .bind($value) + let row = sqlx::query(&query) .fetch_one(&mut conn) .await?; - assert!(rec.0, - "[1] DB value mismatch; given value: {:?}\n\ - as received: {:?}\n\ - as returned: {:?}\n\ - round-trip: {:?}", - $value, rec.1, rec.2, rec.3); + let rec: $ty = row.try_get(0)?; - assert_eq!($value, rec.2, - "[2] DB value mismatch; given value: {:?}\n\ - as received: {:?}\n\ - as returned: {:?}\n\ - round-trip: {:?}", - $value, rec.1, rec.2, rec.3); - - assert_eq!($value, rec.3, - "[3] DB value mismatch; given value: {:?}\n\ - as received: {:?}\n\ - as returned: {:?}\n\ - round-trip: {:?}", - $value, rec.1, rec.2, rec.3); + assert!($value == rec); )+ Ok(()) } } }; +} - ($name:ident($db:ident, $ty:ty, $($text:literal == $value:expr),+ $(,)?)) => { +// Test type encoding and decoding for the prepared query API +#[macro_export] +macro_rules! __test_prepared_type { + ($name:ident<$ty:ty>($db:ident, $sql:expr, $($text:literal == $value:expr),+ $(,)?)) => { paste::item! { - #[cfg_attr(feature = "runtime-async-std", async_std::test)] - #[cfg_attr(feature = "runtime-tokio", tokio::test)] + #[sqlx_macros::test] async fn [< test_prepared_type_ $name >] () -> anyhow::Result<()> { - use sqlx::prelude::*; + use sqlx::Row; let mut conn = sqlx_test::new::<$db>().await?; $( - let query = format!($crate::[< $db _query_for_test_prepared_type >]!(), $text); + let query = format!($sql, $text); - let rec: (bool, Option, $ty, $ty) = sqlx::query_as(&query) - .bind($value) + let row = sqlx::query(&query) .bind($value) .bind($value) .fetch_one(&mut conn) .await?; - assert!(rec.0, + let matches: bool = row.try_get(0)?; + let returned: $ty = row.try_get(1)?; + let round_trip: $ty = row.try_get(2)?; + + assert!(matches, "[1] DB value mismatch; given value: {:?}\n\ - as received: {:?}\n\ as returned: {:?}\n\ round-trip: {:?}", - $value, rec.1, rec.2, rec.3); + $value, returned, round_trip); - assert_eq!($value, rec.2, + assert_eq!($value, returned, "[2] DB value mismatch; given value: {:?}\n\ - as received: {:?}\n\ as returned: {:?}\n\ round-trip: {:?}", - $value, rec.1, rec.2, rec.3); + $value, returned, round_trip); - assert_eq!($value, rec.3, + assert_eq!($value, round_trip, "[3] DB value mismatch; given value: {:?}\n\ - as received: {:?}\n\ as returned: {:?}\n\ round-trip: {:?}", - $value, rec.1, rec.2, rec.3); + $value, returned, round_trip); )+ Ok(()) @@ -157,20 +178,20 @@ macro_rules! test_prepared_type { #[macro_export] macro_rules! MySql_query_for_test_prepared_type { () => { - "SELECT {0} <=> ?, '' as _1, ? as _2, ? as _3" + "SELECT {0} <=> ?, {0}, ?" }; } #[macro_export] macro_rules! Sqlite_query_for_test_prepared_type { () => { - "SELECT {0} is ?, cast(? as text) as _1, {0} as _2, ? as _3" + "SELECT {0} is ?, {0}, ?" }; } #[macro_export] macro_rules! Postgres_query_for_test_prepared_type { () => { - "SELECT {0} is not distinct from $1, $2::text as _1, {0} as _2, $3 as _3" + "SELECT {0} is not distinct from $1, {0}, $2" }; } diff --git a/tests/.dockerignore b/tests/.dockerignore new file mode 100644 index 00000000..72e8ffc0 --- /dev/null +++ b/tests/.dockerignore @@ -0,0 +1 @@ +* diff --git a/tests/.env b/tests/.env new file mode 100644 index 00000000..0f552d00 --- /dev/null +++ b/tests/.env @@ -0,0 +1,2 @@ +# environment values for docker-compose +COMPOSE_PROJECT_NAME=sqlx diff --git a/tests/Dockerfile b/tests/Dockerfile new file mode 100644 index 00000000..72141dcc --- /dev/null +++ b/tests/Dockerfile @@ -0,0 +1,4 @@ +FROM rust:stretch + +RUN rustup toolchain install nightly-2020-05-30 && \ + rustup default nightly-2020-05-30 diff --git a/tests/certs/ca.crt b/tests/certs/ca.crt new file mode 100644 index 00000000..e8f81a62 --- /dev/null +++ b/tests/certs/ca.crt @@ -0,0 +1,21 @@ +-----BEGIN CERTIFICATE----- +MIIDdTCCAl2gAwIBAgIUDlkBz/x3GgypHOBLD9grAx2DQT0wDQYJKoZIhvcNAQEL +BQAwSjELMAkGA1UEBhMCVVMxEzARBgNVBAgMCkNhbGlmb3JuaWExFDASBgNVBAoM +C0xhdW5jaEJhZGdlMRAwDgYDVQQDDAdzcWx4LWNhMB4XDTIwMDUyMjA2NDEzNFoX +DTQ3MTAwNzA2NDEzNFowSjELMAkGA1UEBhMCVVMxEzARBgNVBAgMCkNhbGlmb3Ju +aWExFDASBgNVBAoMC0xhdW5jaEJhZGdlMRAwDgYDVQQDDAdzcWx4LWNhMIIBIjAN +BgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAwiG50Cfhp/3jRRF2Zk+bWsFe52S4 +s+xfGX6NoQwWZRUWqUuiYNXDXPX82lMXcdoTV8U/QgKAG7P19zwPN2q+pNPdd6S1 +t1SSJP5ozaQRHFxwVGwj99Nu7La/00Qo0P4VI5kldISHz0fFA6BBT7XGSKwRQj0N +gk7Gc2Si5EG3WTaAkz5A+jsbBBzJ4CrqK/O2jHRJDExeptxKu4tY5RkX1lI9HAvm +AUMPtlguEdldWJo5qOOpytEEyhnDRbiat4hNV68UzsYB5AUucRXgatgENCoADQf/ +qEqFU0RjFml72H4RHxY2iPGEF+GPxVm/u2wnJHOSQ/fUO1x+kcGEXJ5Z+wIDAQAB +o1MwUTAdBgNVHQ4EFgQUeHc1DDZ2/SJwIjFk4QkuiyXJiyQwHwYDVR0jBBgwFoAU +eHc1DDZ2/SJwIjFk4QkuiyXJiyQwDwYDVR0TAQH/BAUwAwEB/zANBgkqhkiG9w0B +AQsFAAOCAQEAO5lYFNaFwYjSkSzO19xery699gdzAFKrDxVrKg1Pybatiz6WXMRp +O/5wo61wXg3qMFhj5WdKj0A/nbkGnLoFgW99nw1V7vDqEba5FDfautZNnImIpNkg +WPXN7NRlVBDBKKg+Vx0DFBo9ucAa11gwT+eSdLf4GCwQW8NahmloOorDbg0/CAPw +65duNS260+LsHXqb9jlxB2KXCi6GhenwyIglGtT9g3vh5ntIH53fr1P1aP+zup6b +Atry/KLvi+qLWtgWQwPGDlsft7ieBU0mpX+avs9lKmtwkxaFOfG5aHylxjbC1mKa +v497AO7TATCTsHhCaWgkIg4GAT61fUK9iA== +-----END CERTIFICATE----- diff --git a/tests/certs/ca.srl b/tests/certs/ca.srl new file mode 100644 index 00000000..b37bf35c --- /dev/null +++ b/tests/certs/ca.srl @@ -0,0 +1 @@ +1FCE7310D7E3C29BA5ED14EEF264E66C25727029 diff --git a/tests/certs/server.crt b/tests/certs/server.crt new file mode 100644 index 00000000..2a0e814b --- /dev/null +++ b/tests/certs/server.crt @@ -0,0 +1,19 @@ +-----BEGIN CERTIFICATE----- +MIIDGDCCAgACFB/OcxDX48Kbpe0U7vJk5mwlcnApMA0GCSqGSIb3DQEBCwUAMEox +CzAJBgNVBAYTAlVTMRMwEQYDVQQIDApDYWxpZm9ybmlhMRQwEgYDVQQKDAtMYXVu +Y2hCYWRnZTEQMA4GA1UEAwwHc3FseC1jYTAeFw0yMDA1MjIwNjQyNDJaFw00NzEw +MDcwNjQyNDJaMEcxCzAJBgNVBAYTAlVTMRMwEQYDVQQIDApDYWxpZm9ybmlhMRQw +EgYDVQQKDAtMYXVuY2hCYWRnZTENMAsGA1UEAwwEc3FseDCCASIwDQYJKoZIhvcN +AQEBBQADggEPADCCAQoCggEBAM6rGt+k8fcURVGOIVpYcSpHYVJqvWaYEp+SGmvQ +KdMGjE1AHHwd6iT692G3hrZ4hHzvEmrWX4Cp7o9GEF1v912qVWxjUAuufJUFt8Jb +Xa6h0n/PDbgIqU5RQPoOnvlkLrE3fC+BYpBCy+TuEezrTINIOnKVUplYJWYJ78/u +nrensQOj/IeOF1kSeeF049nG7dnqEA+5qTMLAFTVacMIcGyDZX7AqDTsNpwy5QMq +fld1RRPOoNLTYcHq9q3a8XeVXONmxMGh9oatnsIIxGuw22exfvhJzNKgx/+5x6UI +2R+MRBdqz7UWksN37X/lhG8fm5a1zdvmaGC/JSfkaQAMM78CAwEAATANBgkqhkiG +9w0BAQsFAAOCAQEAsz7ty4bK9ml1YwRrWNcSAk0kmGufThhAYhMOu+7rfsV6V5x5 +Eh+TvmBoLNFQ/i7LZdmVGTdp90co+FgLHtJ0j/HQguNou02VZ5/5GCSDsJWGxR+6 +nZj9M8yP+thYapd7ndkWmjDBioTRKUVeQN8c3L8u6G+ElUJYImebaK2GxivPUCC0 +ReHYTACYTf9GdgYYplYdNK+KiKpMLCKvB9f5sXI0Rk8pDXrQxoK34FacBttGdHek +Dbiq26LCszdwbUJhamvne8XFpNqAAh5WMA+b6bKH1OQpr+VNdkAsDZwuAj/MQqJE +vP7IzDLrnjy0fjeI0nW0J8/ch0+1BayJsz8aoQ== +-----END CERTIFICATE----- diff --git a/tests/docker-compose.yml b/tests/docker-compose.yml new file mode 100644 index 00000000..8308b080 --- /dev/null +++ b/tests/docker-compose.yml @@ -0,0 +1,174 @@ +version: "3.8" + +services: + sqlx: + build: "." + volumes: + - "../:/home/rust/src" + - "$HOME/.cargo/registry:/usr/local/cargo/registry" + working_dir: "/home/rust/src" + environment: + CARGO_INCREMENTAL: "0" + RUSTFLAGS: "-Zprofile -Ccodegen-units=1 -Copt-level=0 -Clink-dead-code -Coverflow-checks=off -Zpanic_abort_tests -Cpanic=abort" + RUSTDOCFLAGS: "-Cpanic=abort" + + # + # MySQL 5.6.x, 5.7.x, 8.x + # https://www.mysql.com/support/supportedplatforms/database.html + # + + mysql_8: + image: mysql:8.0 + volumes: + - "./mysql/setup.sql:/docker-entrypoint-initdb.d/setup.sql" + environment: + MYSQL_ROOT_PASSWORD: password + MYSQL_DATABASE: sqlx + + mysql_5_7: + image: mysql:5.7 + volumes: + - "./mysql/setup.sql:/docker-entrypoint-initdb.d/setup.sql" + environment: + MYSQL_ROOT_PASSWORD: password + MYSQL_DATABASE: sqlx + + mysql_5_6: + image: mysql:5.6 + volumes: + - "./mysql/setup.sql:/docker-entrypoint-initdb.d/setup.sql" + environment: + MYSQL_ROOT_PASSWORD: password + MYSQL_DATABASE: sqlx + + # + # MariaDB 10.5, 10.4, 10.3, 10.2, 10.1 + # https://mariadb.org/about/#maintenance-policy + # + + mariadb_10_5: + image: mariadb:10.5 + volumes: + - "./mysql/setup.sql:/docker-entrypoint-initdb.d/setup.sql" + environment: + MYSQL_ROOT_PASSWORD: password + MYSQL_DATABASE: sqlx + + mariadb_10_4: + image: mariadb:10.4 + volumes: + - "./mysql/setup.sql:/docker-entrypoint-initdb.d/setup.sql" + environment: + MYSQL_ROOT_PASSWORD: password + MYSQL_DATABASE: sqlx + + mariadb_10_3: + image: mariadb:10.3 + volumes: + - "./mysql/setup.sql:/docker-entrypoint-initdb.d/setup.sql" + environment: + MYSQL_ROOT_PASSWORD: password + MYSQL_DATABASE: sqlx + + mariadb_10_2: + image: mariadb:10.2 + volumes: + - "./mysql/setup.sql:/docker-entrypoint-initdb.d/setup.sql" + environment: + MYSQL_ROOT_PASSWORD: password + MYSQL_DATABASE: sqlx + + mariadb_10_1: + image: mariadb:10.1 + volumes: + - "./mysql/setup.sql:/docker-entrypoint-initdb.d/setup.sql" + environment: + MYSQL_ROOT_PASSWORD: password + MYSQL_DATABASE: sqlx + + # + # PostgreSQL 12.x, 10.x, 9.6.x, 9.5.x + # https://www.postgresql.org/support/versioning/ + # + + postgres_13: + build: + context: . + dockerfile: postgres/Dockerfile + args: + VERSION: 13-beta1 + environment: + POSTGRES_DB: sqlx + POSTGRES_USER: postgres + POSTGRES_PASSWORD: password + POSTGRES_HOST_AUTH_METHOD: scram-sha-256 + POSTGRES_INITDB_ARGS: --auth-host=scram-sha-256 + volumes: + - "./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 + + postgres_12: + build: + context: . + dockerfile: postgres/Dockerfile + args: + VERSION: 12.3 + environment: + POSTGRES_DB: sqlx + POSTGRES_USER: postgres + POSTGRES_PASSWORD: password + POSTGRES_HOST_AUTH_METHOD: scram-sha-256 + POSTGRES_INITDB_ARGS: --auth-host=scram-sha-256 + volumes: + - "./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 + + postgres_10: + build: + context: . + dockerfile: postgres/Dockerfile + args: + VERSION: 10.13 + environment: + POSTGRES_DB: sqlx + POSTGRES_USER: postgres + POSTGRES_PASSWORD: password + POSTGRES_HOST_AUTH_METHOD: trust + volumes: + - "./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 + + postgres_9_6: + build: + context: . + dockerfile: postgres/Dockerfile + args: + VERSION: 9.6 + environment: + POSTGRES_DB: sqlx + POSTGRES_USER: postgres + POSTGRES_PASSWORD: password + POSTGRES_HOST_AUTH_METHOD: md5 + volumes: + - "./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 + + postgres_9_5: + build: + context: . + dockerfile: postgres/Dockerfile + args: + VERSION: 9.5 + environment: + POSTGRES_DB: sqlx + POSTGRES_USER: postgres + POSTGRES_PASSWORD: password + POSTGRES_HOST_AUTH_METHOD: password + volumes: + - "./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 diff --git a/tests/fixtures/mysql.sql b/tests/fixtures/mysql.sql deleted file mode 100644 index 60f81ccd..00000000 --- a/tests/fixtures/mysql.sql +++ /dev/null @@ -1,7 +0,0 @@ -CREATE TABLE accounts ( - id INTEGER PRIMARY KEY AUTOINCREMENT, - created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP, - name VARCHAR(255) NOT NULL, - is_active BOOLEAN, - score DOUBLE -); diff --git a/tests/fixtures/postgres.sql b/tests/fixtures/postgres.sql deleted file mode 100644 index ae8a6cb0..00000000 --- a/tests/fixtures/postgres.sql +++ /dev/null @@ -1,14 +0,0 @@ -CREATE TABLE accounts ( - id BIGSERIAL PRIMARY KEY, - created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(), - name TEXT NOT NULL, - is_active BOOLEAN, - score DOUBLE PRECISION -); - --- https://www.postgresql.org/docs/current/rowtypes.html#ROWTYPES-DECLARING -CREATE TYPE inventory_item AS ( - name TEXT, - supplier_id INT, - price BIGINT -); diff --git a/tests/keys/ca.key b/tests/keys/ca.key new file mode 100644 index 00000000..ba4f84db --- /dev/null +++ b/tests/keys/ca.key @@ -0,0 +1,28 @@ +-----BEGIN PRIVATE KEY----- +MIIEvgIBADANBgkqhkiG9w0BAQEFAASCBKgwggSkAgEAAoIBAQDCIbnQJ+Gn/eNF +EXZmT5tawV7nZLiz7F8Zfo2hDBZlFRapS6Jg1cNc9fzaUxdx2hNXxT9CAoAbs/X3 +PA83ar6k0913pLW3VJIk/mjNpBEcXHBUbCP3027str/TRCjQ/hUjmSV0hIfPR8UD +oEFPtcZIrBFCPQ2CTsZzZKLkQbdZNoCTPkD6OxsEHMngKuor87aMdEkMTF6m3Eq7 +i1jlGRfWUj0cC+YBQw+2WC4R2V1Ymjmo46nK0QTKGcNFuJq3iE1XrxTOxgHkBS5x +FeBq2AQ0KgANB/+oSoVTRGMWaXvYfhEfFjaI8YQX4Y/FWb+7bCckc5JD99Q7XH6R +wYRcnln7AgMBAAECggEALAMNZ13DURzES8Jbv3JI3Fh+taMmJNRv8w23+k0NPrl7 +O8KD+8Q62HaEbtLru8ofHIUBhGugs6cnGngpUv0GX8QQr7FN6VRpFa4AAK8zmeRz +KxChTuxGd7Au6SzIvCj+jeWIklQBnkK9LcdFR1cErzEjcIr65xII99xW+bzUXosR +k+aPOSUQXpvkzVgh03uxYBFS3MdF3o+YsLUIQMce4FGwCZJp2vuJfIo8Lfa0s4wd +fae6J6lq2dSoNG9q6rHYFJ3C+IjHxO+Ak/kEvxpSam8oz8+9YsuJ8idjweoHRo+P +mCIZVeAHHdtBM2T4xhvV51HOa89fB8SHVhtww/3OYQKBgQDma5IEbGOtOAi+muam +4by5npDA/3mwXwHADaO5GA7g0BA6XSMjNBQ58fMd/MoJZiUMUFyxzVpXGpLbNNTr +V+w9ol7YoVh6RHpdkpo96MlAwkpxJr06csHX0NDrw4WHvQuBGch4Mm9UnBZaRZhb +VE+s8Piw3cQocd3Kvy5vEflhkwKBgQDXrtrxv1rdsWbt7JYMtm0TIBWVzDV/5qcJ +n4FmkSAjHdGHUwsGCuBCWQ7d+YLtoeCR1XThds2TnNu+zJAHRyFWaUxh+XeWNFnk +R2KAW0WzRS/CMXBYV5k0oXnginFjtigO8MVlbsdunY7IfgeMh4/HXt6cYiOiA/Io +SO4Bw0cG+QKBgQCcWP9iOv3DipL8anT5ZZC+TqagHnm+wCia3WZrcLSfvO5V+96c +w3i5/L9faKjaidG3skvDZbjYA7MERKv46Nbm12cODSTRCegR2CkKuwrcAzmp34Rk +xXtcaldosmnHufG1bv5E+MvsGGFebXy888+AZJ4KvN+eJe095k2mlgamOwKBgC6f +rtapbeQUkFKIXRtcaBHFUsUyArKmUp+C6n8YiiDtNjkRm4Vv3nCZPdyALmxeHOSJ +hx2iB+iL9Pi20b+xAaTjWE6plc9Te8ccI0/p6xRItX0+ILIIJac57NW8N6y9WMV6 +CMHXg6cGyjHPBKS9PTh06pxVnqxMPBG3SjP5WaZJAoGBAOA1zE2z6xJCO8cEvo3d +C3FB9NWVqortQtYCn6Y/dt35noLEhe5gfr94BFwhZ2paJZ4dKQXsreaVzMMG6dEP +BjsOPQUHItIOrph2tuWvjWdC51jqgKOGTCCuuX7VJHlhxtaAnrS4RW84yVHugsj0 +c3xRxbj6IWgUQfXU6NhEocXv +-----END PRIVATE KEY----- diff --git a/tests/keys/server.key b/tests/keys/server.key new file mode 100644 index 00000000..a08b5815 --- /dev/null +++ b/tests/keys/server.key @@ -0,0 +1,28 @@ +-----BEGIN PRIVATE KEY----- +MIIEvgIBADANBgkqhkiG9w0BAQEFAASCBKgwggSkAgEAAoIBAQDOqxrfpPH3FEVR +jiFaWHEqR2FSar1mmBKfkhpr0CnTBoxNQBx8Heok+vdht4a2eIR87xJq1l+Aqe6P +RhBdb/ddqlVsY1ALrnyVBbfCW12uodJ/zw24CKlOUUD6Dp75ZC6xN3wvgWKQQsvk +7hHs60yDSDpylVKZWCVmCe/P7p63p7EDo/yHjhdZEnnhdOPZxu3Z6hAPuakzCwBU +1WnDCHBsg2V+wKg07DacMuUDKn5XdUUTzqDS02HB6vat2vF3lVzjZsTBofaGrZ7C +CMRrsNtnsX74SczSoMf/ucelCNkfjEQXas+1FpLDd+1/5YRvH5uWtc3b5mhgvyUn +5GkADDO/AgMBAAECggEBAKEqxlZKJ3frkvM6x7/Q4M97wuWm3/U1+q/+HCSfvT4Y +CSnlKVGRcptMK8dEfAWojolvVwmCDBAmdSe+F90GB/4/a0aPXEj/1Z/RSbCD19br +02Bgc+5kssOkketvo4IRImiJQIs7d0XREXiCP/BmvtBSb2IUGMoE94VPknixOY02 +Dbcxy7s/r7lqqWwaJNT1vDR/7l5kwsZnWfMRleaQPSGSh3ShVANEDCFb2bK0/s6w +DwshCn/Hh8ThVXK9JcchszIyvIIWWIdaaaHf4iN6G/4a0rqTaGAAo6Si9mZO1WK1 +emqnM/KQuhCI/Upwfcu/P5lfhscpc4vIpILUsEV6WKECgYEA8F/mwCIpz3NLe++z +dXCjByhwgpPyaZHJ2P+Av2q9jiIOVhFxheQ+7zuBYc7dOmLDmLoQGaOSQABWkzdm +hjn6NhJoJVY01Np5dSwIFZuC/PQaevidRVCWI1yLxU20ezN6xT2JeDJCuINjyYGG +oropg9KENUFbmNwRUqvVlzCCYLUCgYEA3BpLb9LASpOtiDLvVD+IWxdbcbjVFmuG +/1RPq+1PQNoJdjdEocf9r7K4jMTJuLULmy8MDyox94SqJ+91jvGDDoNisGLRCbic +gUg3HuC1J5oVwlqRC5F21tx9W0ZK85VWkvJQ6MiCZy1eqlaABSEcQ8Mn/4PhX2P/ +0/1sofQw7yMCgYEA0/t/QAng3XZMJ2xo0zUWUQW00zMuITPU0m3hWO4FZQdbpUOU +3gNADTJpE5yfNRJMdLAB6tp5679gmkvNOqp+opjxB5xS0zQo0NCYAJY4mmObxr7h +03MSNPU0vjec5tmrd66hQULx3E7i/Z4g4flTC1HoDh8pbFEHZeTsZHz/PdECgYA/ +p/MtUhx+9Rr5CxIgoYdEIQs3ZqdqJosSiUXJiYakUOrvn6hfycFa8Stiuv9ERkgn +B4JLWH6/AUVc62pqfvrSVblTHiEq2JOa6FHYwlBiNbQZU6wjVlyyY2512WyP6h7x +vNcdm+/q+zontYCs+xh7mJOW2INz3S3+F4s1g7QrVQKBgE0rBT29byusUQCV/AZM +hFIyJIEYO+R3n8b8ocfsMTvAUVJzXe+KAHQIvgjiAX1e4jo0xjgR4FNJyd8olhYR +BSIZyWZTk5KuFjWztt0SCIAdXLiFBbTKsLR044jvjBSIx8XDfXv3kYx6YcbxbnsK +noIkTIopteVr069/pyL6PiUx +-----END PRIVATE KEY----- diff --git a/tests/mysql-derives.rs b/tests/mysql-derives.rs deleted file mode 100644 index e6b2999d..00000000 --- a/tests/mysql-derives.rs +++ /dev/null @@ -1,70 +0,0 @@ -use sqlx::MySql; -use sqlx_test::test_type; -use std::fmt::Debug; - -// Transparent types are rust-side wrappers over DB types -#[derive(PartialEq, Debug, sqlx::Type)] -#[sqlx(transparent)] -struct Transparent(i32); - -// "Weak" enums map to an integer type indicated by #[repr] -#[derive(PartialEq, Copy, Clone, Debug, sqlx::Type)] -#[repr(i32)] -enum Weak { - One = 0, - Two = 2, - Three = 4, -} - -// "Strong" enums can map to TEXT or a custom enum -#[derive(PartialEq, Debug, sqlx::Type)] -#[sqlx(rename_all = "lowercase")] -enum ColorLower { - Red, - Green, - Blue, -} -#[derive(PartialEq, Debug, sqlx::Type)] -#[sqlx(rename_all = "snake_case")] -enum ColorSnake { - RedGreen, - BlueBlack, -} -#[derive(PartialEq, Debug, sqlx::Type)] -#[sqlx(rename_all = "uppercase")] -enum ColorUpper { - Red, - Green, - Blue, -} - -test_type!(transparent( - MySql, - Transparent, - "0" == Transparent(0), - "23523" == Transparent(23523) -)); - -test_type!(weak_enum( - MySql, - Weak, - "0" == Weak::One, - "2" == Weak::Two, - "4" == Weak::Three -)); - -test_type!(strong_color_lower_enum( - MySql, - ColorLower, - "'green'" == ColorLower::Green -)); -test_type!(strong_color_snake_enum( - MySql, - ColorSnake, - "'red_green'" == ColorSnake::RedGreen -)); -test_type!(strong_color_upper_enum( - MySql, - ColorUpper, - "'GREEN'" == ColorUpper::Green -)); diff --git a/tests/mysql-macros.rs b/tests/mysql-macros.rs deleted file mode 100644 index 755d3851..00000000 --- a/tests/mysql-macros.rs +++ /dev/null @@ -1,99 +0,0 @@ -use sqlx::MySql; -use sqlx_test::new; - -#[cfg_attr(feature = "runtime-async-std", async_std::test)] -#[cfg_attr(feature = "runtime-tokio", tokio::test)] -async fn macro_select_from_cte() -> anyhow::Result<()> { - let mut conn = new::().await?; - let account = - sqlx::query!("select * from (select (1) as id, 'Herp Derpinson' as name, cast(null as char) email) accounts") - .fetch_one(&mut conn) - .await?; - - assert_eq!(account.id, 1); - assert_eq!(account.name, "Herp Derpinson"); - // MySQL can tell us the nullability of expressions, ain't that cool - assert_eq!(account.email, None); - - Ok(()) -} - -#[cfg_attr(feature = "runtime-async-std", async_std::test)] -#[cfg_attr(feature = "runtime-tokio", tokio::test)] -async fn macro_select_from_cte_bind() -> anyhow::Result<()> { - let mut conn = new::().await?; - let account = sqlx::query!( - "select * from (select (1) as id, 'Herp Derpinson' as name) accounts where id = ?", - 1i32 - ) - .fetch_one(&mut conn) - .await?; - - println!("{:?}", account); - println!("{}: {}", account.id, account.name); - - Ok(()) -} - -#[derive(Debug)] -struct RawAccount { - r#type: i32, - name: Option, -} - -#[cfg_attr(feature = "runtime-async-std", async_std::test)] -#[cfg_attr(feature = "runtime-tokio", tokio::test)] -async fn test_query_as_raw() -> anyhow::Result<()> { - let mut conn = new::().await?; - - let account = sqlx::query_as!( - RawAccount, - "SELECT * from (select 1 as type, cast(null as char) as name) accounts" - ) - .fetch_one(&mut conn) - .await?; - - assert_eq!(account.name, None); - assert_eq!(account.r#type, 1); - - println!("{:?}", account); - - Ok(()) -} - -#[cfg_attr(feature = "runtime-async-std", async_std::test)] -#[cfg_attr(feature = "runtime-tokio", tokio::test)] -async fn test_query_as_bool() -> anyhow::Result<()> { - let mut conn = new::().await?; - - struct Article { - id: i32, - deleted: bool, - } - - let article = sqlx::query_as_unchecked!( - Article, - "select * from (select 51 as id, true as deleted) articles" - ) - .fetch_one(&mut conn) - .await?; - - assert_eq!(51, article.id); - assert_eq!(true, article.deleted); - - Ok(()) -} - -#[cfg_attr(feature = "runtime-async-std", async_std::test)] -#[cfg_attr(feature = "runtime-tokio", tokio::test)] -async fn test_query_bytes() -> anyhow::Result<()> { - let mut conn = new::().await?; - - let rec = sqlx::query!("SELECT X'01AF' as _1") - .fetch_one(&mut conn) - .await?; - - assert_eq!(rec._1, &[0x01_u8, 0xAF_u8]); - - Ok(()) -} diff --git a/tests/mysql-raw.rs b/tests/mysql-raw.rs deleted file mode 100644 index c264e810..00000000 --- a/tests/mysql-raw.rs +++ /dev/null @@ -1,56 +0,0 @@ -//! Tests for the raw (unprepared) query API for MySql. - -use sqlx::{Cursor, Executor, MySql, Row}; -use sqlx_test::new; - -/// Test a simple select expression. This should return the row. -#[cfg_attr(feature = "runtime-async-std", async_std::test)] -#[cfg_attr(feature = "runtime-tokio", tokio::test)] -async fn test_select_expression() -> anyhow::Result<()> { - let mut conn = new::().await?; - - let mut cursor = conn.fetch("SELECT 5"); - let row = cursor.next().await?.unwrap(); - - assert!(5i32 == row.try_get::(0)?); - - Ok(()) -} - -/// Test that we can interleave reads and writes to the database -/// in one simple query. Using the `Cursor` API we should be -/// able to fetch from both queries in sequence. -#[cfg_attr(feature = "runtime-async-std", async_std::test)] -#[cfg_attr(feature = "runtime-tokio", tokio::test)] -async fn test_multi_read_write() -> anyhow::Result<()> { - let mut conn = new::().await?; - - let mut cursor = conn.fetch( - " -CREATE TEMPORARY TABLE messages ( - id BIGINT PRIMARY KEY AUTO_INCREMENT, - text TEXT NOT NULL -); - -SELECT 'Hello World' as _1; - -INSERT INTO messages (text) VALUES ('this is a test'); - -SELECT id, text FROM messages; - ", - ); - - let row = cursor.next().await?.unwrap(); - - assert!("Hello World" == row.try_get::<&str, _>("_1")?); - - let row = cursor.next().await?.unwrap(); - - let id: i64 = row.try_get("id")?; - let text: &str = row.try_get("text")?; - - assert_eq!(1_i64, id); - assert_eq!("this is a test", text); - - Ok(()) -} diff --git a/tests/mysql.rs b/tests/mysql.rs deleted file mode 100644 index 24590178..00000000 --- a/tests/mysql.rs +++ /dev/null @@ -1,234 +0,0 @@ -use futures::TryStreamExt; -use sqlx::{mysql::MySqlQueryAs, Connection, Executor, MySql, MySqlPool}; -use sqlx_test::new; -use std::time::Duration; - -#[cfg_attr(feature = "runtime-async-std", async_std::test)] -#[cfg_attr(feature = "runtime-tokio", tokio::test)] -async fn it_connects() -> anyhow::Result<()> { - Ok(new::().await?.ping().await?) -} - -#[cfg_attr(feature = "runtime-async-std", async_std::test)] -#[cfg_attr(feature = "runtime-tokio", tokio::test)] -async fn it_drops_results_in_affected_rows() -> anyhow::Result<()> { - let mut conn = new::().await?; - - // ~1800 rows should be iterated and dropped - let affected = conn.execute("select * from mysql.time_zone").await?; - - // In MySQL, rows being returned isn't enough to flag it as an _affected_ row - assert_eq!(0, affected); - - Ok(()) -} - -#[cfg_attr(feature = "runtime-async-std", async_std::test)] -#[cfg_attr(feature = "runtime-tokio", tokio::test)] -async fn it_executes() -> anyhow::Result<()> { - let mut conn = new::().await?; - - let _ = conn - .execute( - r#" -CREATE TEMPORARY TABLE users (id INTEGER PRIMARY KEY) - "#, - ) - .await?; - - for index in 1..=10_i32 { - let cnt = sqlx::query("INSERT INTO users (id) VALUES (?)") - .bind(index) - .execute(&mut conn) - .await?; - - assert_eq!(cnt, 1); - } - - let sum: i32 = sqlx::query_as("SELECT id FROM users") - .fetch(&mut conn) - .try_fold(0_i32, |acc, (x,): (i32,)| async move { Ok(acc + x) }) - .await?; - - assert_eq!(sum, 55); - - Ok(()) -} - -#[cfg_attr(feature = "runtime-async-std", async_std::test)] -#[cfg_attr(feature = "runtime-tokio", tokio::test)] -async fn it_selects_null() -> anyhow::Result<()> { - let mut conn = new::().await?; - - let (val,): (Option,) = sqlx::query_as("SELECT NULL").fetch_one(&mut conn).await?; - - assert!(val.is_none()); - - Ok(()) -} - -#[cfg_attr(feature = "runtime-async-std", async_std::test)] -#[cfg_attr(feature = "runtime-tokio", tokio::test)] -async fn test_describe() -> anyhow::Result<()> { - let mut conn = new::().await?; - - let _ = conn - .execute( - r#" - CREATE TEMPORARY TABLE describe_test ( - id int primary key auto_increment, - name text not null, - hash blob - ) - "#, - ) - .await?; - - let describe = conn - .describe("select nt.*, false from describe_test nt") - .await?; - - assert_eq!(describe.result_columns[0].non_null, Some(true)); - assert_eq!( - describe.result_columns[0] - .type_info - .as_ref() - .unwrap() - .to_string(), - "INT" - ); - assert_eq!(describe.result_columns[1].non_null, Some(true)); - assert_eq!( - describe.result_columns[1] - .type_info - .as_ref() - .unwrap() - .to_string(), - "TEXT" - ); - assert_eq!(describe.result_columns[2].non_null, Some(false)); - assert_eq!( - describe.result_columns[2] - .type_info - .as_ref() - .unwrap() - .to_string(), - "BLOB" - ); - assert_eq!(describe.result_columns[3].non_null, Some(true)); - - let bool_ty_name = describe.result_columns[3] - .type_info - .as_ref() - .unwrap() - .to_string(); - - // MySQL 5.7, 8 and MariaDB 10.1 return BIG_INT, MariaDB 10.4 returns INT (optimization?) - assert!( - ["BIGINT", "INT"].contains(&bool_ty_name.as_str()), - "type name returned: {}", - bool_ty_name - ); - - Ok(()) -} - -#[cfg_attr(feature = "runtime-async-std", async_std::test)] -#[cfg_attr(feature = "runtime-tokio", tokio::test)] -async fn pool_immediately_fails_with_db_error() -> anyhow::Result<()> { - // Malform the database url by changing the password - let url = dotenv::var("DATABASE_URL")?.replace("password", "not-the-password"); - - let pool = MySqlPool::new(&url).await?; - - let res = pool.acquire().await; - - match res { - Err(sqlx::Error::Database(err)) if (*err).message().contains("Access denied") => { - // Access was properly denied - } - - Err(e) => panic!("unexpected error: {:?}", e), - - Ok(_) => panic!("unexpected ok"), - } - - Ok(()) -} - -// run with `cargo test --features mysql -- --ignored --nocapture pool_smoke_test` -#[ignore] -#[cfg_attr(feature = "runtime-async-std", async_std::test)] -#[cfg_attr(feature = "runtime-tokio", tokio::test)] -async fn pool_smoke_test() -> anyhow::Result<()> { - #[cfg(feature = "runtime-tokio")] - use tokio::{task::spawn, time::delay_for as sleep, time::timeout}; - - #[cfg(feature = "runtime-async-std")] - use async_std::{future::timeout, task::sleep, task::spawn}; - - eprintln!("starting pool"); - - let pool = MySqlPool::builder() - .connect_timeout(Duration::from_secs(5)) - .min_size(5) - .max_size(10) - .build(&dotenv::var("DATABASE_URL")?) - .await?; - - // spin up more tasks than connections available, and ensure we don't deadlock - for i in 0..20 { - let pool = pool.clone(); - spawn(async move { - loop { - if let Err(e) = sqlx::query("select 1 + 1").execute(&mut &pool).await { - eprintln!("pool task {} dying due to {}", i, e); - break; - } - } - }); - } - - for _ in 0..5 { - let pool = pool.clone(); - spawn(async move { - while !pool.is_closed() { - // drop acquire() futures in a hot loop - // https://github.com/launchbadge/sqlx/issues/83 - drop(pool.acquire()); - } - }); - } - - eprintln!("sleeping for 30 seconds"); - - sleep(Duration::from_secs(30)).await; - - assert_eq!(pool.size(), 10); - - eprintln!("closing pool"); - - timeout(Duration::from_secs(30), pool.close()).await?; - - eprintln!("pool closed successfully"); - - Ok(()) -} - -#[cfg_attr(feature = "runtime-async-std", async_std::test)] -#[cfg_attr(feature = "runtime-tokio", tokio::test)] -async fn test_fetch_one_and_ping() -> anyhow::Result<()> { - let mut conn = new::().await?; - - let (_id,): (i32,) = sqlx::query_as("SELECT 1 as id") - .fetch_one(&mut conn) - .await?; - - conn.ping().await?; - - let (_id,): (i32,) = sqlx::query_as("SELECT 1 as id") - .fetch_one(&mut conn) - .await?; - - Ok(()) -} diff --git a/tests/mysql/describe.rs b/tests/mysql/describe.rs new file mode 100644 index 00000000..47796ef1 --- /dev/null +++ b/tests/mysql/describe.rs @@ -0,0 +1,39 @@ +use futures::TryStreamExt; +use sqlx::mysql::{MySql, MySqlRow}; +use sqlx::{Connection, Executor, Row}; +use sqlx_core::describe::Column; +use sqlx_test::new; + +fn type_names(columns: &[Column]) -> Vec { + columns + .iter() + .filter_map(|col| Some(col.type_info.as_ref()?.to_string())) + .collect() +} + +#[sqlx_macros::test] +async fn it_describes_simple() -> anyhow::Result<()> { + let mut conn = new::().await?; + + let d = conn.describe("SELECT * FROM tweet").await?; + let columns = d.columns; + + assert_eq!(columns[0].name, "id"); + assert_eq!(columns[1].name, "created_at"); + assert_eq!(columns[2].name, "text"); + assert_eq!(columns[3].name, "owner_id"); + + assert_eq!(columns[0].not_null, Some(true)); + assert_eq!(columns[1].not_null, Some(true)); + assert_eq!(columns[2].not_null, Some(true)); + assert_eq!(columns[3].not_null, Some(false)); + + let column_type_names = type_names(&columns); + + assert_eq!(column_type_names[0], "BIGINT"); + assert_eq!(column_type_names[1], "TIMESTAMP"); + assert_eq!(column_type_names[2], "TEXT"); + assert_eq!(column_type_names[3], "BIGINT"); + + Ok(()) +} diff --git a/tests/mysql/mysql.rs b/tests/mysql/mysql.rs new file mode 100644 index 00000000..c1193bf8 --- /dev/null +++ b/tests/mysql/mysql.rs @@ -0,0 +1,144 @@ +use futures::TryStreamExt; +use sqlx::mysql::{MySql, MySqlRow}; +use sqlx::{Connection, Executor, Row}; +use sqlx_test::new; + +#[sqlx_macros::test] +async fn it_connects() -> anyhow::Result<()> { + let mut conn = new::().await?; + + conn.ping().await?; + + conn.close().await?; + + Ok(()) +} + +#[sqlx_macros::test] +async fn it_maths() -> anyhow::Result<()> { + let mut conn = new::().await?; + + let value = sqlx::query("select 1 + CAST(? AS SIGNED)") + .bind(5_i32) + .try_map(|row: MySqlRow| row.try_get::(0)) + .fetch_one(&mut conn) + .await?; + + assert_eq!(6i32, value); + + Ok(()) +} + +#[sqlx_macros::test] +async fn it_executes() -> anyhow::Result<()> { + let mut conn = new::().await?; + + let _ = conn + .execute( + r#" +CREATE TEMPORARY TABLE users (id INTEGER PRIMARY KEY); + "#, + ) + .await?; + + for index in 1..=10_i32 { + let cnt = sqlx::query("INSERT INTO users (id) VALUES (?)") + .bind(index) + .execute(&mut conn) + .await?; + + assert_eq!(cnt, 1); + } + + let sum: i32 = sqlx::query("SELECT id FROM users") + .try_map(|row: MySqlRow| row.try_get::(0)) + .fetch(&mut conn) + .try_fold(0_i32, |acc, x| async move { Ok(acc + x) }) + .await?; + + assert_eq!(sum, 55); + + Ok(()) +} + +#[sqlx_macros::test] +async fn it_drops_results_in_affected_rows() -> anyhow::Result<()> { + let mut conn = new::().await?; + + // ~1800 rows should be iterated and dropped + let affected = conn + .execute("select * from mysql.time_zone limit 1575") + .await?; + + // In MySQL, rows being returned isn't enough to flag it as an _affected_ row + assert_eq!(0, affected); + + Ok(()) +} + +#[sqlx_macros::test] +async fn it_selects_null() -> anyhow::Result<()> { + let mut conn = new::().await?; + + let (val,): (Option,) = sqlx::query_as("SELECT NULL").fetch_one(&mut conn).await?; + + assert!(val.is_none()); + + let val: Option = conn.fetch_one("SELECT NULL").await?.try_get(0)?; + + assert!(val.is_none()); + + Ok(()) +} + +#[sqlx_macros::test] +async fn it_can_fetch_one_and_ping() -> anyhow::Result<()> { + let mut conn = new::().await?; + + let (_id,): (i32,) = sqlx::query_as("SELECT 1 as id") + .fetch_one(&mut conn) + .await?; + + conn.ping().await?; + + let (_id,): (i32,) = sqlx::query_as("SELECT 1 as id") + .fetch_one(&mut conn) + .await?; + + Ok(()) +} + +/// Test that we can interleave reads and writes to the database in one simple query. +#[sqlx_macros::test] +async fn it_interleaves_reads_and_writes() -> anyhow::Result<()> { + let mut conn = new::().await?; + + let mut s = conn.fetch( + " +CREATE TEMPORARY TABLE messages ( + id BIGINT PRIMARY KEY AUTO_INCREMENT, + text TEXT NOT NULL +); + +SELECT 'Hello World' as _1; + +INSERT INTO messages (text) VALUES ('this is a test'); + +SELECT id, text FROM messages; + ", + ); + + let row = s.try_next().await?.unwrap(); + + assert!("Hello World" == row.try_get::<&str, _>("_1")?); + + let row = s.try_next().await?.unwrap(); + + let id: i64 = row.try_get("id")?; + let text: &str = row.try_get("text")?; + + assert_eq!(1_i64, id); + assert_eq!("this is a test", text); + + Ok(()) +} diff --git a/tests/mysql/setup.sql b/tests/mysql/setup.sql new file mode 100644 index 00000000..17b35143 --- /dev/null +++ b/tests/mysql/setup.sql @@ -0,0 +1,8 @@ +-- https://github.com/prisma/database-schema-examples/tree/master/postgres/basic-twitter#basic-twitter +CREATE TABLE tweet +( + id BIGINT PRIMARY KEY AUTO_INCREMENT, + created_at TIMESTAMP NOT NULL DEFAULT NOW(), + text TEXT NOT NULL, + owner_id BIGINT +); diff --git a/tests/mysql-types.rs b/tests/mysql/types.rs similarity index 67% rename from tests/mysql-types.rs rename to tests/mysql/types.rs index a393e3e3..ee1457c2 100644 --- a/tests/mysql-types.rs +++ b/tests/mysql/types.rs @@ -1,49 +1,35 @@ extern crate time_ as time; -use sqlx::MySql; +use sqlx::mysql::MySql; use sqlx_test::test_type; -test_type!(null( - MySql, - Option, - "NULL" == None:: -)); +test_type!(bool(MySql, "false" == false, "true" == true)); -test_type!(bool(MySql, bool, "false" == false, "true" == true)); +test_type!(u8(MySql, "CAST(253 AS UNSIGNED)" == 253_u8)); +test_type!(i8(MySql, "5" == 5_i8, "0" == 0_i8)); -test_type!(u8(MySql, u8, "CAST(253 AS UNSIGNED)" == 253_u8)); -test_type!(i8(MySql, i8, "5" == 5_i8, "0" == 0_i8)); +test_type!(u16(MySql, "CAST(21415 AS UNSIGNED)" == 21415_u16)); +test_type!(i16(MySql, "21415" == 21415_i16)); -test_type!(u16(MySql, u16, "CAST(21415 AS UNSIGNED)" == 21415_u16)); -test_type!(i16(MySql, i16, "21415" == 21415_i16)); +test_type!(u32(MySql, "CAST(2141512 AS UNSIGNED)" == 2141512_u32)); +test_type!(i32(MySql, "2141512" == 2141512_i32)); -test_type!(u32(MySql, u32, "CAST(2141512 AS UNSIGNED)" == 2141512_u32)); -test_type!(i32(MySql, i32, "2141512" == 2141512_i32)); +test_type!(u64(MySql, "CAST(2141512 AS UNSIGNED)" == 2141512_u64)); +test_type!(i64(MySql, "2141512" == 2141512_i64)); -test_type!(u64(MySql, u64, "CAST(2141512 AS UNSIGNED)" == 2141512_u64)); -test_type!(i64(MySql, i64, "2141512" == 2141512_i64)); - -test_type!(double(MySql, f64, "3.14159265E0" == 3.14159265f64)); +test_type!(f64(MySql, "3.14159265e0" == 3.14159265_f64)); // NOTE: This behavior can be very surprising. MySQL implicitly widens FLOAT bind parameters // to DOUBLE. This results in the weirdness you see below. MySQL generally recommends to stay // away from FLOATs. -test_type!(float( - MySql, - f32, - "3.1410000324249268e0" == 3.141f32 as f64 as f32 -)); +test_type!(f32(MySql, "3.1410000324249268e0" == 3.141f32 as f64 as f32)); -test_type!(string( - MySql, - String, +test_type!(string(MySql, "'helloworld'" == "helloworld", "''" == "" )); -test_type!(bytes( - MySql, - Vec, +test_type!(bytes>(MySql, "X'DEADBEEF'" == vec![0xDE_u8, 0xAD, 0xBE, 0xEF], "X''" @@ -57,28 +43,24 @@ mod chrono { use super::*; use sqlx::types::chrono::{DateTime, NaiveDate, NaiveDateTime, NaiveTime, Utc}; - test_type!(chrono_date( + test_type!(chrono_date( MySql, - NaiveDate, "DATE '2001-01-05'" == NaiveDate::from_ymd(2001, 1, 5), "DATE '2050-11-23'" == NaiveDate::from_ymd(2050, 11, 23) )); - test_type!(chrono_time( + test_type!(chrono_time( MySql, - NaiveTime, "TIME '05:10:20.115100'" == NaiveTime::from_hms_micro(5, 10, 20, 115100) )); - test_type!(chrono_date_time( + test_type!(chrono_date_time( MySql, - NaiveDateTime, "TIMESTAMP '2019-01-02 05:10:20'" == NaiveDate::from_ymd(2019, 1, 2).and_hms(5, 10, 20) )); - test_type!(chrono_timestamp( + test_type!(chrono_timestamp>( MySql, - DateTime::, "TIMESTAMP '2019-01-02 05:10:20.115100'" == DateTime::::from_utc( NaiveDate::from_ymd(2019, 1, 2).and_hms_micro(5, 10, 20, 115100), @@ -93,30 +75,26 @@ mod time_tests { use sqlx::types::time::{Date, OffsetDateTime, PrimitiveDateTime, Time}; use time::{date, time}; - test_type!(time_date( + test_type!(time_date( MySql, - Date, "DATE '2001-01-05'" == date!(2001 - 1 - 5), "DATE '2050-11-23'" == date!(2050 - 11 - 23) )); - test_type!(time_time( + test_type!(time_time