mysql_handler/hton/transaction.rs
1// Copyright (C) 2026 ren-yamanashi
2//
3// This program is free software; you can redistribute it and/or modify
4// it under the terms of the GNU General Public License, version 2.0,
5// as published by the Free Software Foundation.
6//
7// This program is designed to work with certain software (including
8// but not limited to OpenSSL) that is licensed under separate terms,
9// as designated in a particular file or component or in included license
10// documentation. The authors of this program hereby grant you an additional
11// permission to link the program and your derivative works with the
12// separately licensed software that they have either included with
13// the program or referenced in the documentation.
14//
15// This program is distributed in the hope that it will be useful,
16// but WITHOUT ANY WARRANTY; without even the implied warranty of
17// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
18// GNU General Public License for more details.
19//
20// You should have received a copy of the GNU General Public License
21// along with this program; if not, see <https://www.gnu.org/licenses/>.
22
23//! Per-connection transaction state.
24
25use crate::engine::EngineResult;
26
27/// The transaction state for one connection.
28///
29/// A transactional [`Handlerton`](crate::hton::Handlerton) creates one of these
30/// per connection (via [`Handlerton::begin_transaction`]). MySQL stores it in
31/// the connection's `ha_data` slot and drives it through `commit` / `rollback`,
32/// so a `TxnSession` outlives the per-table handler and accumulates work across
33/// every statement of the transaction.
34///
35/// The `Send` bound is required because a connection may be served by different
36/// threads over its lifetime, so the session moves across threads — do not
37/// relax it.
38///
39/// `all` distinguishes the two boundaries MySQL signals on the same callback:
40/// `true` is a real transaction commit/rollback (the connection is in
41/// autocommit, or `COMMIT` / `ROLLBACK` ran); `false` is the end of a single
42/// statement within a larger transaction.
43///
44/// [`Handlerton::begin_transaction`]: crate::hton::Handlerton::begin_transaction
45pub trait TxnSession: Send {
46 /// Commit the work accumulated so far.
47 ///
48 /// # Errors
49 /// Returns an [`EngineError`](crate::engine::EngineError) if the commit
50 /// fails; MySQL surfaces it to the client and the statement / transaction
51 /// is reported as failed.
52 fn commit(&mut self, all: bool) -> EngineResult;
53
54 /// Discard the work accumulated so far.
55 ///
56 /// # Errors
57 /// Returns an [`EngineError`](crate::engine::EngineError) if the rollback
58 /// fails; this is reported to MySQL but the transaction is still considered
59 /// rolled back.
60 fn rollback(&mut self, all: bool) -> EngineResult;
61
62 /// Stage a row written into `table` as part of this transaction.
63 ///
64 /// A transactional engine receives each row write here (rather than on the
65 /// per-table handler) so the change is buffered until `commit` makes it
66 /// visible or `rollback` discards it. `row` is the MySQL row image
67 /// (`record[0]`). The default ignores the write; an engine that stores data
68 /// overrides this to buffer it.
69 ///
70 /// # Errors
71 /// Returns an [`EngineError`](crate::engine::EngineError) if the row cannot
72 /// be staged (e.g. out of memory).
73 fn write_row(&mut self, table: &str, row: &[u8]) -> EngineResult {
74 let _ = (table, row);
75 Ok(())
76 }
77
78 /// Stage a row update. `old` is the pre-image (for rollback /
79 /// pre-image-keyed map lookups) and `new` is the post-image.
80 /// Same buffering contract as [`Self::write_row`]; default is a
81 /// no-op.
82 ///
83 /// # Errors
84 /// Returns an [`EngineError`](crate::engine::EngineError) if the
85 /// update cannot be staged.
86 fn update_row(&mut self, table: &str, old: &[u8], new: &[u8]) -> EngineResult {
87 let _ = (table, old, new);
88 Ok(())
89 }
90
91 /// Stage a row deletion. `row` is the pre-image of the row about
92 /// to be removed. Same buffering contract as [`Self::write_row`];
93 /// default is a no-op.
94 ///
95 /// # Errors
96 /// Returns an [`EngineError`](crate::engine::EngineError) if the
97 /// delete cannot be staged.
98 fn delete_row(&mut self, table: &str, row: &[u8]) -> EngineResult {
99 let _ = (table, row);
100 Ok(())
101 }
102
103 /// Prepare phase: flush the transaction so a following `commit` is durable.
104 ///
105 /// MySQL drives this whenever the engine takes part in two-phase commit —
106 /// most importantly alongside the binary log, which is on by default — so a
107 /// transactional engine must handle it. The default reports success, which
108 /// is correct for an engine with nothing durable to prepare; override it to
109 /// flush real state.
110 ///
111 /// # Errors
112 /// Returns an [`EngineError`](crate::engine::EngineError) if the engine
113 /// cannot prepare; MySQL then rolls the transaction back.
114 fn prepare(&mut self, all: bool) -> EngineResult {
115 let _ = all;
116 Ok(())
117 }
118
119 /// Establish a savepoint at the transaction's current point. `sv` is the
120 /// engine's per-savepoint scratch (`savepoint_offset` bytes, declared by
121 /// [`Handlerton::savepoint_offset`](crate::hton::Handlerton::savepoint_offset)):
122 /// write whatever the engine needs to identify this savepoint on rollback.
123 /// `sv` is only byte-aligned, so write it through `copy_from_slice` /
124 /// `to_le_bytes` rather than a typed pointer store. Defaults to no-op success.
125 ///
126 /// # Errors
127 /// Returns an [`EngineError`](crate::engine::EngineError) if the savepoint
128 /// cannot be recorded.
129 fn savepoint_set(&mut self, sv: &mut [u8]) -> EngineResult {
130 let _ = sv;
131 Ok(())
132 }
133
134 /// Roll the transaction back to the savepoint whose scratch is `sv` (as
135 /// written by [`savepoint_set`](Self::savepoint_set)), discarding work done
136 /// since. Defaults to no-op success.
137 ///
138 /// # Errors
139 /// Returns an [`EngineError`](crate::engine::EngineError) if the rollback
140 /// to savepoint fails.
141 fn savepoint_rollback(&mut self, sv: &[u8]) -> EngineResult {
142 let _ = sv;
143 Ok(())
144 }
145
146 /// Release (forget) the savepoint whose scratch is `sv`, keeping its work
147 /// part of the transaction. Defaults to no-op success.
148 ///
149 /// # Errors
150 /// Returns an [`EngineError`](crate::engine::EngineError) if the release
151 /// fails.
152 fn savepoint_release(&mut self, sv: &[u8]) -> EngineResult {
153 let _ = sv;
154 Ok(())
155 }
156
157 /// Whether it is safe to release metadata locks acquired after a savepoint
158 /// when rolling back to it. Defaults to `true` (the engine holds no locks
159 /// that a savepoint rollback must preserve).
160 fn savepoint_rollback_can_release_mdl(&self) -> bool {
161 true
162 }
163}