use crate::api_core::types::TransactionHandle;
use crate::embedded::c_api::error::{
error_code_from_core_error, linkrs_error_code_t, set_last_error_message,
};
use crate::embedded::c_api::result::LinkrsResultHandle;
use crate::embedded::c_api::session::LinkrsSessionHandle;
use crate::embedded::c_api::types::{linkrs_result_t, linkrs_session_t, linkrs_txn_t};
use std::ffi::{c_char, c_int, CStr};
use std::ptr;
pub struct LinkrsTxnHandle {
pub(crate) session: *mut LinkrsSessionHandle,
pub(crate) txn_handle: Option<TransactionHandle>,
pub(crate) committed: bool,
pub(crate) rolled_back: bool,
}
impl LinkrsTxnHandle {
fn is_session_valid(&self) -> bool {
!self.session.is_null()
}
fn get_session(&self) -> Option<&LinkrsSessionHandle> {
if self.is_session_valid() {
Some(unsafe { &*self.session })
} else {
None
}
}
}
impl Drop for LinkrsTxnHandle {
fn drop(&mut self) {
if !self.committed && !self.rolled_back {
if let Some(txn_handle) = self.txn_handle.take() {
if let Some(session) = self.get_session() {
let _ = session.inner.rollback_transaction(txn_handle);
}
}
}
}
}
#[no_mangle]
pub unsafe extern "C" fn linkrs_txn_begin(
session: *mut linkrs_session_t,
txn: *mut *mut linkrs_txn_t,
) -> c_int {
if session.is_null() || txn.is_null() {
return linkrs_error_code_t::LINKRS_MISUSE as c_int;
}
let handle = &*(session as *mut LinkrsSessionHandle);
match handle.inner.begin_transaction() {
Ok(transaction) => {
let txn_handle = transaction.txn_handle();
let txn_handle_box = Box::new(LinkrsTxnHandle {
session: session as *mut LinkrsSessionHandle,
txn_handle: Some(txn_handle),
committed: false,
rolled_back: false,
});
std::mem::forget(transaction);
*txn = Box::into_raw(txn_handle_box) as *mut linkrs_txn_t;
linkrs_error_code_t::LINKRS_OK as c_int
}
Err(e) => {
let error_code = linkrs_error_code_t::LINKRS_ABORT as c_int;
let error_msg = format!("{}", e);
set_last_error_message(error_msg);
*txn = ptr::null_mut();
error_code
}
}
}
#[no_mangle]
pub unsafe extern "C" fn linkrs_txn_begin_readonly(
session: *mut linkrs_session_t,
txn: *mut *mut linkrs_txn_t,
) -> c_int {
if session.is_null() || txn.is_null() {
return linkrs_error_code_t::LINKRS_MISUSE as c_int;
}
let handle = &*(session as *mut LinkrsSessionHandle);
let config = crate::embedded::TransactionConfig::new().read_only();
match handle.inner.begin_transaction_with_config(config) {
Ok(transaction) => {
let txn_handle = transaction.txn_handle();
let txn_handle_box = Box::new(LinkrsTxnHandle {
session: session as *mut LinkrsSessionHandle,
txn_handle: Some(txn_handle),
committed: false,
rolled_back: false,
});
std::mem::forget(transaction);
*txn = Box::into_raw(txn_handle_box) as *mut linkrs_txn_t;
linkrs_error_code_t::LINKRS_OK as c_int
}
Err(e) => {
let error_code = linkrs_error_code_t::LINKRS_ABORT as c_int;
let error_msg = format!("{}", e);
set_last_error_message(error_msg);
*txn = ptr::null_mut();
error_code
}
}
}
#[no_mangle]
pub unsafe extern "C" fn linkrs_txn_execute(
txn: *mut linkrs_txn_t,
query: *const c_char,
result: *mut *mut linkrs_result_t,
) -> c_int {
if txn.is_null() || query.is_null() || result.is_null() {
return linkrs_error_code_t::LINKRS_MISUSE as c_int;
}
let query_str = match CStr::from_ptr(query).to_str() {
Ok(s) => s,
Err(_) => return linkrs_error_code_t::LINKRS_MISUSE as c_int,
};
let handle = &mut *(txn as *mut LinkrsTxnHandle);
if handle.committed || handle.rolled_back {
return linkrs_error_code_t::LINKRS_MISUSE as c_int;
}
let session = match handle.get_session() {
Some(s) => s,
None => return linkrs_error_code_t::LINKRS_MISUSE as c_int,
};
let txn_handle = match handle.txn_handle.as_ref() {
Some(h) => h,
None => return linkrs_error_code_t::LINKRS_INTERNAL as c_int,
};
let ctx = crate::api_core::QueryRequest {
space_id: session.inner.space_id(),
space_name: session.inner.space_name().map(|s| s.to_string()),
auto_commit: false,
transaction_id: Some(txn_handle.0),
parameters: None,
session_variables: None,
query_id: None,
isolation_level: None,
parsed_statement: None,
consistency: Default::default(),
};
let mut query_api = session.inner.query_api_mut();
match query_api.execute(query_str, ctx) {
Ok(core_result) => {
let query_result = crate::embedded::result::QueryResult::from_core(core_result);
let result_handle = Box::new(LinkrsResultHandle {
inner: query_result,
});
*result = Box::into_raw(result_handle) as *mut linkrs_result_t;
linkrs_error_code_t::LINKRS_OK as c_int
}
Err(e) => {
let (error_code, _) = error_code_from_core_error(&e);
let error_msg = format!("{}", e);
set_last_error_message(error_msg);
*result = ptr::null_mut();
error_code
}
}
}
#[no_mangle]
pub unsafe extern "C" fn linkrs_txn_commit(txn: *mut linkrs_txn_t) -> c_int {
if txn.is_null() {
return linkrs_error_code_t::LINKRS_MISUSE as c_int;
}
let handle = &mut *(txn as *mut LinkrsTxnHandle);
if handle.committed || handle.rolled_back {
return linkrs_error_code_t::LINKRS_MISUSE as c_int;
}
if !handle.is_session_valid() {
return linkrs_error_code_t::LINKRS_MISUSE as c_int;
}
let session_ptr = handle.session;
let session = &*session_ptr;
let txn_id = match handle.txn_handle.as_ref() {
Some(h) => h.0,
None => return linkrs_error_code_t::LINKRS_INTERNAL as c_int,
};
let result = session.inner.txn_manager().commit_transaction(txn_id);
match result {
Ok(_) => {
handle.txn_handle.take();
handle.committed = true;
linkrs_error_code_t::LINKRS_OK as c_int
}
Err(e) if e.kind() == linkrs_transaction::TransactionErrorKind::CommitVetoed => {
linkrs_txn_rollback(txn)
}
Err(e) => {
handle.txn_handle.take();
let error_code = linkrs_error_code_t::LINKRS_ABORT as c_int;
let error_msg = format!("{}", e);
set_last_error_message(error_msg);
error_code
}
}
}
#[no_mangle]
pub unsafe extern "C" fn linkrs_txn_rollback(txn: *mut linkrs_txn_t) -> c_int {
if txn.is_null() {
return linkrs_error_code_t::LINKRS_MISUSE as c_int;
}
let handle = &mut *(txn as *mut LinkrsTxnHandle);
if handle.committed || handle.rolled_back {
return linkrs_error_code_t::LINKRS_MISUSE as c_int;
}
if !handle.is_session_valid() {
return linkrs_error_code_t::LINKRS_MISUSE as c_int;
}
let session_ptr = handle.session;
{
let session = &*session_ptr;
if let Some(callback) = session.rollback_hook {
let user_data = session.rollback_hook_user_data;
if std::panic::catch_unwind(std::panic::AssertUnwindSafe(|| {
callback(user_data);
}))
.is_err()
{
log::error!("rollback hook panicked; continuing rollback");
}
}
}
let txn_handle = match handle.txn_handle.take() {
Some(h) => h,
None => return linkrs_error_code_t::LINKRS_INTERNAL as c_int,
};
let session = &*session_ptr;
match session.inner.rollback_transaction(txn_handle) {
Ok(_) => {
handle.rolled_back = true;
linkrs_error_code_t::LINKRS_OK as c_int
}
Err(e) => {
let error_code = linkrs_error_code_t::LINKRS_ABORT as c_int;
let error_msg = format!("{}", e);
set_last_error_message(error_msg);
error_code
}
}
}
#[no_mangle]
pub unsafe extern "C" fn linkrs_txn_savepoint(txn: *mut linkrs_txn_t, name: *const c_char) -> i64 {
if txn.is_null() || name.is_null() {
return -1;
}
let name_str = match CStr::from_ptr(name).to_str() {
Ok(s) => s,
Err(_) => return -1,
};
let handle = &mut *(txn as *mut LinkrsTxnHandle);
if handle.committed || handle.rolled_back {
return -1;
}
let session = match handle.get_session() {
Some(s) => s,
None => return -1,
};
let txn_handle = match handle.txn_handle.as_ref() {
Some(h) => h,
None => return -1,
};
match session.inner.create_savepoint(txn_handle, name_str) {
Ok(id) => id.0 as i64,
Err(_) => -1,
}
}
#[no_mangle]
pub unsafe extern "C" fn linkrs_txn_release_savepoint(
txn: *mut linkrs_txn_t,
savepoint_id: i64,
) -> c_int {
if txn.is_null() {
return linkrs_error_code_t::LINKRS_MISUSE as c_int;
}
let handle = &mut *(txn as *mut LinkrsTxnHandle);
if handle.committed || handle.rolled_back {
return linkrs_error_code_t::LINKRS_MISUSE as c_int;
}
let session = match handle.get_session() {
Some(s) => s,
None => return linkrs_error_code_t::LINKRS_MISUSE as c_int,
};
let txn_handle = match handle.txn_handle.as_ref() {
Some(h) => h,
None => return linkrs_error_code_t::LINKRS_MISUSE as c_int,
};
let savepoint = crate::api_core::types::SavepointId(savepoint_id as u64);
match session.inner.release_savepoint(txn_handle, savepoint) {
Ok(_) => linkrs_error_code_t::LINKRS_OK as c_int,
Err(e) => {
let core_error = crate::api_core::error::CoreError::TransactionFailed(format!("{}", e));
let (error_code, _) = error_code_from_core_error(&core_error);
let error_msg = format!("{}", e);
set_last_error_message(error_msg);
error_code
}
}
}
#[no_mangle]
pub unsafe extern "C" fn linkrs_txn_rollback_to_savepoint(
txn: *mut linkrs_txn_t,
savepoint_id: i64,
) -> c_int {
if txn.is_null() {
return linkrs_error_code_t::LINKRS_MISUSE as c_int;
}
let handle = &mut *(txn as *mut LinkrsTxnHandle);
if handle.committed || handle.rolled_back {
return linkrs_error_code_t::LINKRS_MISUSE as c_int;
}
let session = match handle.get_session() {
Some(s) => s,
None => return linkrs_error_code_t::LINKRS_MISUSE as c_int,
};
let txn_handle = match handle.txn_handle.as_ref() {
Some(h) => h,
None => return linkrs_error_code_t::LINKRS_INTERNAL as c_int,
};
let savepoint = crate::api_core::types::SavepointId(savepoint_id as u64);
match session.inner.rollback_to_savepoint(txn_handle, savepoint) {
Ok(_) => linkrs_error_code_t::LINKRS_OK as c_int,
Err(e) => {
let core_error = crate::api_core::error::CoreError::TransactionFailed(format!("{}", e));
let (error_code, _) = error_code_from_core_error(&core_error);
let error_msg = format!("{}", e);
set_last_error_message(error_msg);
error_code
}
}
}
#[no_mangle]
pub unsafe extern "C" fn linkrs_txn_free(txn: *mut linkrs_txn_t) {
if !txn.is_null() {
let _ = Box::from_raw(txn as *mut LinkrsTxnHandle);
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_txn_begin_null_params() {
let result = unsafe { linkrs_txn_begin(ptr::null_mut(), ptr::null_mut()) };
assert_eq!(result, linkrs_error_code_t::LINKRS_MISUSE as c_int);
}
#[test]
fn test_txn_free_null() {
unsafe { linkrs_txn_free(ptr::null_mut()) };
}
#[test]
fn test_txn_commit_null() {
let result = unsafe { linkrs_txn_commit(ptr::null_mut()) };
assert_eq!(result, linkrs_error_code_t::LINKRS_MISUSE as c_int);
}
#[test]
fn test_txn_rollback_null() {
let result = unsafe { linkrs_txn_rollback(ptr::null_mut()) };
assert_eq!(result, linkrs_error_code_t::LINKRS_MISUSE as c_int);
}
}