#![cfg(any(feature = "postgres-sync", feature = "tokio-postgres"))]
use crate::common::schema::postgres::*;
use drizzle::postgres::prelude::*;
#[derive(PostgresFromRow)]
struct TxSettings {
isolation: String,
read_only: String,
deferrable: String,
}
#[drizzle::test]
fn transaction_config_reaches_the_server(db: &mut TestDb<SimpleSchema>) {
let config = TransactionConfig::builder()
.serializable()
.read_only()
.deferrable()
.build();
result!(db.transaction(config, |tx| {
let settings: TxSettings = result!(tx.get(SQL::raw(
"SELECT current_setting('transaction_isolation') AS isolation, \
current_setting('transaction_read_only') AS read_only, \
current_setting('transaction_deferrable') AS deferrable"
)))?
.expect("a row");
assert_eq!(settings.isolation, "serializable");
assert_eq!(settings.read_only, "on");
assert_eq!(settings.deferrable, "on");
Ok(())
}))?;
}
#[cfg(feature = "tokio-postgres")]
#[test]
fn test_pg_owned_prepared_send_sync() {
fn assert_send_sync<T: Send + Sync>() {}
assert_send_sync::<drizzle_postgres::builder::prepared::OwnedPreparedStatement>();
}
#[cfg(feature = "tokio-postgres")]
#[test]
fn test_tokio_postgres_transaction_futures_are_send() {
use drizzle::postgres::TransactionConfig;
fn assert_send<T: Send>(_: T) {}
fn transaction_future(db: &mut drizzle::postgres::tokio::Drizzle<SimpleSchema>) {
let SimpleSchema { simple } = SimpleSchema::new();
assert_send(
db.transaction(TransactionConfig::default(), async move |tx| {
tx.insert(simple)
.values([InsertSimple::new("sent")])
.execute()
.await?;
tx.update(simple)
.set(UpdateSimple::default().with_name("still sent"))
.r#where(true)
.execute()
.await?;
let rows: Vec<SelectSimple> = tx.select(()).from(simple).all().await?;
Ok(rows.len())
}),
);
}
let _ = transaction_future;
}
#[drizzle::test]
fn swallowed_statement_errors_fail_the_commit(db: &mut TestDb<SimpleSchema>) {
let SimpleSchema { simple } = schema;
let outcome = result!(db.transaction(TransactionConfig::default(), |tx| {
result!(
tx.insert(simple)
.values([InsertSimple::new("kept")])
.execute()
)?;
let _ = result!(tx.execute(SQL::raw("SELECT 1 / 0")));
Ok(())
}));
assert!(outcome.is_err(), "{outcome:?}");
let rows: Vec<SelectSimple> = db.select(()).from(simple).all();
assert!(rows.is_empty());
}
#[drizzle::test]
fn a_failed_statement_rolls_back_only_its_savepoint(db: &mut TestDb<SimpleSchema>) {
let SimpleSchema { simple } = schema;
result!(db.transaction(TransactionConfig::default(), |tx| {
result!(
tx.insert(simple)
.values([InsertSimple::new("outer")])
.execute()
)?;
let savepoint = result!(tx.savepoint(|sp| {
result!(
sp.insert(simple)
.values([InsertSimple::new("inner")])
.execute()
)?;
let _ = result!(sp.execute(SQL::raw("SELECT 1 / 0")));
Ok(())
}));
assert!(savepoint.is_err());
result!(
tx.insert(simple)
.values([InsertSimple::new("after")])
.execute()
)?;
Ok(())
}))?;
let names: Vec<String> = db
.select(simple.name)
.from(simple)
.order_by(asc(simple.name))
.all();
assert_eq!(names, ["after", "outer"]);
}
#[drizzle::test]
fn transactions_select_distinct_on(db: &mut TestDb<SimpleSchema>) {
let SimpleSchema { simple } = schema;
db.insert(simple)
.values([
InsertSimple::new("a"),
InsertSimple::new("a"),
InsertSimple::new("b"),
])
.execute();
let names = result!(db.transaction(TransactionConfig::default(), |tx| {
let rows: Vec<SelectSimple> = result!(
tx.select_distinct_on(simple.name, (simple.id, simple.name))
.from(simple)
.order_by((asc(simple.name), asc(simple.id)))
.all()
)?;
Ok(rows.into_iter().map(|row| row.name).collect::<Vec<_>>())
}))?;
assert_eq!(names, ["a", "b"]);
}
#[derive(PostgresFromRow)]
struct RowCount {
n: i64,
}
fn database_url() -> String {
std::env::var("DATABASE_URL").unwrap_or_else(|_| {
"host=localhost user=postgres password=postgres dbname=drizzle_test".to_string()
})
}
#[cfg(feature = "postgres-sync")]
#[test]
fn an_imported_snapshot_reads_the_exporters_data_sync() {
use drizzle::postgres::sync::Drizzle;
let connect = || postgres::Client::connect(&database_url(), postgres::NoTls).unwrap();
let mut writer = connect();
writer
.batch_execute(
"DROP TABLE IF EXISTS snapshot_probe_sync; \
CREATE TABLE snapshot_probe_sync (id integer); \
INSERT INTO snapshot_probe_sync VALUES (1)",
)
.unwrap();
let (mut exporter, ()) = Drizzle::<()>::new(connect());
let (mut importer, ()) = Drizzle::<()>::new(connect());
let count = SQL::raw("SELECT count(*) AS n FROM snapshot_probe_sync");
let repeatable_read = TransactionConfig::builder().repeatable_read().build();
exporter
.transaction(repeatable_read, |tx| {
let snapshot = tx.snapshot()?;
writer
.execute("INSERT INTO snapshot_probe_sync VALUES (2)", &[])
.unwrap();
let config = TransactionConfig::builder()
.repeatable_read()
.snapshot(snapshot)
.build();
let seen: RowCount = importer
.transaction(config, |other| other.get(count.clone()))?
.expect("a row");
assert_eq!(seen.n, 1);
let fresh: RowCount = importer
.transaction(repeatable_read, |other| other.get(count.clone()))?
.expect("a row");
assert_eq!(fresh.n, 2);
Ok(())
})
.unwrap();
writer
.batch_execute("DROP TABLE snapshot_probe_sync")
.unwrap();
}
#[cfg(feature = "tokio-postgres")]
#[tokio::test]
async fn an_imported_snapshot_reads_the_exporters_data_tokio() {
use drizzle::postgres::tokio::Drizzle;
async fn connect() -> tokio_postgres::Client {
let (client, connection) = tokio_postgres::connect(&database_url(), tokio_postgres::NoTls)
.await
.unwrap();
tokio::spawn(connection);
client
}
let writer = connect().await;
writer
.batch_execute(
"DROP TABLE IF EXISTS snapshot_probe_tokio; \
CREATE TABLE snapshot_probe_tokio (id integer); \
INSERT INTO snapshot_probe_tokio VALUES (1)",
)
.await
.unwrap();
let (mut exporter, ()) = Drizzle::<()>::new(connect().await);
let (mut importer, ()) = Drizzle::<()>::new(connect().await);
let count = SQL::raw("SELECT count(*) AS n FROM snapshot_probe_tokio");
let repeatable_read = TransactionConfig::builder().repeatable_read().build();
exporter
.transaction(repeatable_read, async |tx| {
let snapshot = tx.snapshot().await?;
writer
.execute("INSERT INTO snapshot_probe_tokio VALUES (2)", &[])
.await
.unwrap();
let count = count.clone();
let task = tokio::spawn(async move {
let config = TransactionConfig::builder()
.repeatable_read()
.snapshot(snapshot)
.build();
let seen: RowCount = importer
.transaction(config, async |other| other.get(count.clone()).await)
.await?
.expect("a row");
let fresh: RowCount = importer
.transaction(repeatable_read, async |other| {
other.get(count.clone()).await
})
.await?
.expect("a row");
drizzle::Result::Ok((seen.n, fresh.n))
});
assert_eq!(task.await.unwrap()?, (1, 2));
Ok(())
})
.await
.unwrap();
writer
.batch_execute("DROP TABLE snapshot_probe_tokio")
.await
.unwrap();
}