parent
386ab5ad6e
commit
214413d9dc
@ -1,6 +1,6 @@
|
|||||||
use crate::{
|
use crate::{
|
||||||
CancelToken, Config, CopyInWriter, CopyOutReader, GenericClient, RowIter, Statement,
|
CancelToken, Config, CopyInWriter, CopyOutReader, GenericClient, RowIter, Statement,
|
||||||
ToStatement, Transaction,
|
ToStatement, Transaction, TransactionBuilder,
|
||||||
};
|
};
|
||||||
use std::ops::{Deref, DerefMut};
|
use std::ops::{Deref, DerefMut};
|
||||||
use tokio::runtime::Runtime;
|
use tokio::runtime::Runtime;
|
||||||
@ -486,6 +486,33 @@ impl Client {
|
|||||||
CancelToken::new(self.client.cancel_token())
|
CancelToken::new(self.client.cancel_token())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Returns a builder for a transaction with custom settings.
|
||||||
|
///
|
||||||
|
/// Unlike the `transaction` method, the builder can be used to control the transaction's isolation level and other
|
||||||
|
/// attributes.
|
||||||
|
///
|
||||||
|
/// # Examples
|
||||||
|
///
|
||||||
|
/// ```no_run
|
||||||
|
/// use postgres::{Client, IsolationLevel, NoTls};
|
||||||
|
///
|
||||||
|
/// # fn main() -> Result<(), postgres::Error> {
|
||||||
|
/// let mut client = Client::connect("host=localhost user=postgres", NoTls)?;
|
||||||
|
///
|
||||||
|
/// let mut transaction = client.build_transaction()
|
||||||
|
/// .isolation_level(IsolationLevel::RepeatableRead)
|
||||||
|
/// .start()?;
|
||||||
|
/// transaction.execute("UPDATE foo SET bar = 10", &[])?;
|
||||||
|
/// // ...
|
||||||
|
///
|
||||||
|
/// transaction.commit()?;
|
||||||
|
/// # Ok(())
|
||||||
|
/// # }
|
||||||
|
/// ```
|
||||||
|
pub fn build_transaction(&mut self) -> TransactionBuilder<'_> {
|
||||||
|
TransactionBuilder::new(&mut self.runtime, self.client.build_transaction())
|
||||||
|
}
|
||||||
|
|
||||||
/// Determines if the client's connection has already closed.
|
/// Determines if the client's connection has already closed.
|
||||||
///
|
///
|
||||||
/// If this returns `true`, the client is no longer usable.
|
/// If this returns `true`, the client is no longer usable.
|
||||||
|
@ -51,7 +51,8 @@
|
|||||||
|
|
||||||
pub use fallible_iterator;
|
pub use fallible_iterator;
|
||||||
pub use tokio_postgres::{
|
pub use tokio_postgres::{
|
||||||
error, row, tls, types, Column, Portal, SimpleQueryMessage, Socket, Statement, ToStatement,
|
error, row, tls, types, Column, IsolationLevel, Portal, SimpleQueryMessage, Socket, Statement,
|
||||||
|
ToStatement,
|
||||||
};
|
};
|
||||||
|
|
||||||
pub use crate::cancel_token::CancelToken;
|
pub use crate::cancel_token::CancelToken;
|
||||||
@ -68,6 +69,7 @@ pub use crate::row_iter::RowIter;
|
|||||||
#[doc(no_inline)]
|
#[doc(no_inline)]
|
||||||
pub use crate::tls::NoTls;
|
pub use crate::tls::NoTls;
|
||||||
pub use crate::transaction::*;
|
pub use crate::transaction::*;
|
||||||
|
pub use crate::transaction_builder::TransactionBuilder;
|
||||||
|
|
||||||
pub mod binary_copy;
|
pub mod binary_copy;
|
||||||
mod cancel_token;
|
mod cancel_token;
|
||||||
@ -79,6 +81,7 @@ mod generic_client;
|
|||||||
mod lazy_pin;
|
mod lazy_pin;
|
||||||
mod row_iter;
|
mod row_iter;
|
||||||
mod transaction;
|
mod transaction;
|
||||||
|
mod transaction_builder;
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod test;
|
mod test;
|
||||||
|
47
postgres/src/transaction_builder.rs
Normal file
47
postgres/src/transaction_builder.rs
Normal file
@ -0,0 +1,47 @@
|
|||||||
|
use crate::{Error, IsolationLevel, Transaction};
|
||||||
|
use tokio::runtime::Runtime;
|
||||||
|
|
||||||
|
/// A builder for database transactions.
|
||||||
|
pub struct TransactionBuilder<'a> {
|
||||||
|
runtime: &'a mut Runtime,
|
||||||
|
builder: tokio_postgres::TransactionBuilder<'a>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'a> TransactionBuilder<'a> {
|
||||||
|
pub(crate) fn new(
|
||||||
|
runtime: &'a mut Runtime,
|
||||||
|
builder: tokio_postgres::TransactionBuilder<'a>,
|
||||||
|
) -> TransactionBuilder<'a> {
|
||||||
|
TransactionBuilder { runtime, builder }
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Sets the isolation level of the transaction.
|
||||||
|
pub fn isolation_level(mut self, isolation_level: IsolationLevel) -> Self {
|
||||||
|
self.builder = self.builder.isolation_level(isolation_level);
|
||||||
|
self
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Sets the transaction to read-only.
|
||||||
|
pub fn read_only(mut self) -> Self {
|
||||||
|
self.builder = self.builder.read_only();
|
||||||
|
self
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Sets the transaction to be deferrable.
|
||||||
|
///
|
||||||
|
/// If the transaction is also serializable and read only, creation of the transaction may block, but when it
|
||||||
|
/// completes the transaction is able to run with less overhead and a guarantee that it will not be aborted due to
|
||||||
|
/// serialization failure.
|
||||||
|
pub fn deferrable(mut self) -> Self {
|
||||||
|
self.builder = self.builder.deferrable();
|
||||||
|
self
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Begins the transaction.
|
||||||
|
///
|
||||||
|
/// The transaction will roll back by default - use the `commit` method to commit it.
|
||||||
|
pub fn start(self) -> Result<Transaction<'a>, Error> {
|
||||||
|
let transaction = self.runtime.block_on(self.builder.start())?;
|
||||||
|
Ok(Transaction::new(self.runtime, transaction))
|
||||||
|
}
|
||||||
|
}
|
@ -13,6 +13,7 @@ use crate::Socket;
|
|||||||
use crate::{
|
use crate::{
|
||||||
copy_in, copy_out, prepare, query, simple_query, slice_iter, CancelToken, CopyInSink, Error,
|
copy_in, copy_out, prepare, query, simple_query, slice_iter, CancelToken, CopyInSink, Error,
|
||||||
GenericClient, Row, SimpleQueryMessage, Statement, ToStatement, Transaction,
|
GenericClient, Row, SimpleQueryMessage, Statement, ToStatement, Transaction,
|
||||||
|
TransactionBuilder,
|
||||||
};
|
};
|
||||||
use async_trait::async_trait;
|
use async_trait::async_trait;
|
||||||
use bytes::{Buf, BytesMut};
|
use bytes::{Buf, BytesMut};
|
||||||
@ -461,6 +462,14 @@ impl Client {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Returns a builder for a transaction with custom settings.
|
||||||
|
///
|
||||||
|
/// Unlike the `transaction` method, the builder can be used to control the transaction's isolation level and other
|
||||||
|
/// attributes.
|
||||||
|
pub fn build_transaction(&mut self) -> TransactionBuilder<'_> {
|
||||||
|
TransactionBuilder::new(self)
|
||||||
|
}
|
||||||
|
|
||||||
/// Attempts to cancel an in-progress query.
|
/// Attempts to cancel an in-progress query.
|
||||||
///
|
///
|
||||||
/// The server provides no information about whether a cancellation attempt was successful or not. An error will
|
/// The server provides no information about whether a cancellation attempt was successful or not. An error will
|
||||||
|
@ -120,6 +120,7 @@ use crate::tls::MakeTlsConnect;
|
|||||||
pub use crate::tls::NoTls;
|
pub use crate::tls::NoTls;
|
||||||
pub use crate::to_statement::ToStatement;
|
pub use crate::to_statement::ToStatement;
|
||||||
pub use crate::transaction::Transaction;
|
pub use crate::transaction::Transaction;
|
||||||
|
pub use crate::transaction_builder::{IsolationLevel, TransactionBuilder};
|
||||||
use crate::types::ToSql;
|
use crate::types::ToSql;
|
||||||
|
|
||||||
pub mod binary_copy;
|
pub mod binary_copy;
|
||||||
@ -154,6 +155,7 @@ mod statement;
|
|||||||
pub mod tls;
|
pub mod tls;
|
||||||
mod to_statement;
|
mod to_statement;
|
||||||
mod transaction;
|
mod transaction;
|
||||||
|
mod transaction_builder;
|
||||||
pub mod types;
|
pub mod types;
|
||||||
|
|
||||||
/// A convenience function which parses a connection string and connects to the database.
|
/// A convenience function which parses a connection string and connects to the database.
|
||||||
|
103
tokio-postgres/src/transaction_builder.rs
Normal file
103
tokio-postgres/src/transaction_builder.rs
Normal file
@ -0,0 +1,103 @@
|
|||||||
|
use crate::{Client, Error, Transaction};
|
||||||
|
|
||||||
|
/// The isolation level of a database transaction.
|
||||||
|
#[derive(Debug, Copy, Clone)]
|
||||||
|
#[non_exhaustive]
|
||||||
|
pub enum IsolationLevel {
|
||||||
|
/// Equivalent to `ReadCommitted`.
|
||||||
|
ReadUncommitted,
|
||||||
|
|
||||||
|
/// An individual statement in the transaction will see rows committed before it began.
|
||||||
|
ReadCommitted,
|
||||||
|
|
||||||
|
/// All statements in the transaction will see the same view of rows committed before the first query in the
|
||||||
|
/// transaction.
|
||||||
|
RepeatableRead,
|
||||||
|
|
||||||
|
/// The reads and writes in this transaction must be able to be committed as an atomic "unit" with respect to reads
|
||||||
|
/// and writes of all other concurrent serializable transactions without interleaving.
|
||||||
|
Serializable,
|
||||||
|
}
|
||||||
|
|
||||||
|
/// A builder for database transactions.
|
||||||
|
pub struct TransactionBuilder<'a> {
|
||||||
|
client: &'a mut Client,
|
||||||
|
isolation_level: Option<IsolationLevel>,
|
||||||
|
read_only: bool,
|
||||||
|
deferrable: bool,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'a> TransactionBuilder<'a> {
|
||||||
|
pub(crate) fn new(client: &'a mut Client) -> TransactionBuilder<'a> {
|
||||||
|
TransactionBuilder {
|
||||||
|
client,
|
||||||
|
isolation_level: None,
|
||||||
|
read_only: false,
|
||||||
|
deferrable: false,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Sets the isolation level of the transaction.
|
||||||
|
pub fn isolation_level(mut self, isolation_level: IsolationLevel) -> Self {
|
||||||
|
self.isolation_level = Some(isolation_level);
|
||||||
|
self
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Sets the transaction to read-only.
|
||||||
|
pub fn read_only(mut self) -> Self {
|
||||||
|
self.read_only = true;
|
||||||
|
self
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Sets the transaction to be deferrable.
|
||||||
|
///
|
||||||
|
/// If the transaction is also serializable and read only, creation of the transaction may block, but when it
|
||||||
|
/// completes the transaction is able to run with less overhead and a guarantee that it will not be aborted due to
|
||||||
|
/// serialization failure.
|
||||||
|
pub fn deferrable(mut self) -> Self {
|
||||||
|
self.deferrable = true;
|
||||||
|
self
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Begins the transaction.
|
||||||
|
///
|
||||||
|
/// The transaction will roll back by default - use the `commit` method to commit it.
|
||||||
|
pub async fn start(self) -> Result<Transaction<'a>, Error> {
|
||||||
|
let mut query = "START TRANSACTION".to_string();
|
||||||
|
let mut first = true;
|
||||||
|
|
||||||
|
if let Some(level) = self.isolation_level {
|
||||||
|
first = false;
|
||||||
|
|
||||||
|
query.push_str(" ISOLATION LEVEL ");
|
||||||
|
let level = match level {
|
||||||
|
IsolationLevel::ReadUncommitted => "READ UNCOMMITTED",
|
||||||
|
IsolationLevel::ReadCommitted => "READ COMMITTED",
|
||||||
|
IsolationLevel::RepeatableRead => "REPEATABLE READ",
|
||||||
|
IsolationLevel::Serializable => "SERIALIZABLE",
|
||||||
|
};
|
||||||
|
query.push_str(level);
|
||||||
|
}
|
||||||
|
|
||||||
|
if self.read_only {
|
||||||
|
if !first {
|
||||||
|
query.push(',');
|
||||||
|
}
|
||||||
|
first = false;
|
||||||
|
|
||||||
|
query.push_str(" READ ONLY");
|
||||||
|
}
|
||||||
|
|
||||||
|
if self.deferrable {
|
||||||
|
if !first {
|
||||||
|
query.push(',');
|
||||||
|
}
|
||||||
|
|
||||||
|
query.push_str(" DEFERRABLE");
|
||||||
|
}
|
||||||
|
|
||||||
|
self.client.batch_execute(&query).await?;
|
||||||
|
|
||||||
|
Ok(Transaction::new(self.client))
|
||||||
|
}
|
||||||
|
}
|
@ -12,7 +12,9 @@ use tokio::time;
|
|||||||
use tokio_postgres::error::SqlState;
|
use tokio_postgres::error::SqlState;
|
||||||
use tokio_postgres::tls::{NoTls, NoTlsStream};
|
use tokio_postgres::tls::{NoTls, NoTlsStream};
|
||||||
use tokio_postgres::types::{Kind, Type};
|
use tokio_postgres::types::{Kind, Type};
|
||||||
use tokio_postgres::{AsyncMessage, Client, Config, Connection, Error, SimpleQueryMessage};
|
use tokio_postgres::{
|
||||||
|
AsyncMessage, Client, Config, Connection, Error, IsolationLevel, SimpleQueryMessage,
|
||||||
|
};
|
||||||
|
|
||||||
mod binary_copy;
|
mod binary_copy;
|
||||||
mod parse;
|
mod parse;
|
||||||
@ -398,6 +400,41 @@ async fn transaction_rollback_drop() {
|
|||||||
assert_eq!(rows.len(), 0);
|
assert_eq!(rows.len(), 0);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[tokio::test]
|
||||||
|
async fn transaction_builder() {
|
||||||
|
let mut client = connect("user=postgres").await;
|
||||||
|
|
||||||
|
client
|
||||||
|
.batch_execute(
|
||||||
|
"CREATE TEMPORARY TABLE foo(
|
||||||
|
id SERIAL,
|
||||||
|
name TEXT
|
||||||
|
)",
|
||||||
|
)
|
||||||
|
.await
|
||||||
|
.unwrap();
|
||||||
|
|
||||||
|
let transaction = client
|
||||||
|
.build_transaction()
|
||||||
|
.isolation_level(IsolationLevel::Serializable)
|
||||||
|
.read_only()
|
||||||
|
.deferrable()
|
||||||
|
.start()
|
||||||
|
.await
|
||||||
|
.unwrap();
|
||||||
|
transaction
|
||||||
|
.batch_execute("INSERT INTO foo (name) VALUES ('steven')")
|
||||||
|
.await
|
||||||
|
.unwrap();
|
||||||
|
transaction.commit().await.unwrap();
|
||||||
|
|
||||||
|
let stmt = client.prepare("SELECT name FROM foo").await.unwrap();
|
||||||
|
let rows = client.query(&stmt, &[]).await.unwrap();
|
||||||
|
|
||||||
|
assert_eq!(rows.len(), 1);
|
||||||
|
assert_eq!(rows[0].get::<_, &str>(0), "steven");
|
||||||
|
}
|
||||||
|
|
||||||
#[tokio::test]
|
#[tokio::test]
|
||||||
async fn copy_in() {
|
async fn copy_in() {
|
||||||
let client = connect("user=postgres").await;
|
let client = connect("user=postgres").await;
|
||||||
|
Loading…
Reference in New Issue
Block a user