use super::*;
use sqlx::postgres::PgPoolOptions;
use std::time::Duration;
async fn live_pool() -> sqlx::PgPool {
let url = std::env::var("DATABASE_URL").expect("DATABASE_URL must be set");
PgPoolOptions::new()
.max_connections(1)
.connect(&url)
.await
.expect("database must be reachable")
}
#[test]
fn prepared_queries_hand_sqlx_an_owned_safe_sql_string() {
let prepared = prepare(RenderedPgQuery {
sql: Arc::from("SELECT 1"),
binds: Vec::new(),
shape_id: crate::QueryShapeId(7),
})
.unwrap();
let safe = prepared.sql();
assert_eq!(safe.0.as_ref(), "SELECT 1");
assert!(Arc::ptr_eq(&safe.0, &prepared.sql));
assert_eq!(prepared.shape_id, crate::QueryShapeId(7));
}
#[test]
fn the_sql_assertion_does_not_borrow_the_prepared_query() {
fn assert_owned<T: sqlx::SqlSafeStr>(value: T) -> T {
value
}
let prepared = prepare(RenderedPgQuery {
sql: Arc::from("SELECT $1"),
binds: Vec::new(),
shape_id: crate::QueryShapeId(8),
})
.unwrap();
let sql = assert_owned(prepared.sql());
drop(prepared);
assert_eq!(sql.0.as_ref(), "SELECT $1");
}
#[tokio::test]
#[ignore = "requires a disposable PostgreSQL instance in DATABASE_URL"]
async fn configure_statement_timeout_reports_an_expired_deadline() {
let pool = live_pool().await;
let deadline = Instant::now() - Duration::from_secs(1);
let connection = pool.acquire().await.expect("pool hands out slots");
let error = configure_statement_timeout(connection, deadline, TimeoutStage::ListQuery)
.await
.unwrap_err();
assert!(
matches!(error, PgSearchError::Timeout(TimeoutStage::ListQuery)),
"{error:?}"
);
}
#[tokio::test]
#[ignore = "requires a disposable PostgreSQL instance in DATABASE_URL"]
async fn configure_statement_timeout_returns_a_usable_connection() {
let pool = live_pool().await;
let deadline = Instant::now() + Duration::from_secs(5);
let connection = pool.acquire().await.expect("pool hands out slots");
let connection = configure_statement_timeout(connection, deadline, TimeoutStage::ListQuery)
.await
.expect("a live server accepts the timeout setting");
let (mut connection, ()) = finish_statement(connection, Ok(()), TimeoutStage::ListQuery)
.await
.expect("reset succeeds");
connection.return_to_pool().await;
}
#[tokio::test]
#[ignore = "requires a disposable PostgreSQL instance in DATABASE_URL"]
async fn finish_statement_reports_a_failed_query() {
let pool = live_pool().await;
let connection = pool.acquire().await.expect("pool hands out slots");
let failure: Result<(), sqlx::Error> = Err(sqlx::Error::RowNotFound);
let error = finish_statement(connection, failure, TimeoutStage::ClusterQuery)
.await
.unwrap_err();
assert!(matches!(error, PgSearchError::Sql(_)), "{error:?}");
}
#[tokio::test]
#[ignore = "requires a disposable PostgreSQL instance in DATABASE_URL"]
async fn finish_statement_maps_a_statement_timeout_to_a_timeout_error() {
let pool = live_pool().await;
let connection = pool.acquire().await.expect("pool hands out slots");
let failure: Result<(), sqlx::Error> = Err(sqlx::Error::Database(Box::new(TestDatabaseError(
"57014".to_owned(),
))));
let error = finish_statement(connection, failure, TimeoutStage::ListQuery)
.await
.unwrap_err();
assert!(
matches!(error, PgSearchError::Timeout(TimeoutStage::ListQuery)),
"{error:?}"
);
}
#[tokio::test]
#[ignore = "requires a disposable PostgreSQL instance in DATABASE_URL"]
async fn close_hard_releases_the_connection() {
let pool = live_pool().await;
let connection = pool.acquire().await.expect("pool hands out slots");
close_hard(connection).await;
let value: i32 = sqlx::query_scalar("SELECT 1")
.fetch_one(&pool)
.await
.expect("pool remains usable");
assert_eq!(value, 1);
}
#[test]
fn elapsed_micros_is_monotonic() {
let started = Instant::now();
let first = elapsed_micros(started);
let second = elapsed_micros(started);
assert!(second >= first);
}
#[derive(Debug)]
struct TestDatabaseError(String);
impl std::fmt::Display for TestDatabaseError {
fn fmt(&self, formatter: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
formatter.write_str("test database error")
}
}
impl std::error::Error for TestDatabaseError {}
impl sqlx::error::DatabaseError for TestDatabaseError {
fn message(&self) -> &'static str {
"canceling statement due to statement timeout"
}
fn kind(&self) -> sqlx::error::ErrorKind {
sqlx::error::ErrorKind::Other
}
fn as_error(&self) -> &(dyn std::error::Error + Send + Sync + 'static) {
self
}
fn as_error_mut(&mut self) -> &mut (dyn std::error::Error + Send + Sync + 'static) {
self
}
fn into_error(self: Box<Self>) -> Box<dyn std::error::Error + Send + Sync + 'static> {
self
}
fn code(&self) -> Option<std::borrow::Cow<'_, str>> {
Some(std::borrow::Cow::Borrowed(&self.0))
}
}