1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
//! Op → SQL translation: the [`Reducer`] trait a data domain implements to turn
//! each of its ops into the backend writes that materialize it.
//!
//! Kept as its own module so the [ingestion processor](crate::processor) can
//! drive any reducer generically. See [`Reducer`] for the three-phase
//! prepare/apply/post_apply contract that lets one op map onto every backend.
use Timestamp;
use crate;
/// 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()`):
///
/// 1. [`prepare`](Reducer::prepare) runs *before* the batch. It is the only
/// phase allowed to read or issue DDL, and it returns the
/// [`ReadState`](Reducer::ReadState) `apply` needs — so every read is hoisted
/// out of the batch.
/// 2. [`apply`](Reducer::apply) emits the op's mutation statements into the open
/// batch. It is pure and read-free (it consumes the `ReadState`), so the
/// batch stays a flat, declarative statement list.
/// 3. [`post_apply`](Reducer::post_apply) runs *after* the batch commits, when
/// `RETURNING` rows 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`](crate::tracker::LogTracker) it is paired with. Behind a
/// tracker that rejects a repeated `(peer_id, entry_idx)` (e.g.
/// [`LogIndexTracker`](crate::tracker::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](crate::tracker::LogTracker#duplicate-rejection).