use crate::{
backend::FfiBackend,
connection::{
ConnectionVTable, execute_impl, execute_unprepared_impl, query_all_impl,
query_one_impl
},
db_err::FfiDbErr,
exec_result::FfiExecResult,
proxy_row::{FfiOptionalProxyRow, FfiProxyRow},
result::FfiResult,
statement::FfiStatement,
string::StringPtr,
vec::VecPtr
};
use async_compat::Compat;
use async_ffi::{BorrowingFfiFuture, FfiFuture, FutureExt};
use async_trait::async_trait;
use futures_util::future::FutureExt as _;
use sea_orm::{
ConnectionTrait, DatabaseBackend, DatabaseTransaction, DbErr, ExecResult,
QueryResult, Statement
};
use std::{ffi::c_void, future::Future, mem::ManuallyDrop};
macro_rules! doc_safety {
() => {
concat!(
"\n",
"# Safety\n",
"\n",
"This function is only safe to be called on the same side of the FFI ",
"boundary that was used to construct the connection pointer using the ",
"[`new`](TransactionPtr::new) function, or any binary that was compiled ",
"using the exact same dependencies and the exact same compiler and linker."
)
};
}
#[repr(transparent)]
struct TransactionPtr(*mut c_void);
impl TransactionPtr {
fn new(inner: Box<Compat<DatabaseTransaction>>) -> Self {
Self(Box::into_raw(inner) as _)
}
#[doc = doc_safety!()]
unsafe fn get(&self) -> &DatabaseTransaction {
let ptr: *mut Compat<DatabaseTransaction> = self.0.cast();
(*ptr).get_ref()
}
#[doc = doc_safety!()]
#[allow(clippy::wrong_self_convention)] unsafe fn into_inner(&mut self) -> Box<Compat<DatabaseTransaction>> {
let ptr: *mut Compat<DatabaseTransaction> = self.0.cast();
Box::from_raw(ptr)
}
}
type DropTransaction = extern "C" fn(&mut TransactionPtr);
#[doc = doc_safety!()]
extern "C" fn drop_transaction(ptr: &mut TransactionPtr) {
drop(unsafe { ptr.into_inner() });
}
type CommitTransaction =
extern "C" fn(&mut TransactionPtr) -> FfiFuture<FfiResult<(), FfiDbErr>>;
#[doc = doc_safety!()]
extern "C" fn commit_transaction(
ptr: &mut TransactionPtr
) -> FfiFuture<FfiResult<(), FfiDbErr>> {
let conn = unsafe { ptr.into_inner() };
Compat::new(
async move { conn.into_inner().commit().await.map_err(Into::into).into() }
)
.into_ffi()
}
#[doc = doc_safety!()]
extern "C" fn rollback_transaction(
ptr: &mut TransactionPtr
) -> FfiFuture<FfiResult<(), FfiDbErr>> {
let conn = unsafe { ptr.into_inner() };
Compat::new(async move {
conn.into_inner()
.rollback()
.await
.map_err(Into::into)
.into()
})
.into_ffi()
}
#[doc = doc_safety!()]
extern "C" fn get_database_backend(ptr: &TransactionPtr) -> FfiBackend {
unsafe { ptr.get() }.get_database_backend().into()
}
#[doc = doc_safety!()]
extern "C" fn execute(
ptr: &TransactionPtr,
stmt: FfiStatement
) -> BorrowingFfiFuture<'_, FfiResult<FfiExecResult, FfiDbErr>> {
execute_impl(unsafe { ptr.get() }, stmt)
}
#[doc = doc_safety!()]
extern "C" fn execute_unprepared(
ptr: &TransactionPtr,
sql: StringPtr
) -> BorrowingFfiFuture<'_, FfiResult<FfiExecResult, FfiDbErr>> {
execute_unprepared_impl(unsafe { ptr.get() }, sql)
}
#[doc = doc_safety!()]
extern "C" fn query_one(
ptr: &TransactionPtr,
stmt: FfiStatement
) -> BorrowingFfiFuture<'_, FfiResult<FfiOptionalProxyRow, FfiDbErr>> {
query_one_impl(unsafe { ptr.get() }, stmt)
}
#[doc = doc_safety!()]
extern "C" fn query_all(
ptr: &TransactionPtr,
stmt: FfiStatement
) -> BorrowingFfiFuture<'_, FfiResult<VecPtr<FfiProxyRow>, FfiDbErr>> {
query_all_impl(unsafe { ptr.get() }, stmt)
}
#[doc = doc_safety!()]
extern "C" fn is_mock_connection(ptr: &TransactionPtr) -> bool {
unsafe { ptr.get() }.is_mock_connection()
}
#[repr(C)]
pub struct FfiTransaction {
ptr: TransactionPtr,
drop: DropTransaction,
commit: CommitTransaction,
rollback: CommitTransaction,
vtable: ConnectionVTable<TransactionPtr>
}
impl FfiTransaction {
pub(crate) fn new(inner: DatabaseTransaction) -> Self {
Self {
ptr: TransactionPtr::new(Box::new(Compat::new(inner))),
drop: drop_transaction,
commit: commit_transaction,
rollback: rollback_transaction,
vtable: ConnectionVTable {
backend: get_database_backend,
execute,
execute_unprepared,
query_one,
query_all,
is_mock_connection
}
}
}
pub fn commit(self) -> impl Future<Output = Result<(), DbErr>> + Send {
let mut this = ManuallyDrop::new(self);
(this.commit)(&mut this.ptr)
.map(|res: FfiResult<(), FfiDbErr>| res.into_result().map_err(Into::into))
}
pub fn rollback(self) -> impl Future<Output = Result<(), DbErr>> + Send {
let mut this = ManuallyDrop::new(self);
(this.commit)(&mut this.ptr)
.map(|res: FfiResult<(), FfiDbErr>| res.into_result().map_err(Into::into))
}
}
impl Drop for FfiTransaction {
fn drop(&mut self) {
(self.drop)(&mut self.ptr)
}
}
unsafe impl Send for TransactionPtr {}
unsafe impl Sync for TransactionPtr {}
unsafe impl Send for FfiTransaction {}
unsafe impl Sync for FfiTransaction {}
#[async_trait]
impl ConnectionTrait for FfiTransaction {
fn get_database_backend(&self) -> DatabaseBackend {
self.vtable.get_database_backend(&self.ptr)
}
async fn execute(&self, stmt: Statement) -> Result<ExecResult, DbErr> {
self.vtable.execute(&self.ptr, stmt).await
}
async fn execute_unprepared(&self, sql: &str) -> Result<ExecResult, DbErr> {
self.vtable.execute_unprepared(&self.ptr, sql).await
}
async fn query_one(&self, stmt: Statement) -> Result<Option<QueryResult>, DbErr> {
self.vtable.query_one(&self.ptr, stmt).await
}
async fn query_all(&self, stmt: Statement) -> Result<Vec<QueryResult>, DbErr> {
self.vtable.query_all(&self.ptr, stmt).await
}
fn is_mock_connection(&self) -> bool {
self.vtable.is_mock_connection(&self.ptr)
}
}