use bsql::Pool;
async fn pool() -> Pool {
Pool::connect("postgres://bsql:bsql@localhost/bsql_test")
.await
.expect("Failed to connect to test database. Is PostgreSQL running?")
}
#[bsql::pg_enum]
pub enum TicketStatus {
#[sql("new")]
New,
#[sql("in_progress")]
InProgress,
#[sql("resolved")]
Resolved,
#[sql("closed")]
Closed,
}
#[tokio::test]
async fn pg_enum_select_as_text() {
let pool = pool().await;
let id = 1i32;
let ticket = bsql::query!("SELECT id, status::text AS status FROM tickets WHERE id = $id: i32")
.fetch_one(&pool)
.await
.unwrap();
assert_eq!(ticket.id, 1);
assert_eq!(ticket.status, Some("new".to_owned()));
}
#[tokio::test]
async fn pg_enum_display() {
assert_eq!(format!("{}", TicketStatus::New), "new");
assert_eq!(format!("{}", TicketStatus::InProgress), "in_progress");
assert_eq!(format!("{}", TicketStatus::Resolved), "resolved");
assert_eq!(format!("{}", TicketStatus::Closed), "closed");
}
#[tokio::test]
async fn pg_enum_equality() {
assert_eq!(TicketStatus::New, TicketStatus::New);
assert_ne!(TicketStatus::New, TicketStatus::Closed);
}
#[tokio::test]
async fn pg_enum_clone_copy() {
let status = TicketStatus::InProgress;
let copied = status;
assert_eq!(status, copied);
fn needs_clone<T: Clone>(v: &T) -> T {
v.clone()
}
let cloned = needs_clone(&status);
assert_eq!(status, cloned);
}
#[tokio::test]
async fn pg_enum_debug() {
assert_eq!(format!("{:?}", TicketStatus::Resolved), "Resolved");
}
#[tokio::test]
async fn pg_enum_without_text_cast() {
let pool = pool().await;
let id = 1i32;
let ticket = bsql::query!("SELECT id, status FROM tickets WHERE id = $id: i32")
.fetch_one(&pool)
.await
.unwrap();
assert_eq!(ticket.id, 1);
assert_eq!(ticket.status, "new");
}
#[tokio::test]
async fn pg_enum_in_join_context() {
let pool = pool().await;
let rows = bsql::query!(
"SELECT t.id, t.status, u.login
FROM tickets t
JOIN users u ON u.id = t.created_by_user_id
ORDER BY t.id LIMIT 2"
)
.fetch_all(&pool)
.await
.unwrap();
assert_eq!(rows.len(), 2);
assert_eq!(rows[0].status, "new");
}
#[tokio::test]
async fn pg_enum_in_subquery() {
let pool = pool().await;
let rows = bsql::query!(
"SELECT id, status FROM (
SELECT id, status FROM tickets ORDER BY id LIMIT 2
) sub ORDER BY id"
)
.fetch_all(&pool)
.await
.unwrap();
assert_eq!(rows.len(), 2);
assert_eq!(rows[0].status, "new");
assert_eq!(rows[1].status, "in_progress");
}
#[cfg(feature = "uuid")]
mod uuid_tests {
use super::*;
#[tokio::test]
async fn select_uuid_column() {
let pool = pool().await;
let id = 1i32;
let ticket = bsql::query!("SELECT id, ticket_uuid FROM tickets WHERE id = $id: i32")
.fetch_one(&pool)
.await
.unwrap();
assert_eq!(ticket.id, 1);
let _uuid: uuid::Uuid = ticket.ticket_uuid;
}
#[tokio::test]
async fn uuid_round_trip() {
let pool = pool().await;
let test_uuid = uuid::Uuid::new_v4();
let title = "UUID round-trip test";
let uid = 1i32;
let ticket = bsql::query!(
"INSERT INTO tickets (title, status, created_by_user_id, ticket_uuid)
VALUES ($title: &str, 'new', $uid: i32, $test_uuid: uuid::Uuid)
RETURNING id, ticket_uuid"
)
.fetch_one(&pool)
.await
.unwrap();
assert_eq!(ticket.ticket_uuid, test_uuid);
let inserted_id = ticket.id;
bsql::query!("DELETE FROM tickets WHERE id = $inserted_id: i32")
.execute(&pool)
.await
.unwrap();
}
#[tokio::test]
async fn uuid_param_filter() {
let pool = pool().await;
let id = 1i32;
let ticket = bsql::query!("SELECT id, ticket_uuid FROM tickets WHERE id = $id: i32")
.fetch_one(&pool)
.await
.unwrap();
let target_uuid = ticket.ticket_uuid;
let found =
bsql::query!("SELECT id FROM tickets WHERE ticket_uuid = $target_uuid: uuid::Uuid")
.fetch_one(&pool)
.await
.unwrap();
assert_eq!(found.id, 1);
}
}
#[cfg(feature = "time")]
mod time_tests {
use super::*;
#[tokio::test]
async fn select_nullable_timestamptz() {
let pool = pool().await;
let ticket =
bsql::query!("SELECT id, deadline FROM tickets WHERE deadline IS NULL LIMIT 1")
.fetch_one(&pool)
.await
.unwrap();
assert!(ticket.deadline.is_none());
}
#[tokio::test]
async fn timestamptz_round_trip() {
let pool = pool().await;
let now = time::OffsetDateTime::now_utc();
let id = 1i32;
bsql::query!(
"UPDATE tickets SET deadline = $now: time::OffsetDateTime WHERE id = $id: i32"
)
.execute(&pool)
.await
.unwrap();
let ticket = bsql::query!("SELECT id, deadline FROM tickets WHERE id = $id: i32")
.fetch_one(&pool)
.await
.unwrap();
let deadline = ticket.deadline.expect("deadline should be set");
let diff = (deadline - now).whole_milliseconds().unsigned_abs();
assert!(diff < 2, "timestamps differ by {diff}ms");
bsql::query!("UPDATE tickets SET deadline = NULL WHERE id = $id: i32")
.execute(&pool)
.await
.unwrap();
}
#[tokio::test]
async fn select_date_column() {
let pool = pool().await;
let id = 1i32;
let ticket = bsql::query!("SELECT id, created_date FROM tickets WHERE id = $id: i32")
.fetch_one(&pool)
.await
.unwrap();
assert_eq!(ticket.id, 1);
let _date: time::Date = ticket.created_date;
}
#[tokio::test]
async fn select_nullable_time_column() {
let pool = pool().await;
let title = "nullable_time_test";
let uid = 1i32;
let ticket = bsql::query!(
"INSERT INTO tickets (title, status, created_by_user_id)
VALUES ($title: &str, 'new', $uid: i32)
RETURNING id, start_time"
)
.fetch_one(&pool)
.await
.unwrap();
assert!(ticket.id > 0);
assert!(ticket.start_time.is_none());
}
#[tokio::test]
async fn time_round_trip() {
let pool = pool().await;
let t = time::Time::from_hms(14, 30, 0).expect("valid time");
let title = "time_round_trip_test";
let uid = 1i32;
let ticket = bsql::query!(
"INSERT INTO tickets (title, status, created_by_user_id, start_time)
VALUES ($title: &str, 'new', $uid: i32, $t: time::Time)
RETURNING id, start_time"
)
.fetch_one(&pool)
.await
.unwrap();
let start_time = ticket.start_time.expect("start_time should be set");
assert_eq!(start_time.hour(), 14);
assert_eq!(start_time.minute(), 30);
let ticket_id = ticket.id;
bsql::query!("DELETE FROM tickets WHERE id = $ticket_id: i32")
.execute(&pool)
.await
.unwrap();
}
#[tokio::test]
async fn date_param() {
let pool = pool().await;
let today = time::OffsetDateTime::now_utc().date();
let tickets =
bsql::query!("SELECT id FROM tickets WHERE created_date = $today: time::Date")
.fetch_all(&pool)
.await
.unwrap();
assert!(!tickets.is_empty());
}
}
#[tokio::test]
async fn enum_column_cast_to_text() {
let pool = pool().await;
let id = 1i32;
let ticket = bsql::query!("SELECT id, status::text AS status FROM tickets WHERE id = $id: i32")
.fetch_one(&pool)
.await
.unwrap();
assert_eq!(ticket.id, 1);
assert_eq!(ticket.status, Some("new".to_owned()));
}
#[tokio::test]
async fn insert_with_enum_literal() {
let pool = pool().await;
let title = "Enum literal test";
let uid = 1i32;
let ticket = bsql::query!(
"INSERT INTO tickets (title, status, created_by_user_id)
VALUES ($title: &str, 'resolved', $uid: i32)
RETURNING id, status::text AS status"
)
.fetch_one(&pool)
.await
.unwrap();
assert_eq!(ticket.status.as_deref(), Some("resolved"));
let del_id = ticket.id;
bsql::query!("DELETE FROM tickets WHERE id = $del_id: i32")
.execute(&pool)
.await
.unwrap();
}
#[tokio::test]
async fn enum_string_as_param() {
let pool = pool().await;
let status = "new";
let tickets = bsql::query!("SELECT id FROM tickets WHERE status::text = $status: &str")
.fetch_all(&pool)
.await
.unwrap();
assert!(!tickets.is_empty());
}
#[cfg(all(feature = "chrono", not(feature = "time")))]
mod chrono_tests {
use super::*;
#[tokio::test]
async fn chrono_timestamptz_round_trip() {
let pool = pool().await;
let now = chrono::Utc::now();
let id = 1i32;
bsql::query!(
"UPDATE tickets SET deadline = $now: chrono::DateTime<chrono::Utc> WHERE id = $id: i32"
)
.execute(&pool)
.await
.unwrap();
let ticket = bsql::query!("SELECT id, deadline FROM tickets WHERE id = $id: i32")
.fetch_one(&pool)
.await
.unwrap();
let deadline = ticket.deadline.expect("deadline should be set");
let diff = (deadline - now).num_milliseconds().unsigned_abs();
assert!(diff < 2, "timestamps differ by {diff}ms");
bsql::query!("UPDATE tickets SET deadline = NULL WHERE id = $id: i32")
.execute(&pool)
.await
.unwrap();
}
}
#[cfg(feature = "decimal")]
mod decimal_tests {
use super::*;
#[tokio::test]
async fn decimal_round_trip() {
let pool = pool().await;
let budget = rust_decimal::Decimal::new(12345, 2); let id = 1i32;
bsql::query!(
"UPDATE tickets SET budget = $budget: rust_decimal::Decimal WHERE id = $id: i32"
)
.execute(&pool)
.await
.unwrap();
let ticket = bsql::query!("SELECT id, budget FROM tickets WHERE id = $id: i32")
.fetch_one(&pool)
.await
.unwrap();
assert_eq!(ticket.id, 1);
let read_budget = ticket.budget.expect("budget should be set");
assert_eq!(read_budget, budget);
bsql::query!("UPDATE tickets SET budget = NULL WHERE id = $id: i32")
.execute(&pool)
.await
.unwrap();
}
}