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
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
//! Database / transaction triggers.
//!
//! Port of JE `com.sleepycat.je.trigger.Trigger` + `TransactionTrigger`.
//!
//! A `Trigger` is a user-supplied callback object registered on a database via
//! [`crate::DatabaseConfig`]. The engine fires its methods on data changes
//! (`put` / `delete`) and on transaction resolution (`commit` / `abort`).
//!
//! # JE mapping (faithful)
//!
//! JE splits the contract across two Java interfaces that a single trigger
//! object may both implement:
//!
//! * `com.sleepycat.je.trigger.Trigger` — `getName`, lifecycle
//! (`addTrigger` / `removeTrigger`) and the record operations
//! `put(txn, key, oldData, newData)` / `delete(txn, key, oldData)`.
//! * `com.sleepycat.je.trigger.TransactionTrigger` — `commit(txn)` /
//! `abort(txn)`, invoked from `Txn.commit` / `Txn.abort` for every database
//! that was modified within the transaction (`TriggerManager.runCommitTriggers`
//! / `runAbortTriggers`).
//!
//! JE dispatches to `TransactionTrigger` via `instanceof` (a trigger that does
//! not implement it simply has no commit/abort behaviour). The Rust idiom is a
//! single `Trigger` trait whose `commit` / `abort` methods default to no-ops:
//! a trigger that only cares about record operations leaves them unimplemented,
//! exactly mirroring "does not implement `TransactionTrigger`". This avoids a
//! second trait object and the downcast dance while preserving the JE
//! semantics.
//!
//! # Transaction argument
//!
//! JE passes the public `Transaction` handle. Noxu passes the transaction id
//! (`Option<u64>`; `None` when the operation is non-transactional /
//! auto-commit) instead. The trait lives in `noxu-dbi`, below `noxu-db` in the
//! dependency graph, so it cannot name `noxu_db::Transaction`; the id is the
//! faithful, dependency-clean signal of "which transaction this fired under"
//! and matches JE's `Transaction.getId()`.
//!
//! # Firing semantics (faithful to JE)
//!
//! * `put` / `delete` fire **within** the transaction, **after** the record
//! modification has been applied — JE `Cursor.putNotify` /
//! `Cursor.deleteInternal` call `TriggerManager.runPutTriggers` /
//! `runDeleteTriggers` after the actual tree mutation. A trigger therefore
//! observes the change and can make accompanying changes under the same
//! transaction; on abort those changes are rolled back with the transaction.
//! * `commit` / `abort` fire on the transaction's resolution, once per
//! modified database, in trigger registration order (JE iterates
//! `dbImpl.getTriggers()` in list order).
//! * Multiple triggers fire in **registration order** (JE stores them in a
//! `List<Trigger>` and iterates it).
//!
//! # Persistence / replication adaptation (diverges from JE — documented)
//!
//! JE's `PersistentTrigger` serializes the trigger's *class name* into the
//! database record and re-instantiates the trigger by name on open. A Rust
//! closure / trait object has no portable, reconstructable name, so — exactly
//! as the DBI-14 comparator API does — Noxu triggers are **runtime-registered
//! only**: they are *not* persisted and *not* replicated. Applications must
//! re-register triggers on every [`crate::DatabaseConfig`] open. This matches
//! JE's own current state: the `Trigger.java` Javadoc warns that "Only
//! transient triggers are currently supported" and that triggers "must be
//! configured on each node in a rep group separately".
/// A user-supplied database / transaction trigger.
///
/// Register one or more triggers on a [`crate::DatabaseConfig`]; the engine
/// fires the record-operation methods ([`put`](Trigger::put) /
/// [`delete`](Trigger::delete)) within the transaction after each change, and
/// the transaction-lifecycle methods ([`commit`](Trigger::commit) /
/// [`abort`](Trigger::abort)) when the transaction resolves.
///
/// JE `com.sleepycat.je.trigger.Trigger` + `TransactionTrigger`.