Skip to main content

SavepointOperation

Trait SavepointOperation 

Source
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 WrapsOperation gets even that by delegation. 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§

Source

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>,

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.

Source

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".

Implementors§