sea-orm-ffi 0.1.3

Compatibility layer for Sea-ORM when crossing a Rust-to-Rust FFI boundary
Documentation
//! This crate provides a compatibility layer for [Sea-ORM][sea_orm] when crossing a
//! Rust-to-Rust FFI boundary.
//!
//! # Example
//!
//! On the host side, you first create your database connection, which we can then
//! convert into an [`FfiConnection`]:
//!
//! ```rust
//! use async_ffi::BorrowingFfiFuture;
//! use sea_orm::DatabaseConnection;
//! use sea_orm_ffi::FfiConnection;
//! # use std::sync::Arc;
//! # async fn async_main() {
//!
//! // See Sea-ORM's documentation on how to obtain a database connection
//! let conn: DatabaseConnection = todo!();
//!
//! // Create FFI-safe connection wrapper.
//! // While this can be used on the host side as well, it is recommended you avoid this
//! // as it will likely be less performant.
//! // Therefore, you might want to wrap `conn` in an `Arc` to share it with the host.
//! let ffi_conn = FfiConnection::new(Box::new(conn));
//!
//! // Obtain the plugin function that needs the database connection via libloading or similar
//! # type Symbol<T> = T;
//! let plugin_function: Symbol<
//! 	extern "C" fn(&FfiConnection) -> BorrowingFfiFuture<'_, ()>
//! > = todo!();
//!
//! // Call the plugin function
//! plugin_function(&ffi_conn).await;
//! # }
//! ```
//!
//! On the plugin side, you can treat [`FfiConnection`] like any other database connection:
//!
//! ```rust
//! mod comment {
//! 	use sea_orm::entity::prelude::*;
//!
//! 	#[derive(Clone, Debug, DeriveEntityModel)]
//! 	#[sea_orm(table_name = "comment")]
//! 	pub struct Model {
//! 		#[sea_orm(primary_key)]
//! 		pub id: i64,
//! 		pub author: String,
//! 		pub comment: String
//! 	}
//!
//! 	#[derive(Debug, DeriveRelation, EnumIter)]
//! 	pub enum Relation {}
//!
//! 	impl ActiveModelBehavior for ActiveModel {}
//! }
//! use comment::Entity as Comment;
//!
//! use async_ffi::{BorrowingFfiFuture, FutureExt as _};
//! use sea_orm::EntityTrait as _;
//! use sea_orm_ffi::FfiConnection;
//!
//! async fn print_comments(conn: &FfiConnection) {
//! 	let Ok(comments) = Comment::find().all(conn).await else {
//! 		eprintln!("Failed to load comments");
//! 		return;
//! 	};
//! 	for comment in comments {
//! 		println!("{comment:?}");
//! 	}
//! }
//!
//! #[no_mangle]
//! extern "C" fn plugin_function(conn: &FfiConnection) -> BorrowingFfiFuture<'_, ()> {
//! 	print_comments(conn).into_ffi()
//! }
//! ```
//!
//! # How it works
//!
//! Sea-ORM relies on two main traits for its database connection: [`ConnectionTrait`]
//! and [`TransactionTrait`]. The former one is really nice because it is _`dyn`
//! compatible_. This is also sometimes called _object safe_.
//!
//! What that means is that we build pointers to the trait without knowing the specific
//! type. For example, we can write `&dyn ConnectionTrait` or `Box<dyn ConnectionTrait>`.
//! In order to pass things via an FFI boundary, we need a type with a stable ABI. ABI
//! stands for Application Binary Interface, and unfortunately Rust's default ABI is not
//! guaranteed to be stable. We must therefore use another, stable ABI. Otherwise, it
//! would be inherently unsafe to load plugins compiled with a different Rust compiler
//! than the plugin host. Luckily, we can use a pointer to `Box<dyn ConnectionTrait>` to
//! pass it through an FFI boundary.
//!
//! The only problem is that that pointer is not ABI stable, so the plugin must treat
//! this pointer as _opaque_, and only pass its value back to the plugin host. We
//! therefore create C-ABI functions in the plugin host that call the functions from
//! [`ConnectionTrait`] and pass those function pointers to the plugin alongside the
//! connection pointer. The plugin can then pass the connection pointer to these function
//! pointers to call the various functions.
//!
//! But what about function arguments? And return types? Good question!
//!
//! The most complicated type here is definitely [`Future`]. Luckily, the problem of
//! using async code with Rust-to-Rust FFI has been solved by [`async-ffi`]. Well,
//! almost. Because [`tokio`] uses some thread-local storage unavailable to plugins
//! loaded with [`libloading`], any attempt to use tokio-specific futures will panic.
//! The only runtimes supported by [`sea-orm`]/[`sqlx`] in the current release are
//! [`async-std`], which is deprecated, and [`tokio`]. We therefore rely on
//! [`async-compat`] to inject a tokio runtime, regardless of the executor that polls
//! the futures. In the future, when [`sqlx`] is released with support for [`smol`],
//! this can and will be removed.
//!
//! The other types are fairly easy to bridge accross the FFI boundary. But, since none
//! of them are ABI stable by themselves, we have to convert them to and from ABI stable
//! types first. This is made possible by the `proxy` feature of [`sea-orm`] that allows
//! constructing our own database responses. We cannot use opaque pointers here as those
//! types are designed to be created on one side and read on the other side of the FFI
//! boundary.
//!
//! But what about the [`TransactionTrait`]? Well, this one is unfortunately not _`dyn`
//! compatible_, which is the unfortunate side effect of it having a function that
//! accepts generic parameters. However, we can still bridge the functions that do not
//! take the generic parameters - namely the [`begin()`][sea_orm::TransactionTrait::begin]
//! function. This should be sufficient to provide transaction support - but not via
//! the [`TransactionTrait`]. While it would be possible to write the
//! [`transaction()`][sea_orm::TransactionTrait::transaction] method on the plugin side,
//! there is no way to create a [`DatabaseTransaction`] from any ABI stable type. Instead,
//! the [`FfiConnection::begin()`] function returns a [`FfiTransaction`], which again
//! implements [`ConnectionTrait`], so you can use it on the plugin side just like you
//! would use [`DatabaseTransaction`] on the host side.
//!
//! # Features
//!
//! [Sea-ORM][sea_orm] makes heavy usage of features, including for the [`Value`] enum
//! which is one of the types bridged by this crate. Therefore, all features that enable
//! types must not only be enable for [`sea-orm`] but also for this crate. At the time
//! of writing, [`sea-orm`] supports more types than we do. Feel free to open an [issue]
//! or [pull request] if you are missing a feature.
//!
//! Note that there will not be a compile error when there is a feature mismatch. Instead,
//! any attempt to use types unsupported by this crate will result in a panic at runtime.
//!
//! ## Migrations
//!
//! Additionally, we have a `refinery` feature that enables support for migrations using
//! the [`refinery`] crate. When this feature is enabled, [`FfiConnection`] implements
//! [`AsyncMigrate`] so you can call
//! [`Runner::run_async()`](refinery_core::Runner::run_async) with the connection.
//!
//! # Changelog
//!
//! Please see [releases] for a list of changes per release.
//!
//!
//!  [`async-compat`]: async_compat
//!  [`async-ffi`]: async_ffi
//!  [`async-std`]: https://crates.io/crates/async-std
//!  [`libloading`]: https://crates.io/crates/libloading
//!  [`refinery`]: https://crates.io/crates/refinery
//!  [`sea-orm`]: sea_orm
//!  [`smol`]: https://crates.io/crates/smol
//!  [`sqlx`]: sea_orm::sqlx
//!  [`tokio`]: https://crates.io/crates/tokio
//!
//!  [`AsyncMigrate`]: refinery_core::AsyncMigrate
//!  [`ConnectionTrait`]: sea_orm::ConnectionTrait
//!  [`DatabaseTransaction`]: sea_orm::DatabaseTransaction
//!  [`Future`]: std::future::Future
//!  [`TransactionTrait`]: sea_orm::TransactionTrait
//!  [`Value`]: sea_orm::Value
//!
//!  [issue]: https://codeberg.org/proto-x/sea-orm-ffi/issues
//!  [pull request]: https://codeberg.org/proto-x/sea-orm-ffi/pulls
//!  [releases]: https://codeberg.org/proto-x/sea-orm-ffi/releases

mod backend;
mod connection;
mod db_err;
mod exec_result;
#[cfg(feature = "refinery")]
mod migrate;
mod option;
mod proxy_row;
mod result;
mod statement;
mod string;
mod time;
mod transaction;
mod value;
mod vec;

pub use self::{connection::FfiConnection, transaction::FfiTransaction};