use crate::{
FfiTransaction,
backend::FfiBackend,
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, FutureExt as _};
use async_trait::async_trait;
use futures_util::future::{BoxFuture, FutureExt as _};
use sea_orm::{
ConnectionTrait, DatabaseBackend, DatabaseTransaction, DbErr, ExecResult,
QueryResult, Statement, TransactionTrait
};
use std::{ffi::c_void, future::Future};
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`](ConnectionPtr::new) function, or any binary that was compiled ",
"using the exact same dependencies and the exact same compiler and linker."
)
};
}
pub trait SeaOrmConnection: ConnectionTrait + Send + Sync {
fn begin(&self) -> BoxFuture<'_, Result<DatabaseTransaction, DbErr>>;
}
impl<T> SeaOrmConnection for T
where
T: ConnectionTrait + TransactionTrait + Send + Sync
{
fn begin(&self) -> BoxFuture<'_, Result<DatabaseTransaction, DbErr>> {
TransactionTrait::begin(self)
}
}
struct BoxedConnection(Box<dyn SeaOrmConnection + Send + Sync>);
#[repr(transparent)]
struct ConnectionPtr(*mut c_void);
impl ConnectionPtr {
fn new(inner: Box<dyn SeaOrmConnection + Send + Sync>) -> Self {
Self(Box::into_raw(Box::new(BoxedConnection(inner))) as _)
}
#[doc = doc_safety!()]
unsafe fn get(&self) -> &(dyn SeaOrmConnection + Send + Sync) {
let ptr: *mut BoxedConnection = self.0.cast();
&*(&*ptr).0
}
#[doc = doc_safety!()]
#[allow(clippy::wrong_self_convention)] unsafe fn into_inner(&mut self) -> Box<dyn SeaOrmConnection + Send + Sync> {
let ptr: *mut BoxedConnection = self.0.cast();
(*Box::from_raw(ptr)).0
}
}
type DropConnection = extern "C" fn(&mut ConnectionPtr);
#[doc = doc_safety!()]
extern "C" fn drop_connection(ptr: &mut ConnectionPtr) {
drop(unsafe { ptr.into_inner() });
}
type BeginTransaction =
extern "C" fn(
&ConnectionPtr
) -> BorrowingFfiFuture<'_, FfiResult<FfiTransaction, FfiDbErr>>;
#[doc = doc_safety!()]
extern "C" fn begin_transaction(
ptr: &ConnectionPtr
) -> BorrowingFfiFuture<'_, FfiResult<FfiTransaction, FfiDbErr>> {
let conn = unsafe { ptr.get() };
Compat::new(async move {
conn.begin()
.await
.map(FfiTransaction::new)
.map_err(Into::into)
.into()
})
.into_ffi()
}
type GetDatabaseBackend<Ptr> = extern "C" fn(&Ptr) -> FfiBackend;
#[doc = doc_safety!()]
extern "C" fn get_database_backend(ptr: &ConnectionPtr) -> FfiBackend {
unsafe { ptr.get() }.get_database_backend().into()
}
type Execute<Ptr> =
extern "C" fn(
&Ptr,
FfiStatement
) -> BorrowingFfiFuture<'_, FfiResult<FfiExecResult, FfiDbErr>>;
pub(crate) fn execute_impl<C>(
conn: &C,
stmt: FfiStatement
) -> BorrowingFfiFuture<'_, FfiResult<FfiExecResult, FfiDbErr>>
where
C: ConnectionTrait + Send + Sync + ?Sized
{
Compat::new(async move {
conn.execute(stmt.into())
.await
.map(|exec_result| {
FfiExecResult::new(conn.get_database_backend(), exec_result)
})
.map_err(Into::into)
.into()
})
.into_ffi()
}
#[doc = doc_safety!()]
extern "C" fn execute(
ptr: &ConnectionPtr,
stmt: FfiStatement
) -> BorrowingFfiFuture<'_, FfiResult<FfiExecResult, FfiDbErr>> {
execute_impl(unsafe { ptr.get() }, stmt)
}
type ExecuteUnprepared<Ptr> =
extern "C" fn(
&Ptr,
StringPtr
) -> BorrowingFfiFuture<'_, FfiResult<FfiExecResult, FfiDbErr>>;
pub(crate) fn execute_unprepared_impl<C>(
conn: &C,
sql: StringPtr
) -> BorrowingFfiFuture<'_, FfiResult<FfiExecResult, FfiDbErr>>
where
C: ConnectionTrait + Send + Sync + ?Sized
{
Compat::new(async move {
conn.execute_unprepared(sql.as_str())
.await
.map(|exec_result| {
FfiExecResult::new(conn.get_database_backend(), exec_result)
})
.map_err(Into::into)
.into()
})
.into_ffi()
}
#[doc = doc_safety!()]
extern "C" fn execute_unprepared(
ptr: &ConnectionPtr,
sql: StringPtr
) -> BorrowingFfiFuture<'_, FfiResult<FfiExecResult, FfiDbErr>> {
execute_unprepared_impl(unsafe { ptr.get() }, sql)
}
type QueryOne<Ptr> =
extern "C" fn(
&Ptr,
FfiStatement
) -> BorrowingFfiFuture<'_, FfiResult<FfiOptionalProxyRow, FfiDbErr>>;
pub(crate) fn query_one_impl<C>(
conn: &C,
stmt: FfiStatement
) -> BorrowingFfiFuture<'_, FfiResult<FfiOptionalProxyRow, FfiDbErr>>
where
C: ConnectionTrait + Send + Sync + ?Sized
{
Compat::new(async move {
conn.query_one(stmt.into())
.await
.map(Into::into)
.map_err(Into::into)
.into()
})
.into_ffi()
}
#[doc = doc_safety!()]
extern "C" fn query_one(
ptr: &ConnectionPtr,
stmt: FfiStatement
) -> BorrowingFfiFuture<'_, FfiResult<FfiOptionalProxyRow, FfiDbErr>> {
query_one_impl(unsafe { ptr.get() }, stmt)
}
type QueryAll<Ptr> =
extern "C" fn(
&Ptr,
FfiStatement
) -> BorrowingFfiFuture<'_, FfiResult<VecPtr<FfiProxyRow>, FfiDbErr>>;
pub(crate) fn query_all_impl<C>(
conn: &C,
stmt: FfiStatement
) -> BorrowingFfiFuture<'_, FfiResult<VecPtr<FfiProxyRow>, FfiDbErr>>
where
C: ConnectionTrait + Send + Sync + ?Sized
{
Compat::new(async move {
conn.query_all(stmt.into())
.await
.map(|rows| {
rows.into_iter()
.map(Into::into)
.collect::<Vec<FfiProxyRow>>()
.into()
})
.map_err(Into::into)
.into()
})
.into_ffi()
}
#[doc = doc_safety!()]
extern "C" fn query_all(
ptr: &ConnectionPtr,
stmt: FfiStatement
) -> BorrowingFfiFuture<'_, FfiResult<VecPtr<FfiProxyRow>, FfiDbErr>> {
query_all_impl(unsafe { ptr.get() }, stmt)
}
type IsMockConnection<Ptr> = extern "C" fn(&Ptr) -> bool;
#[doc = doc_safety!()]
extern "C" fn is_mock_connection(ptr: &ConnectionPtr) -> bool {
unsafe { ptr.get() }.is_mock_connection()
}
#[repr(C)]
pub(crate) struct ConnectionVTable<Ptr> {
pub(crate) backend: GetDatabaseBackend<Ptr>,
pub(crate) execute: Execute<Ptr>,
pub(crate) execute_unprepared: ExecuteUnprepared<Ptr>,
pub(crate) query_one: QueryOne<Ptr>,
pub(crate) query_all: QueryAll<Ptr>,
pub(crate) is_mock_connection: IsMockConnection<Ptr>
}
#[repr(C)]
pub struct FfiConnection {
ptr: ConnectionPtr,
drop: DropConnection,
begin: BeginTransaction,
vtable: ConnectionVTable<ConnectionPtr>
}
impl FfiConnection {
pub fn new(inner: Box<dyn SeaOrmConnection + Send + Sync>) -> Self {
Self {
ptr: ConnectionPtr::new(inner),
drop: drop_connection,
begin: begin_transaction,
vtable: ConnectionVTable {
backend: get_database_backend,
execute,
execute_unprepared,
query_one,
query_all,
is_mock_connection
}
}
}
pub fn begin(
&self
) -> impl Future<Output = Result<FfiTransaction, DbErr>> + Send + '_ {
(self.begin)(&self.ptr).map(|res| res.into_result().map_err(Into::into))
}
}
impl Drop for FfiConnection {
fn drop(&mut self) {
(self.drop)(&mut self.ptr)
}
}
unsafe impl Send for ConnectionPtr {}
unsafe impl Sync for ConnectionPtr {}
unsafe impl<Ptr> Send for ConnectionVTable<Ptr> {}
unsafe impl<Ptr> Sync for ConnectionVTable<Ptr> {}
unsafe impl Send for FfiConnection {}
unsafe impl Sync for FfiConnection {}
impl<Ptr> ConnectionVTable<Ptr> {
pub(crate) fn get_database_backend(&self, ptr: &Ptr) -> DatabaseBackend {
(self.backend)(ptr).into()
}
pub(crate) async fn execute(
&self,
ptr: &Ptr,
stmt: Statement
) -> Result<ExecResult, DbErr> {
(self.execute)(ptr, stmt.into())
.await
.map(Into::into)
.map_err(Into::into)
.into()
}
pub(crate) async fn execute_unprepared(
&self,
ptr: &Ptr,
sql: &str
) -> Result<ExecResult, DbErr> {
let sql: String = sql.into();
(self.execute_unprepared)(ptr, sql.into())
.await
.map(Into::into)
.map_err(Into::into)
.into()
}
pub(crate) async fn query_one(
&self,
ptr: &Ptr,
stmt: Statement
) -> Result<Option<QueryResult>, DbErr> {
(self.query_one)(ptr, stmt.into())
.await
.map(Into::into)
.map_err(Into::into)
.into()
}
pub(crate) async fn query_all(
&self,
ptr: &Ptr,
stmt: Statement
) -> Result<Vec<QueryResult>, DbErr> {
(self.query_all)(ptr, stmt.into())
.await
.map(|rows| rows.into_vec().into_iter().map(Into::into).collect())
.map_err(Into::into)
.into()
}
pub(crate) fn is_mock_connection(&self, ptr: &Ptr) -> bool {
(self.is_mock_connection)(ptr)
}
}
#[async_trait]
impl ConnectionTrait for FfiConnection {
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)
}
}