#![allow(dead_code)]
use resolute::test_db::{
test_addr as addr, test_database as db, test_password as pass, test_user as user,
};
use resolute::{ExclusivePool, Executor};
#[tokio::test]
async fn test_typed_pool_connect() {
let pool = ExclusivePool::connect(addr(), user(), pass(), db(), 3)
.await
.unwrap();
let m = pool.metrics();
assert_eq!(m.total, 1); }
#[tokio::test]
async fn test_typed_pool_query() {
let pool = ExclusivePool::connect(addr(), user(), pass(), db(), 3)
.await
.unwrap();
let client = pool.get().await.unwrap();
let rows = client.query("SELECT 42::int4 AS n", &[]).await.unwrap();
assert_eq!(rows[0].get::<i32>(0).unwrap(), 42);
}
#[tokio::test]
async fn test_typed_pool_parameterized() {
let pool = ExclusivePool::connect(addr(), user(), pass(), db(), 3)
.await
.unwrap();
let client = pool.get().await.unwrap();
let rows = client
.query("SELECT name FROM api.authors WHERE id = $1", &[&1i32])
.await
.unwrap();
assert_eq!(rows[0].get::<String>(0).unwrap(), "Alice");
}
#[tokio::test]
async fn test_typed_pool_reuse() {
let pool = ExclusivePool::connect(addr(), user(), pass(), db(), 3)
.await
.unwrap();
for i in 0..10i32 {
let client = pool.get().await.unwrap();
let rows = client.query("SELECT $1::int4 AS n", &[&i]).await.unwrap();
assert_eq!(rows[0].get::<i32>(0).unwrap(), i);
}
let m = pool.metrics();
assert!(
m.total <= 3,
"pool should reuse connections, not create new ones"
);
}
#[tokio::test]
async fn test_typed_pool_from_row() {
#[derive(resolute::FromRow)]
struct Author {
id: i32,
name: String,
}
let pool = ExclusivePool::connect(addr(), user(), pass(), db(), 3)
.await
.unwrap();
let client = pool.get().await.unwrap();
let rows = client
.query("SELECT id, name FROM api.authors ORDER BY id", &[])
.await
.unwrap();
let authors: Vec<Author> = rows
.iter()
.map(|r| resolute::FromRow::from_row(r).unwrap())
.collect();
assert!(authors.len() >= 3);
assert_eq!(authors[0].name, "Alice");
}
#[tokio::test]
async fn test_typed_pool_drain() {
let pool = ExclusivePool::connect(addr(), user(), pass(), db(), 2)
.await
.unwrap();
pool.drain().await;
assert_eq!(pool.metrics().total, 0);
}
#[tokio::test]
async fn test_typed_pool_query_named() {
let pool = ExclusivePool::connect(addr(), user(), pass(), db(), 3)
.await
.unwrap();
let client = pool.get().await.unwrap();
let rows = client
.query_named(
"SELECT :id::int4 AS n, :name::text AS s",
&[
("id", &42i32 as &dyn resolute::SqlParam),
("name", &"pooled" as &dyn resolute::SqlParam),
],
)
.await
.unwrap();
assert_eq!(rows[0].get::<i32>(0).unwrap(), 42);
assert_eq!(rows[0].get::<String>(1).unwrap(), "pooled");
}
#[tokio::test]
async fn test_typed_pool_execute_named() {
let pool = ExclusivePool::connect(addr(), user(), pass(), db(), 3)
.await
.unwrap();
let client = pool.get().await.unwrap();
client
.simple_query("CREATE TEMP TABLE pool_named_test (id int, val text)")
.await
.unwrap();
let count = client
.execute_named(
"INSERT INTO pool_named_test VALUES (:id, :val)",
&[
("id", &1i32 as &dyn resolute::SqlParam),
("val", &"hello" as &dyn resolute::SqlParam),
],
)
.await
.unwrap();
assert_eq!(count, 1);
}
#[tokio::test]
async fn test_pool_not_poisoned_by_aborted_tx_drop() {
let pool = ExclusivePool::connect(addr(), user(), pass(), db(), 1)
.await
.unwrap();
{
let client = pool.get().await.unwrap();
let tx = client.begin().await.unwrap();
let err = tx
.query("SELECT * FROM table_that_does_not_exist", &[])
.await;
assert!(err.is_err(), "expected the bogus query to fail");
}
let client = pool.get().await.unwrap();
let rows = client
.query("SELECT 1::int4 AS n", &[])
.await
.expect("pool was poisoned: next checkout still in aborted tx state");
assert_eq!(rows[0].get::<i32>(0).unwrap(), 1);
}
#[tokio::test]
async fn test_pool_reuses_conn_after_aborted_tx_drop() {
let pool = ExclusivePool::connect(addr(), user(), pass(), db(), 1)
.await
.unwrap();
let created_before = pool.metrics().total_created;
{
let client = pool.get().await.unwrap();
let tx = client.begin().await.unwrap();
let _ = tx
.query("SELECT * FROM table_that_does_not_exist", &[])
.await;
}
let client = pool.get().await.unwrap();
let rows = client.query("SELECT 1::int4", &[]).await.unwrap();
assert_eq!(rows[0].get::<i32>(0).unwrap(), 1);
let m = pool.metrics();
assert_eq!(
m.total_created, created_before,
"drop should have rolled back via ROLLBACK queue, not destroyed the conn (created={}, expected={})",
m.total_created, created_before
);
assert_eq!(m.total_destroyed, 0);
}
#[tokio::test]
async fn test_tx_drop_when_conn_not_alive_pool_would_discard() {
use resolute::Client;
let client = Client::connect(addr(), user(), pass(), db()).await.unwrap();
let tx = client.begin().await.unwrap();
client.conn().__force_mark_dead_for_test();
assert!(!client.conn().is_alive());
drop(tx);
let conn = client.conn();
assert!(
!conn.is_alive() || conn.is_broken(),
"pool's has_pending_data() must report true: !is_alive={} is_broken={}",
!conn.is_alive(),
conn.is_broken()
);
}
#[tokio::test]
async fn test_client_recovers_after_tx_dropped_aborted() {
use resolute::Client;
let client = Client::connect(addr(), user(), pass(), db()).await.unwrap();
assert!(!client.conn().is_broken());
{
let tx = client.begin().await.unwrap();
let _ = tx
.query("SELECT * FROM table_that_does_not_exist", &[])
.await;
}
assert!(
!client.conn().is_broken(),
"conn should not be flagged broken when ROLLBACK was queued successfully"
);
let rows = client
.query("SELECT 1::int4 AS n", &[])
.await
.expect("session should recover after drop-time ROLLBACK");
assert_eq!(rows[0].get::<i32>(0).unwrap(), 1);
}
#[tokio::test]
async fn test_typed_pool_connection_survives_return() {
let pool = ExclusivePool::connect(addr(), user(), pass(), db(), 1)
.await
.unwrap();
{
let client = pool.get().await.unwrap();
let rows = client.query("SELECT 1::int4 AS n", &[]).await.unwrap();
assert_eq!(rows[0].get::<i32>(0).unwrap(), 1);
}
tokio::time::sleep(std::time::Duration::from_millis(50)).await;
{
let client = pool.get().await.unwrap();
let rows = client.query("SELECT 2::int4 AS n", &[]).await.unwrap();
assert_eq!(rows[0].get::<i32>(0).unwrap(), 2);
}
let m = pool.metrics();
assert!(
m.total_created <= 2,
"should reuse connections, created: {}",
m.total_created
);
}