Skip to main content

sea_orm_ffi/
transaction.rs

1use crate::{
2	backend::FfiBackend,
3	connection::{
4		ConnectionVTable, execute_impl, execute_unprepared_impl, query_all_impl,
5		query_one_impl
6	},
7	db_err::FfiDbErr,
8	exec_result::FfiExecResult,
9	proxy_row::{FfiOptionalProxyRow, FfiProxyRow},
10	result::FfiResult,
11	statement::FfiStatement,
12	string::StringPtr,
13	vec::VecPtr
14};
15use async_compat::Compat;
16use async_ffi::{BorrowingFfiFuture, FfiFuture, FutureExt};
17use async_trait::async_trait;
18use futures_util::future::FutureExt as _;
19use sea_orm::{
20	ConnectionTrait, DatabaseBackend, DatabaseTransaction, DbErr, ExecResult,
21	QueryResult, Statement
22};
23use std::{ffi::c_void, future::Future, mem::ManuallyDrop};
24
25/// Common safety documentation for [`TransactionPtr`] functions.
26macro_rules! doc_safety {
27	() => {
28		concat!(
29			"\n",
30			"# Safety\n",
31			"\n",
32			"This function is only safe to be called on the same side of the FFI ",
33			"boundary that was used to construct the connection pointer using the ",
34			"[`new`](TransactionPtr::new) function, or any binary that was compiled ",
35			"using the exact same dependencies and the exact same compiler and linker."
36		)
37	};
38}
39
40/// A pointer to [`DatabaseTransaction`].
41///
42/// This pointer must be treated as opaque on the side of the FFI boundary that did not
43/// create the pointer.
44#[repr(transparent)]
45struct TransactionPtr(*mut c_void);
46
47impl TransactionPtr {
48	fn new(inner: Box<Compat<DatabaseTransaction>>) -> Self {
49		Self(Box::into_raw(inner) as _)
50	}
51
52	/// Access the data stored in this pointer
53	#[doc = doc_safety!()]
54	unsafe fn get(&self) -> &DatabaseTransaction {
55		let ptr: *mut Compat<DatabaseTransaction> = self.0.cast();
56		(*ptr).get_ref()
57	}
58
59	/// Return the inner type this pointer points to.
60	#[doc = doc_safety!()]
61	#[allow(clippy::wrong_self_convention)] // well, `Drop` only gives `&mut Self`
62	unsafe fn into_inner(&mut self) -> Box<Compat<DatabaseTransaction>> {
63		let ptr: *mut Compat<DatabaseTransaction> = self.0.cast();
64		Box::from_raw(ptr)
65	}
66}
67
68/// The FFI callback type that is called when an [`FfiTransaction`] is [drop]ped.
69type DropTransaction = extern "C" fn(&mut TransactionPtr);
70
71/// Drop the transaction that this pointer points to.
72#[doc = doc_safety!()]
73extern "C" fn drop_transaction(ptr: &mut TransactionPtr) {
74	drop(unsafe { ptr.into_inner() });
75}
76
77/// The FFI callback type that is called when
78/// [`commit()`](FfiTransaction::commit)
79/// is called on [`FfiTransaction`].
80type CommitTransaction =
81	extern "C" fn(&mut TransactionPtr) -> FfiFuture<FfiResult<(), FfiDbErr>>;
82
83/// FFI callback for [`commit()`](FfiTransaction::commit).
84#[doc = doc_safety!()]
85extern "C" fn commit_transaction(
86	ptr: &mut TransactionPtr
87) -> FfiFuture<FfiResult<(), FfiDbErr>> {
88	let conn = unsafe { ptr.into_inner() };
89	Compat::new(
90		async move { conn.into_inner().commit().await.map_err(Into::into).into() }
91	)
92	.into_ffi()
93}
94
95/// FFI callback for [`rollback()`](FfiTransaction::rollback).
96#[doc = doc_safety!()]
97extern "C" fn rollback_transaction(
98	ptr: &mut TransactionPtr
99) -> FfiFuture<FfiResult<(), FfiDbErr>> {
100	let conn = unsafe { ptr.into_inner() };
101	Compat::new(async move {
102		conn.into_inner()
103			.rollback()
104			.await
105			.map_err(Into::into)
106			.into()
107	})
108	.into_ffi()
109}
110
111/// FFI callback for [`get_database_backend()`](ConnectionTrait::get_database_backend).
112#[doc = doc_safety!()]
113extern "C" fn get_database_backend(ptr: &TransactionPtr) -> FfiBackend {
114	unsafe { ptr.get() }.get_database_backend().into()
115}
116
117/// FFI callback for [`execute()`](ConnectionTrait::execute).
118#[doc = doc_safety!()]
119extern "C" fn execute(
120	ptr: &TransactionPtr,
121	stmt: FfiStatement
122) -> BorrowingFfiFuture<'_, FfiResult<FfiExecResult, FfiDbErr>> {
123	execute_impl(unsafe { ptr.get() }, stmt)
124}
125
126/// FFI callback for [`execute_unprepared()`](ConnectionTrait::execute_unprepared).
127#[doc = doc_safety!()]
128extern "C" fn execute_unprepared(
129	ptr: &TransactionPtr,
130	sql: StringPtr
131) -> BorrowingFfiFuture<'_, FfiResult<FfiExecResult, FfiDbErr>> {
132	execute_unprepared_impl(unsafe { ptr.get() }, sql)
133}
134
135/// FFI callback for [`query_one()`](ConnectionTrait::query_one).
136#[doc = doc_safety!()]
137extern "C" fn query_one(
138	ptr: &TransactionPtr,
139	stmt: FfiStatement
140) -> BorrowingFfiFuture<'_, FfiResult<FfiOptionalProxyRow, FfiDbErr>> {
141	query_one_impl(unsafe { ptr.get() }, stmt)
142}
143
144/// FFI callback for [`query_all()`](ConnectionTrait::query_all).
145#[doc = doc_safety!()]
146extern "C" fn query_all(
147	ptr: &TransactionPtr,
148	stmt: FfiStatement
149) -> BorrowingFfiFuture<'_, FfiResult<VecPtr<FfiProxyRow>, FfiDbErr>> {
150	query_all_impl(unsafe { ptr.get() }, stmt)
151}
152
153/// FFI callback for [`is_mock_connection()`](ConnectionTrait::is_mock_connection).
154#[doc = doc_safety!()]
155extern "C" fn is_mock_connection(ptr: &TransactionPtr) -> bool {
156	unsafe { ptr.get() }.is_mock_connection()
157}
158
159/// An FFI-safe implementation of [`sea-orm`'s](sea_orm) [`DatabaseTransaction`].
160///
161/// Note that there is no FFI-safe way to use
162/// [`TransactionTrait`](sea_orm::TransactionTrait). Use the [`FfiConnection::begin()`]
163/// or [`FfiConnection::transaction()`] methods instead.
164#[repr(C)]
165pub struct FfiTransaction {
166	ptr: TransactionPtr,
167	drop: DropTransaction,
168	commit: CommitTransaction,
169	rollback: CommitTransaction,
170	vtable: ConnectionVTable<TransactionPtr>
171}
172
173impl FfiTransaction {
174	pub(crate) fn new(inner: DatabaseTransaction) -> Self {
175		Self {
176			ptr: TransactionPtr::new(Box::new(Compat::new(inner))),
177			drop: drop_transaction,
178			commit: commit_transaction,
179			rollback: rollback_transaction,
180			vtable: ConnectionVTable {
181				backend: get_database_backend,
182				execute,
183				execute_unprepared,
184				query_one,
185				query_all,
186				is_mock_connection
187			}
188		}
189	}
190
191	pub fn commit(self) -> impl Future<Output = Result<(), DbErr>> + Send {
192		let mut this = ManuallyDrop::new(self);
193		(this.commit)(&mut this.ptr)
194			.map(|res: FfiResult<(), FfiDbErr>| res.into_result().map_err(Into::into))
195	}
196
197	pub fn rollback(self) -> impl Future<Output = Result<(), DbErr>> + Send {
198		let mut this = ManuallyDrop::new(self);
199		(this.commit)(&mut this.ptr)
200			.map(|res: FfiResult<(), FfiDbErr>| res.into_result().map_err(Into::into))
201	}
202}
203
204impl Drop for FfiTransaction {
205	fn drop(&mut self) {
206		(self.drop)(&mut self.ptr)
207	}
208}
209
210// Safety: The Box'ed `DatabaseTransaction` is `Send` and `Sync`,
211// and the other types are just extern "C" fn pointers.
212unsafe impl Send for TransactionPtr {}
213unsafe impl Sync for TransactionPtr {}
214unsafe impl Send for FfiTransaction {}
215unsafe impl Sync for FfiTransaction {}
216
217#[async_trait]
218impl ConnectionTrait for FfiTransaction {
219	fn get_database_backend(&self) -> DatabaseBackend {
220		self.vtable.get_database_backend(&self.ptr)
221	}
222
223	async fn execute(&self, stmt: Statement) -> Result<ExecResult, DbErr> {
224		self.vtable.execute(&self.ptr, stmt).await
225	}
226
227	async fn execute_unprepared(&self, sql: &str) -> Result<ExecResult, DbErr> {
228		self.vtable.execute_unprepared(&self.ptr, sql).await
229	}
230
231	async fn query_one(&self, stmt: Statement) -> Result<Option<QueryResult>, DbErr> {
232		self.vtable.query_one(&self.ptr, stmt).await
233	}
234
235	async fn query_all(&self, stmt: Statement) -> Result<Vec<QueryResult>, DbErr> {
236		self.vtable.query_all(&self.ptr, stmt).await
237	}
238
239	fn is_mock_connection(&self) -> bool {
240		self.vtable.is_mock_connection(&self.ptr)
241	}
242}