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, ¶ms);
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}