Connection Types and Traits
The type of the <connection> argument for generated functions is generic, requiring either the AtomicOperation or IntoOneTimeExecutor trait to be implemented on the type.
There is a blanket implementation that makes every AtomicOperation implement IntoOneTimeExecutor - but the reverse is not the case.
AtomicOperation
The AtomicOperation trait represents a transactional operation that can execute multiple database operations atomically with consistent snapshots of the data.
pub trait AtomicOperation: Send {
/// Function for querying when the operation is taking place - if it is cached.
fn maybe_now(&self) -> Option<chrono::DateTime<chrono::Utc>> {
None
}
/// Returns the underlying sqlx::Executor implementation.
fn as_executor(&mut self) -> &mut sqlx::PgConnection;
/// Registers a commit hook that will run pre_commit before and post_commit after
/// the transaction commits. Returns Ok(()) if the hook was registered,
/// Err(hook) if hooks are not supported.
fn add_commit_hook<H: CommitHook>(&mut self, hook: H) -> Result<(), H> {
Err(hook)
}
}
Implementations of AtomicOperation:
&mut sqlx::Transaction<'_, Postgres>&mut DbOp<'_>&mut DbOpWithTime<'_>&mut OpWithTime<'_, Op>(whereOp: AtomicOperation)HookOperation<'_>(used internally by hooks)
IntoOneTimeExecutor
The IntoOneTimeExecutor trait ensures in a typesafe way that only 1 database operation can occur by consuming the inner reference.
Implementations of IntoOneTimeExecutor:
&PgPool- checks out a new connection for each operation- Any type implementing
AtomicOperation- guarantees consistency across multiple operations
async fn find_by_id_in_op<'a, OP>(op: OP, id: EntityId)
where
OP: IntoOneTimeExecutor<'a>;
async fn create_in_op<OP>(op: &mut OP, new_entity: NewEntity)
where
OP: AtomicOperation;
Both traits wrap access to an sqlx::Executor implementation that ultimately executes the query.
Method Variants
All CRUD fns that es-entity generates come in 2 variants:
async fn create(new_entity: NewEntity)
async fn create_in_op(<connection>, new_entity: NewEntity)
async fn update(entity: &mut Entity)
async fn update_in_op(<connection>, entity: &mut Entity)
async fn find_by_id(id: EntityId)
async fn find_by_id_in_op(<connection>, id: EntityId)
etc
In all cases the _in_op variant accepts a first argument that represents the connection to the database.
The non-_in_op variant simply wraps the _in_op call by passing an appropriate connection argument internally.
Operation Requirements
In es-entity mutating fns generally require 2 roundtrips to update the index table and append to the events table.
Hence create_in_op, update_in_op and delete_in_op all require &mut impl AtomicOperation first arguments.
Most queries on the other hand are executed with 1 round trip (to fetch the events) and thus accept impl IntoOneTimeExecutor<'_> first arguments.
Exceptions to this are for nested entities which will be explained in a later section.