use crate::{
common::{
mimer_options::*,
return_codes::MIMER_SUCCESS,
traits::{GetHandle, MimerHandle},
},
connection::Connection,
};
use mimerrust_sys as ffi;
#[doc(hidden)]
use std::{
cmp::Ordering,
ops::{Deref, DerefMut},
result::Result::{Err, Ok},
};
pub struct Transaction<'a> {
connection: &'a mut Connection,
}
impl GetHandle for Transaction<'_> {
fn get_handle(&self) -> Result<MimerHandle, i32> {
self.connection.get_handle()
}
fn get_session_handle(
&self,
) -> Result<Option<parking_lot::MappedMutexGuard<mimerrust_sys::MimerSession>>, i32> {
self.connection.get_session_handle()
}
}
impl Transaction<'_> {
pub(crate) fn new(conn: &mut Connection, toption: TransactionMode) -> Result<Transaction, i32> {
unsafe {
let rc = ffi::MimerBeginTransaction(
*conn.get_session_handle()?.unwrap(), toption as i32,
);
match rc.cmp(MIMER_SUCCESS) {
Ordering::Greater => {
panic!("Return code is positive from C API function which doesn't return a positive value")
}
Ordering::Equal => Ok(Transaction { connection: conn }),
Ordering::Less => Err(rc),
}
}
}
pub fn commit(mut self) -> Result<i32, i32> {
self.end_transaction(EndTransactionMode::Commit)
}
pub fn rollback(mut self) -> Result<i32, i32> {
self.end_transaction(EndTransactionMode::Rollback)
}
fn end_transaction(&mut self, trans_option: EndTransactionMode) -> Result<i32, i32> {
let handle = self.get_session_handle()?.unwrap(); unsafe {
let rc = ffi::MimerEndTransaction(*handle, trans_option as i32);
match rc.cmp(MIMER_SUCCESS) {
Ordering::Greater => {
panic!("Return code is positive from C API function which doesn't return a positive value")
}
Ordering::Equal => Ok(rc),
Ordering::Less => Err(rc),
}
}
}
}
impl<'a> Drop for Transaction<'a> {
fn drop(&mut self) {
self.end_transaction(EndTransactionMode::Rollback).ok();
}
}
impl Deref for Transaction<'_> {
type Target = Connection;
fn deref(&self) -> &Connection {
self.connection
}
}
impl DerefMut for Transaction<'_> {
fn deref_mut(&mut self) -> &mut Self::Target {
self.connection
}
}
#[cfg(test)]
mod transaction_tests {
use super::*;
use crate::testing::*;
use core::panic;
#[test]
fn create_transaction() {
let mut conn = establish_connection();
drop_create_table(&conn, EXAMPLE_TABLE, EXAMPLE_TABLE_COLUMNS);
let trans_option = TransactionMode::ReadWrite;
match Transaction::new(&mut conn, trans_option) {
Ok(_) => (),
Err(ec) => panic!("Could not create transaction: {}", ec),
};
}
#[test]
fn transaction_drop() {
let mut conn = establish_connection();
let trans_option = TransactionMode::ReadWrite;
match Transaction::new(&mut conn, trans_option) {
Ok(t) => t,
Err(ec) => panic!("Could not create transaction: {ec}"),
};
match Transaction::new(&mut conn, trans_option) {
Ok(_) => (),
Err(ec) => panic!("Could not create transaction: {ec}"),
};
}
#[test]
fn rollback_on_drop() {
let mut conn = establish_connection();
let trans_option = TransactionMode::ReadWrite;
let mut trans = match Transaction::new(&mut conn, trans_option) {
Ok(t) => t,
Err(ec) => panic!("Could not create transaction: {ec}"),
};
match trans.end_transaction(EndTransactionMode::Rollback) {
Ok(_rc) => (),
Err(ec) => panic!("Could not end transaction: {ec}"),
}
match trans.end_transaction(EndTransactionMode::Rollback) {
Ok(_rc) => panic!("Should not be able to end transaction twice"),
Err(ec) => assert_eq!(-24101, ec),
};
}
#[test]
fn execute_while_transaction() {
let mut conn = establish_connection();
drop_create_table(&conn, EXAMPLE_TABLE, EXAMPLE_TABLE_COLUMNS);
let trans_option = TransactionMode::ReadWrite;
let trans = Transaction::new(&mut conn, trans_option).unwrap();
trans
.execute_statement(&format!(
"INSERT INTO {EXAMPLE_TABLE} {EXAMPLE_TABLE_COLUMN_NAMES} {EXAMPLE_TABLE_EXAMPLE_VALUES}"
))
.unwrap();
match trans.rollback() {
Ok(rc) => rc,
Err(ec) => panic!("Ending transaction failed: {}", ec),
};
}
#[test]
fn transaction_rollback() {
let mut conn = establish_connection();
drop_create_table(&conn, EXAMPLE_TABLE, EXAMPLE_TABLE_COLUMNS);
let trans_option = TransactionMode::ReadWrite;
let trans = Transaction::new(&mut conn, trans_option).unwrap();
trans
.execute_statement(&format!(
"INSERT INTO {EXAMPLE_TABLE} {EXAMPLE_TABLE_COLUMN_NAMES} {EXAMPLE_TABLE_EXAMPLE_VALUES}"
))
.unwrap();
match trans.rollback() {
Ok(rc) => rc,
Err(ec) => panic!("Ending transaction failed: {}", ec),
};
let stmt = conn
.prepare(
&format!("SELECT * FROM {EXAMPLE_TABLE}"),
CursorMode::Forward,
)
.unwrap();
let mut cursor = stmt.open_cursor().unwrap();
match cursor.next_row().unwrap() {
Some(_) => panic!("Select found entries when transaction rolled back"),
None => (),
}
}
#[test]
fn transaction_commit() {
let mut conn = establish_connection();
drop_create_table(&conn, EXAMPLE_TABLE, EXAMPLE_TABLE_COLUMNS);
let trans_option = TransactionMode::ReadWrite;
let trans = Transaction::new(&mut conn, trans_option).unwrap();
let _ = trans.execute_statement(
"INSERT INTO test_table (column_1, column_2) VALUES ('some value', 42)",
);
match trans.commit() {
Ok(rc) => rc,
Err(ec) => panic!("Ending transaction failed: {}", ec),
};
let stmt = conn
.prepare(
&format!("SELECT * FROM {EXAMPLE_TABLE}"),
CursorMode::Forward,
)
.unwrap();
let mut cursor = stmt.open_cursor().unwrap();
cursor
.next_row()
.unwrap()
.expect("Select didn't find any entries when transaction was commited");
}
#[test]
fn transaction_read_only() {
let mut conn = establish_connection();
drop_create_table(&conn, EXAMPLE_TABLE, EXAMPLE_TABLE_COLUMNS);
let trans_option = TransactionMode::ReadOnly;
let trans = Transaction::new(&mut conn, trans_option).unwrap();
let _ = trans.execute_statement(
"INSERT INTO test_table (column_1, column_2) VALUES ('some value', 42)",
);
match trans.commit() {
Ok(rc) => rc,
Err(ec) => panic!("Ending transaction failed: {}", ec),
};
let stmt = conn
.prepare(
&format!("SELECT * FROM {EXAMPLE_TABLE}"),
CursorMode::Forward,
)
.unwrap();
let mut cursor = stmt.open_cursor().unwrap();
match cursor.next_row().unwrap() {
Some(_) => panic!("Select found entries when transaction was in read only mode"),
None => (),
}
}
#[test]
fn transaction_deref() {
let mut conn = establish_connection();
drop_create_table(&conn, EXAMPLE_TABLE, EXAMPLE_TABLE_COLUMNS);
let trans_option = TransactionMode::ReadWrite;
let trans = Transaction::new(&mut conn, trans_option).unwrap();
let _ = trans.execute_statement(
"INSERT INTO test_table (column_1, number) VALUES ('some value', 42)",
);
match trans.commit() {
Ok(rc) => rc,
Err(ec) => panic!("Ending transaction failed: {}", ec),
};
}
#[test]
fn transaction_begin_deref() {
let mut conn = establish_connection();
let trans_option = TransactionMode::ReadWrite;
let mut trans = Transaction::new(&mut conn, trans_option).unwrap();
match trans.begin_transaction(trans_option) {
Ok(_) => panic!("Should not be able to create another transaction"),
Err(ec) => assert_eq!(-14011, ec),
};
}
}