Skip to main content

sea_orm_ffi/
connection.rs

1use crate::{
2	FfiTransaction,
3	backend::FfiBackend,
4	db_err::FfiDbErr,
5	exec_result::FfiExecResult,
6	proxy_row::{FfiOptionalProxyRow, FfiProxyRow},
7	result::FfiResult,
8	statement::FfiStatement,
9	string::StringPtr,
10	vec::VecPtr
11};
12use async_compat::Compat;
13use async_ffi::{BorrowingFfiFuture, FutureExt as _};
14use async_trait::async_trait;
15use futures_util::future::{BoxFuture, FutureExt as _};
16use sea_orm::{
17	ConnectionTrait, DatabaseBackend, DatabaseTransaction, DbErr, ExecResult,
18	QueryResult, Statement, TransactionTrait
19};
20use std::{ffi::c_void, future::Future};
21
22/// Common safety documentation for [`ConnectionPtr`] functions.
23macro_rules! doc_safety {
24	() => {
25		concat!(
26			"\n",
27			"# Safety\n",
28			"\n",
29			"This function is only safe to be called on the same side of the FFI ",
30			"boundary that was used to construct the connection pointer using the ",
31			"[`new`](ConnectionPtr::new) function, or any binary that was compiled ",
32			"using the exact same dependencies and the exact same compiler and linker."
33		)
34	};
35}
36
37/// Unfortunately, [`TransactionTrait`] is not `dyn`-compatible. Therefore, we extract
38/// the `begin()` function and add it to this trait explicitly.
39pub trait SeaOrmConnection: ConnectionTrait + Send + Sync {
40	fn begin(&self) -> BoxFuture<'_, Result<DatabaseTransaction, DbErr>>;
41}
42
43impl<T> SeaOrmConnection for T
44where
45	T: ConnectionTrait + TransactionTrait + Send + Sync
46{
47	fn begin(&self) -> BoxFuture<'_, Result<DatabaseTransaction, DbErr>> {
48		TransactionTrait::begin(self)
49	}
50}
51
52/// Boxed [`SeaOrmConnection`].
53///
54/// We cannot directly move a `*mut dyn SeaOrmConnection` over an FFI boundary as this
55/// type is not FFI safe. It a so called _fat pointer_, which means that it doesn't
56/// have a guaranteed memory layout, even if we ignore the `SeaOrmConnection` part of
57/// the pointer.
58///
59/// Therefore, we pass pointers to this struct instead. While this is technically a
60/// pointer to a pointer, the devil is, as always, in the detail: A pointer to this
61/// struct is a _thin pointer_, so we get a _thin pointer_ to a _fat pointer_. And a
62/// _thin pointer_ can be casted to and from a `*mut c_void`, and that is an FFI-safe
63/// pointer. Dereferencing that pointer is of course only safe if the memory layout
64/// is equivalent, so the part of the FFI boundary that did not create the pointer
65/// must treat it is opaque.
66struct BoxedConnection(Box<dyn SeaOrmConnection + Send + Sync>);
67
68/// A pointer to `dyn SeaOrmConnection`.
69///
70/// This pointer must be treated as opaque on the side of the FFI boundary that did not
71/// create the pointer.
72#[repr(transparent)]
73struct ConnectionPtr(*mut c_void);
74
75impl ConnectionPtr {
76	fn new(inner: Box<dyn SeaOrmConnection + Send + Sync>) -> Self {
77		Self(Box::into_raw(Box::new(BoxedConnection(inner))) as _)
78	}
79
80	/// Access the data stored in this pointer
81	#[doc = doc_safety!()]
82	unsafe fn get(&self) -> &(dyn SeaOrmConnection + Send + Sync) {
83		let ptr: *mut BoxedConnection = self.0.cast();
84		&*(&*ptr).0
85	}
86
87	/// Return the inner type this pointer points to.
88	#[doc = doc_safety!()]
89	#[allow(clippy::wrong_self_convention)] // well, `Drop` only gives `&mut Self`
90	unsafe fn into_inner(&mut self) -> Box<dyn SeaOrmConnection + Send + Sync> {
91		let ptr: *mut BoxedConnection = self.0.cast();
92		(*Box::from_raw(ptr)).0
93	}
94}
95
96/// The FFI callback type that is called when an [`FfiConnection`] is [drop]ped.
97type DropConnection = extern "C" fn(&mut ConnectionPtr);
98
99/// Drop the connection that this pointer points to.
100#[doc = doc_safety!()]
101extern "C" fn drop_connection(ptr: &mut ConnectionPtr) {
102	drop(unsafe { ptr.into_inner() });
103}
104
105/// The FFI callback type that is called when
106/// [`begin()`](FfiConnection::begin)
107/// is called on [`FfiConnection`].
108type BeginTransaction =
109	extern "C" fn(
110		&ConnectionPtr
111	) -> BorrowingFfiFuture<'_, FfiResult<FfiTransaction, FfiDbErr>>;
112
113/// FFI callback for [`begin()`](FfiConnection::begin).
114#[doc = doc_safety!()]
115extern "C" fn begin_transaction(
116	ptr: &ConnectionPtr
117) -> BorrowingFfiFuture<'_, FfiResult<FfiTransaction, FfiDbErr>> {
118	let conn = unsafe { ptr.get() };
119	Compat::new(async move {
120		conn.begin()
121			.await
122			.map(|transaction| FfiTransaction::new(Box::new(transaction)))
123			.map_err(Into::into)
124			.into()
125	})
126	.into_ffi()
127}
128
129/// The FFI callback type that is called when
130/// [`get_database_backend()`](ConnectionTrait::get_database_backend)
131/// is called on [`FfiConnection`].
132type GetDatabaseBackend<Ptr> = extern "C" fn(&Ptr) -> FfiBackend;
133
134/// FFI callback for [`get_database_backend()`](ConnectionTrait::get_database_backend).
135#[doc = doc_safety!()]
136extern "C" fn get_database_backend(ptr: &ConnectionPtr) -> FfiBackend {
137	unsafe { ptr.get() }.get_database_backend().into()
138}
139
140/// The FFI callback type that is called when
141/// [`execute()`](ConnectionTrait::execute)
142/// is called on [`FfiConnection`].
143type Execute<Ptr> =
144	extern "C" fn(
145		&Ptr,
146		FfiStatement
147	) -> BorrowingFfiFuture<'_, FfiResult<FfiExecResult, FfiDbErr>>;
148
149/// FFI callback implementation for [`query_one()`](ConnectionTrait::query_one).
150pub(crate) fn execute_impl<C>(
151	conn: &C,
152	stmt: FfiStatement
153) -> BorrowingFfiFuture<'_, FfiResult<FfiExecResult, FfiDbErr>>
154where
155	C: ConnectionTrait + Send + Sync + ?Sized
156{
157	Compat::new(async move {
158		conn.execute(stmt.into())
159			.await
160			.map(|exec_result| {
161				FfiExecResult::new(conn.get_database_backend(), exec_result)
162			})
163			.map_err(Into::into)
164			.into()
165	})
166	.into_ffi()
167}
168
169/// FFI callback for [`execute()`](ConnectionTrait::execute).
170#[doc = doc_safety!()]
171extern "C" fn execute(
172	ptr: &ConnectionPtr,
173	stmt: FfiStatement
174) -> BorrowingFfiFuture<'_, FfiResult<FfiExecResult, FfiDbErr>> {
175	execute_impl(unsafe { ptr.get() }, stmt)
176}
177
178/// The FFI callback type that is called when
179/// [`execute()`](ConnectionTrait::execute)
180/// is called on [`FfiConnection`].
181type ExecuteUnprepared<Ptr> =
182	extern "C" fn(
183		&Ptr,
184		StringPtr
185	) -> BorrowingFfiFuture<'_, FfiResult<FfiExecResult, FfiDbErr>>;
186
187/// FFI callback implementation for [`execute_unprepared()`](ConnectionTrait::execute_unprepared).
188pub(crate) fn execute_unprepared_impl<C>(
189	conn: &C,
190	sql: StringPtr
191) -> BorrowingFfiFuture<'_, FfiResult<FfiExecResult, FfiDbErr>>
192where
193	C: ConnectionTrait + Send + Sync + ?Sized
194{
195	Compat::new(async move {
196		conn.execute_unprepared(sql.as_str())
197			.await
198			.map(|exec_result| {
199				FfiExecResult::new(conn.get_database_backend(), exec_result)
200			})
201			.map_err(Into::into)
202			.into()
203	})
204	.into_ffi()
205}
206
207/// FFI callback for [`execute_unprepared()`](ConnectionTrait::execute_unprepared).
208#[doc = doc_safety!()]
209extern "C" fn execute_unprepared(
210	ptr: &ConnectionPtr,
211	sql: StringPtr
212) -> BorrowingFfiFuture<'_, FfiResult<FfiExecResult, FfiDbErr>> {
213	execute_unprepared_impl(unsafe { ptr.get() }, sql)
214}
215
216/// The FFI callback type that is called when
217/// [`query_one()`](ConnectionTrait::query_one)
218/// is called on [`FfiConnection`].
219type QueryOne<Ptr> =
220	extern "C" fn(
221		&Ptr,
222		FfiStatement
223	) -> BorrowingFfiFuture<'_, FfiResult<FfiOptionalProxyRow, FfiDbErr>>;
224
225/// FFI callback implementation for [`query_one()`](ConnectionTrait::query_one).
226pub(crate) fn query_one_impl<C>(
227	conn: &C,
228	stmt: FfiStatement
229) -> BorrowingFfiFuture<'_, FfiResult<FfiOptionalProxyRow, FfiDbErr>>
230where
231	C: ConnectionTrait + Send + Sync + ?Sized
232{
233	Compat::new(async move {
234		conn.query_one(stmt.into())
235			.await
236			.map(Into::into)
237			.map_err(Into::into)
238			.into()
239	})
240	.into_ffi()
241}
242
243/// FFI callback for [`query_one()`](ConnectionTrait::query_one).
244#[doc = doc_safety!()]
245extern "C" fn query_one(
246	ptr: &ConnectionPtr,
247	stmt: FfiStatement
248) -> BorrowingFfiFuture<'_, FfiResult<FfiOptionalProxyRow, FfiDbErr>> {
249	query_one_impl(unsafe { ptr.get() }, stmt)
250}
251
252/// The FFI callback type that is called when
253/// [`query_all()`](ConnectionTrait::query_all)
254/// is called on [`FfiConnection`].
255type QueryAll<Ptr> =
256	extern "C" fn(
257		&Ptr,
258		FfiStatement
259	) -> BorrowingFfiFuture<'_, FfiResult<VecPtr<FfiProxyRow>, FfiDbErr>>;
260
261/// FFI callback implementation for [`query_all()`](ConnectionTrait::query_all).
262pub(crate) fn query_all_impl<C>(
263	conn: &C,
264	stmt: FfiStatement
265) -> BorrowingFfiFuture<'_, FfiResult<VecPtr<FfiProxyRow>, FfiDbErr>>
266where
267	C: ConnectionTrait + Send + Sync + ?Sized
268{
269	Compat::new(async move {
270		conn.query_all(stmt.into())
271			.await
272			.map(|rows| {
273				rows.into_iter()
274					.map(Into::into)
275					.collect::<Vec<FfiProxyRow>>()
276					.into()
277			})
278			.map_err(Into::into)
279			.into()
280	})
281	.into_ffi()
282}
283
284/// FFI callback for [`query_all()`](ConnectionTrait::query_all).
285#[doc = doc_safety!()]
286extern "C" fn query_all(
287	ptr: &ConnectionPtr,
288	stmt: FfiStatement
289) -> BorrowingFfiFuture<'_, FfiResult<VecPtr<FfiProxyRow>, FfiDbErr>> {
290	query_all_impl(unsafe { ptr.get() }, stmt)
291}
292
293/// The FFI callback type that is called when
294/// [`is_mock_connection()`](ConnectionTrait::is_mock_connection)
295/// is called on [`FfiConnection`].
296type IsMockConnection<Ptr> = extern "C" fn(&Ptr) -> bool;
297
298/// FFI callback for [`is_mock_connection()`](ConnectionTrait::is_mock_connection).
299#[doc = doc_safety!()]
300extern "C" fn is_mock_connection(ptr: &ConnectionPtr) -> bool {
301	unsafe { ptr.get() }.is_mock_connection()
302}
303
304#[repr(C)]
305pub(crate) struct ConnectionVTable<Ptr> {
306	pub(crate) backend: GetDatabaseBackend<Ptr>,
307	pub(crate) execute: Execute<Ptr>,
308	pub(crate) execute_unprepared: ExecuteUnprepared<Ptr>,
309	pub(crate) query_one: QueryOne<Ptr>,
310	pub(crate) query_all: QueryAll<Ptr>,
311	pub(crate) is_mock_connection: IsMockConnection<Ptr>
312}
313
314/// An FFI-safe implementation of [`sea-orm`'s](sea_orm) [`ConnectionTrait`].
315///
316/// See the [crate level documentation](crate) for more details.
317#[repr(C)]
318pub struct FfiConnection {
319	ptr: ConnectionPtr,
320	drop: DropConnection,
321	begin: BeginTransaction,
322	vtable: ConnectionVTable<ConnectionPtr>
323}
324
325impl FfiConnection {
326	pub fn new(inner: Box<dyn SeaOrmConnection + Send + Sync>) -> Self {
327		Self {
328			ptr: ConnectionPtr::new(inner),
329			drop: drop_connection,
330			begin: begin_transaction,
331			vtable: ConnectionVTable {
332				backend: get_database_backend,
333				execute,
334				execute_unprepared,
335				query_one,
336				query_all,
337				is_mock_connection
338			}
339		}
340	}
341
342	pub fn begin(
343		&self
344	) -> impl Future<Output = Result<FfiTransaction, DbErr>> + Send + '_ {
345		(self.begin)(&self.ptr).map(|res| res.into_result().map_err(Into::into))
346	}
347}
348
349impl Drop for FfiConnection {
350	fn drop(&mut self) {
351		(self.drop)(&mut self.ptr)
352	}
353}
354
355// Safety: The Box'ed `dyn Connection` has `+ Send` and `+ Sync` bounds,
356// and the other types are just extern "C" fn pointers.
357unsafe impl Send for ConnectionPtr {}
358unsafe impl Sync for ConnectionPtr {}
359unsafe impl<Ptr> Send for ConnectionVTable<Ptr> {}
360unsafe impl<Ptr> Sync for ConnectionVTable<Ptr> {}
361unsafe impl Send for FfiConnection {}
362unsafe impl Sync for FfiConnection {}
363
364impl<Ptr> ConnectionVTable<Ptr> {
365	pub(crate) fn get_database_backend(&self, ptr: &Ptr) -> DatabaseBackend {
366		(self.backend)(ptr).into()
367	}
368
369	pub(crate) async fn execute(
370		&self,
371		ptr: &Ptr,
372		stmt: Statement
373	) -> Result<ExecResult, DbErr> {
374		(self.execute)(ptr, stmt.into())
375			.await
376			.map(Into::into)
377			.map_err(Into::into)
378			.into()
379	}
380
381	pub(crate) async fn execute_unprepared(
382		&self,
383		ptr: &Ptr,
384		sql: &str
385	) -> Result<ExecResult, DbErr> {
386		// TODO we could introduce a StrPtr and not allocate here
387		//      but I assume the number of calls to unprepared statements is quite low
388		let sql: String = sql.into();
389
390		(self.execute_unprepared)(ptr, sql.into())
391			.await
392			.map(Into::into)
393			.map_err(Into::into)
394			.into()
395	}
396
397	pub(crate) async fn query_one(
398		&self,
399		ptr: &Ptr,
400		stmt: Statement
401	) -> Result<Option<QueryResult>, DbErr> {
402		(self.query_one)(ptr, stmt.into())
403			.await
404			.map(Into::into)
405			.map_err(Into::into)
406			.into()
407	}
408
409	pub(crate) async fn query_all(
410		&self,
411		ptr: &Ptr,
412		stmt: Statement
413	) -> Result<Vec<QueryResult>, DbErr> {
414		(self.query_all)(ptr, stmt.into())
415			.await
416			.map(|rows| rows.into_vec().into_iter().map(Into::into).collect())
417			.map_err(Into::into)
418			.into()
419	}
420
421	pub(crate) fn is_mock_connection(&self, ptr: &Ptr) -> bool {
422		(self.is_mock_connection)(ptr)
423	}
424}
425
426#[async_trait]
427impl ConnectionTrait for FfiConnection {
428	fn get_database_backend(&self) -> DatabaseBackend {
429		self.vtable.get_database_backend(&self.ptr)
430	}
431
432	async fn execute(&self, stmt: Statement) -> Result<ExecResult, DbErr> {
433		self.vtable.execute(&self.ptr, stmt).await
434	}
435
436	async fn execute_unprepared(&self, sql: &str) -> Result<ExecResult, DbErr> {
437		self.vtable.execute_unprepared(&self.ptr, sql).await
438	}
439
440	async fn query_one(&self, stmt: Statement) -> Result<Option<QueryResult>, DbErr> {
441		self.vtable.query_one(&self.ptr, stmt).await
442	}
443
444	async fn query_all(&self, stmt: Statement) -> Result<Vec<QueryResult>, DbErr> {
445		self.vtable.query_all(&self.ptr, stmt).await
446	}
447
448	fn is_mock_connection(&self) -> bool {
449		self.vtable.is_mock_connection(&self.ptr)
450	}
451}