use core::fmt;
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
#[repr(u8)]
pub enum TxStatus {
Active = 0,
MarkedRollback = 1,
Prepared = 2,
Committed = 3,
RolledBack = 4,
Unknown = 5,
NoTransaction = 6,
Preparing = 7,
Committing = 8,
RollingBack = 9,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
#[repr(i32)]
pub enum JtaStatus {
Active = 0,
MarkedRollback = 1,
Prepared = 2,
Committed = 3,
RolledBack = 4,
Unknown = 5,
NoTransaction = 6,
Preparing = 7,
Committing = 8,
RollingBack = 9,
}
impl fmt::Display for TxStatus {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.write_str(match self {
Self::Active => "StatusActive",
Self::MarkedRollback => "StatusMarkedRollback",
Self::Prepared => "StatusPrepared",
Self::Committed => "StatusCommitted",
Self::RolledBack => "StatusRolledBack",
Self::Unknown => "StatusUnknown",
Self::NoTransaction => "StatusNoTransaction",
Self::Preparing => "StatusPreparing",
Self::Committing => "StatusCommitting",
Self::RollingBack => "StatusRollingBack",
})
}
}
#[must_use]
pub fn jta_status_from_cos(s: TxStatus) -> JtaStatus {
match s {
TxStatus::Active => JtaStatus::Active,
TxStatus::MarkedRollback => JtaStatus::MarkedRollback,
TxStatus::Prepared => JtaStatus::Prepared,
TxStatus::Committed => JtaStatus::Committed,
TxStatus::RolledBack => JtaStatus::RolledBack,
TxStatus::Unknown => JtaStatus::Unknown,
TxStatus::NoTransaction => JtaStatus::NoTransaction,
TxStatus::Preparing => JtaStatus::Preparing,
TxStatus::Committing => JtaStatus::Committing,
TxStatus::RollingBack => JtaStatus::RollingBack,
}
}
#[must_use]
pub fn jta_status_to_cos(s: JtaStatus) -> TxStatus {
match s {
JtaStatus::Active => TxStatus::Active,
JtaStatus::MarkedRollback => TxStatus::MarkedRollback,
JtaStatus::Prepared => TxStatus::Prepared,
JtaStatus::Committed => TxStatus::Committed,
JtaStatus::RolledBack => TxStatus::RolledBack,
JtaStatus::Unknown => TxStatus::Unknown,
JtaStatus::NoTransaction => TxStatus::NoTransaction,
JtaStatus::Preparing => TxStatus::Preparing,
JtaStatus::Committing => TxStatus::Committing,
JtaStatus::RollingBack => TxStatus::RollingBack,
}
}
pub trait TxBridge {
fn status(&self) -> TxStatus;
fn begin(&mut self) -> Result<(), &'static str>;
fn commit(&mut self) -> Result<(), &'static str>;
fn rollback(&mut self) -> Result<(), &'static str>;
fn set_rollback_only(&mut self) -> Result<(), &'static str>;
}
#[derive(Debug, Default, Clone, PartialEq, Eq)]
pub struct InMemoryTxBridge {
state: Option<TxStatus>,
}
impl InMemoryTxBridge {
#[must_use]
pub fn new() -> Self {
Self::default()
}
}
impl TxBridge for InMemoryTxBridge {
fn status(&self) -> TxStatus {
self.state.unwrap_or(TxStatus::NoTransaction)
}
fn begin(&mut self) -> Result<(), &'static str> {
if self.state.is_some() {
return Err("transaction already active");
}
self.state = Some(TxStatus::Active);
Ok(())
}
fn commit(&mut self) -> Result<(), &'static str> {
match self.state {
Some(TxStatus::Active) => {
self.state = Some(TxStatus::Committed);
Ok(())
}
Some(TxStatus::MarkedRollback) => {
self.state = Some(TxStatus::RolledBack);
Err("commit forced rollback (marked rollback)")
}
_ => Err("no active transaction to commit"),
}
}
fn rollback(&mut self) -> Result<(), &'static str> {
match self.state {
Some(TxStatus::Active | TxStatus::MarkedRollback) => {
self.state = Some(TxStatus::RolledBack);
Ok(())
}
_ => Err("no active transaction to rollback"),
}
}
fn set_rollback_only(&mut self) -> Result<(), &'static str> {
match self.state {
Some(TxStatus::Active) => {
self.state = Some(TxStatus::MarkedRollback);
Ok(())
}
_ => Err("no active transaction"),
}
}
}
#[cfg(test)]
#[allow(clippy::expect_used, clippy::unwrap_used, clippy::panic)]
mod tests {
use super::*;
use alloc::format;
#[test]
fn cos_to_jta_round_trip_for_all_states() {
for cos in [
TxStatus::Active,
TxStatus::MarkedRollback,
TxStatus::Prepared,
TxStatus::Committed,
TxStatus::RolledBack,
TxStatus::Unknown,
TxStatus::NoTransaction,
TxStatus::Preparing,
TxStatus::Committing,
TxStatus::RollingBack,
] {
let jta = jta_status_from_cos(cos);
let back = jta_status_to_cos(jta);
assert_eq!(cos, back);
assert_eq!(cos as u8, jta as i32 as u8);
}
}
#[test]
fn fresh_bridge_has_no_transaction() {
let b = InMemoryTxBridge::new();
assert_eq!(b.status(), TxStatus::NoTransaction);
}
#[test]
fn begin_then_commit_succeeds() {
let mut b = InMemoryTxBridge::new();
b.begin().unwrap();
assert_eq!(b.status(), TxStatus::Active);
b.commit().unwrap();
assert_eq!(b.status(), TxStatus::Committed);
}
#[test]
fn double_begin_fails() {
let mut b = InMemoryTxBridge::new();
b.begin().unwrap();
assert!(b.begin().is_err());
}
#[test]
fn rollback_only_then_commit_fails_with_rollback() {
let mut b = InMemoryTxBridge::new();
b.begin().unwrap();
b.set_rollback_only().unwrap();
assert_eq!(b.status(), TxStatus::MarkedRollback);
assert!(b.commit().is_err());
assert_eq!(b.status(), TxStatus::RolledBack);
}
#[test]
fn rollback_active_succeeds() {
let mut b = InMemoryTxBridge::new();
b.begin().unwrap();
b.rollback().unwrap();
assert_eq!(b.status(), TxStatus::RolledBack);
}
#[test]
fn set_rollback_only_without_tx_fails() {
let mut b = InMemoryTxBridge::new();
assert!(b.set_rollback_only().is_err());
}
#[test]
fn display_for_status_returns_spec_name() {
assert_eq!(format!("{}", TxStatus::Active), "StatusActive");
assert_eq!(format!("{}", TxStatus::RolledBack), "StatusRolledBack");
}
}