zero_postgres/sync/
transaction.rs

1//! Transaction support for synchronous PostgreSQL connections.
2
3use super::Conn;
4use super::named_portal::NamedPortal;
5use crate::conversion::ToParams;
6use crate::error::{Error, Result};
7use crate::statement::IntoStatement;
8
9/// A PostgreSQL transaction for the synchronous connection.
10///
11/// This struct provides transaction control. The connection is passed
12/// to `commit` and `rollback` methods to execute the transaction commands.
13pub struct Transaction {
14    connection_id: u32,
15}
16
17impl Transaction {
18    /// Create a new transaction (internal use only).
19    pub(crate) fn new(connection_id: u32) -> Self {
20        Self { connection_id }
21    }
22
23    /// Commit the transaction.
24    ///
25    /// This consumes the transaction and sends a COMMIT statement to the server.
26    /// The connection must be passed as an argument to execute the commit.
27    ///
28    /// # Errors
29    ///
30    /// Returns `Error::InvalidUsage` if the connection is not the same
31    /// as the one that started the transaction.
32    pub fn commit(self, conn: &mut Conn) -> Result<()> {
33        let actual = conn.connection_id();
34        if self.connection_id != actual {
35            return Err(Error::InvalidUsage(format!(
36                "connection mismatch: expected {}, got {}",
37                self.connection_id, actual
38            )));
39        }
40        conn.query_drop("COMMIT")?;
41        Ok(())
42    }
43
44    /// Rollback the transaction.
45    ///
46    /// This consumes the transaction and sends a ROLLBACK statement to the server.
47    /// The connection must be passed as an argument to execute the rollback.
48    ///
49    /// # Errors
50    ///
51    /// Returns `Error::InvalidUsage` if the connection is not the same
52    /// as the one that started the transaction.
53    pub fn rollback(self, conn: &mut Conn) -> Result<()> {
54        let actual = conn.connection_id();
55        if self.connection_id != actual {
56            return Err(Error::InvalidUsage(format!(
57                "connection mismatch: expected {}, got {}",
58                self.connection_id, actual
59            )));
60        }
61        conn.query_drop("ROLLBACK")?;
62        Ok(())
63    }
64
65    /// Create a named portal for iterative row fetching within this transaction.
66    ///
67    /// Named portals are safe to use within an explicit transaction because
68    /// SYNC messages do not destroy them (only COMMIT/ROLLBACK does).
69    ///
70    /// The statement can be either:
71    /// - A `&PreparedStatement` returned from `conn.prepare()`
72    /// - A raw SQL `&str` for one-shot execution
73    ///
74    /// # Example
75    ///
76    /// ```ignore
77    /// let tx = conn.begin()?;
78    /// let mut portal = tx.exec_portal_named(&mut conn, &stmt, ())?;
79    ///
80    /// while !portal.is_complete() {
81    ///     let rows: Vec<(i32,)> = portal.exec_collect(&mut conn, 100)?;
82    ///     process(rows);
83    /// }
84    ///
85    /// portal.close(&mut conn)?;
86    /// tx.commit(&mut conn)?;
87    /// ```
88    ///
89    /// # Errors
90    ///
91    /// Returns `Error::InvalidUsage` if the connection is not the same
92    /// as the one that started the transaction.
93    pub fn exec_portal_named<S: IntoStatement, P: ToParams>(
94        &self,
95        conn: &mut Conn,
96        statement: S,
97        params: P,
98    ) -> Result<NamedPortal<'_>> {
99        let actual = conn.connection_id();
100        if self.connection_id != actual {
101            return Err(Error::InvalidUsage(format!(
102                "connection mismatch: expected {}, got {}",
103                self.connection_id, actual
104            )));
105        }
106
107        let portal_name = conn.next_portal_name();
108        let result = conn.create_named_portal(&portal_name, &statement, &params);
109
110        if let Err(e) = &result {
111            if e.is_connection_broken() {
112                conn.is_broken = true;
113            }
114            return Err(result.unwrap_err());
115        }
116
117        Ok(NamedPortal::new(portal_name))
118    }
119}