use dbkit::{BaseHandler, ConnectionManager, DbValue, FetchMode, WriteOp};
use sqlx::Row;
async fn setup(table: &str) -> Option<BaseHandler> {
let Ok(url) = std::env::var("DATABASE_URL") else {
eprintln!("DATABASE_URL not set — skipping any_pool_pg test");
return None;
};
let conn = ConnectionManager::new(&url).await.expect("connect");
let handler = BaseHandler::new(conn.pool().clone());
handler
.execute_write(WriteOp::BatchDDL {
queries: &[
&format!("DROP TABLE IF EXISTS {table}"),
&format!("CREATE TABLE {table} (id BIGINT PRIMARY KEY, name TEXT)"),
],
})
.await
.expect("create table");
Some(handler)
}
async fn count(handler: &BaseHandler, table: &str) -> i64 {
let row = handler
.execute_write(WriteOp::Single {
query: &format!("SELECT count(*) FROM {table}"),
params: vec![],
mode: FetchMode::One,
})
.await
.expect("count")
.one()
.expect("one row");
row.get::<i64, _>(0)
}
#[tokio::test]
async fn null_binds_into_text_column() {
let table = "dbkit_any_null_test";
let Some(handler) = setup(table).await else { return };
let insert = format!("INSERT INTO {table} (id, name) VALUES ($1, $2)");
handler
.execute_write(WriteOp::Single {
query: &insert,
params: vec![DbValue::Int(1), DbValue::Text("a".into())],
mode: FetchMode::None,
})
.await
.expect("insert concrete text");
handler
.execute_write(WriteOp::Single {
query: &insert,
params: vec![DbValue::Int(2), DbValue::Null],
mode: FetchMode::None,
})
.await
.expect("insert NULL into text column");
handler
.execute_write(WriteOp::Single {
query: &insert,
params: vec![DbValue::Int(3), DbValue::Text("c".into())],
mode: FetchMode::None,
})
.await
.expect("insert concrete text after NULL");
let row = handler
.execute_write(WriteOp::Single {
query: &format!("SELECT name FROM {table} WHERE id = $1"),
params: vec![DbValue::Int(2)],
mode: FetchMode::One,
})
.await
.expect("select")
.one()
.expect("one row");
assert_eq!(row.get::<Option<String>, _>(0), None, "stored value is NULL");
assert_eq!(count(&handler, table).await, 3);
}
#[tokio::test]
async fn batch_with_mixed_nulls_commits() {
let table = "dbkit_any_batch_null_test";
let Some(handler) = setup(table).await else { return };
let params_list: Vec<Vec<DbValue>> = (0..50i64)
.map(|i| {
let name = if i % 3 == 0 {
DbValue::Null
} else {
DbValue::Text(format!("n{i}"))
};
vec![DbValue::Int(i), name]
})
.collect();
handler
.execute_write(WriteOp::BatchParams {
query: &format!("INSERT INTO {table} (id, name) VALUES ($1, $2)"),
params_list,
isolate_rows: false,
})
.await
.expect("mixed NULL batch");
assert_eq!(count(&handler, table).await, 50);
}
#[tokio::test]
async fn isolated_batch_survives_bad_row() {
let table = "dbkit_any_isolate_test";
let Some(handler) = setup(table).await else { return };
let params_list: Vec<Vec<DbValue>> = vec![
vec![DbValue::Int(1), DbValue::Text("one".into())],
vec![DbValue::Int(2), DbValue::Text("two".into())],
vec![DbValue::Int(2), DbValue::Text("dup".into())],
vec![DbValue::Int(3), DbValue::Null],
];
handler
.execute_write(WriteOp::BatchParams {
query: &format!("INSERT INTO {table} (id, name) VALUES ($1, $2)"),
params_list,
isolate_rows: true,
})
.await
.expect("isolated batch");
assert_eq!(count(&handler, table).await, 3, "good rows committed, bad row skipped");
let row = handler
.execute_write(WriteOp::Single {
query: &format!("SELECT name FROM {table} WHERE id = $1"),
params: vec![DbValue::Int(2)],
mode: FetchMode::One,
})
.await
.expect("select")
.one()
.expect("one row");
assert_eq!(row.get::<String, _>(0), "two", "first writer wins; dup row rolled back");
}