mirror of
https://github.com/launchbadge/sqlx.git
synced 2025-12-25 17:50:23 +00:00
feat: introduce docker-compose based testing for running locally against many database combinations
This commit is contained in:
parent
afd831b0d3
commit
e5b6047009
4
.gitignore
vendored
4
.gitignore
vendored
@ -9,6 +9,4 @@ target/
|
||||
|
||||
# Environment
|
||||
.env
|
||||
|
||||
tests/fixtures/*.sqlite-shm
|
||||
tests/fixtures/*.sqlite-wal
|
||||
!tests/.env
|
||||
|
||||
1
Cargo.lock
generated
1
Cargo.lock
generated
@ -2042,6 +2042,7 @@ dependencies = [
|
||||
"serde_json",
|
||||
"sqlx-core",
|
||||
"sqlx-macros",
|
||||
"sqlx-rt",
|
||||
"sqlx-test",
|
||||
"time 0.2.16",
|
||||
"tokio 0.2.21",
|
||||
|
||||
62
Cargo.toml
62
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" ]
|
||||
|
||||
@ -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);
|
||||
|
||||
@ -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)]
|
||||
|
||||
@ -160,8 +160,8 @@ fn assert_pool_traits() {
|
||||
fn assert_send_sync<T: Send + Sync>() {}
|
||||
fn assert_clone<T: Clone>() {}
|
||||
|
||||
fn assert_pool<C: Connect>() {
|
||||
assert_send_sync::<Pool<C>>();
|
||||
assert_clone::<Pool<C>>();
|
||||
fn assert_pool<DB: Database>() {
|
||||
assert_send_sync::<Pool<DB>>();
|
||||
assert_clone::<Pool<DB>>();
|
||||
}
|
||||
}
|
||||
|
||||
@ -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"[..]);
|
||||
}
|
||||
|
||||
@ -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)]
|
||||
|
||||
@ -308,7 +308,7 @@ where
|
||||
{
|
||||
Query {
|
||||
database: PhantomData,
|
||||
arguments: Default::default(),
|
||||
arguments: Some(Default::default()),
|
||||
query: sql,
|
||||
}
|
||||
}
|
||||
|
||||
@ -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<Either<u64, ()>, 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
|
||||
|
||||
@ -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()
|
||||
}
|
||||
|
||||
@ -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(
|
||||
|
||||
@ -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<String>, $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<String>, $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} <=> ?, '<UNKNOWN>' 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"
|
||||
};
|
||||
}
|
||||
|
||||
1
tests/.dockerignore
Normal file
1
tests/.dockerignore
Normal file
@ -0,0 +1 @@
|
||||
*
|
||||
2
tests/.env
Normal file
2
tests/.env
Normal file
@ -0,0 +1,2 @@
|
||||
# environment values for docker-compose
|
||||
COMPOSE_PROJECT_NAME=sqlx
|
||||
4
tests/Dockerfile
Normal file
4
tests/Dockerfile
Normal file
@ -0,0 +1,4 @@
|
||||
FROM rust:stretch
|
||||
|
||||
RUN rustup toolchain install nightly-2020-05-30 && \
|
||||
rustup default nightly-2020-05-30
|
||||
21
tests/certs/ca.crt
Normal file
21
tests/certs/ca.crt
Normal file
@ -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-----
|
||||
1
tests/certs/ca.srl
Normal file
1
tests/certs/ca.srl
Normal file
@ -0,0 +1 @@
|
||||
1FCE7310D7E3C29BA5ED14EEF264E66C25727029
|
||||
19
tests/certs/server.crt
Normal file
19
tests/certs/server.crt
Normal file
@ -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-----
|
||||
174
tests/docker-compose.yml
Normal file
174
tests/docker-compose.yml
Normal file
@ -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
|
||||
7
tests/fixtures/mysql.sql
vendored
7
tests/fixtures/mysql.sql
vendored
@ -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
|
||||
);
|
||||
14
tests/fixtures/postgres.sql
vendored
14
tests/fixtures/postgres.sql
vendored
@ -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
|
||||
);
|
||||
28
tests/keys/ca.key
Normal file
28
tests/keys/ca.key
Normal file
@ -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-----
|
||||
28
tests/keys/server.key
Normal file
28
tests/keys/server.key
Normal file
@ -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-----
|
||||
@ -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
|
||||
));
|
||||
@ -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::<MySql>().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::<MySql>().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<String>,
|
||||
}
|
||||
|
||||
#[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::<MySql>().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::<MySql>().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::<MySql>().await?;
|
||||
|
||||
let rec = sqlx::query!("SELECT X'01AF' as _1")
|
||||
.fetch_one(&mut conn)
|
||||
.await?;
|
||||
|
||||
assert_eq!(rec._1, &[0x01_u8, 0xAF_u8]);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
@ -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::<MySql>().await?;
|
||||
|
||||
let mut cursor = conn.fetch("SELECT 5");
|
||||
let row = cursor.next().await?.unwrap();
|
||||
|
||||
assert!(5i32 == row.try_get::<i32, _>(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::<MySql>().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(())
|
||||
}
|
||||
234
tests/mysql.rs
234
tests/mysql.rs
@ -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::<MySql>().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::<MySql>().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::<MySql>().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::<MySql>().await?;
|
||||
|
||||
let (val,): (Option<i32>,) = 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::<MySql>().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::<MySql>().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(())
|
||||
}
|
||||
39
tests/mysql/describe.rs
Normal file
39
tests/mysql/describe.rs
Normal file
@ -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<MySql>]) -> Vec<String> {
|
||||
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::<MySql>().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(())
|
||||
}
|
||||
144
tests/mysql/mysql.rs
Normal file
144
tests/mysql/mysql.rs
Normal file
@ -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::<MySql>().await?;
|
||||
|
||||
conn.ping().await?;
|
||||
|
||||
conn.close().await?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[sqlx_macros::test]
|
||||
async fn it_maths() -> anyhow::Result<()> {
|
||||
let mut conn = new::<MySql>().await?;
|
||||
|
||||
let value = sqlx::query("select 1 + CAST(? AS SIGNED)")
|
||||
.bind(5_i32)
|
||||
.try_map(|row: MySqlRow| row.try_get::<i32, _>(0))
|
||||
.fetch_one(&mut conn)
|
||||
.await?;
|
||||
|
||||
assert_eq!(6i32, value);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[sqlx_macros::test]
|
||||
async fn it_executes() -> anyhow::Result<()> {
|
||||
let mut conn = new::<MySql>().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::<i32, _>(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::<MySql>().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::<MySql>().await?;
|
||||
|
||||
let (val,): (Option<i32>,) = sqlx::query_as("SELECT NULL").fetch_one(&mut conn).await?;
|
||||
|
||||
assert!(val.is_none());
|
||||
|
||||
let val: Option<i32> = 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::<MySql>().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::<MySql>().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(())
|
||||
}
|
||||
8
tests/mysql/setup.sql
Normal file
8
tests/mysql/setup.sql
Normal file
@ -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
|
||||
);
|
||||
@ -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<i16>,
|
||||
"NULL" == None::<i16>
|
||||
));
|
||||
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<String>(MySql,
|
||||
"'helloworld'" == "helloworld",
|
||||
"''" == ""
|
||||
));
|
||||
|
||||
test_type!(bytes(
|
||||
MySql,
|
||||
Vec<u8>,
|
||||
test_type!(bytes<Vec<u8>>(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<NaiveDate>(
|
||||
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<NaiveTime>(
|
||||
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<NaiveDateTime>(
|
||||
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<DateTime::<Utc>>(
|
||||
MySql,
|
||||
DateTime::<Utc>,
|
||||
"TIMESTAMP '2019-01-02 05:10:20.115100'"
|
||||
== DateTime::<Utc>::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<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<Time>(
|
||||
MySql,
|
||||
Time,
|
||||
"TIME '05:10:20.115100'" == time!(5:10:20.115100)
|
||||
));
|
||||
|
||||
test_type!(time_date_time(
|
||||
test_type!(time_date_time<PrimitiveDateTime>(
|
||||
MySql,
|
||||
PrimitiveDateTime,
|
||||
"TIMESTAMP '2019-01-02 05:10:20'" == date!(2019 - 1 - 2).with_time(time!(5:10:20)),
|
||||
"TIMESTAMP '2019-01-02 05:10:20.115100'"
|
||||
== date!(2019 - 1 - 2).with_time(time!(5:10:20.115100))
|
||||
));
|
||||
|
||||
test_type!(time_timestamp(
|
||||
test_type!(time_timestamp<OffsetDateTime>(
|
||||
MySql,
|
||||
OffsetDateTime,
|
||||
"TIMESTAMP '2019-01-02 05:10:20.115100'"
|
||||
== date!(2019 - 1 - 2)
|
||||
.with_time(time!(5:10:20.115100))
|
||||
@ -125,9 +103,8 @@ mod time_tests {
|
||||
}
|
||||
|
||||
#[cfg(feature = "bigdecimal")]
|
||||
test_type!(decimal(
|
||||
test_type!(decimal<sqlx::types::BigDecimal>(
|
||||
MySql,
|
||||
sqlx::types::BigDecimal,
|
||||
"CAST(0 as DECIMAL(0, 0))" == "0".parse::<sqlx::types::BigDecimal>().unwrap(),
|
||||
"CAST(1 AS DECIMAL(1, 0))" == "1".parse::<sqlx::types::BigDecimal>().unwrap(),
|
||||
"CAST(10000 AS DECIMAL(5, 0))" == "10000".parse::<sqlx::types::BigDecimal>().unwrap(),
|
||||
@ -144,10 +121,9 @@ mod json_tests {
|
||||
use sqlx::types::Json;
|
||||
use sqlx_test::test_type;
|
||||
|
||||
test_type!(json(
|
||||
test_type!(json<JsonValue>(
|
||||
MySql,
|
||||
JsonValue,
|
||||
"SELECT CAST({0} AS JSON) <=> CAST(? AS JSON), '<UNKNOWN>' as _1, ? as _2, ? as _3",
|
||||
"SELECT CAST({0} AS JSON) <=> CAST(? AS JSON), CAST({0} AS JSON) as _2, ? as _3",
|
||||
"'\"Hello, World\"'" == json!("Hello, World"),
|
||||
"'\"😎\"'" == json!("😎"),
|
||||
"'\"🙋♀️\"'" == json!("🙋♀️"),
|
||||
@ -160,10 +136,9 @@ mod json_tests {
|
||||
age: u32,
|
||||
}
|
||||
|
||||
test_type!(json_struct(
|
||||
test_type!(json_struct<Json<Friend>>(
|
||||
MySql,
|
||||
Json<Friend>,
|
||||
"SELECT CAST({0} AS JSON) <=> CAST(? AS JSON), '<UNKNOWN>' as _1, ? as _2, ? as _3",
|
||||
"SELECT CAST({0} AS JSON) <=> CAST(? AS JSON), CAST({0} AS JSON) as _2, ? as _3",
|
||||
"\'{\"name\": \"Joe\", \"age\":33}\'" == Json(Friend { name: "Joe".to_string(), age: 33 })
|
||||
));
|
||||
}
|
||||
@ -1,71 +0,0 @@
|
||||
//! Tests for the raw (unprepared) query API for Postgres.
|
||||
|
||||
use sqlx::{Cursor, Executor, Postgres, Row};
|
||||
use sqlx_test::new;
|
||||
|
||||
/// Tests the edge case of executing a completely empty query string.
|
||||
///
|
||||
/// This gets flagged as an `EmptyQueryResponse` in Postgres. We currently
|
||||
/// catch this and just return no rows.
|
||||
#[cfg_attr(feature = "runtime-async-std", async_std::test)]
|
||||
#[cfg_attr(feature = "runtime-tokio", tokio::test)]
|
||||
async fn test_empty_query() -> anyhow::Result<()> {
|
||||
let mut conn = new::<Postgres>().await?;
|
||||
let affected = conn.execute("").await?;
|
||||
|
||||
assert_eq!(affected, 0);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// 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::<Postgres>().await?;
|
||||
|
||||
let mut cursor = conn.fetch("SELECT 5");
|
||||
let row = cursor.next().await?.unwrap();
|
||||
|
||||
assert!(5i32 == row.try_get::<i32, _>(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::<Postgres>().await?;
|
||||
|
||||
let mut cursor = conn.fetch(
|
||||
"
|
||||
CREATE TABLE IF NOT EXISTS _sqlx_test_postgres_5112 (
|
||||
id BIGSERIAL PRIMARY KEY,
|
||||
text TEXT NOT NULL
|
||||
);
|
||||
|
||||
SELECT 'Hello World' as _1;
|
||||
|
||||
INSERT INTO _sqlx_test_postgres_5112 (text) VALUES ('this is a test');
|
||||
|
||||
SELECT id, text FROM _sqlx_test_postgres_5112;
|
||||
",
|
||||
);
|
||||
|
||||
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(())
|
||||
}
|
||||
@ -1,713 +0,0 @@
|
||||
extern crate time_ as time;
|
||||
|
||||
use sqlx::decode::Decode;
|
||||
use sqlx::encode::Encode;
|
||||
use sqlx::postgres::types::raw::{PgNumeric, PgNumericSign, PgRecordDecoder, PgRecordEncoder};
|
||||
use sqlx::postgres::{PgQueryAs, PgRawBuffer, PgTypeInfo, PgValue};
|
||||
use sqlx::{Cursor, Executor, Postgres, Row, Type};
|
||||
use sqlx_test::{new, test_prepared_type, test_type};
|
||||
|
||||
// TODO: With support for concatenation of sql literals in query! macros this should be updated
|
||||
macro_rules! array_macro_test {
|
||||
($name:ident($type:ty, $($sql:literal == $value:expr),+ $(,)?)) => {
|
||||
paste::item! {
|
||||
#[allow(unused_imports)]
|
||||
#[cfg_attr(feature = "runtime-async-std", async_std::test)]
|
||||
#[cfg_attr(feature = "runtime-tokio", tokio::test)]
|
||||
async fn [< test_array_type_ $name >] () -> anyhow::Result<()> {
|
||||
use sqlx::prelude::*;
|
||||
|
||||
let mut conn = sqlx_test::new::<Postgres>().await?;
|
||||
|
||||
$(
|
||||
let v: &[$type] = $value;
|
||||
let res = sqlx::query!($sql, v).fetch_one(&mut conn).await?;
|
||||
|
||||
// these must be unwrapped in query! because postgres infers expressions
|
||||
// to be potentially NULL at all times even if it's impossible to be NULL
|
||||
assert_eq!(res.value.unwrap(), v);
|
||||
assert_eq!(res.out.unwrap(), v);
|
||||
)+
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
test_type!(null(
|
||||
Postgres,
|
||||
Option<i16>,
|
||||
"NULL::int2" == None::<i16>
|
||||
));
|
||||
|
||||
test_type!(bool(
|
||||
Postgres,
|
||||
bool,
|
||||
"false::boolean" == false,
|
||||
"true::boolean" == true
|
||||
));
|
||||
array_macro_test!(bool(
|
||||
bool,
|
||||
"select '{true,false,true}'::boolean[] as value, $1::boolean[] as out" == &[true, false, true]
|
||||
));
|
||||
|
||||
test_type!(i8(Postgres, i8, "120::\"char\"" == 120_i8));
|
||||
test_type!(i16(Postgres, i16, "821::smallint" == 821_i16));
|
||||
|
||||
test_type!(i32(
|
||||
Postgres,
|
||||
i32,
|
||||
"94101::int" == 94101_i32,
|
||||
"-5101::int" == -5101_i32
|
||||
));
|
||||
array_macro_test!(i32(
|
||||
i32,
|
||||
"select '{1,3,-5}'::int[] as value, $1::int[] as out" == &[1, 3, -5]
|
||||
));
|
||||
|
||||
test_type!(u32(Postgres, u32, "94101::oid" == 94101_u32));
|
||||
test_type!(i64(Postgres, i64, "9358295312::bigint" == 9358295312_i64));
|
||||
|
||||
test_type!(f32(Postgres, f32, "9419.122::real" == 9419.122_f32));
|
||||
test_type!(f64(
|
||||
Postgres,
|
||||
f64,
|
||||
"939399419.1225182::double precision" == 939399419.1225182_f64
|
||||
));
|
||||
array_macro_test!(f64(
|
||||
f64,
|
||||
"select '{939399419.1225182,-12.0}'::double precision[] as value, $1::double precision[] as out"
|
||||
== &[939399419.1225182_f64, -12.0]
|
||||
));
|
||||
|
||||
test_type!(string(
|
||||
Postgres,
|
||||
String,
|
||||
"'this is foo'" == "this is foo",
|
||||
"''" == ""
|
||||
));
|
||||
|
||||
test_type!(bytea(
|
||||
Postgres,
|
||||
Vec<u8>,
|
||||
"E'\\\\xDEADBEEF'::bytea"
|
||||
== vec![0xDE_u8, 0xAD, 0xBE, 0xEF],
|
||||
"E'\\\\x'::bytea"
|
||||
== Vec::<u8>::new(),
|
||||
"E'\\\\x0000000052'::bytea"
|
||||
== vec![0_u8, 0, 0, 0, 0x52]
|
||||
));
|
||||
|
||||
// PgNumeric only works on the wire protocol
|
||||
test_prepared_type!(numeric(
|
||||
Postgres,
|
||||
PgNumeric,
|
||||
"0::numeric"
|
||||
== PgNumeric::Number {
|
||||
sign: PgNumericSign::Positive,
|
||||
weight: 0,
|
||||
scale: 0,
|
||||
digits: vec![]
|
||||
},
|
||||
"(-0)::numeric"
|
||||
== PgNumeric::Number {
|
||||
sign: PgNumericSign::Positive,
|
||||
weight: 0,
|
||||
scale: 0,
|
||||
digits: vec![]
|
||||
},
|
||||
"1::numeric"
|
||||
== PgNumeric::Number {
|
||||
sign: PgNumericSign::Positive,
|
||||
weight: 0,
|
||||
scale: 0,
|
||||
digits: vec![1]
|
||||
},
|
||||
"1234::numeric"
|
||||
== PgNumeric::Number {
|
||||
sign: PgNumericSign::Positive,
|
||||
weight: 0,
|
||||
scale: 0,
|
||||
digits: vec![1234]
|
||||
},
|
||||
"10000::numeric"
|
||||
== PgNumeric::Number {
|
||||
sign: PgNumericSign::Positive,
|
||||
weight: 1,
|
||||
scale: 0,
|
||||
digits: vec![1]
|
||||
},
|
||||
"0.1::numeric"
|
||||
== PgNumeric::Number {
|
||||
sign: PgNumericSign::Positive,
|
||||
weight: -1,
|
||||
scale: 1,
|
||||
digits: vec![1000]
|
||||
},
|
||||
"0.01234::numeric"
|
||||
== PgNumeric::Number {
|
||||
sign: PgNumericSign::Positive,
|
||||
weight: -1,
|
||||
scale: 5,
|
||||
digits: vec![123, 4000]
|
||||
},
|
||||
"12.34::numeric"
|
||||
== PgNumeric::Number {
|
||||
sign: PgNumericSign::Positive,
|
||||
weight: 0,
|
||||
scale: 2,
|
||||
digits: vec![12, 3400]
|
||||
},
|
||||
"'NaN'::numeric" == PgNumeric::NotANumber,
|
||||
));
|
||||
|
||||
#[cfg(feature = "bigdecimal")]
|
||||
test_type!(decimal(
|
||||
Postgres,
|
||||
sqlx::types::BigDecimal,
|
||||
// https://github.com/launchbadge/sqlx/issues/283
|
||||
"0::numeric" == "0".parse::<sqlx::types::BigDecimal>().unwrap(),
|
||||
"1::numeric" == "1".parse::<sqlx::types::BigDecimal>().unwrap(),
|
||||
"10000::numeric" == "10000".parse::<sqlx::types::BigDecimal>().unwrap(),
|
||||
"0.1::numeric" == "0.1".parse::<sqlx::types::BigDecimal>().unwrap(),
|
||||
"0.01234::numeric" == "0.01234".parse::<sqlx::types::BigDecimal>().unwrap(),
|
||||
"12.34::numeric" == "12.34".parse::<sqlx::types::BigDecimal>().unwrap(),
|
||||
"12345.6789::numeric" == "12345.6789".parse::<sqlx::types::BigDecimal>().unwrap(),
|
||||
));
|
||||
|
||||
// TODO: This is a minimal example that reproduces a typechecking error with
|
||||
// arrays of BigDecimal in macros.
|
||||
//
|
||||
// The error is:
|
||||
// error: unsupported type _NUMERIC for param #1
|
||||
//
|
||||
// The implementation for bigdecimal is of the same form as all the other types.
|
||||
// My (oeb25) hypothesis is that it is due to some overlap with PgNumeric, but I've been
|
||||
// conclude any results.
|
||||
// I have left the implementation in its ill form. It should not interfere with any of the other
|
||||
// types, but it just doesn't compile if you try to use arrays of bigdecimal in query macros.
|
||||
|
||||
// #[cfg(feature = "bigdecimal")]
|
||||
// #[test]
|
||||
// fn minimal_decimal_macro_repro() {
|
||||
// use sqlx::prelude::*;
|
||||
// let v: &[sqlx::types::BigDecimal] = &[];
|
||||
// sqlx::query!("select $1::numeric[] as out", v);
|
||||
// }
|
||||
|
||||
// array_macro_test!(decimal(
|
||||
// sqlx::types::BigDecimal,
|
||||
// "select '{12345.6789}'::numeric[] as value, $1::numeric[] as out"
|
||||
// == &["12345.6789".parse::<sqlx::types::BigDecimal>().unwrap()]
|
||||
// ));
|
||||
|
||||
#[cfg(feature = "uuid")]
|
||||
test_type!(uuid(
|
||||
Postgres,
|
||||
sqlx::types::Uuid,
|
||||
"'b731678f-636f-4135-bc6f-19440c13bd19'::uuid"
|
||||
== sqlx::types::Uuid::parse_str("b731678f-636f-4135-bc6f-19440c13bd19").unwrap(),
|
||||
"'00000000-0000-0000-0000-000000000000'::uuid"
|
||||
== sqlx::types::Uuid::parse_str("00000000-0000-0000-0000-000000000000").unwrap()
|
||||
));
|
||||
#[cfg(feature = "uuid")]
|
||||
array_macro_test!(uuid(sqlx::types::Uuid, "select '{b731678f-636f-4135-bc6f-19440c13bd19,00000000-0000-0000-0000-000000000000}'::uuid[] as value, $1::uuid[] as out"
|
||||
== &[
|
||||
sqlx::types::Uuid::parse_str("b731678f-636f-4135-bc6f-19440c13bd19").unwrap(),
|
||||
sqlx::types::Uuid::parse_str("00000000-0000-0000-0000-000000000000").unwrap()
|
||||
]));
|
||||
|
||||
#[cfg(feature = "ipnetwork")]
|
||||
test_type!(ipnetwork(
|
||||
Postgres,
|
||||
sqlx::types::ipnetwork::IpNetwork,
|
||||
"'127.0.0.1'::inet"
|
||||
== "127.0.0.1"
|
||||
.parse::<sqlx::types::ipnetwork::IpNetwork>()
|
||||
.unwrap(),
|
||||
"'8.8.8.8/24'::inet"
|
||||
== "8.8.8.8/24"
|
||||
.parse::<sqlx::types::ipnetwork::IpNetwork>()
|
||||
.unwrap(),
|
||||
"'::ffff:1.2.3.0'::inet"
|
||||
== "::ffff:1.2.3.0"
|
||||
.parse::<sqlx::types::ipnetwork::IpNetwork>()
|
||||
.unwrap(),
|
||||
"'2001:4f8:3:ba::/64'::inet"
|
||||
== "2001:4f8:3:ba::/64"
|
||||
.parse::<sqlx::types::ipnetwork::IpNetwork>()
|
||||
.unwrap(),
|
||||
"'192.168'::cidr"
|
||||
== "192.168.0.0/24"
|
||||
.parse::<sqlx::types::ipnetwork::IpNetwork>()
|
||||
.unwrap(),
|
||||
"'::ffff:1.2.3.0/120'::cidr"
|
||||
== "::ffff:1.2.3.0/120"
|
||||
.parse::<sqlx::types::ipnetwork::IpNetwork>()
|
||||
.unwrap(),
|
||||
));
|
||||
#[cfg(feature = "ipnetwork")]
|
||||
array_macro_test!(ipnetwork(
|
||||
sqlx::types::ipnetwork::IpNetwork,
|
||||
"select '{127.0.0.1,8.8.8.8/24}'::inet[] as value, $1::inet[] as out"
|
||||
== &[
|
||||
"127.0.0.1"
|
||||
.parse::<sqlx::types::ipnetwork::IpNetwork>()
|
||||
.unwrap(),
|
||||
"8.8.8.8/24"
|
||||
.parse::<sqlx::types::ipnetwork::IpNetwork>()
|
||||
.unwrap()
|
||||
]
|
||||
));
|
||||
|
||||
#[cfg(feature = "chrono")]
|
||||
mod chrono {
|
||||
use sqlx::types::chrono::{DateTime, NaiveDate, NaiveDateTime, NaiveTime, Utc};
|
||||
|
||||
use super::*;
|
||||
|
||||
test_type!(chrono_date(
|
||||
Postgres,
|
||||
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(
|
||||
Postgres,
|
||||
NaiveTime,
|
||||
"TIME '05:10:20.115100'" == NaiveTime::from_hms_micro(5, 10, 20, 115100)
|
||||
));
|
||||
|
||||
test_type!(chrono_date_time(
|
||||
Postgres,
|
||||
NaiveDateTime,
|
||||
"'2019-01-02 05:10:20'::timestamp" == NaiveDate::from_ymd(2019, 1, 2).and_hms(5, 10, 20)
|
||||
));
|
||||
array_macro_test!(chrono_date_time(
|
||||
NaiveDateTime,
|
||||
"select '{2019-01-02 05:10:20}'::timestamp[] as value, $1::timestamp[] as out"
|
||||
== &[NaiveDate::from_ymd(2019, 1, 2).and_hms(5, 10, 20)]
|
||||
));
|
||||
|
||||
test_type!(chrono_date_time_tz(
|
||||
Postgres,
|
||||
DateTime::<Utc>,
|
||||
"TIMESTAMPTZ '2019-01-02 05:10:20.115100'"
|
||||
== DateTime::<Utc>::from_utc(
|
||||
NaiveDate::from_ymd(2019, 1, 2).and_hms_micro(5, 10, 20, 115100),
|
||||
Utc,
|
||||
)
|
||||
));
|
||||
// TODO: Can't seem to get this to work
|
||||
// array_macro_test!(chrono_date_time_tz(
|
||||
// DateTime::<Utc>,
|
||||
// "select ARRAY[TIMESTAMPTZ '2019-01-02 05:10:20.115100'] as value, $1::TIMESTAMPTZ as out"
|
||||
// == &[DateTime::<Utc>::from_utc(
|
||||
// NaiveDate::from_ymd(2019, 1, 2).and_hms_micro(5, 10, 20, 115100),
|
||||
// Utc,
|
||||
// )]
|
||||
// ));
|
||||
}
|
||||
|
||||
#[cfg(feature = "time")]
|
||||
mod time_tests {
|
||||
use super::*;
|
||||
use sqlx::types::time::{Date, OffsetDateTime, PrimitiveDateTime, Time};
|
||||
use time::{date, time};
|
||||
|
||||
test_type!(time_date(
|
||||
Postgres,
|
||||
Date,
|
||||
"DATE '2001-01-05'" == date!(2001 - 1 - 5),
|
||||
"DATE '2050-11-23'" == date!(2050 - 11 - 23)
|
||||
));
|
||||
|
||||
test_type!(time_time(
|
||||
Postgres,
|
||||
Time,
|
||||
"TIME '05:10:20.115100'" == time!(5:10:20.115100)
|
||||
));
|
||||
|
||||
test_type!(time_date_time(
|
||||
Postgres,
|
||||
PrimitiveDateTime,
|
||||
"TIMESTAMP '2019-01-02 05:10:20'" == date!(2019 - 1 - 2).with_time(time!(5:10:20)),
|
||||
"TIMESTAMP '2019-01-02 05:10:20.115100'"
|
||||
== date!(2019 - 1 - 2).with_time(time!(5:10:20.115100))
|
||||
));
|
||||
|
||||
test_type!(time_timestamp(
|
||||
Postgres,
|
||||
OffsetDateTime,
|
||||
"TIMESTAMPTZ '2019-01-02 05:10:20.115100'"
|
||||
== date!(2019 - 1 - 2)
|
||||
.with_time(time!(5:10:20.115100))
|
||||
.assume_utc()
|
||||
));
|
||||
}
|
||||
|
||||
// This is trying to break my complete lack of understanding of null bitmaps for array/record
|
||||
// decoding. The docs in pg are either wrong or I'm reading the wrong docs.
|
||||
test_type!(lots_of_nulls_vec(Postgres, Vec<Option<bool>>,
|
||||
"ARRAY[NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, true]::bool[]" == {
|
||||
vec![None, None, None, None, None, None, None, None, None, None, None, None, None, None, None, None, None, None, None, None, None, None, None, Some(true)]
|
||||
},
|
||||
));
|
||||
|
||||
test_type!(bool_vec(Postgres, Vec<bool>,
|
||||
"ARRAY[true, true, false, true]::bool[]" == vec![true, true, false, true],
|
||||
));
|
||||
|
||||
test_type!(bool_opt_vec(Postgres, Vec<Option<bool>>,
|
||||
"ARRAY[NULL, true, NULL, false]::bool[]" == vec![None, Some(true), None, Some(false)],
|
||||
));
|
||||
|
||||
test_type!(f32_vec(Postgres, Vec<f32>,
|
||||
"ARRAY[0.0, 1.0, 3.14, 1.234, -0.002, 100000.0]::real[]" == vec![0.0_f32, 1.0, 3.14, 1.234, -0.002, 100000.0],
|
||||
));
|
||||
|
||||
test_type!(f64_vec(Postgres, Vec<f64>,
|
||||
"ARRAY[0.0, 1.0, 3.14, 1.234, -0.002, 100000.0]::double precision[]" == vec![0.0_f64, 1.0, 3.14, 1.234, -0.002, 100000.0],
|
||||
));
|
||||
|
||||
test_type!(i16_vec(Postgres, Vec<i16>,
|
||||
"ARRAY[1, 152, -12412]::smallint[]" == vec![1_i16, 152, -12412],
|
||||
"ARRAY[]::smallint[]" == Vec::<i16>::new(),
|
||||
"ARRAY[0]::smallint[]" == vec![0_i16]
|
||||
));
|
||||
|
||||
test_type!(string_vec(Postgres, Vec<String>,
|
||||
"ARRAY['', '\"']::text[]"
|
||||
== vec!["".to_string(), "\"".to_string()],
|
||||
|
||||
"ARRAY['Hello, World', '', 'Goodbye']::text[]"
|
||||
== vec!["Hello, World".to_string(), "".to_string(), "Goodbye".to_string()],
|
||||
));
|
||||
|
||||
//
|
||||
// These require some annoyingly different tests as anonymous records cannot be read from the
|
||||
// database. If someone enterprising comes along and wants to try and just the macro to handle
|
||||
// this, that would be super awesome.
|
||||
//
|
||||
|
||||
#[cfg_attr(feature = "runtime-async-std", async_std::test)]
|
||||
#[cfg_attr(feature = "runtime-tokio", tokio::test)]
|
||||
async fn test_prepared_anonymous_record() -> anyhow::Result<()> {
|
||||
let mut conn = new::<Postgres>().await?;
|
||||
|
||||
// Tuple of no elements is not possible
|
||||
// Tuple of 1 element requires a concrete type
|
||||
// Tuple with a NULL requires a concrete type
|
||||
|
||||
// Tuple of 2 elements
|
||||
let rec: ((bool, i32),) = sqlx::query_as("SELECT (true, 23512)")
|
||||
.fetch_one(&mut conn)
|
||||
.await?;
|
||||
|
||||
assert_eq!((rec.0).0, true);
|
||||
assert_eq!((rec.0).1, 23512);
|
||||
|
||||
// Tuple with an empty string
|
||||
let rec: ((bool, String),) = sqlx::query_as("SELECT (true,'')")
|
||||
.fetch_one(&mut conn)
|
||||
.await?;
|
||||
|
||||
assert_eq!((rec.0).1, "");
|
||||
|
||||
// Tuple with a string with an interior comma
|
||||
let rec: ((bool, String),) = sqlx::query_as("SELECT (true,'Hello, World!')")
|
||||
.fetch_one(&mut conn)
|
||||
.await?;
|
||||
|
||||
assert_eq!((rec.0).1, "Hello, World!");
|
||||
|
||||
// Tuple with a string with an emoji
|
||||
let rec: ((bool, String),) = sqlx::query_as("SELECT (true,'Hello, 🐕!')")
|
||||
.fetch_one(&mut conn)
|
||||
.await?;
|
||||
|
||||
assert_eq!((rec.0).1, "Hello, 🐕!");
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[cfg_attr(feature = "runtime-async-std", async_std::test)]
|
||||
#[cfg_attr(feature = "runtime-tokio", tokio::test)]
|
||||
async fn test_unprepared_anonymous_record() -> anyhow::Result<()> {
|
||||
let mut conn = new::<Postgres>().await?;
|
||||
|
||||
// Tuple of no elements is not possible
|
||||
// Tuple of 1 element requires a concrete type
|
||||
// Tuple with a NULL requires a concrete type
|
||||
|
||||
// Tuple of 2 elements
|
||||
let mut cursor = conn.fetch("SELECT (true, 23512)");
|
||||
let row = cursor.next().await?.unwrap();
|
||||
let rec: (bool, i32) = row.get(0);
|
||||
|
||||
assert_eq!(rec.0, true);
|
||||
assert_eq!(rec.1, 23512);
|
||||
|
||||
// Tuple with an empty string
|
||||
let mut cursor = conn.fetch("SELECT (true, '')");
|
||||
let row = cursor.next().await?.unwrap();
|
||||
let rec: (bool, String) = row.get(0);
|
||||
|
||||
assert_eq!(rec.1, "");
|
||||
|
||||
// Tuple with a string with an interior comma
|
||||
let mut cursor = conn.fetch("SELECT (true, 'Hello, World!')");
|
||||
let row = cursor.next().await?.unwrap();
|
||||
let rec: (bool, String) = row.get(0);
|
||||
|
||||
assert_eq!(rec.1, "Hello, World!");
|
||||
|
||||
// Tuple with a string with an emoji
|
||||
let mut cursor = conn.fetch("SELECT (true, 'Hello, 🐕!')");
|
||||
let row = cursor.next().await?.unwrap();
|
||||
let rec: (bool, String) = row.get(0);
|
||||
|
||||
assert_eq!(rec.1, "Hello, 🐕!");
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[cfg_attr(feature = "runtime-async-std", async_std::test)]
|
||||
#[cfg_attr(feature = "runtime-tokio", tokio::test)]
|
||||
async fn test_unprepared_anonymous_record_arrays() -> anyhow::Result<()> {
|
||||
let mut conn = new::<Postgres>().await?;
|
||||
|
||||
// record of arrays
|
||||
let mut cursor = conn.fetch("SELECT (ARRAY['', '\"']::text[], false)");
|
||||
let row = cursor.next().await?.unwrap();
|
||||
let rec: (Vec<String>, bool) = row.get(0);
|
||||
|
||||
assert_eq!(rec, (vec!["".to_string(), "\"".to_string()], false));
|
||||
|
||||
// array of records
|
||||
let mut cursor = conn.fetch("SELECT ARRAY[('','\"'), (NULL,'')]::record[]");
|
||||
let row = cursor.next().await?.unwrap();
|
||||
let rec: Vec<(Option<String>, String)> = row.get(0);
|
||||
|
||||
assert_eq!(
|
||||
rec,
|
||||
vec![
|
||||
(Some(String::from("")), String::from("\"")),
|
||||
(None, String::from(""))
|
||||
]
|
||||
);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[cfg_attr(feature = "runtime-async-std", async_std::test)]
|
||||
#[cfg_attr(feature = "runtime-tokio", tokio::test)]
|
||||
async fn test_prepared_anonymous_record_arrays() -> anyhow::Result<()> {
|
||||
let mut conn = new::<Postgres>().await?;
|
||||
|
||||
// record of arrays
|
||||
let rec: ((Vec<String>, bool),) = sqlx::query_as("SELECT (ARRAY['', '\"']::text[], false)")
|
||||
.fetch_one(&mut conn)
|
||||
.await?;
|
||||
|
||||
assert_eq!(rec.0, (vec!["".to_string(), "\"".to_string()], false));
|
||||
|
||||
// array of records
|
||||
let rec: (Vec<(Option<String>, String)>,) =
|
||||
sqlx::query_as("SELECT ARRAY[('','\"'), (NULL,'')]::record[]")
|
||||
.fetch_one(&mut conn)
|
||||
.await?;
|
||||
|
||||
assert_eq!(
|
||||
rec.0,
|
||||
vec![
|
||||
(Some(String::from("")), String::from("\"")),
|
||||
(None, String::from(""))
|
||||
]
|
||||
);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[cfg_attr(feature = "runtime-async-std", async_std::test)]
|
||||
#[cfg_attr(feature = "runtime-tokio", tokio::test)]
|
||||
async fn test_prepared_structs() -> anyhow::Result<()> {
|
||||
let mut conn = new::<Postgres>().await?;
|
||||
|
||||
//
|
||||
// Setup custom types if needed
|
||||
//
|
||||
|
||||
conn.execute(
|
||||
r#"
|
||||
DO $$ BEGIN
|
||||
CREATE TYPE _sqlx_record_empty AS ();
|
||||
CREATE TYPE _sqlx_record_1 AS (_1 int8);
|
||||
EXCEPTION
|
||||
WHEN duplicate_object THEN null;
|
||||
END $$;
|
||||
"#,
|
||||
)
|
||||
.await?;
|
||||
|
||||
//
|
||||
// Record of no elements
|
||||
//
|
||||
|
||||
struct RecordEmpty {}
|
||||
|
||||
impl Type<Postgres> for RecordEmpty {
|
||||
fn type_info() -> PgTypeInfo {
|
||||
PgTypeInfo::with_name("_sqlx_record_empty")
|
||||
}
|
||||
}
|
||||
|
||||
impl Encode<Postgres> for RecordEmpty {
|
||||
fn encode(&self, buf: &mut PgRawBuffer) {
|
||||
PgRecordEncoder::new(buf).finish();
|
||||
}
|
||||
}
|
||||
|
||||
impl<'de> Decode<'de, Postgres> for RecordEmpty {
|
||||
fn decode(_value: PgValue<'de>) -> sqlx::Result<Self> {
|
||||
Ok(RecordEmpty {})
|
||||
}
|
||||
}
|
||||
|
||||
let _: (RecordEmpty, RecordEmpty) = sqlx::query_as("SELECT '()'::_sqlx_record_empty, $1")
|
||||
.bind(RecordEmpty {})
|
||||
.fetch_one(&mut conn)
|
||||
.await?;
|
||||
|
||||
//
|
||||
// Record of one element
|
||||
//
|
||||
|
||||
#[derive(Debug, PartialEq)]
|
||||
struct Record1 {
|
||||
_1: i64,
|
||||
}
|
||||
|
||||
impl Type<Postgres> for Record1 {
|
||||
fn type_info() -> PgTypeInfo {
|
||||
PgTypeInfo::with_name("_sqlx_record_1")
|
||||
}
|
||||
}
|
||||
|
||||
impl Encode<Postgres> for Record1 {
|
||||
fn encode(&self, buf: &mut PgRawBuffer) {
|
||||
PgRecordEncoder::new(buf).encode(self._1).finish();
|
||||
}
|
||||
}
|
||||
|
||||
impl<'de> Decode<'de, Postgres> for Record1 {
|
||||
fn decode(value: PgValue<'de>) -> sqlx::Result<Self> {
|
||||
let mut decoder = PgRecordDecoder::new(value)?;
|
||||
|
||||
let _1 = decoder.decode()?;
|
||||
|
||||
Ok(Record1 { _1 })
|
||||
}
|
||||
}
|
||||
|
||||
let rec: (Record1, Record1) = sqlx::query_as("SELECT '(324235)'::_sqlx_record_1, $1")
|
||||
.bind(Record1 { _1: 324235 })
|
||||
.fetch_one(&mut conn)
|
||||
.await?;
|
||||
|
||||
assert_eq!(rec.0, rec.1);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
//
|
||||
// JSON
|
||||
//
|
||||
|
||||
#[cfg(feature = "json")]
|
||||
mod json {
|
||||
use super::*;
|
||||
use serde_json::value::RawValue;
|
||||
use serde_json::{json, Value as JsonValue};
|
||||
use sqlx::postgres::PgRow;
|
||||
use sqlx::types::Json;
|
||||
use sqlx::Row;
|
||||
|
||||
// When testing JSON, coerce to JSONB for `=` comparison as `JSON = JSON` is not
|
||||
// supported in PostgreSQL
|
||||
|
||||
test_type!(json(
|
||||
Postgres,
|
||||
JsonValue,
|
||||
"SELECT {0}::jsonb is not distinct from $1::jsonb, $2::text as _1, {0} as _2, $3 as _3",
|
||||
"'\"Hello, World\"'::json" == json!("Hello, World"),
|
||||
"'\"😎\"'::json" == json!("😎"),
|
||||
"'\"🙋♀️\"'::json" == json!("🙋♀️"),
|
||||
"'[\"Hello\", \"World!\"]'::json" == json!(["Hello", "World!"])
|
||||
));
|
||||
|
||||
test_type!(jsonb(
|
||||
Postgres,
|
||||
JsonValue,
|
||||
"'\"Hello, World\"'::jsonb" == json!("Hello, World"),
|
||||
"'\"😎\"'::jsonb" == json!("😎"),
|
||||
"'\"🙋♀️\"'::jsonb" == json!("🙋♀️"),
|
||||
"'[\"Hello\", \"World!\"]'::jsonb" == json!(["Hello", "World!"])
|
||||
));
|
||||
|
||||
#[derive(serde::Deserialize, serde::Serialize, Debug, PartialEq)]
|
||||
struct Friend {
|
||||
name: String,
|
||||
age: u32,
|
||||
}
|
||||
|
||||
test_type!(jsonb_struct(Postgres, Json<Friend>,
|
||||
"'{\"name\":\"Joe\",\"age\":33}'::jsonb" == Json(Friend { name: "Joe".to_string(), age: 33 })
|
||||
));
|
||||
|
||||
test_type!(json_struct(
|
||||
Postgres,
|
||||
Json<Friend>,
|
||||
"SELECT {0}::jsonb is not distinct from $1::jsonb, $2::text as _1, {0} as _2, $3 as _3",
|
||||
"'{\"name\":\"Joe\",\"age\":33}'::json" == Json(Friend { name: "Joe".to_string(), age: 33 })
|
||||
));
|
||||
|
||||
#[cfg_attr(feature = "runtime-async-std", async_std::test)]
|
||||
#[cfg_attr(feature = "runtime-tokio", tokio::test)]
|
||||
async fn test_prepared_jsonb_raw_value() -> anyhow::Result<()> {
|
||||
let mut conn = new::<Postgres>().await?;
|
||||
|
||||
let mut cursor = sqlx::query("SELECT '{\"hello\": \"world\"}'::jsonb").fetch(&mut conn);
|
||||
let row: PgRow = cursor.next().await?.unwrap();
|
||||
let value: &RawValue = row.get::<&RawValue, usize>(0_usize);
|
||||
|
||||
assert_eq!(value.get(), "{\"hello\": \"world\"}");
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[cfg_attr(feature = "runtime-async-std", async_std::test)]
|
||||
#[cfg_attr(feature = "runtime-tokio", tokio::test)]
|
||||
async fn test_json_value_in_macro() -> anyhow::Result<()> {
|
||||
let mut conn = sqlx_test::new::<Postgres>().await?;
|
||||
|
||||
let v: serde_json::Value = json!({
|
||||
"name": "Joe".to_string(),
|
||||
"age": 33
|
||||
});
|
||||
|
||||
let res = sqlx::query!(
|
||||
"SELECT '{\"name\":\"Joe\",\"age\":33}'::jsonb as _1, $1::jsonb as _2",
|
||||
v,
|
||||
)
|
||||
.fetch_one(&mut conn)
|
||||
.await?;
|
||||
|
||||
assert_eq!(Some(v), res._1);
|
||||
assert_eq!(res._1, res._2);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
@ -1,385 +0,0 @@
|
||||
use futures::TryStreamExt;
|
||||
use sqlx::postgres::{PgPool, PgQueryAs, PgRow};
|
||||
use sqlx::{Connection, Cursor, Executor, Postgres, Row};
|
||||
use sqlx_test::new;
|
||||
use std::time::Duration;
|
||||
|
||||
// TODO: As soon as I tried to deserialize a json value in a function, inferance for this test stopped working. I am at a loss as to how to resolve this.
|
||||
#[cfg(not(feature = "json"))]
|
||||
#[cfg_attr(feature = "runtime-async-std", async_std::test)]
|
||||
#[cfg_attr(feature = "runtime-tokio", tokio::test)]
|
||||
async fn it_connects() -> anyhow::Result<()> {
|
||||
let mut conn = new::<Postgres>().await?;
|
||||
|
||||
let value = sqlx::query("select 1 + 1")
|
||||
.try_map(|row: PgRow| row.try_get::<i32, _>(0))
|
||||
.fetch_one(&mut conn)
|
||||
.await?;
|
||||
|
||||
assert_eq!(2i32, value);
|
||||
|
||||
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::<Postgres>().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 ($1)")
|
||||
.bind(index)
|
||||
.execute(&mut conn)
|
||||
.await?;
|
||||
|
||||
assert_eq!(cnt, 1);
|
||||
}
|
||||
|
||||
let sum: i32 = sqlx::query("SELECT id FROM users")
|
||||
.try_map(|row: PgRow| row.try_get::<i32, _>(0))
|
||||
.fetch(&mut conn)
|
||||
.try_fold(0_i32, |acc, x| async move { Ok(acc + x) })
|
||||
.await?;
|
||||
|
||||
assert_eq!(sum, 55);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
// https://github.com/launchbadge/sqlx/issues/104
|
||||
#[cfg_attr(feature = "runtime-async-std", async_std::test)]
|
||||
#[cfg_attr(feature = "runtime-tokio", tokio::test)]
|
||||
async fn it_can_return_interleaved_nulls_issue_104() -> anyhow::Result<()> {
|
||||
let mut conn = new::<Postgres>().await?;
|
||||
|
||||
let tuple = sqlx::query("SELECT NULL, 10::INT, NULL, 20::INT, NULL, 40::INT, NULL, 80::INT")
|
||||
.try_map(|row: PgRow| {
|
||||
Ok((
|
||||
row.get::<Option<i32>, _>(0),
|
||||
row.get::<Option<i32>, _>(1),
|
||||
row.get::<Option<i32>, _>(2),
|
||||
row.get::<Option<i32>, _>(3),
|
||||
row.get::<Option<i32>, _>(4),
|
||||
row.get::<Option<i32>, _>(5),
|
||||
row.get::<Option<i32>, _>(6),
|
||||
row.get::<Option<i32>, _>(7),
|
||||
))
|
||||
})
|
||||
.fetch_one(&mut conn)
|
||||
.await?;
|
||||
|
||||
assert_eq!(tuple.0, None);
|
||||
assert_eq!(tuple.1, Some(10));
|
||||
assert_eq!(tuple.2, None);
|
||||
assert_eq!(tuple.3, Some(20));
|
||||
assert_eq!(tuple.4, None);
|
||||
assert_eq!(tuple.5, Some(40));
|
||||
assert_eq!(tuple.6, None);
|
||||
assert_eq!(tuple.7, Some(80));
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[cfg_attr(feature = "runtime-async-std", async_std::test)]
|
||||
#[cfg_attr(feature = "runtime-tokio", tokio::test)]
|
||||
async fn it_can_work_with_transactions() -> anyhow::Result<()> {
|
||||
let mut conn = new::<Postgres>().await?;
|
||||
|
||||
conn.execute("CREATE TABLE IF NOT EXISTS _sqlx_users_1922 (id INTEGER PRIMARY KEY)")
|
||||
.await?;
|
||||
|
||||
conn.execute("TRUNCATE _sqlx_users_1922").await?;
|
||||
|
||||
// begin .. rollback
|
||||
|
||||
let mut tx = conn.begin().await?;
|
||||
|
||||
sqlx::query("INSERT INTO _sqlx_users_1922 (id) VALUES ($1)")
|
||||
.bind(10_i32)
|
||||
.execute(&mut tx)
|
||||
.await?;
|
||||
|
||||
conn = tx.rollback().await?;
|
||||
|
||||
let (count,): (i64,) = sqlx::query_as("SELECT COUNT(*) FROM _sqlx_users_1922")
|
||||
.fetch_one(&mut conn)
|
||||
.await?;
|
||||
|
||||
assert_eq!(count, 0);
|
||||
|
||||
// begin .. commit
|
||||
|
||||
let mut tx = conn.begin().await?;
|
||||
|
||||
sqlx::query("INSERT INTO _sqlx_users_1922 (id) VALUES ($1)")
|
||||
.bind(10_i32)
|
||||
.execute(&mut tx)
|
||||
.await?;
|
||||
|
||||
conn = tx.commit().await?;
|
||||
|
||||
let (count,): (i64,) = sqlx::query_as("SELECT COUNT(*) FROM _sqlx_users_1922")
|
||||
.fetch_one(&mut conn)
|
||||
.await?;
|
||||
|
||||
assert_eq!(count, 1);
|
||||
|
||||
// begin .. (drop)
|
||||
|
||||
{
|
||||
let mut tx = conn.begin().await?;
|
||||
|
||||
sqlx::query("INSERT INTO _sqlx_users_1922 (id) VALUES ($1)")
|
||||
.bind(20_i32)
|
||||
.execute(&mut tx)
|
||||
.await?;
|
||||
}
|
||||
|
||||
conn = new::<Postgres>().await?;
|
||||
|
||||
let (count,): (i64,) = sqlx::query_as("SELECT COUNT(*) FROM _sqlx_users_1922")
|
||||
.fetch_one(&mut conn)
|
||||
.await?;
|
||||
|
||||
assert_eq!(count, 1);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[cfg_attr(feature = "runtime-async-std", async_std::test)]
|
||||
#[cfg_attr(feature = "runtime-tokio", tokio::test)]
|
||||
async fn it_can_work_with_nested_transactions() -> anyhow::Result<()> {
|
||||
let mut conn = new::<Postgres>().await?;
|
||||
|
||||
conn.execute("CREATE TABLE IF NOT EXISTS _sqlx_users_2523 (id INTEGER PRIMARY KEY)")
|
||||
.await?;
|
||||
|
||||
conn.execute("TRUNCATE _sqlx_users_2523").await?;
|
||||
|
||||
// begin
|
||||
let mut tx = conn.begin().await?;
|
||||
|
||||
// insert a user
|
||||
sqlx::query("INSERT INTO _sqlx_users_2523 (id) VALUES ($1)")
|
||||
.bind(50_i32)
|
||||
.execute(&mut tx)
|
||||
.await?;
|
||||
|
||||
// begin once more
|
||||
let mut tx = tx.begin().await?;
|
||||
|
||||
// insert another user
|
||||
sqlx::query("INSERT INTO _sqlx_users_2523 (id) VALUES ($1)")
|
||||
.bind(10_i32)
|
||||
.execute(&mut tx)
|
||||
.await?;
|
||||
|
||||
// never mind, rollback
|
||||
let mut tx = tx.rollback().await?;
|
||||
|
||||
// did we really?
|
||||
let (count,): (i64,) = sqlx::query_as("SELECT COUNT(*) FROM _sqlx_users_2523")
|
||||
.fetch_one(&mut tx)
|
||||
.await?;
|
||||
|
||||
assert_eq!(count, 1);
|
||||
|
||||
// actually, commit
|
||||
let mut conn = tx.commit().await?;
|
||||
|
||||
// did we really?
|
||||
let (count,): (i64,) = sqlx::query_as("SELECT COUNT(*) FROM _sqlx_users_2523")
|
||||
.fetch_one(&mut conn)
|
||||
.await?;
|
||||
|
||||
assert_eq!(count, 1);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[cfg_attr(feature = "runtime-async-std", async_std::test)]
|
||||
#[cfg_attr(feature = "runtime-tokio", tokio::test)]
|
||||
async fn it_can_rollback_nested_transactions() -> anyhow::Result<()> {
|
||||
let mut conn = new::<Postgres>().await?;
|
||||
|
||||
conn.execute("CREATE TABLE IF NOT EXISTS _sqlx_users_512412 (id INTEGER PRIMARY KEY)")
|
||||
.await?;
|
||||
|
||||
conn.execute("TRUNCATE _sqlx_users_512412").await?;
|
||||
|
||||
// begin
|
||||
let mut tx = conn.begin().await?;
|
||||
|
||||
// insert a user
|
||||
sqlx::query("INSERT INTO _sqlx_users_512412 (id) VALUES ($1)")
|
||||
.bind(50_i32)
|
||||
.execute(&mut tx)
|
||||
.await?;
|
||||
|
||||
// begin once more
|
||||
let mut tx = tx.begin().await?;
|
||||
|
||||
// insert another user
|
||||
sqlx::query("INSERT INTO _sqlx_users_512412 (id) VALUES ($1)")
|
||||
.bind(10_i32)
|
||||
.execute(&mut tx)
|
||||
.await?;
|
||||
|
||||
// stop the phone, drop the entire transaction
|
||||
tx.close().await?;
|
||||
|
||||
// did we really?
|
||||
let mut conn = new::<Postgres>().await?;
|
||||
let (count,): (i64,) = sqlx::query_as("SELECT COUNT(*) FROM _sqlx_users_512412")
|
||||
.fetch_one(&mut conn)
|
||||
.await?;
|
||||
|
||||
assert_eq!(count, 0);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
// run with `cargo test --features postgres -- --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 = PgPool::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(&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_invalid_query() -> anyhow::Result<()> {
|
||||
let mut conn = new::<Postgres>().await?;
|
||||
|
||||
conn.execute("definitely not a correct query")
|
||||
.await
|
||||
.unwrap_err();
|
||||
|
||||
let mut cursor = conn.fetch("select 1");
|
||||
let row = cursor.next().await?.unwrap();
|
||||
|
||||
assert_eq!(row.get::<i32, _>(0), 1i32);
|
||||
|
||||
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::<Postgres>().await?;
|
||||
|
||||
let _ = conn
|
||||
.execute(
|
||||
r#"
|
||||
CREATE TEMP TABLE describe_test (
|
||||
id SERIAL primary key,
|
||||
name text not null,
|
||||
hash bytea
|
||||
)
|
||||
"#,
|
||||
)
|
||||
.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(),
|
||||
"INT4"
|
||||
);
|
||||
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(),
|
||||
"BYTEA"
|
||||
);
|
||||
assert_eq!(describe.result_columns[3].non_null, None);
|
||||
assert_eq!(
|
||||
describe.result_columns[3]
|
||||
.type_info
|
||||
.as_ref()
|
||||
.unwrap()
|
||||
.to_string(),
|
||||
"BOOL"
|
||||
);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
10
tests/postgres/Dockerfile
Normal file
10
tests/postgres/Dockerfile
Normal file
@ -0,0 +1,10 @@
|
||||
ARG VERSION
|
||||
FROM postgres:${VERSION}-alpine
|
||||
|
||||
# Copy SSL certificate (and key)
|
||||
COPY certs/server.crt /var/lib/postgresql/server.crt
|
||||
COPY keys/server.key /var/lib/postgresql/server.key
|
||||
|
||||
# Fix permissions
|
||||
RUN chown 70:70 /var/lib/postgresql/server.crt /var/lib/postgresql/server.key
|
||||
RUN chmod 0600 /var/lib/postgresql/server.crt /var/lib/postgresql/server.key
|
||||
@ -96,8 +96,7 @@ test_type!(strong_enum(
|
||||
"'four'::text" == Strong::Three
|
||||
));
|
||||
|
||||
#[cfg_attr(feature = "runtime-async-std", async_std::test)]
|
||||
#[cfg_attr(feature = "runtime-tokio", tokio::test)]
|
||||
#[sqlx_macros::test]
|
||||
async fn test_enum_type() -> anyhow::Result<()> {
|
||||
let mut conn = new::<Postgres>().await?;
|
||||
|
||||
@ -231,8 +230,7 @@ SELECT $1 = 'RED'::color_upper, $1
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[cfg_attr(feature = "runtime-async-std", async_std::test)]
|
||||
#[cfg_attr(feature = "runtime-tokio", tokio::test)]
|
||||
#[sqlx_macros::test]
|
||||
async fn test_record_type() -> anyhow::Result<()> {
|
||||
let mut conn = new::<Postgres>().await?;
|
||||
|
||||
@ -275,8 +273,7 @@ END $$;
|
||||
}
|
||||
|
||||
#[cfg(feature = "macros")]
|
||||
#[cfg_attr(feature = "runtime-async-std", async_std::test)]
|
||||
#[cfg_attr(feature = "runtime-tokio", tokio::test)]
|
||||
#[sqlx_macros::test]
|
||||
async fn test_from_row() -> anyhow::Result<()> {
|
||||
// Needed for PgQueryAs
|
||||
use sqlx::prelude::*;
|
||||
@ -322,8 +319,7 @@ async fn test_from_row() -> anyhow::Result<()> {
|
||||
}
|
||||
|
||||
#[cfg(feature = "macros")]
|
||||
#[cfg_attr(feature = "runtime-async-std", async_std::test)]
|
||||
#[cfg_attr(feature = "runtime-tokio", tokio::test)]
|
||||
#[sqlx_macros::test]
|
||||
async fn test_from_row_with_keyword() -> anyhow::Result<()> {
|
||||
use sqlx::prelude::*;
|
||||
|
||||
@ -355,8 +351,7 @@ async fn test_from_row_with_keyword() -> anyhow::Result<()> {
|
||||
}
|
||||
|
||||
#[cfg(feature = "macros")]
|
||||
#[cfg_attr(feature = "runtime-async-std", async_std::test)]
|
||||
#[cfg_attr(feature = "runtime-tokio", tokio::test)]
|
||||
#[sqlx_macros::test]
|
||||
async fn test_from_row_with_rename() -> anyhow::Result<()> {
|
||||
use sqlx::prelude::*;
|
||||
|
||||
114
tests/postgres/describe.rs
Normal file
114
tests/postgres/describe.rs
Normal file
@ -0,0 +1,114 @@
|
||||
use sqlx::{postgres::Postgres, Executor};
|
||||
use sqlx_core::describe::Column;
|
||||
use sqlx_test::new;
|
||||
|
||||
fn type_names(columns: &[Column<Postgres>]) -> Vec<String> {
|
||||
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::<Postgres>().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], "int8");
|
||||
assert_eq!(column_type_names[1], "timestamptz");
|
||||
assert_eq!(column_type_names[2], "text");
|
||||
assert_eq!(column_type_names[3], "int8");
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[sqlx_macros::test]
|
||||
async fn it_describes_expression() -> anyhow::Result<()> {
|
||||
let mut conn = new::<Postgres>().await?;
|
||||
|
||||
let d = conn.describe("SELECT 1::int8 + 10").await?;
|
||||
let columns = d.columns;
|
||||
|
||||
// ?column? will cause the macro to emit an error ad ask the user to explicitly name the type
|
||||
assert_eq!(columns[0].name, "?column?");
|
||||
|
||||
// postgres cannot infer nullability from an expression
|
||||
// this will cause the macro to emit `Option<_>`
|
||||
assert_eq!(columns[0].not_null, None);
|
||||
|
||||
let column_type_names = type_names(&columns);
|
||||
|
||||
assert_eq!(column_type_names[0], "int8");
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[sqlx_macros::test]
|
||||
async fn it_describes_enum() -> anyhow::Result<()> {
|
||||
let mut conn = new::<Postgres>().await?;
|
||||
|
||||
let d = conn.describe("SELECT 'open'::status as _1").await?;
|
||||
let columns = d.columns;
|
||||
|
||||
assert_eq!(columns[0].name, "_1");
|
||||
assert_eq!(columns[0].not_null, None);
|
||||
|
||||
let ty = columns[0].type_info.as_ref().unwrap();
|
||||
|
||||
assert_eq!(ty.to_string(), "status");
|
||||
assert_eq!(
|
||||
format!("{:?}", ty.__kind()),
|
||||
r#"Enum(["new", "open", "closed"])"#
|
||||
);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[sqlx_macros::test]
|
||||
async fn it_describes_record() -> anyhow::Result<()> {
|
||||
let mut conn = new::<Postgres>().await?;
|
||||
|
||||
let d = conn.describe("SELECT (true, 10::int2)").await?;
|
||||
let columns = d.columns;
|
||||
|
||||
let ty = columns[0].type_info.as_ref().unwrap();
|
||||
assert_eq!(ty.to_string(), "record");
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[sqlx_macros::test]
|
||||
async fn it_describes_composite() -> anyhow::Result<()> {
|
||||
let mut conn = new::<Postgres>().await?;
|
||||
|
||||
let d = conn
|
||||
.describe("SELECT ROW('name',10,500)::inventory_item")
|
||||
.await?;
|
||||
|
||||
let columns = d.columns;
|
||||
|
||||
let ty = columns[0].type_info.as_ref().unwrap();
|
||||
|
||||
assert_eq!(ty.to_string(), "inventory_item");
|
||||
|
||||
assert_eq!(
|
||||
format!("{:?}", ty.__kind()),
|
||||
r#"Composite([("name", PgTypeInfo(Text)), ("supplier_id", PgTypeInfo(Int4)), ("price", PgTypeInfo(Int8))])"#
|
||||
);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
@ -3,8 +3,7 @@ use sqlx_test::new;
|
||||
|
||||
use futures::TryStreamExt;
|
||||
|
||||
#[cfg_attr(feature = "runtime-async-std", async_std::test)]
|
||||
#[cfg_attr(feature = "runtime-tokio", tokio::test)]
|
||||
#[sqlx_macros::test]
|
||||
async fn test_query() -> anyhow::Result<()> {
|
||||
let mut conn = new::<Postgres>().await?;
|
||||
|
||||
@ -20,8 +19,7 @@ async fn test_query() -> anyhow::Result<()> {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[cfg_attr(feature = "runtime-async-std", async_std::test)]
|
||||
#[cfg_attr(feature = "runtime-tokio", tokio::test)]
|
||||
#[sqlx_macros::test]
|
||||
async fn test_no_result() -> anyhow::Result<()> {
|
||||
let mut conn = new::<Postgres>().await?;
|
||||
|
||||
@ -32,8 +30,7 @@ async fn test_no_result() -> anyhow::Result<()> {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[cfg_attr(feature = "runtime-async-std", async_std::test)]
|
||||
#[cfg_attr(feature = "runtime-tokio", tokio::test)]
|
||||
#[sqlx_macros::test]
|
||||
async fn test_text_var_char_char_n() -> anyhow::Result<()> {
|
||||
let mut conn = new::<Postgres>().await?;
|
||||
|
||||
@ -64,8 +61,7 @@ async fn test_text_var_char_char_n() -> anyhow::Result<()> {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[cfg_attr(feature = "runtime-async-std", async_std::test)]
|
||||
#[cfg_attr(feature = "runtime-tokio", tokio::test)]
|
||||
#[sqlx_macros::test]
|
||||
async fn _file() -> anyhow::Result<()> {
|
||||
let mut conn = new::<Postgres>().await?;
|
||||
|
||||
@ -84,8 +80,7 @@ struct Account {
|
||||
name: Option<String>,
|
||||
}
|
||||
|
||||
#[cfg_attr(feature = "runtime-async-std", async_std::test)]
|
||||
#[cfg_attr(feature = "runtime-tokio", tokio::test)]
|
||||
#[sqlx_macros::test]
|
||||
async fn test_query_as() -> anyhow::Result<()> {
|
||||
let mut conn = new::<Postgres>().await?;
|
||||
|
||||
@ -111,8 +106,7 @@ struct RawAccount {
|
||||
name: Option<String>,
|
||||
}
|
||||
|
||||
#[cfg_attr(feature = "runtime-async-std", async_std::test)]
|
||||
#[cfg_attr(feature = "runtime-tokio", tokio::test)]
|
||||
#[sqlx_macros::test]
|
||||
async fn test_query_as_raw() -> anyhow::Result<()> {
|
||||
let mut conn = new::<Postgres>().await?;
|
||||
|
||||
@ -131,8 +125,7 @@ async fn test_query_as_raw() -> anyhow::Result<()> {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[cfg_attr(feature = "runtime-async-std", async_std::test)]
|
||||
#[cfg_attr(feature = "runtime-tokio", tokio::test)]
|
||||
#[sqlx_macros::test]
|
||||
async fn test_query_file_as() -> anyhow::Result<()> {
|
||||
let mut conn = new::<Postgres>().await?;
|
||||
|
||||
@ -145,8 +138,7 @@ async fn test_query_file_as() -> anyhow::Result<()> {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[cfg_attr(feature = "runtime-async-std", async_std::test)]
|
||||
#[cfg_attr(feature = "runtime-tokio", tokio::test)]
|
||||
#[sqlx_macros::test]
|
||||
async fn query_by_string() -> anyhow::Result<()> {
|
||||
let mut conn = new::<Postgres>().await?;
|
||||
|
||||
@ -172,8 +164,7 @@ async fn query_by_string() -> anyhow::Result<()> {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[cfg_attr(feature = "runtime-async-std", async_std::test)]
|
||||
#[cfg_attr(feature = "runtime-tokio", tokio::test)]
|
||||
#[sqlx_macros::test]
|
||||
async fn test_nullable_err() -> anyhow::Result<()> {
|
||||
#[derive(Debug)]
|
||||
struct Account {
|
||||
@ -200,8 +191,7 @@ async fn test_nullable_err() -> anyhow::Result<()> {
|
||||
panic!("expected `UnexpectedNullError`, got {}", err)
|
||||
}
|
||||
|
||||
#[cfg_attr(feature = "runtime-async-std", async_std::test)]
|
||||
#[cfg_attr(feature = "runtime-tokio", tokio::test)]
|
||||
#[sqlx_macros::test]
|
||||
async fn test_many_args() -> anyhow::Result<()> {
|
||||
let mut conn = new::<Postgres>().await?;
|
||||
|
||||
@ -221,8 +211,7 @@ async fn test_many_args() -> anyhow::Result<()> {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[cfg_attr(feature = "runtime-async-std", async_std::test)]
|
||||
#[cfg_attr(feature = "runtime-tokio", tokio::test)]
|
||||
#[sqlx_macros::test]
|
||||
async fn test_array_from_slice() -> anyhow::Result<()> {
|
||||
let mut conn = new::<Postgres>().await?;
|
||||
|
||||
@ -247,8 +236,7 @@ async fn test_array_from_slice() -> anyhow::Result<()> {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[cfg_attr(feature = "runtime-async-std", async_std::test)]
|
||||
#[cfg_attr(feature = "runtime-tokio", tokio::test)]
|
||||
#[sqlx_macros::test]
|
||||
async fn fetch_is_usable_issue_224() -> anyhow::Result<()> {
|
||||
// ensures that the stream returned by `query::Map::fetch()` is usable with `TryStreamExt`
|
||||
let mut conn = new::<Postgres>().await?;
|
||||
443
tests/postgres/postgres.rs
Normal file
443
tests/postgres/postgres.rs
Normal file
@ -0,0 +1,443 @@
|
||||
use futures::TryStreamExt;
|
||||
use sqlx::postgres::PgRow;
|
||||
use sqlx::{postgres::Postgres, Executor, Row};
|
||||
use sqlx_test::new;
|
||||
|
||||
#[sqlx_macros::test]
|
||||
async fn it_connects() -> anyhow::Result<()> {
|
||||
let mut conn = new::<Postgres>().await?;
|
||||
|
||||
let value = sqlx::query("select 1 + 1")
|
||||
.try_map(|row: PgRow| row.try_get::<i32, _>(0))
|
||||
.fetch_one(&mut conn)
|
||||
.await?;
|
||||
|
||||
assert_eq!(2i32, value);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[sqlx_macros::test]
|
||||
async fn it_maths() -> anyhow::Result<()> {
|
||||
let mut conn = new::<Postgres>().await?;
|
||||
|
||||
let value = sqlx::query("select 1 + $1::int")
|
||||
.bind(5_i32)
|
||||
.try_map(|row: PgRow| row.try_get::<i32, _>(0))
|
||||
.fetch_one(&mut conn)
|
||||
.await?;
|
||||
|
||||
assert_eq!(6i32, value);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[sqlx_macros::test]
|
||||
async fn it_executes() -> anyhow::Result<()> {
|
||||
let mut conn = new::<Postgres>().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 ($1)")
|
||||
.bind(index)
|
||||
.execute(&mut conn)
|
||||
.await?;
|
||||
|
||||
assert_eq!(cnt, 1);
|
||||
}
|
||||
|
||||
let sum: i32 = sqlx::query("SELECT id FROM users")
|
||||
.try_map(|row: PgRow| row.try_get::<i32, _>(0))
|
||||
.fetch(&mut conn)
|
||||
.try_fold(0_i32, |acc, x| async move { Ok(acc + x) })
|
||||
.await?;
|
||||
|
||||
assert_eq!(sum, 55);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
// https://github.com/launchbadge/sqlx/issues/104
|
||||
#[sqlx_macros::test]
|
||||
async fn it_can_return_interleaved_nulls_issue_104() -> anyhow::Result<()> {
|
||||
let mut conn = new::<Postgres>().await?;
|
||||
|
||||
let tuple = sqlx::query("SELECT NULL, 10::INT, NULL, 20::INT, NULL, 40::INT, NULL, 80::INT")
|
||||
.try_map(|row: PgRow| {
|
||||
Ok((
|
||||
row.get::<Option<i32>, _>(0),
|
||||
row.get::<Option<i32>, _>(1),
|
||||
row.get::<Option<i32>, _>(2),
|
||||
row.get::<Option<i32>, _>(3),
|
||||
row.get::<Option<i32>, _>(4),
|
||||
row.get::<Option<i32>, _>(5),
|
||||
row.get::<Option<i32>, _>(6),
|
||||
row.get::<Option<i32>, _>(7),
|
||||
))
|
||||
})
|
||||
.fetch_one(&mut conn)
|
||||
.await?;
|
||||
|
||||
assert_eq!(tuple.0, None);
|
||||
assert_eq!(tuple.1, Some(10));
|
||||
assert_eq!(tuple.2, None);
|
||||
assert_eq!(tuple.3, Some(20));
|
||||
assert_eq!(tuple.4, None);
|
||||
assert_eq!(tuple.5, Some(40));
|
||||
assert_eq!(tuple.6, None);
|
||||
assert_eq!(tuple.7, Some(80));
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[sqlx_macros::test]
|
||||
async fn it_can_query_scalar() -> anyhow::Result<()> {
|
||||
let mut conn = new::<Postgres>().await?;
|
||||
|
||||
let scalar: i32 = sqlx::query_scalar("SELECT 42").fetch_one(&mut conn).await?;
|
||||
assert_eq!(scalar, 42);
|
||||
|
||||
let scalar: Option<i32> = sqlx::query_scalar("SELECT 42").fetch_one(&mut conn).await?;
|
||||
assert_eq!(scalar, Some(42));
|
||||
|
||||
let scalar: Option<i32> = sqlx::query_scalar("SELECT NULL")
|
||||
.fetch_one(&mut conn)
|
||||
.await?;
|
||||
assert_eq!(scalar, None);
|
||||
|
||||
let scalar: Option<i64> = sqlx::query_scalar("SELECT 42::bigint")
|
||||
.fetch_optional(&mut conn)
|
||||
.await?;
|
||||
assert_eq!(scalar, Some(42));
|
||||
|
||||
let scalar: Option<i16> = sqlx::query_scalar("").fetch_optional(&mut conn).await?;
|
||||
assert_eq!(scalar, None);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[sqlx_macros::test]
|
||||
/// This is seperate from `it_can_query_scalar` because while implementing it I ran into a
|
||||
/// bug which that prevented `Vec<i32>` from compiling but allowed Vec<Option<i32>>.
|
||||
async fn it_can_query_all_scalar() -> anyhow::Result<()> {
|
||||
let mut conn = new::<Postgres>().await?;
|
||||
|
||||
let scalar: Vec<i32> = sqlx::query_scalar("SELECT $1")
|
||||
.bind(42)
|
||||
.fetch_all(&mut conn)
|
||||
.await?;
|
||||
assert_eq!(scalar, vec![42]);
|
||||
|
||||
let scalar: Vec<Option<i32>> = sqlx::query_scalar("SELECT $1 UNION ALL SELECT NULL")
|
||||
.bind(42)
|
||||
.fetch_all(&mut conn)
|
||||
.await?;
|
||||
assert_eq!(scalar, vec![Some(42), None]);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
// #[cfg_attr(feature = "runtime-async-std", async_std::test)]
|
||||
// #[cfg_attr(feature = "runtime-tokio", tokio::test)]
|
||||
// async fn it_can_work_with_transactions() -> anyhow::Result<()> {
|
||||
// let mut conn = new::<Postgres>().await?;
|
||||
//
|
||||
// conn.execute("CREATE TABLE IF NOT EXISTS _sqlx_users_1922 (id INTEGER PRIMARY KEY)")
|
||||
// .await?;
|
||||
//
|
||||
// conn.execute("TRUNCATE _sqlx_users_1922").await?;
|
||||
//
|
||||
// // begin .. rollback
|
||||
//
|
||||
// let mut tx = conn.begin().await?;
|
||||
//
|
||||
// sqlx::query("INSERT INTO _sqlx_users_1922 (id) VALUES ($1)")
|
||||
// .bind(10_i32)
|
||||
// .execute(&mut tx)
|
||||
// .await?;
|
||||
//
|
||||
// conn = tx.rollback().await?;
|
||||
//
|
||||
// let (count,): (i64,) = sqlx::query_as("SELECT COUNT(*) FROM _sqlx_users_1922")
|
||||
// .fetch_one(&mut conn)
|
||||
// .await?;
|
||||
//
|
||||
// assert_eq!(count, 0);
|
||||
//
|
||||
// // begin .. commit
|
||||
//
|
||||
// let mut tx = conn.begin().await?;
|
||||
//
|
||||
// sqlx::query("INSERT INTO _sqlx_users_1922 (id) VALUES ($1)")
|
||||
// .bind(10_i32)
|
||||
// .execute(&mut tx)
|
||||
// .await?;
|
||||
//
|
||||
// conn = tx.commit().await?;
|
||||
//
|
||||
// let (count,): (i64,) = sqlx::query_as("SELECT COUNT(*) FROM _sqlx_users_1922")
|
||||
// .fetch_one(&mut conn)
|
||||
// .await?;
|
||||
//
|
||||
// assert_eq!(count, 1);
|
||||
//
|
||||
// // begin .. (drop)
|
||||
//
|
||||
// {
|
||||
// let mut tx = conn.begin().await?;
|
||||
//
|
||||
// sqlx::query("INSERT INTO _sqlx_users_1922 (id) VALUES ($1)")
|
||||
// .bind(20_i32)
|
||||
// .execute(&mut tx)
|
||||
// .await?;
|
||||
// }
|
||||
//
|
||||
// conn = new::<Postgres>().await?;
|
||||
//
|
||||
// let (count,): (i64,) = sqlx::query_as("SELECT COUNT(*) FROM _sqlx_users_1922")
|
||||
// .fetch_one(&mut conn)
|
||||
// .await?;
|
||||
//
|
||||
// assert_eq!(count, 1);
|
||||
//
|
||||
// Ok(())
|
||||
// }
|
||||
//
|
||||
// #[cfg_attr(feature = "runtime-async-std", async_std::test)]
|
||||
// #[cfg_attr(feature = "runtime-tokio", tokio::test)]
|
||||
// async fn it_can_work_with_nested_transactions() -> anyhow::Result<()> {
|
||||
// let mut conn = new::<Postgres>().await?;
|
||||
//
|
||||
// conn.execute("CREATE TABLE IF NOT EXISTS _sqlx_users_2523 (id INTEGER PRIMARY KEY)")
|
||||
// .await?;
|
||||
//
|
||||
// conn.execute("TRUNCATE _sqlx_users_2523").await?;
|
||||
//
|
||||
// // begin
|
||||
// let mut tx = conn.begin().await?;
|
||||
//
|
||||
// // insert a user
|
||||
// sqlx::query("INSERT INTO _sqlx_users_2523 (id) VALUES ($1)")
|
||||
// .bind(50_i32)
|
||||
// .execute(&mut tx)
|
||||
// .await?;
|
||||
//
|
||||
// // begin once more
|
||||
// let mut tx = tx.begin().await?;
|
||||
//
|
||||
// // insert another user
|
||||
// sqlx::query("INSERT INTO _sqlx_users_2523 (id) VALUES ($1)")
|
||||
// .bind(10_i32)
|
||||
// .execute(&mut tx)
|
||||
// .await?;
|
||||
//
|
||||
// // never mind, rollback
|
||||
// let mut tx = tx.rollback().await?;
|
||||
//
|
||||
// // did we really?
|
||||
// let (count,): (i64,) = sqlx::query_as("SELECT COUNT(*) FROM _sqlx_users_2523")
|
||||
// .fetch_one(&mut tx)
|
||||
// .await?;
|
||||
//
|
||||
// assert_eq!(count, 1);
|
||||
//
|
||||
// // actually, commit
|
||||
// let mut conn = tx.commit().await?;
|
||||
//
|
||||
// // did we really?
|
||||
// let (count,): (i64,) = sqlx::query_as("SELECT COUNT(*) FROM _sqlx_users_2523")
|
||||
// .fetch_one(&mut conn)
|
||||
// .await?;
|
||||
//
|
||||
// assert_eq!(count, 1);
|
||||
//
|
||||
// Ok(())
|
||||
// }
|
||||
//
|
||||
// #[cfg_attr(feature = "runtime-async-std", async_std::test)]
|
||||
// #[cfg_attr(feature = "runtime-tokio", tokio::test)]
|
||||
// async fn it_can_rollback_nested_transactions() -> anyhow::Result<()> {
|
||||
// let mut conn = new::<Postgres>().await?;
|
||||
//
|
||||
// conn.execute("CREATE TABLE IF NOT EXISTS _sqlx_users_512412 (id INTEGER PRIMARY KEY)")
|
||||
// .await?;
|
||||
//
|
||||
// conn.execute("TRUNCATE _sqlx_users_512412").await?;
|
||||
//
|
||||
// // begin
|
||||
// let mut tx = conn.begin().await?;
|
||||
//
|
||||
// // insert a user
|
||||
// sqlx::query("INSERT INTO _sqlx_users_512412 (id) VALUES ($1)")
|
||||
// .bind(50_i32)
|
||||
// .execute(&mut tx)
|
||||
// .await?;
|
||||
//
|
||||
// // begin once more
|
||||
// let mut tx = tx.begin().await?;
|
||||
//
|
||||
// // insert another user
|
||||
// sqlx::query("INSERT INTO _sqlx_users_512412 (id) VALUES ($1)")
|
||||
// .bind(10_i32)
|
||||
// .execute(&mut tx)
|
||||
// .await?;
|
||||
//
|
||||
// // stop the phone, drop the entire transaction
|
||||
// tx.close().await?;
|
||||
//
|
||||
// // did we really?
|
||||
// let mut conn = new::<Postgres>().await?;
|
||||
// let (count,): (i64,) = sqlx::query_as("SELECT COUNT(*) FROM _sqlx_users_512412")
|
||||
// .fetch_one(&mut conn)
|
||||
// .await?;
|
||||
//
|
||||
// assert_eq!(count, 0);
|
||||
//
|
||||
// Ok(())
|
||||
// }
|
||||
//
|
||||
// // run with `cargo test --features postgres -- --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 = PgPool::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(&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(())
|
||||
// }
|
||||
|
||||
#[sqlx_macros::test]
|
||||
async fn test_invalid_query() -> anyhow::Result<()> {
|
||||
let mut conn = new::<Postgres>().await?;
|
||||
|
||||
conn.execute("definitely not a correct query")
|
||||
.await
|
||||
.unwrap_err();
|
||||
|
||||
let mut s = conn.fetch("select 1");
|
||||
let row = s.try_next().await?.unwrap();
|
||||
|
||||
assert_eq!(row.get::<i32, _>(0), 1i32);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Tests the edge case of executing a completely empty query string.
|
||||
///
|
||||
/// This gets flagged as an `EmptyQueryResponse` in Postgres. We
|
||||
/// catch this and just return no rows.
|
||||
#[sqlx_macros::test]
|
||||
async fn test_empty_query() -> anyhow::Result<()> {
|
||||
let mut conn = new::<Postgres>().await?;
|
||||
let affected = conn.execute("").await?;
|
||||
|
||||
assert_eq!(affected, 0);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Test a simple select expression. This should return the row.
|
||||
#[sqlx_macros::test]
|
||||
async fn test_select_expression() -> anyhow::Result<()> {
|
||||
let mut conn = new::<Postgres>().await?;
|
||||
|
||||
let mut s = conn.fetch("SELECT 5");
|
||||
let row = s.try_next().await?.unwrap();
|
||||
|
||||
assert!(5i32 == row.try_get::<i32, _>(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.
|
||||
#[sqlx_macros::test]
|
||||
async fn test_multi_read_write() -> anyhow::Result<()> {
|
||||
let mut conn = new::<Postgres>().await?;
|
||||
|
||||
let mut s = conn.fetch(
|
||||
"
|
||||
CREATE TABLE IF NOT EXISTS _sqlx_test_postgres_5112 (
|
||||
id BIGSERIAL PRIMARY KEY,
|
||||
text TEXT NOT NULL
|
||||
);
|
||||
|
||||
SELECT 'Hello World' as _1;
|
||||
|
||||
INSERT INTO _sqlx_test_postgres_5112 (text) VALUES ('this is a test');
|
||||
|
||||
SELECT id, text FROM _sqlx_test_postgres_5112;
|
||||
",
|
||||
);
|
||||
|
||||
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(())
|
||||
}
|
||||
19
tests/postgres/setup.sql
Normal file
19
tests/postgres/setup.sql
Normal file
@ -0,0 +1,19 @@
|
||||
-- https://www.postgresql.org/docs/current/sql-createtype.html
|
||||
CREATE TYPE status AS ENUM ('new', 'open', 'closed');
|
||||
|
||||
-- https://www.postgresql.org/docs/current/rowtypes.html#ROWTYPES-DECLARING
|
||||
CREATE TYPE inventory_item AS
|
||||
(
|
||||
name TEXT,
|
||||
supplier_id INT,
|
||||
price BIGINT
|
||||
);
|
||||
|
||||
-- https://github.com/prisma/database-schema-examples/tree/master/postgres/basic-twitter#basic-twitter
|
||||
CREATE TABLE tweet
|
||||
(
|
||||
id BIGSERIAL PRIMARY KEY,
|
||||
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
|
||||
text TEXT NOT NULL,
|
||||
owner_id BIGINT
|
||||
);
|
||||
335
tests/postgres/types.rs
Normal file
335
tests/postgres/types.rs
Normal file
@ -0,0 +1,335 @@
|
||||
extern crate time_ as time;
|
||||
|
||||
use sqlx::postgres::Postgres;
|
||||
use sqlx_test::{test_decode_type, test_prepared_type, test_type};
|
||||
|
||||
test_type!(null<Option<i16>>(Postgres,
|
||||
"NULL::int2" == None::<i16>
|
||||
));
|
||||
|
||||
test_type!(null_vec<Vec<Option<i16>>>(Postgres,
|
||||
"array[10,NULL,50]::int2[]" == vec![Some(10_i16), None, Some(50)],
|
||||
));
|
||||
|
||||
test_type!(bool<bool>(Postgres,
|
||||
"false::boolean" == false,
|
||||
"true::boolean" == true
|
||||
));
|
||||
|
||||
test_type!(bool_vec<Vec<bool>>(Postgres,
|
||||
"array[true,false,true]::bool[]" == vec![true, false, true],
|
||||
));
|
||||
|
||||
test_type!(byte_vec<Vec<u8>>(Postgres,
|
||||
"E'\\\\xDEADBEEF'::bytea"
|
||||
== vec![0xDE_u8, 0xAD, 0xBE, 0xEF],
|
||||
"E'\\\\x'::bytea"
|
||||
== Vec::<u8>::new(),
|
||||
"E'\\\\x0000000052'::bytea"
|
||||
== vec![0_u8, 0, 0, 0, 0x52]
|
||||
));
|
||||
|
||||
// BYTEA cannot be decoded by-reference from a simple query as postgres sends it as hex
|
||||
test_prepared_type!(byte_slice<&[u8]>(Postgres,
|
||||
"E'\\\\xDEADBEEF'::bytea"
|
||||
== &[0xDE_u8, 0xAD, 0xBE, 0xEF][..],
|
||||
"E'\\\\x0000000052'::bytea"
|
||||
== &[0_u8, 0, 0, 0, 0x52][..]
|
||||
));
|
||||
|
||||
test_type!(str<&str>(Postgres,
|
||||
"'this is foo'" == "this is foo",
|
||||
"''" == "",
|
||||
"'identifier'::name" == "identifier",
|
||||
"'five'::char(4)" == "five",
|
||||
"'more text'::varchar" == "more text",
|
||||
));
|
||||
|
||||
test_type!(string<String>(Postgres,
|
||||
"'this is foo'" == format!("this is foo"),
|
||||
));
|
||||
|
||||
test_type!(string_vec<Vec<String>>(Postgres,
|
||||
"array['one','two','three']::text[]"
|
||||
== vec!["one","two","three"],
|
||||
|
||||
"array['', '\"']::text[]"
|
||||
== vec!["", "\""],
|
||||
|
||||
"array['Hello, World', '', 'Goodbye']::text[]"
|
||||
== vec!["Hello, World", "", "Goodbye"]
|
||||
));
|
||||
|
||||
test_type!(i8(
|
||||
Postgres,
|
||||
"0::\"char\"" == 0_i8,
|
||||
"120::\"char\"" == 120_i8,
|
||||
));
|
||||
|
||||
test_type!(u32(Postgres, "325235::oid" == 325235_u32,));
|
||||
|
||||
test_type!(i16(
|
||||
Postgres,
|
||||
"-2144::smallint" == -2144_i16,
|
||||
"821::smallint" == 821_i16,
|
||||
));
|
||||
|
||||
test_type!(i32(
|
||||
Postgres,
|
||||
"94101::int" == 94101_i32,
|
||||
"-5101::int" == -5101_i32
|
||||
));
|
||||
|
||||
test_type!(i32_vec<Vec<i32>>(Postgres,
|
||||
"'{5,10,50,100}'::int[]" == vec![5_i32, 10, 50, 100],
|
||||
"'{1050}'::int[]" == vec![1050_i32],
|
||||
"'{}'::int[]" == Vec::<i32>::new(),
|
||||
"'{1,3,-5}'::int[]" == vec![1_i32, 3, -5]
|
||||
));
|
||||
|
||||
test_type!(i64(Postgres, "9358295312::bigint" == 9358295312_i64));
|
||||
|
||||
test_type!(f32(Postgres, "9419.122::real" == 9419.122_f32));
|
||||
|
||||
test_type!(f64(
|
||||
Postgres,
|
||||
"939399419.1225182::double precision" == 939399419.1225182_f64
|
||||
));
|
||||
|
||||
test_type!(f64_vec<Vec<f64>>(Postgres,
|
||||
"'{939399419.1225182,-12.0}'::float8[]" == vec![939399419.1225182_f64, -12.0]
|
||||
));
|
||||
|
||||
test_decode_type!(bool_tuple<(bool,)>(Postgres, "row(true)" == (true,)));
|
||||
|
||||
test_decode_type!(num_tuple<(i32, i64, f64,)>(Postgres, "row(10,515::int8,3.124::float8)" == (10,515,3.124)));
|
||||
|
||||
test_decode_type!(empty_tuple<()>(Postgres, "row()" == ()));
|
||||
|
||||
test_decode_type!(string_tuple<(String, String, String)>(Postgres,
|
||||
"row('one','two','three')"
|
||||
== ("one".to_string(), "two".to_string(), "three".to_string()),
|
||||
|
||||
"row('', '\"', '\"\"\"\"\"\"')"
|
||||
== ("".to_string(), "\"".to_string(), "\"\"\"\"\"\"".to_string()),
|
||||
|
||||
"row('Hello, World', '', 'Goodbye')"
|
||||
== ("Hello, World".to_string(), "".to_string(), "Goodbye".to_string())
|
||||
));
|
||||
|
||||
#[cfg(feature = "uuid")]
|
||||
test_type!(uuid<sqlx::types::Uuid>(Postgres,
|
||||
"'b731678f-636f-4135-bc6f-19440c13bd19'::uuid"
|
||||
== sqlx::types::Uuid::parse_str("b731678f-636f-4135-bc6f-19440c13bd19").unwrap(),
|
||||
"'00000000-0000-0000-0000-000000000000'::uuid"
|
||||
== sqlx::types::Uuid::parse_str("00000000-0000-0000-0000-000000000000").unwrap()
|
||||
));
|
||||
|
||||
#[cfg(feature = "uuid")]
|
||||
test_type!(uuid_vec<Vec<sqlx::types::Uuid>>(Postgres,
|
||||
"'{b731678f-636f-4135-bc6f-19440c13bd19,00000000-0000-0000-0000-000000000000}'::uuid[]"
|
||||
== vec![
|
||||
sqlx::types::Uuid::parse_str("b731678f-636f-4135-bc6f-19440c13bd19").unwrap(),
|
||||
sqlx::types::Uuid::parse_str("00000000-0000-0000-0000-000000000000").unwrap()
|
||||
]
|
||||
));
|
||||
|
||||
#[cfg(feature = "ipnetwork")]
|
||||
test_type!(ipnetwork<sqlx::types::ipnetwork::IpNetwork>(Postgres,
|
||||
"'127.0.0.1'::inet"
|
||||
== "127.0.0.1"
|
||||
.parse::<sqlx::types::ipnetwork::IpNetwork>()
|
||||
.unwrap(),
|
||||
"'8.8.8.8/24'::inet"
|
||||
== "8.8.8.8/24"
|
||||
.parse::<sqlx::types::ipnetwork::IpNetwork>()
|
||||
.unwrap(),
|
||||
"'::ffff:1.2.3.0'::inet"
|
||||
== "::ffff:1.2.3.0"
|
||||
.parse::<sqlx::types::ipnetwork::IpNetwork>()
|
||||
.unwrap(),
|
||||
"'2001:4f8:3:ba::/64'::inet"
|
||||
== "2001:4f8:3:ba::/64"
|
||||
.parse::<sqlx::types::ipnetwork::IpNetwork>()
|
||||
.unwrap(),
|
||||
"'192.168'::cidr"
|
||||
== "192.168.0.0/24"
|
||||
.parse::<sqlx::types::ipnetwork::IpNetwork>()
|
||||
.unwrap(),
|
||||
"'::ffff:1.2.3.0/120'::cidr"
|
||||
== "::ffff:1.2.3.0/120"
|
||||
.parse::<sqlx::types::ipnetwork::IpNetwork>()
|
||||
.unwrap(),
|
||||
));
|
||||
|
||||
#[cfg(feature = "ipnetwork")]
|
||||
test_type!(ipnetwork_vec<Vec<sqlx::types::ipnetwork::IpNetwork>>(Postgres,
|
||||
"'{127.0.0.1,8.8.8.8/24}'::inet[]"
|
||||
== vec![
|
||||
"127.0.0.1".parse::<sqlx::types::ipnetwork::IpNetwork>().unwrap(),
|
||||
"8.8.8.8/24".parse::<sqlx::types::ipnetwork::IpNetwork>().unwrap()
|
||||
]
|
||||
));
|
||||
|
||||
#[cfg(feature = "chrono")]
|
||||
mod chrono {
|
||||
use super::*;
|
||||
use sqlx::types::chrono::{DateTime, NaiveDate, NaiveDateTime, NaiveTime, Utc};
|
||||
|
||||
test_type!(chrono_date<NaiveDate>(Postgres,
|
||||
"DATE '2001-01-05'" == NaiveDate::from_ymd(2001, 1, 5),
|
||||
"DATE '2050-11-23'" == NaiveDate::from_ymd(2050, 11, 23)
|
||||
));
|
||||
|
||||
test_type!(chrono_time<NaiveTime>(Postgres,
|
||||
"TIME '05:10:20.115100'" == NaiveTime::from_hms_micro(5, 10, 20, 115100)
|
||||
));
|
||||
|
||||
test_type!(chrono_date_time<NaiveDateTime>(Postgres,
|
||||
"'2019-01-02 05:10:20'::timestamp" == NaiveDate::from_ymd(2019, 1, 2).and_hms(5, 10, 20)
|
||||
));
|
||||
|
||||
test_type!(chrono_date_time_vec<Vec<NaiveDateTime>>(Postgres,
|
||||
"array['2019-01-02 05:10:20']::timestamp[]"
|
||||
== vec![NaiveDate::from_ymd(2019, 1, 2).and_hms(5, 10, 20)]
|
||||
));
|
||||
|
||||
test_type!(chrono_date_time_tz<DateTime::<Utc>>(Postgres,
|
||||
"TIMESTAMPTZ '2019-01-02 05:10:20.115100'"
|
||||
== DateTime::<Utc>::from_utc(
|
||||
NaiveDate::from_ymd(2019, 1, 2).and_hms_micro(5, 10, 20, 115100),
|
||||
Utc,
|
||||
)
|
||||
));
|
||||
|
||||
test_type!(chrono_date_time_tz_vec<Vec<DateTime::<Utc>>>(Postgres,
|
||||
"array['2019-01-02 05:10:20.115100']::timestamptz[]"
|
||||
== vec![
|
||||
DateTime::<Utc>::from_utc(
|
||||
NaiveDate::from_ymd(2019, 1, 2).and_hms_micro(5, 10, 20, 115100),
|
||||
Utc,
|
||||
)
|
||||
]
|
||||
));
|
||||
}
|
||||
|
||||
#[cfg(feature = "time")]
|
||||
mod time_tests {
|
||||
use super::*;
|
||||
use sqlx::types::time::{Date, OffsetDateTime, PrimitiveDateTime, Time};
|
||||
use time::{date, time};
|
||||
|
||||
test_type!(time_date<Date>(
|
||||
Postgres,
|
||||
"DATE '2001-01-05'" == date!(2001 - 1 - 5),
|
||||
"DATE '2050-11-23'" == date!(2050 - 11 - 23)
|
||||
));
|
||||
|
||||
test_type!(time_time<Time>(
|
||||
Postgres,
|
||||
"TIME '05:10:20.115100'" == time!(5:10:20.115100)
|
||||
));
|
||||
|
||||
test_type!(time_date_time<PrimitiveDateTime>(
|
||||
Postgres,
|
||||
"TIMESTAMP '2019-01-02 05:10:20'" == date!(2019 - 1 - 2).with_time(time!(5:10:20)),
|
||||
"TIMESTAMP '2019-01-02 05:10:20.115100'"
|
||||
== date!(2019 - 1 - 2).with_time(time!(5:10:20.115100))
|
||||
));
|
||||
|
||||
test_type!(time_timestamp<OffsetDateTime>(
|
||||
Postgres,
|
||||
"TIMESTAMPTZ '2019-01-02 05:10:20.115100'"
|
||||
== date!(2019 - 1 - 2)
|
||||
.with_time(time!(5:10:20.115100))
|
||||
.assume_utc()
|
||||
));
|
||||
}
|
||||
|
||||
#[cfg(feature = "json")]
|
||||
mod json {
|
||||
use super::*;
|
||||
use serde_json::value::RawValue as JsonRawValue;
|
||||
use serde_json::{json, Value as JsonValue};
|
||||
use sqlx::postgres::PgRow;
|
||||
use sqlx::types::Json;
|
||||
use sqlx::{Executor, Row};
|
||||
use sqlx_test::new;
|
||||
|
||||
// When testing JSON, coerce to JSONB for `=` comparison as `JSON = JSON` is not
|
||||
// supported in PostgreSQL
|
||||
|
||||
test_type!(json<JsonValue>(
|
||||
Postgres,
|
||||
"SELECT {0}::jsonb is not distinct from $1::jsonb, {0} as _2, $2 as _3",
|
||||
"'\"Hello, World\"'::json" == json!("Hello, World"),
|
||||
"'\"😎\"'::json" == json!("😎"),
|
||||
"'\"🙋♀️\"'::json" == json!("🙋♀️"),
|
||||
"'[\"Hello\", \"World!\"]'::json" == json!(["Hello", "World!"])
|
||||
));
|
||||
|
||||
test_type!(jsonb<JsonValue>(
|
||||
Postgres,
|
||||
"'\"Hello, World\"'::jsonb" == json!("Hello, World"),
|
||||
"'\"😎\"'::jsonb" == json!("😎"),
|
||||
"'\"🙋♀️\"'::jsonb" == json!("🙋♀️"),
|
||||
"'[\"Hello\", \"World!\"]'::jsonb" == json!(["Hello", "World!"])
|
||||
));
|
||||
|
||||
#[derive(serde::Deserialize, serde::Serialize, Debug, PartialEq)]
|
||||
struct Friend {
|
||||
name: String,
|
||||
age: u32,
|
||||
}
|
||||
|
||||
test_type!(json_struct<Json<Friend>>(Postgres,
|
||||
"'{\"name\":\"Joe\",\"age\":33}'::jsonb" == Json(Friend { name: "Joe".to_string(), age: 33 })
|
||||
));
|
||||
|
||||
test_type!(json_struct_vec<Vec<Json<Friend>>>(Postgres,
|
||||
"array['{\"name\":\"Joe\",\"age\":33}','{\"name\":\"Bob\",\"age\":22}']::jsonb[]"
|
||||
== vec![
|
||||
Json(Friend { name: "Joe".to_string(), age: 33 }),
|
||||
Json(Friend { name: "Bob".to_string(), age: 22 }),
|
||||
]
|
||||
));
|
||||
|
||||
#[sqlx_macros::test]
|
||||
async fn test_json_raw_value() -> anyhow::Result<()> {
|
||||
let mut conn = new::<Postgres>().await?;
|
||||
|
||||
// unprepared, text API
|
||||
let row: PgRow = conn
|
||||
.fetch_one("SELECT '{\"hello\": \"world\"}'::jsonb")
|
||||
.await?;
|
||||
|
||||
let value: &JsonRawValue = row.try_get(0)?;
|
||||
|
||||
assert_eq!(value.get(), "{\"hello\": \"world\"}");
|
||||
|
||||
// prepared, binary API
|
||||
let row: PgRow = conn
|
||||
.fetch_one(sqlx::query("SELECT '{\"hello\": \"world\"}'::jsonb"))
|
||||
.await?;
|
||||
|
||||
let value: &JsonRawValue = row.try_get(0)?;
|
||||
|
||||
assert_eq!(value.get(), "{\"hello\": \"world\"}");
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(feature = "bigdecimal")]
|
||||
test_type!(decimal<sqlx::types::BigDecimal>(Postgres,
|
||||
|
||||
// https://github.com/launchbadge/sqlx/issues/283
|
||||
"0::numeric" == "0".parse::<sqlx::types::BigDecimal>().unwrap(),
|
||||
|
||||
"1::numeric" == "1".parse::<sqlx::types::BigDecimal>().unwrap(),
|
||||
"10000::numeric" == "10000".parse::<sqlx::types::BigDecimal>().unwrap(),
|
||||
"0.1::numeric" == "0.1".parse::<sqlx::types::BigDecimal>().unwrap(),
|
||||
"0.01234::numeric" == "0.01234".parse::<sqlx::types::BigDecimal>().unwrap(),
|
||||
"12.34::numeric" == "12.34".parse::<sqlx::types::BigDecimal>().unwrap(),
|
||||
"12345.6789::numeric" == "12345.6789".parse::<sqlx::types::BigDecimal>().unwrap(),
|
||||
));
|
||||
@ -1,70 +0,0 @@
|
||||
use sqlx::Sqlite;
|
||||
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(
|
||||
Sqlite,
|
||||
Transparent,
|
||||
"0" == Transparent(0),
|
||||
"23523" == Transparent(23523)
|
||||
));
|
||||
|
||||
test_type!(weak_enum(
|
||||
Sqlite,
|
||||
Weak,
|
||||
"0" == Weak::One,
|
||||
"2" == Weak::Two,
|
||||
"4" == Weak::Three
|
||||
));
|
||||
|
||||
test_type!(strong_color_lower_enum(
|
||||
Sqlite,
|
||||
ColorLower,
|
||||
"'green'" == ColorLower::Green
|
||||
));
|
||||
test_type!(strong_color_snake_enum(
|
||||
Sqlite,
|
||||
ColorSnake,
|
||||
"'red_green'" == ColorSnake::RedGreen
|
||||
));
|
||||
test_type!(strong_color_upper_enum(
|
||||
Sqlite,
|
||||
ColorUpper,
|
||||
"'GREEN'" == ColorUpper::Green
|
||||
));
|
||||
@ -1,77 +0,0 @@
|
||||
use sqlx::Sqlite;
|
||||
use sqlx_test::new;
|
||||
|
||||
#[cfg_attr(feature = "runtime-async-std", async_std::test)]
|
||||
#[cfg_attr(feature = "runtime-tokio", tokio::test)]
|
||||
async fn macro_select() -> anyhow::Result<()> {
|
||||
let mut conn = new::<Sqlite>().await?;
|
||||
|
||||
let account = sqlx::query!("select id, name, is_active from accounts where id = 1")
|
||||
.fetch_one(&mut conn)
|
||||
.await?;
|
||||
|
||||
assert_eq!(1, account.id);
|
||||
assert_eq!("Herp Derpinson", account.name);
|
||||
assert_eq!(account.is_active, None);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[cfg_attr(feature = "runtime-async-std", async_std::test)]
|
||||
#[cfg_attr(feature = "runtime-tokio", tokio::test)]
|
||||
async fn macro_select_bind() -> anyhow::Result<()> {
|
||||
let mut conn = new::<Sqlite>().await?;
|
||||
|
||||
let account = sqlx::query!(
|
||||
"select id, name, is_active from accounts where id = ?",
|
||||
1i32
|
||||
)
|
||||
.fetch_one(&mut conn)
|
||||
.await?;
|
||||
|
||||
assert_eq!(1, account.id);
|
||||
assert_eq!("Herp Derpinson", account.name);
|
||||
assert_eq!(account.is_active, None);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
struct RawAccount {
|
||||
id: i32,
|
||||
name: String,
|
||||
is_active: Option<bool>,
|
||||
}
|
||||
|
||||
#[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::<Sqlite>().await?;
|
||||
|
||||
let account = sqlx::query_as!(RawAccount, "SELECT id, name, is_active from accounts")
|
||||
.fetch_one(&mut conn)
|
||||
.await?;
|
||||
|
||||
assert_eq!(account.id, 1);
|
||||
assert_eq!(account.name, "Herp Derpinson");
|
||||
assert_eq!(account.is_active, None);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[cfg_attr(feature = "runtime-async-std", async_std::test)]
|
||||
#[cfg_attr(feature = "runtime-tokio", tokio::test)]
|
||||
async fn macro_select_from_view() -> anyhow::Result<()> {
|
||||
let mut conn = new::<Sqlite>().await?;
|
||||
|
||||
let account = sqlx::query!("SELECT id, name, is_active from accounts_view")
|
||||
.fetch_one(&mut conn)
|
||||
.await?;
|
||||
|
||||
// SQLite tells us the true origin of these columns even through the view
|
||||
assert_eq!(account.id, 1);
|
||||
assert_eq!(account.name, "Herp Derpinson");
|
||||
assert_eq!(account.is_active, None);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
@ -1,52 +0,0 @@
|
||||
//! Tests for the raw (unprepared) query API for Sqlite.
|
||||
|
||||
use sqlx::{Cursor, Executor, Row, Sqlite};
|
||||
use sqlx_test::new;
|
||||
|
||||
#[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::<Sqlite>().await?;
|
||||
|
||||
let mut cursor = conn.fetch("SELECT 5");
|
||||
let row = cursor.next().await?.unwrap();
|
||||
|
||||
assert!(5i32 == row.try_get::<i32, _>(0)?);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[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::<Sqlite>().await?;
|
||||
|
||||
let mut cursor = conn.fetch(
|
||||
"
|
||||
CREATE TABLE IF NOT EXISTS _sqlx_test (
|
||||
id INT PRIMARY KEY,
|
||||
text TEXT NOT NULL
|
||||
);
|
||||
|
||||
SELECT 'Hello World' as _1;
|
||||
|
||||
INSERT INTO _sqlx_test (text) VALUES ('this is a test');
|
||||
|
||||
SELECT id, text FROM _sqlx_test;
|
||||
",
|
||||
);
|
||||
|
||||
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!(0, id);
|
||||
assert_eq!("this is a test", text);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
@ -1,46 +0,0 @@
|
||||
use sqlx::Sqlite;
|
||||
use sqlx_test::test_type;
|
||||
|
||||
test_type!(null(
|
||||
Sqlite,
|
||||
Option<i32>,
|
||||
"NULL" == None::<i32>
|
||||
));
|
||||
|
||||
test_type!(bool(Sqlite, bool, "FALSE" == false, "TRUE" == true));
|
||||
|
||||
test_type!(i32(Sqlite, i32, "94101" == 94101_i32));
|
||||
|
||||
test_type!(i64(Sqlite, i64, "9358295312" == 9358295312_i64));
|
||||
|
||||
// NOTE: This behavior can be surprising. Floating-point parameters are widening to double which can
|
||||
// result in strange rounding.
|
||||
test_type!(f32(
|
||||
Sqlite,
|
||||
f32,
|
||||
"3.1410000324249268" == 3.141f32 as f64 as f32
|
||||
));
|
||||
|
||||
test_type!(f64(
|
||||
Sqlite,
|
||||
f64,
|
||||
"939399419.1225182" == 939399419.1225182_f64
|
||||
));
|
||||
|
||||
test_type!(string(
|
||||
Sqlite,
|
||||
String,
|
||||
"'this is foo'" == "this is foo",
|
||||
"''" == ""
|
||||
));
|
||||
|
||||
test_type!(bytes(
|
||||
Sqlite,
|
||||
Vec<u8>,
|
||||
"X'DEADBEEF'"
|
||||
== vec![0xDE_u8, 0xAD, 0xBE, 0xEF],
|
||||
"X''"
|
||||
== Vec::<u8>::new(),
|
||||
"X'0000000052'"
|
||||
== vec![0_u8, 0, 0, 0, 0x52]
|
||||
));
|
||||
212
tests/sqlite.rs
212
tests/sqlite.rs
@ -1,212 +0,0 @@
|
||||
use futures::TryStreamExt;
|
||||
use sqlx::{sqlite::SqliteQueryAs, Connect, Connection, Executor, Sqlite, SqliteConnection};
|
||||
use sqlx_test::new;
|
||||
|
||||
#[cfg_attr(feature = "runtime-async-std", async_std::test)]
|
||||
#[cfg_attr(feature = "runtime-tokio", tokio::test)]
|
||||
async fn it_connects() -> anyhow::Result<()> {
|
||||
Ok(new::<Sqlite>().await?.ping().await?)
|
||||
}
|
||||
|
||||
#[cfg_attr(feature = "runtime-async-std", async_std::test)]
|
||||
#[cfg_attr(feature = "runtime-tokio", tokio::test)]
|
||||
async fn it_fails_to_connect() -> anyhow::Result<()> {
|
||||
// empty connection string
|
||||
assert!(SqliteConnection::connect("").await.is_err());
|
||||
assert!(
|
||||
SqliteConnection::connect("sqlite:///please_do_not_run_sqlx_tests_as_root")
|
||||
.await
|
||||
.is_err()
|
||||
);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[cfg_attr(feature = "runtime-async-std", async_std::test)]
|
||||
#[cfg_attr(feature = "runtime-tokio", tokio::test)]
|
||||
async fn it_fails_to_parse() -> anyhow::Result<()> {
|
||||
let mut conn = new::<Sqlite>().await?;
|
||||
let res = conn.execute("SEELCT 1").await;
|
||||
|
||||
assert!(res.is_err());
|
||||
|
||||
let err = res.unwrap_err().to_string();
|
||||
|
||||
assert_eq!("near \"SEELCT\": syntax error", err);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[cfg_attr(feature = "runtime-async-std", async_std::test)]
|
||||
#[cfg_attr(feature = "runtime-tokio", tokio::test)]
|
||||
async fn it_handles_empty_queries() -> anyhow::Result<()> {
|
||||
let mut conn = new::<Sqlite>().await?;
|
||||
let affected = conn.execute("").await?;
|
||||
|
||||
assert_eq!(affected, 0);
|
||||
|
||||
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::<Sqlite>().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_can_execute_multiple_statements() -> anyhow::Result<()> {
|
||||
let mut conn = new::<Sqlite>().await?;
|
||||
|
||||
let affected = conn
|
||||
.execute(
|
||||
r#"
|
||||
CREATE TEMPORARY TABLE users (id INTEGER PRIMARY KEY, other INTEGER);
|
||||
INSERT INTO users DEFAULT VALUES;
|
||||
"#,
|
||||
)
|
||||
.await?;
|
||||
|
||||
assert_eq!(affected, 1);
|
||||
|
||||
for index in 2..5_i32 {
|
||||
let (id, other): (i32, i32) = sqlx::query_as(
|
||||
r#"
|
||||
INSERT INTO users (other) VALUES (?);
|
||||
SELECT id, other FROM users WHERE id = last_insert_rowid();
|
||||
"#,
|
||||
)
|
||||
.bind(index)
|
||||
.fetch_one(&mut conn)
|
||||
.await?;
|
||||
|
||||
assert_eq!(id, index);
|
||||
assert_eq!(other, index);
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[cfg_attr(feature = "runtime-async-std", async_std::test)]
|
||||
#[cfg_attr(feature = "runtime-tokio", tokio::test)]
|
||||
async fn it_describes() -> anyhow::Result<()> {
|
||||
let mut conn = new::<Sqlite>().await?;
|
||||
|
||||
let _ = conn
|
||||
.execute(
|
||||
r#"
|
||||
CREATE TEMPORARY TABLE describe_test (
|
||||
_1 int primary key,
|
||||
_2 text not null,
|
||||
_3 blob,
|
||||
_4 boolean,
|
||||
_5 float,
|
||||
_6 varchar(255),
|
||||
_7 double,
|
||||
_8 bigint
|
||||
)
|
||||
"#,
|
||||
)
|
||||
.await?;
|
||||
|
||||
let describe = conn
|
||||
.describe("select nt.*, false from describe_test nt")
|
||||
.await?;
|
||||
|
||||
assert_eq!(
|
||||
describe.result_columns[0]
|
||||
.type_info
|
||||
.as_ref()
|
||||
.unwrap()
|
||||
.to_string(),
|
||||
"INTEGER"
|
||||
);
|
||||
assert_eq!(
|
||||
describe.result_columns[1]
|
||||
.type_info
|
||||
.as_ref()
|
||||
.unwrap()
|
||||
.to_string(),
|
||||
"TEXT"
|
||||
);
|
||||
assert_eq!(
|
||||
describe.result_columns[2]
|
||||
.type_info
|
||||
.as_ref()
|
||||
.unwrap()
|
||||
.to_string(),
|
||||
"BLOB"
|
||||
);
|
||||
assert_eq!(
|
||||
describe.result_columns[3]
|
||||
.type_info
|
||||
.as_ref()
|
||||
.unwrap()
|
||||
.to_string(),
|
||||
"BOOLEAN"
|
||||
);
|
||||
assert_eq!(
|
||||
describe.result_columns[4]
|
||||
.type_info
|
||||
.as_ref()
|
||||
.unwrap()
|
||||
.to_string(),
|
||||
"DOUBLE"
|
||||
);
|
||||
assert_eq!(
|
||||
describe.result_columns[5]
|
||||
.type_info
|
||||
.as_ref()
|
||||
.unwrap()
|
||||
.to_string(),
|
||||
"TEXT"
|
||||
);
|
||||
assert_eq!(
|
||||
describe.result_columns[6]
|
||||
.type_info
|
||||
.as_ref()
|
||||
.unwrap()
|
||||
.to_string(),
|
||||
"DOUBLE"
|
||||
);
|
||||
assert_eq!(
|
||||
describe.result_columns[7]
|
||||
.type_info
|
||||
.as_ref()
|
||||
.unwrap()
|
||||
.to_string(),
|
||||
"INTEGER"
|
||||
);
|
||||
|
||||
// Expressions can not be described
|
||||
assert!(describe.result_columns[8].type_info.is_none());
|
||||
|
||||
Ok(())
|
||||
}
|
||||
54
tests/sqlite/describe.rs
Normal file
54
tests/sqlite/describe.rs
Normal file
@ -0,0 +1,54 @@
|
||||
use sqlx::{sqlite::Sqlite, Executor};
|
||||
use sqlx_core::describe::Column;
|
||||
use sqlx_test::new;
|
||||
|
||||
fn type_names(columns: &[Column<Sqlite>]) -> Vec<String> {
|
||||
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::<Sqlite>().await?;
|
||||
|
||||
let d = conn.describe("SELECT * FROM tweet").await?;
|
||||
|
||||
let columns = d.columns;
|
||||
|
||||
assert_eq!(columns[0].name, "id");
|
||||
assert_eq!(columns[1].name, "text");
|
||||
assert_eq!(columns[2].name, "is_sent");
|
||||
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)); // owner_id
|
||||
|
||||
let column_type_names = type_names(&columns);
|
||||
|
||||
assert_eq!(column_type_names[0], "BIGINT");
|
||||
assert_eq!(column_type_names[1], "TEXT");
|
||||
assert_eq!(column_type_names[2], "BOOLEAN");
|
||||
assert_eq!(column_type_names[3], "BIGINT");
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[sqlx_macros::test]
|
||||
async fn it_describes_expression() -> anyhow::Result<()> {
|
||||
let mut conn = new::<Sqlite>().await?;
|
||||
|
||||
let d = conn.describe("SELECT 1 + 10").await?;
|
||||
let columns = d.columns;
|
||||
|
||||
assert_eq!(columns[0].name, "1 + 10");
|
||||
assert_eq!(columns[0].not_null, None);
|
||||
|
||||
// SQLite cannot infer types for expressions
|
||||
assert_eq!(columns[0].type_info, None);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
8
tests/sqlite/setup.sql
Normal file
8
tests/sqlite/setup.sql
Normal file
@ -0,0 +1,8 @@
|
||||
-- https://github.com/prisma/database-schema-examples/tree/master/postgres/basic-twitter#basic-twitter
|
||||
CREATE TABLE tweet
|
||||
(
|
||||
id BIGINT NOT NULL PRIMARY KEY,
|
||||
text TEXT NOT NULL,
|
||||
is_sent BOOLEAN NOT NULL DEFAULT TRUE,
|
||||
owner_id BIGINT
|
||||
);
|
||||
Binary file not shown.
253
tests/sqlite/sqlite.rs
Normal file
253
tests/sqlite/sqlite.rs
Normal file
@ -0,0 +1,253 @@
|
||||
use futures::TryStreamExt;
|
||||
use sqlx::{query, sqlite::Sqlite, Connect, Connection, Executor, Row, SqliteConnection};
|
||||
use sqlx_test::new;
|
||||
|
||||
#[sqlx_macros::test]
|
||||
async fn it_connects() -> anyhow::Result<()> {
|
||||
Ok(new::<Sqlite>().await?.ping().await?)
|
||||
}
|
||||
|
||||
#[sqlx_macros::test]
|
||||
async fn it_fetches_and_inflates_row() -> anyhow::Result<()> {
|
||||
let mut conn = new::<Sqlite>().await?;
|
||||
|
||||
// process rows, one-at-a-time
|
||||
// this reuses the memory of the row
|
||||
|
||||
{
|
||||
let expected = [15, 39, 51];
|
||||
let mut i = 0;
|
||||
let mut s = conn.fetch("SELECT 15 UNION SELECT 51 UNION SELECT 39");
|
||||
|
||||
while let Some(row) = s.try_next().await? {
|
||||
let v1 = row.get::<i32, _>(0);
|
||||
assert_eq!(expected[i], v1);
|
||||
i += 1;
|
||||
}
|
||||
}
|
||||
|
||||
// same query, but fetch all rows at once
|
||||
// this triggers the internal inflation
|
||||
|
||||
let rows = conn
|
||||
.fetch_all("SELECT 15 UNION SELECT 51 UNION SELECT 39")
|
||||
.await?;
|
||||
|
||||
assert_eq!(rows.len(), 3);
|
||||
assert_eq!(rows[0].get::<i32, _>(0), 15);
|
||||
assert_eq!(rows[1].get::<i32, _>(0), 39);
|
||||
assert_eq!(rows[2].get::<i32, _>(0), 51);
|
||||
|
||||
// same query but fetch the first row a few times from a non-persistent query
|
||||
// these rows should be immediately inflated
|
||||
|
||||
let row1 = conn
|
||||
.fetch_one("SELECT 15 UNION SELECT 51 UNION SELECT 39")
|
||||
.await?;
|
||||
|
||||
assert_eq!(row1.get::<i32, _>(0), 15);
|
||||
|
||||
let row2 = conn
|
||||
.fetch_one("SELECT 15 UNION SELECT 51 UNION SELECT 39")
|
||||
.await?;
|
||||
|
||||
assert_eq!(row1.get::<i32, _>(0), 15);
|
||||
assert_eq!(row2.get::<i32, _>(0), 15);
|
||||
|
||||
// same query (again) but make it persistent
|
||||
// and fetch the first row a few times
|
||||
|
||||
let row1 = conn
|
||||
.fetch_one(query("SELECT 15 UNION SELECT 51 UNION SELECT 39"))
|
||||
.await?;
|
||||
|
||||
assert_eq!(row1.get::<i32, _>(0), 15);
|
||||
|
||||
let row2 = conn
|
||||
.fetch_one(query("SELECT 15 UNION SELECT 51 UNION SELECT 39"))
|
||||
.await?;
|
||||
|
||||
assert_eq!(row1.get::<i32, _>(0), 15);
|
||||
assert_eq!(row2.get::<i32, _>(0), 15);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[sqlx_macros::test]
|
||||
async fn it_fetches_in_loop() -> anyhow::Result<()> {
|
||||
// this is trying to check for any data races
|
||||
// there were a few that triggered *sometimes* while building out StatementWorker
|
||||
for _ in 0..1000_usize {
|
||||
let mut conn = new::<Sqlite>().await?;
|
||||
let v: Vec<(i32,)> = sqlx::query_as("SELECT 1").fetch_all(&mut conn).await?;
|
||||
|
||||
assert_eq!(v[0].0, 1);
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[sqlx_macros::test]
|
||||
async fn it_opens_in_memory() -> anyhow::Result<()> {
|
||||
// If the filename is ":memory:", then a private, temporary in-memory database
|
||||
// is created for the connection.
|
||||
let _ = SqliteConnection::connect(":memory:").await?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[sqlx_macros::test]
|
||||
async fn it_opens_temp_on_disk() -> anyhow::Result<()> {
|
||||
// If the filename is an empty string, then a private, temporary on-disk database will
|
||||
// be created.
|
||||
let _ = SqliteConnection::connect("").await?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[sqlx_macros::test]
|
||||
async fn it_fails_to_parse() -> anyhow::Result<()> {
|
||||
let mut conn = new::<Sqlite>().await?;
|
||||
let res = conn.execute("SEELCT 1").await;
|
||||
|
||||
assert!(res.is_err());
|
||||
|
||||
let err = res.unwrap_err().to_string();
|
||||
|
||||
assert_eq!(
|
||||
"error returned from database: near \"SEELCT\": syntax error",
|
||||
err
|
||||
);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[sqlx_macros::test]
|
||||
async fn it_handles_empty_queries() -> anyhow::Result<()> {
|
||||
let mut conn = new::<Sqlite>().await?;
|
||||
let affected = conn.execute("").await?;
|
||||
|
||||
assert_eq!(affected, 0);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[sqlx_macros::test]
|
||||
fn it_binds_parameters() -> anyhow::Result<()> {
|
||||
let mut conn = new::<Sqlite>().await?;
|
||||
|
||||
let v: i32 = sqlx::query_scalar("SELECT ?")
|
||||
.bind(10_i32)
|
||||
.fetch_one(&mut conn)
|
||||
.await?;
|
||||
|
||||
assert_eq!(v, 10);
|
||||
|
||||
let v: (i32, i32) = sqlx::query_as("SELECT ?1, ?")
|
||||
.bind(10_i32)
|
||||
.fetch_one(&mut conn)
|
||||
.await?;
|
||||
|
||||
assert_eq!(v.0, 10);
|
||||
assert_eq!(v.1, 10);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[sqlx_macros::test]
|
||||
async fn it_executes_queries() -> anyhow::Result<()> {
|
||||
let mut conn = new::<Sqlite>().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 * 2)
|
||||
.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, 110);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[sqlx_macros::test]
|
||||
async fn it_can_execute_multiple_statements() -> anyhow::Result<()> {
|
||||
let mut conn = new::<Sqlite>().await?;
|
||||
|
||||
let affected = conn
|
||||
.execute(
|
||||
r#"
|
||||
CREATE TEMPORARY TABLE users (id INTEGER PRIMARY KEY, other INTEGER);
|
||||
INSERT INTO users DEFAULT VALUES;
|
||||
"#,
|
||||
)
|
||||
.await?;
|
||||
|
||||
assert_eq!(affected, 1);
|
||||
|
||||
for index in 2..5_i32 {
|
||||
let (id, other): (i32, i32) = sqlx::query_as(
|
||||
r#"
|
||||
INSERT INTO users (other) VALUES (?);
|
||||
SELECT id, other FROM users WHERE id = last_insert_rowid();
|
||||
"#,
|
||||
)
|
||||
.bind(index)
|
||||
.fetch_one(&mut conn)
|
||||
.await?;
|
||||
|
||||
assert_eq!(id, index);
|
||||
assert_eq!(other, index);
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[sqlx_macros::test]
|
||||
async fn it_interleaves_reads_and_writes() -> anyhow::Result<()> {
|
||||
let mut conn = new::<Sqlite>().await?;
|
||||
|
||||
let mut cursor = conn.fetch(
|
||||
"
|
||||
CREATE TABLE IF NOT EXISTS _sqlx_test (
|
||||
id INT PRIMARY KEY,
|
||||
text TEXT NOT NULL
|
||||
);
|
||||
|
||||
SELECT 'Hello World' as _1;
|
||||
|
||||
INSERT INTO _sqlx_test (text) VALUES ('this is a test');
|
||||
|
||||
SELECT id, text FROM _sqlx_test;
|
||||
",
|
||||
);
|
||||
|
||||
let row = cursor.try_next().await?.unwrap();
|
||||
|
||||
assert!("Hello World" == row.try_get::<&str, _>("_1")?);
|
||||
|
||||
let row = cursor.try_next().await?.unwrap();
|
||||
|
||||
let id: i64 = row.try_get("id")?;
|
||||
let text: &str = row.try_get("text")?;
|
||||
|
||||
assert_eq!(0, id);
|
||||
assert_eq!("this is a test", text);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
33
tests/sqlite/types.rs
Normal file
33
tests/sqlite/types.rs
Normal file
@ -0,0 +1,33 @@
|
||||
use sqlx::sqlite::Sqlite;
|
||||
use sqlx_test::test_type;
|
||||
|
||||
test_type!(null<Option<i32>>(Sqlite,
|
||||
"NULL" == None::<i32>
|
||||
));
|
||||
|
||||
test_type!(bool(Sqlite, "FALSE" == false, "TRUE" == true));
|
||||
|
||||
test_type!(i32(Sqlite, "94101" == 94101_i32));
|
||||
|
||||
test_type!(i64(Sqlite, "9358295312" == 9358295312_i64));
|
||||
|
||||
// NOTE: This behavior can be surprising. Floating-point parameters are widening to double which can
|
||||
// result in strange rounding.
|
||||
test_type!(f32(Sqlite, "3.1410000324249268" == 3.141f32 as f64 as f32));
|
||||
|
||||
test_type!(f64(Sqlite, "939399419.1225182" == 939399419.1225182_f64));
|
||||
|
||||
test_type!(str<String>(Sqlite,
|
||||
"'this is foo'" == "this is foo",
|
||||
"cast(x'7468697320006973206E756C2D636F6E7461696E696E67' as text)" == "this \0is nul-containing",
|
||||
"''" == ""
|
||||
));
|
||||
|
||||
test_type!(bytes<Vec<u8>>(Sqlite,
|
||||
"X'DEADBEEF'"
|
||||
== vec![0xDE_u8, 0xAD, 0xBE, 0xEF],
|
||||
"X''"
|
||||
== Vec::<u8>::new(),
|
||||
"X'0000000052'"
|
||||
== vec![0_u8, 0, 0, 0, 0x52]
|
||||
));
|
||||
@ -1,2 +0,0 @@
|
||||
select * from (select (1) as id, 'Herp Derpinson' as name) accounts
|
||||
where id = ?
|
||||
@ -1 +0,0 @@
|
||||
SELECT * from (VALUES (1, 'Herp Derpinson')) accounts(id, name)
|
||||
@ -1,6 +1,7 @@
|
||||
use std::path::Path;
|
||||
|
||||
#[test]
|
||||
#[ignore]
|
||||
fn ui_tests() {
|
||||
let t = trybuild::TestCases::new();
|
||||
|
||||
|
||||
209
tests/x.py
Executable file
209
tests/x.py
Executable file
@ -0,0 +1,209 @@
|
||||
#!/usr/bin/env python
|
||||
import subprocess
|
||||
import os
|
||||
import sys
|
||||
import time
|
||||
import argparse
|
||||
from glob import glob
|
||||
|
||||
parser = argparse.ArgumentParser()
|
||||
parser.add_argument("-t", "--target")
|
||||
parser.add_argument("-l", "--list-targets", action="store_true")
|
||||
parser.add_argument("--test")
|
||||
|
||||
argv, unknown = parser.parse_known_args()
|
||||
|
||||
|
||||
def start(service):
|
||||
res = subprocess.run(
|
||||
["docker-compose", "up", "-d", service],
|
||||
stdout=subprocess.PIPE,
|
||||
stderr=subprocess.PIPE,
|
||||
cwd=os.path.dirname(__file__),
|
||||
)
|
||||
|
||||
if res.returncode != 0:
|
||||
print(res.stderr, file=sys.stderr)
|
||||
|
||||
if b"done" in res.stderr:
|
||||
time.sleep(30)
|
||||
|
||||
|
||||
def run(command, comment=None, env=None, service=None, tag=None, args=None):
|
||||
if argv.list_targets:
|
||||
if tag:
|
||||
print(f"{tag}")
|
||||
|
||||
return
|
||||
|
||||
if argv.target and tag != argv.target:
|
||||
return
|
||||
|
||||
if comment is not None:
|
||||
print(f"\x1b[2m # {comment}\x1b[0m")
|
||||
|
||||
environ = []
|
||||
if env is not None:
|
||||
for name, value in env.items():
|
||||
print(f"\x1b[93m $ {name}={value}\x1b[0m")
|
||||
environ.append(f"-e{name}={value}")
|
||||
|
||||
if service is not None:
|
||||
start(service)
|
||||
|
||||
command_args = []
|
||||
|
||||
if argv.test:
|
||||
command_args.extend(["--test", argv.test])
|
||||
|
||||
if unknown:
|
||||
command_args.extend(["--", *unknown])
|
||||
|
||||
if args is not None:
|
||||
command_args.extend(args)
|
||||
|
||||
print(f"\x1b[93m $ {command} {' '.join(command_args)}\x1b[0m")
|
||||
|
||||
res = subprocess.run(
|
||||
[
|
||||
"docker-compose",
|
||||
"run",
|
||||
"--user",
|
||||
f"{os.getuid()}:{os.getgid()}",
|
||||
"--rm",
|
||||
*environ,
|
||||
"sqlx",
|
||||
*command.split(" "),
|
||||
*command_args
|
||||
],
|
||||
cwd=os.path.dirname(__file__),
|
||||
)
|
||||
|
||||
if res.returncode != 0:
|
||||
sys.exit(res.returncode)
|
||||
|
||||
|
||||
# before we start, we clean previous profile data
|
||||
# keeping these around can cause weird errors
|
||||
for path in glob(os.path.join(os.path.dirname(__file__), "../target/**/*.gc*"), recursive=True):
|
||||
os.remove(path)
|
||||
|
||||
#
|
||||
# check
|
||||
#
|
||||
|
||||
run("cargo c", comment="check with a default set of features", tag="check")
|
||||
|
||||
run(
|
||||
"cargo c --no-default-features --features runtime-async-std,all-databases,all-types",
|
||||
comment="check with async-std",
|
||||
tag="check_async_std"
|
||||
)
|
||||
|
||||
run(
|
||||
"cargo c --no-default-features --features runtime-tokio,all-databases,all-types",
|
||||
comment="check with tokio",
|
||||
tag="check_tokio"
|
||||
)
|
||||
|
||||
run(
|
||||
"cargo c --no-default-features --features runtime-actix,all-databases,all-types",
|
||||
comment="check with actix",
|
||||
tag="check_actix"
|
||||
)
|
||||
|
||||
#
|
||||
# unit test
|
||||
#
|
||||
|
||||
run(
|
||||
"cargo test --manifest-path sqlx-core/Cargo.toml --features mysql,postgres,sqlite,all-types",
|
||||
comment="unit test core",
|
||||
tag="unit"
|
||||
)
|
||||
|
||||
run(
|
||||
"cargo test --no-default-features --manifest-path sqlx-core/Cargo.toml --features mysql,postgres,sqlite,all-types,runtime-tokio",
|
||||
comment="unit test core",
|
||||
tag="unit_tokio"
|
||||
)
|
||||
|
||||
#
|
||||
# integration tests
|
||||
#
|
||||
|
||||
for runtime in ["async-std", "tokio", "actix"]:
|
||||
|
||||
#
|
||||
# sqlite
|
||||
#
|
||||
|
||||
run(
|
||||
f"cargo test --no-default-features --features all-types,sqlite,runtime-{runtime}",
|
||||
comment=f"test sqlite",
|
||||
env={"DATABASE_URL": f"sqlite://tests/sqlite/sqlite.db"},
|
||||
tag=f"sqlite" if runtime == "async-std" else f"sqlite_{runtime}",
|
||||
# FIXME: The SQLite driver does not currently support concurrent access to the same database
|
||||
args=["--test-threads=1"],
|
||||
)
|
||||
|
||||
#
|
||||
# postgres
|
||||
#
|
||||
|
||||
for version in ["12", "10", "9.6", "9.5"]:
|
||||
v = version.replace(".", "_")
|
||||
run(
|
||||
f"cargo test --no-default-features --features all-types,postgres,runtime-{runtime}",
|
||||
comment=f"test postgres {version}",
|
||||
env={"DATABASE_URL": f"postgres://postgres:password@postgres_{v}/sqlx"},
|
||||
service=f"postgres_{v}",
|
||||
tag=f"postgres_{v}" if runtime == "async-std" else f"postgres_{v}_{runtime}",
|
||||
)
|
||||
|
||||
#
|
||||
# postgres ssl
|
||||
#
|
||||
|
||||
for version in ["12", "10", "9.6", "9.5"]:
|
||||
v = version.replace(".", "_")
|
||||
run(
|
||||
f"cargo test --no-default-features --features all-types,postgres,runtime-{runtime}",
|
||||
comment=f"test postgres {version} ssl",
|
||||
env={
|
||||
"DATABASE_URL": f"postgres://postgres:password@postgres_{v}/sqlx?sslmode=verify-ca&sslrootcert=.%2Ftests%2Fcerts%2Fca.crt"
|
||||
},
|
||||
service=f"postgres_{v}",
|
||||
tag=f"postgres_{v}_ssl" if runtime == "async-std" else f"postgres_{v}_ssl_{runtime}",
|
||||
)
|
||||
|
||||
#
|
||||
# mysql
|
||||
#
|
||||
|
||||
for version in ["8", "5.7", "5.6"]:
|
||||
v = version.replace(".", "_")
|
||||
run(
|
||||
f"cargo test --no-default-features --features all-types,mysql,runtime-{runtime}",
|
||||
comment=f"test mysql {version}",
|
||||
env={"DATABASE_URL": f"mysql://root:password@mysql_{v}/sqlx"},
|
||||
service=f"mysql_{v}",
|
||||
tag=f"mysql_{v}" if runtime == "async-std" else f"mysql_{v}_{runtime}",
|
||||
)
|
||||
|
||||
#
|
||||
# mariadb
|
||||
#
|
||||
|
||||
for version in ["10_5", "10_4", "10_3", "10_2", "10_1"]:
|
||||
v = version.replace(".", "_")
|
||||
run(
|
||||
f"cargo test --no-default-features --features all-types,mysql,runtime-{runtime}",
|
||||
comment=f"test mariadb {version}",
|
||||
env={"DATABASE_URL": f"mysql://root:password@mariadb_{v}/sqlx"},
|
||||
service=f"mariadb_{v}",
|
||||
tag=f"mariadb_{v}" if runtime == "async-std" else f"mariadb_{v}_{runtime}",
|
||||
)
|
||||
|
||||
# TODO: Use [grcov] if available
|
||||
# ~/.cargo/bin/grcov tests/.cache/target/debug -s sqlx-core/ -t html --llvm --branch -o ./target/debug/coverage
|
||||
Loading…
x
Reference in New Issue
Block a user