pub trait Reducer: Send {
type Op: Send + Sync;
type ReadState;
type ApplyState: Send;
type Event: Send + Clone;
type Error;
// Required methods
fn prepare<'life0, 'life1, 'life2, 'async_trait>(
&'life0 mut self,
db: &'life1 dyn Db,
op: &'life2 Self::Op,
) -> Pin<Box<dyn Future<Output = Result<Self::ReadState, Self::Error>> + Send + 'async_trait>>
where Self: 'async_trait,
'life0: 'async_trait,
'life1: 'async_trait,
'life2: 'async_trait;
fn apply(
&self,
batch: &mut dyn DbBatch,
timestamp: Timestamp,
op: &Self::Op,
read: Self::ReadState,
) -> Result<Self::ApplyState, Self::Error>;
fn post_apply(
&self,
apply_state: Self::ApplyState,
batch_result: &[DbStatementResult],
) -> Result<Vec<Self::Event>, Self::Error>;
}Expand description
Translates a single op into the SQL writes that materialize it, in three
phases so the work maps onto every backend — including ones with no
interactive transaction (e.g. D1’s batch()):
prepareruns before the batch. It is the only phase allowed to read or issue DDL, and it returns theReadStateapplyneeds — so every read is hoisted out of the batch.applyemits the op’s mutation statements into the open batch. It is pure and read-free (it consumes theReadState), so the batch stays a flat, declarative statement list.post_applyruns after the batch commits, whenRETURNINGrows finally exist, and turns the results into zero or more events — empty when the op changed nothing observable.
§Idempotency
Whether a reducer must apply idempotently is not its own choice — it depends
on the LogTracker it is paired with. Behind a
tracker that rejects a repeated (peer_id, entry_idx) (e.g.
LogIndexTracker) a redundant apply rolls
back, so the reducer need not be idempotent; behind one that does not reject,
it must be. See the tracker’s
duplicate-rejection contract.
Required Associated Types§
Sourcetype ApplyState: Send
type ApplyState: Send
Carried from apply to
post_apply: the StmtIds of the emitted
statements plus any op-derived data needed to build the event.
Required Methods§
Sourcefn prepare<'life0, 'life1, 'life2, 'async_trait>(
&'life0 mut self,
db: &'life1 dyn Db,
op: &'life2 Self::Op,
) -> Pin<Box<dyn Future<Output = Result<Self::ReadState, Self::Error>> + Send + 'async_trait>>where
Self: 'async_trait,
'life0: 'async_trait,
'life1: 'async_trait,
'life2: 'async_trait,
fn prepare<'life0, 'life1, 'life2, 'async_trait>(
&'life0 mut self,
db: &'life1 dyn Db,
op: &'life2 Self::Op,
) -> Pin<Box<dyn Future<Output = Result<Self::ReadState, Self::Error>> + Send + 'async_trait>>where
Self: 'async_trait,
'life0: 'async_trait,
'life1: 'async_trait,
'life2: 'async_trait,
Reconcile the schema needed by op (create/alter tables, refresh any
cache) and read whatever apply will need, returning it as a
ReadState. Runs outside the batch — DDL is
additive and safe to commit on its own, and hoisting reads here is what
keeps apply pure.
Sourcefn apply(
&self,
batch: &mut dyn DbBatch,
timestamp: Timestamp,
op: &Self::Op,
read: Self::ReadState,
) -> Result<Self::ApplyState, Self::Error>
fn apply( &self, batch: &mut dyn DbBatch, timestamp: Timestamp, op: &Self::Op, read: Self::ReadState, ) -> Result<Self::ApplyState, Self::Error>
Emit the statements that materialize op at timestamp into batch,
using only op, the cached schema, and read. Read-free, so it stays
expressible as a declarative batch. The returned
ApplyState is provisional until batch commits.
Sourcefn post_apply(
&self,
apply_state: Self::ApplyState,
batch_result: &[DbStatementResult],
) -> Result<Vec<Self::Event>, Self::Error>
fn post_apply( &self, apply_state: Self::ApplyState, batch_result: &[DbStatementResult], ) -> Result<Vec<Self::Event>, Self::Error>
Build the events once the batch has committed — empty when the op changed
nothing observable (e.g. an upsert that lost every column’s LWW, or a
write to a table with no observer-facing name), usually one, or several
when a single op has multiple logical effects. batch_result holds the
whole batch’s per-statement results in add order; locate this op’s
RETURNING rows via the StmtIds stored in apply_state.
Dyn Compatibility§
This trait is dyn compatible.
In older versions of Rust, dyn compatibility was called "object safety".