pub trait SavepointOperation: AtomicOperation {
// Provided methods
fn with_savepoint<T, E, F>(
&mut self,
f: F,
) -> impl Future<Output = Result<Result<T, E>, Error>>
where F: AsyncFnOnce(&mut SavepointOp<'_>) -> Result<T, E> { ... }
fn begin_savepoint(
&mut self,
) -> impl Future<Output = Result<SavepointOp<'_>, Error>> + Send { ... }
}Expand description
Savepoints for every AtomicOperation, derived rather than hand-written.
This trait has a blanket implementation and no methods to implement: an
operation earns savepoints by implementing
AtomicOperation::savepoint_parts, and a wrapper type that implements
the delegate_atomic_operation! macro
generates even that. So
DbOp, DbOpWithTime, SavepointOp (nesting), HookOperation, a bare
sqlx::Transaction, any OpWithTime wrapper, and operation types
defined outside this crate all share one implementation of the pair.
The other half of the point is reachability: because these are trait methods
rather than inherent ones, a function generic over impl AtomicOperation can
take a savepoint. Inherent methods are invisible behind a generic bound,
which is why code wanting savepoints previously had to name a concrete
operation type in its signature.
use es_entity::{AtomicOperation, SavepointOperation};
// Generic over the operation: callers can pass a DbOp, a SavepointOp
// (nesting one level deeper), or a HookOperation from inside a pre_commit.
async fn process_all(
op: &mut impl AtomicOperation,
items: &[Item],
) -> Result<(), sqlx::Error> {
for item in items {
let _ = op.with_savepoint(async |sp| process_one(sp, item).await).await?;
}
Ok(())
}Provided Methods§
Sourcefn with_savepoint<T, E, F>(
&mut self,
f: F,
) -> impl Future<Output = Result<Result<T, E>, Error>>
fn with_savepoint<T, E, F>( &mut self, f: F, ) -> impl Future<Output = Result<Result<T, E>, Error>>
Runs f inside a SAVEPOINT, keeping its work on Ok and undoing it on
Err — see DbOp::with_savepoint for the
full contract, including the two layers of Result.
The returned future is deliberately not declared Send. Doing so
would require proving f’s future Send for every SavepointOp<'_>
lifetime, which needs the unstable async_fn_traits and still fails to
unify under a higher-ranked bound. Auto-trait inference at each call site
handles it instead, so this composes normally inside Send futures —
with the same caveat the inherent form already had: a closure capturing
&self may need to capture an owned clone instead.
Sourcefn begin_savepoint(
&mut self,
) -> impl Future<Output = Result<SavepointOp<'_>, Error>> + Send
fn begin_savepoint( &mut self, ) -> impl Future<Output = Result<SavepointOp<'_>, Error>> + Send
Begins a SAVEPOINT scope explicitly — see
DbOp::begin_savepoint. Must be finished
with release or
rollback; dropping it rolls back.
§Incoherent hook capability
Fails with a protocol error if the operation reports
supports_hooks but hands back a slot
that cannot receive hooks. The two can only disagree one way: an
implementor forwarded supports_hooks to an operation it wraps but
inherited the default
savepoint_parts, which can only
report “no hook buffer”.
Left unchecked that is silent: hooks registered inside the savepoint
would be refused, callers would fall back to
force_execute_pre_commit,
and post_commit/on_rollback would stop running for an operation whose
wrapped op supports them perfectly well. Reporting it turns a missing
method override into a loud failure the first time a savepoint is taken,
rather than a behaviour change nobody notices.
Dyn Compatibility§
This trait is not dyn compatible.
In older versions of Rust, dyn compatibility was called "object safety".