drizzle_mysql/
transaction.rs1use core::marker::PhantomData;
6
7#[derive(Debug, Clone, Copy, PartialEq, Eq)]
9pub enum IsolationLevel {
10 ReadUncommitted,
12 ReadCommitted,
14 RepeatableRead,
16 Serializable,
18}
19
20impl core::fmt::Display for IsolationLevel {
21 fn fmt(&self, formatter: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
22 formatter.write_str(match self {
23 Self::ReadUncommitted => "READ UNCOMMITTED",
24 Self::ReadCommitted => "READ COMMITTED",
25 Self::RepeatableRead => "REPEATABLE READ",
26 Self::Serializable => "SERIALIZABLE",
27 })
28 }
29}
30
31#[derive(Debug, Clone, Copy, PartialEq, Eq)]
33pub enum AccessMode {
34 ReadOnly,
36 ReadWrite,
38}
39
40impl core::fmt::Display for AccessMode {
41 fn fmt(&self, formatter: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
42 formatter.write_str(match self {
43 Self::ReadOnly => "READ ONLY",
44 Self::ReadWrite => "READ WRITE",
45 })
46 }
47}
48
49#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
87pub struct TransactionConfig {
88 isolation_level: Option<IsolationLevel>,
89 access_mode: Option<AccessMode>,
90 consistent_snapshot: bool,
91}
92
93impl TransactionConfig {
94 #[must_use]
96 pub const fn new() -> Self {
97 Self {
98 isolation_level: None,
99 access_mode: None,
100 consistent_snapshot: false,
101 }
102 }
103
104 pub const fn builder() -> ConfigBuilder {
106 ConfigBuilder::new()
107 }
108
109 #[must_use]
114 pub const fn isolation_level(mut self, level: IsolationLevel) -> Self {
115 self.isolation_level = Some(level);
116 if !matches!(level, IsolationLevel::RepeatableRead) {
117 self.consistent_snapshot = false;
118 }
119 self
120 }
121
122 #[must_use]
124 pub const fn access_mode(mut self, mode: AccessMode) -> Self {
125 self.access_mode = Some(mode);
126 self
127 }
128
129 #[must_use]
135 pub const fn with_consistent_snapshot(mut self) -> Self {
136 self.isolation_level = Some(IsolationLevel::RepeatableRead);
137 self.consistent_snapshot = true;
138 self
139 }
140
141 #[must_use]
143 pub const fn isolation(&self) -> Option<IsolationLevel> {
144 self.isolation_level
145 }
146
147 #[must_use]
149 pub const fn access(&self) -> Option<AccessMode> {
150 self.access_mode
151 }
152
153 #[must_use]
155 pub const fn consistent_snapshot(&self) -> bool {
156 self.consistent_snapshot
157 }
158}
159
160#[derive(Debug, Clone, Copy, PartialEq, Eq)]
164#[must_use]
165pub struct ConfigBuilder<Isolation = state::ServerDefault> {
166 config: TransactionConfig,
167 state: PhantomData<Isolation>,
168}
169
170impl ConfigBuilder {
171 const fn new() -> Self {
172 Self {
173 config: TransactionConfig::new(),
174 state: PhantomData,
175 }
176 }
177}
178
179impl<Isolation> ConfigBuilder<Isolation> {
180 const fn isolation<Next>(mut self, level: IsolationLevel) -> ConfigBuilder<Next> {
181 self.config.isolation_level = Some(level);
182 if !matches!(level, IsolationLevel::RepeatableRead) {
183 self.config.consistent_snapshot = false;
184 }
185 ConfigBuilder {
186 config: self.config,
187 state: PhantomData,
188 }
189 }
190
191 pub const fn isolation_level(self, level: IsolationLevel) -> ConfigBuilder<state::Dynamic> {
197 self.isolation(level)
198 }
199
200 pub const fn read_uncommitted(self) -> ConfigBuilder<state::ReadUncommitted> {
202 self.isolation(IsolationLevel::ReadUncommitted)
203 }
204
205 pub const fn read_committed(self) -> ConfigBuilder<state::ReadCommitted> {
207 self.isolation(IsolationLevel::ReadCommitted)
208 }
209
210 pub const fn repeatable_read(self) -> ConfigBuilder<state::RepeatableRead> {
212 self.isolation(IsolationLevel::RepeatableRead)
213 }
214
215 pub const fn serializable(self) -> ConfigBuilder<state::Serializable> {
217 self.isolation(IsolationLevel::Serializable)
218 }
219
220 pub const fn read_only(mut self) -> Self {
222 self.config.access_mode = Some(AccessMode::ReadOnly);
223 self
224 }
225
226 pub const fn read_write(mut self) -> Self {
228 self.config.access_mode = Some(AccessMode::ReadWrite);
229 self
230 }
231
232 #[must_use]
234 pub const fn build(self) -> TransactionConfig {
235 self.config
236 }
237}
238
239impl ConfigBuilder<state::RepeatableRead> {
240 pub const fn snapshot(mut self) -> Self {
245 self.config.consistent_snapshot = true;
246 self
247 }
248}
249
250#[doc(hidden)]
252pub mod state {
253 #[derive(Debug, Clone, Copy, PartialEq, Eq)]
255 pub struct ServerDefault;
256 #[derive(Debug, Clone, Copy, PartialEq, Eq)]
258 pub struct Dynamic;
259 #[derive(Debug, Clone, Copy, PartialEq, Eq)]
261 pub struct ReadUncommitted;
262 #[derive(Debug, Clone, Copy, PartialEq, Eq)]
264 pub struct ReadCommitted;
265 #[derive(Debug, Clone, Copy, PartialEq, Eq)]
267 pub struct RepeatableRead;
268 #[derive(Debug, Clone, Copy, PartialEq, Eq)]
270 pub struct Serializable;
271}
272
273#[cfg(test)]
274mod tests {
275 use super::*;
276
277 #[test]
278 fn transaction_options_default_to_server_policy() {
279 assert_eq!(TransactionConfig::new(), TransactionConfig::default());
280 }
281
282 #[test]
283 fn builder_preserves_selected_options() {
284 let config = TransactionConfig::builder()
285 .repeatable_read()
286 .read_only()
287 .snapshot()
288 .build();
289
290 assert_eq!(config.isolation(), Some(IsolationLevel::RepeatableRead));
291 assert_eq!(config.access(), Some(AccessMode::ReadOnly));
292 assert!(config.consistent_snapshot());
293 }
294
295 #[test]
296 fn runtime_options_remain_available() {
297 let config = TransactionConfig::new()
298 .isolation_level(IsolationLevel::Serializable)
299 .access_mode(AccessMode::ReadWrite)
300 .with_consistent_snapshot();
301
302 assert_eq!(config.isolation(), Some(IsolationLevel::RepeatableRead));
303 assert_eq!(config.access(), Some(AccessMode::ReadWrite));
304 assert!(config.consistent_snapshot());
305 }
306
307 #[test]
308 fn runtime_isolation_change_clears_snapshot() {
309 let config = TransactionConfig::new()
310 .with_consistent_snapshot()
311 .isolation_level(IsolationLevel::Serializable);
312
313 assert_eq!(config.isolation(), Some(IsolationLevel::Serializable));
314 assert!(!config.consistent_snapshot());
315 }
316
317 #[test]
318 fn repeated_runtime_isolation_preserves_snapshot() {
319 let config = TransactionConfig::new()
320 .with_consistent_snapshot()
321 .isolation_level(IsolationLevel::RepeatableRead);
322
323 assert!(config.consistent_snapshot());
324 }
325
326 #[test]
327 fn changing_isolation_clears_snapshot() {
328 let config = TransactionConfig::builder()
329 .repeatable_read()
330 .snapshot()
331 .serializable()
332 .build();
333
334 assert_eq!(config.isolation(), Some(IsolationLevel::Serializable));
335 assert!(!config.consistent_snapshot());
336 }
337
338 #[test]
339 fn repeated_builder_isolation_preserves_snapshot() {
340 let config = TransactionConfig::builder()
341 .repeatable_read()
342 .snapshot()
343 .repeatable_read()
344 .build();
345
346 assert!(config.consistent_snapshot());
347 }
348}