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
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
//! The machinery `#[derive(Inbox)]` generates against: the statements a subscription prepared,
//! how a default event binds and runs one, and the hidden contract each row implements.
//!
//! Nothing here is named by a service; the derive and the broker are its only callers.
use std::fmt::Debug;
use std::future::Future;
use std::time::Duration;
use ruststream::HeaderMap;
use ruststream::codec::CodecError;
use ruststream_sqlx_dialect::{Param, TableSpec};
use sqlx::{Error, FromRow};
use super::QueueRow;
use super::database::QueueDatabase;
use super::form::advisory::events::Candidates;
use super::headers::HeaderCell;
use super::named::kinds::Kinds;
use super::queue::Queue;
mod claim;
mod context;
mod helpers;
mod settle;
mod statements;
mod values;
pub(crate) use claim::{
claim_ids, claim_rows, fetch_by_ids, match_claimed, match_rows, stamp, take_group,
};
pub use context::{Claiming, Leasing, Now, Settling, TimeFor, Via};
pub use helpers::put;
use helpers::run;
pub(crate) use helpers::{
arguments, attempt_in, first_header, later, lease, micros, no_lease, now, unbound, unprepared,
};
pub(crate) use settle::{ack, dead_letter, discard, extend, retry, retry_after};
pub use statements::{Prepared, Savepoint, Stmt};
pub(crate) use statements::{intern, intern_name};
pub use values::Values;
/// A row's whole contract with the broker. Machinery; the crate implements it for every
/// [`InboxTable`](crate::InboxTable), a service never names it.
pub trait Events<DB: QueueDatabase>: QueueRow + for<'r> FromRow<'r, DB::Row> + Unpin {
/// Which events the service implements itself.
const SHAPE: Shape;
/// The lease a delivery holds: the expiry its claim wrote into `locked_until`, which its
/// settlements match; `()` for a table in another form.
type Token: Copy + Debug + Send + Sync + 'static;
/// What a claim keeps of the ids it took until its fetch reads their rows: `Vec<Self::Id>` for
/// the crate's claim of ids before a fetch of the service's own, `()` for every other claim.
/// The subscription owns it and hands it to each claim, so its storage is reused.
type Ids: Default + Send + Sync + 'static;
/// Where a delivery keeps its header map: the map moved out of the `headers` field for a flat
/// struct, a cell built on the first read for a message assembled from a headers struct.
type Headers: HeaderCell<DB, Self>;
/// The kinds a by-name subscription reads and binds the row's columns by, or `None` when it
/// needs the row's own code: an event of the service's own, or a column type outside them.
fn kinds() -> Option<Kinds>;
/// The row's id.
fn id(&self) -> &Self::Id;
/// The delivery's headers, moved out of the `headers` field; none without one.
fn take_headers(&mut self) -> HeaderMap;
/// The first of `headers` the row cannot hold byte for byte: any of them where it has no
/// `headers` field.
fn unfit_header(headers: &HeaderMap) -> Option<&str>;
/// The delivery's key: the `partition_key` field's bytes.
fn partition_key(&self) -> Option<&[u8]>;
/// The delivery's attempt: the `attempt` field's.
fn attempt(&self) -> Option<u64>;
/// The attempt of a claimed `row` the struct could not decode, read alone the way the struct
/// reads it; `None` without an `attempt` column, or where that column does not decode either.
fn read_attempt(row: &DB::Row, queue: &'static Queue) -> Option<u64>;
/// Binds one parameter of a default statement; `false` when the row has no value for it.
///
/// # Errors
///
/// The driver's encoding error.
fn bind(
param: Param,
arguments: &mut DB::Arguments,
values: &Values<'_, DB, Self>,
) -> Result<bool, Error>;
/// The lease the queue's claim takes now: "now" read once from the row's clock, which every
/// time the claim binds starts from, and the lease from that instant.
///
/// # Errors
///
/// [`Error::Configuration`] where the queue holds no lease, the table reads the database's
/// clock, or the table is in another form.
fn lease(queue: &'static Queue, now: Now) -> Result<Leasing<Self::Token>, Error>;
/// Claims up to `cx.limit` rows into `out`; in the lease form the claim takes `lease`. A claim
/// of ids for a fetch reads them into `ids`, the subscription's buffer.
fn claim<'a>(
conn: &'a mut DB::Connection,
cx: &'a Claiming,
lease: Option<&'a Leasing<Self::Token>>,
ids: &'a mut Self::Ids,
out: &'a mut Vec<Claimed<Self>>,
) -> impl Future<Output = Result<(), Error>> + Send + 'a;
/// The `ack` event; in the lease form `held` is the delivery's lease.
fn ack<'a>(
conn: &'a mut DB::Connection,
cx: &'a Settling,
id: &'a Self::Id,
held: Option<&'a Self::Token>,
) -> impl Future<Output = Result<Settled, Error>> + Send + 'a;
/// The `retry` event; says whether it wrote anything for the commit to keep.
fn retry<'a>(
conn: &'a mut DB::Connection,
cx: &'a Settling,
id: &'a Self::Id,
held: Option<&'a Self::Token>,
) -> impl Future<Output = Result<Settled, Error>> + Send + 'a;
/// The `retry_after` event.
fn retry_after<'a>(
conn: &'a mut DB::Connection,
cx: &'a Settling,
id: &'a Self::Id,
held: Option<&'a Self::Token>,
delay: Duration,
) -> impl Future<Output = Result<Settled, Error>> + Send + 'a;
/// The `discard` event.
fn discard<'a>(
conn: &'a mut DB::Connection,
cx: &'a Settling,
id: &'a Self::Id,
held: Option<&'a Self::Token>,
) -> impl Future<Output = Result<Settled, Error>> + Send + 'a;
/// The `dead_letter` event.
fn dead_letter<'a>(
conn: &'a mut DB::Connection,
cx: &'a Settling,
id: &'a Self::Id,
held: Option<&'a Self::Token>,
destination: &'a str,
) -> impl Future<Output = Result<Settled, Error>> + Send + 'a;
/// The `extend` event: writes `until` while the row holds `held`, and is [`Settled::Lost`]
/// once it no longer does.
fn extend<'a>(
conn: &'a mut DB::Connection,
cx: &'a Settling,
id: &'a Self::Id,
held: &'a Self::Token,
until: &'a Self::Token,
) -> impl Future<Output = Result<Settled, Error>> + Send + 'a;
/// The `lock` event of the advisory lock form: tries the lock on `key` for the session of
/// `conn`, without waiting; `true` when it took the lock.
fn lock<'a>(
conn: &'a mut DB::Connection,
cx: &'a Claiming,
key: &'a str,
) -> impl Future<Output = Result<bool, Error>> + Send + 'a;
/// The `unlock` event of the advisory lock form: releases the lock on `key` the session of
/// `conn` holds; `true` when it held it.
fn unlock<'a>(
conn: &'a mut DB::Connection,
cx: &'a Settling,
key: &'a str,
) -> impl Future<Output = Result<bool, Error>> + Send + 'a;
/// Takes the candidate `id` of an advisory claim, whose lock the session of `conn` holds:
/// counts its attempt and pushes the candidate's row onto `out` when the take found it still
/// claimable; `false` when the row is gone or no longer claimable.
fn take<'a>(
conn: &'a mut DB::Connection,
cx: &'a Claiming,
id: &'a Self::Id,
out: &'a mut Vec<Claimed<Self>>,
) -> impl Future<Output = Result<bool, Error>> + Send + 'a;
/// Selects the candidates of an advisory claim into `out`: up to `cx.limit` claimable rows in
/// claim order, each id with its lock key, written over the last claim's.
fn candidates<'a>(
conn: &'a mut DB::Connection,
cx: &'a Claiming,
out: &'a mut Candidates<Self::Id>,
) -> impl Future<Output = Result<(), Error>> + Send + 'a;
}
/// Which events a row's service implements itself.
// One switch per event, read once per subscription at startup.
#[allow(clippy::struct_excessive_bools)]
#[derive(Debug, Clone, Copy, Default, PartialEq, Eq, Hash)]
pub struct Shape {
/// `custom(claim)`.
pub custom_claim: bool,
/// `custom(fetch)`.
pub custom_fetch: bool,
/// `custom(ack)`.
pub custom_ack: bool,
/// `custom(retry)`.
pub custom_retry: bool,
/// `custom(retry_after)`.
pub custom_retry_after: bool,
/// `custom(discard)`.
pub custom_discard: bool,
/// `custom(dead_letter)`.
pub custom_dead_letter: bool,
/// `custom(extend)`.
pub custom_extend: bool,
/// `custom(lock)`: the advisory lock form's lock is the service's, so the database, not the
/// process, keeps the keys in work on every dialect.
pub custom_lock: bool,
/// `custom(unlock)`.
pub custom_unlock: bool,
}
/// The event a statement serves, for binding and for messages.
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub enum Event {
/// Claiming rows.
Claim,
/// Reading the rows of claimed ids.
Fetch,
/// `ack`.
Ack,
/// `retry()`.
Retry,
/// `retry_after(d)`.
RetryAfter,
/// `drop`.
Discard,
/// A spent delivery's move.
DeadLetter,
/// Leasing a row a claim selected: a claim that only selects stamps each row.
Stamp,
/// Extending a delivery's lease, or confirming it.
Extend,
/// Taking the advisory lock on a candidate's key.
Lock,
/// Releasing the advisory lock on a delivery's key.
Unlock,
/// Taking a candidate whose key the delivery's session holds.
Take,
}
impl Event {
/// The event's name, as statements and `custom(..)` spell it.
#[must_use]
pub const fn name(self) -> &'static str {
match self {
Self::Claim => "claim",
Self::Fetch => "fetch",
Self::Ack => "ack",
Self::Retry => "retry",
Self::RetryAfter => "retry_after",
Self::Discard => "discard",
Self::DeadLetter => "dead_letter",
Self::Stamp => "stamp",
Self::Extend => "extend",
Self::Lock => "lock",
Self::Unlock => "unlock",
Self::Take => "take",
}
}
}
/// One claimed row, a claimed id the fetch found no row for, or a row whose columns do not decode
/// into its struct.
#[derive(Debug)]
pub enum Claimed<Row: QueueRow> {
/// The row.
Row(Row),
/// The id; its delivery reports that its row is gone, and the decode-failure policy settles
/// it.
Missing(Row::Id),
/// The id of a row the struct could not decode, its attempt, and why; its delivery reports
/// the driver's error, and the decode-failure policy settles it.
Undecodable {
/// The row's id, read alone.
id: Row::Id,
/// The row's attempt, read alone: the attempt cap spends such a row as any other.
attempt: Option<u64>,
/// The driver's error of decoding the whole row, as the delivery reports it.
// Boxed: every delivery holds its `Claimed` inline, so the error would otherwise widen
// the deliveries of rows that decode.
error: Box<CodecError>,
},
}
/// The driver's `error` of decoding a whole row, as a delivery of the row reports it.
pub(crate) fn undecodable(error: Error) -> CodecError {
CodecError::Decode(Box::new(error))
}
/// Where a claim's select carries the id, read alone from a row the struct could not decode.
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub enum IdAt {
/// The first column: the generated select lists the id first.
First,
/// The column of this name: the select reads `*` where the struct flattens another.
Named(&'static str),
}
impl IdAt {
/// Where the statements built from `spec` carry the id.
#[must_use]
pub const fn of(spec: &TableSpec<'static>) -> Self {
if spec.selects_all() {
Self::Named(spec.id().name())
} else {
Self::First
}
}
}
/// What a settlement did, which decides between the commit and the rollback.
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub enum Settled {
/// A statement ran; the commit keeps it.
Written,
/// Nothing ran; the rollback releases the row.
Untouched,
/// The statement changed no row: in the lease form the row no longer holds the delivery's
/// lease, and the settlement took no effect.
Lost,
}
impl<Row: QueueRow> Claimed<Row> {
/// The claimed id.
pub fn id<DB: QueueDatabase>(&self) -> &Row::Id
where
Row: Events<DB>,
{
match self {
Self::Row(row) => Row::id(row),
Self::Missing(id) | Self::Undecodable { id, .. } => id,
}
}
}
const _: fn() = || {
fn debug<T: Debug>() {}
debug::<Shape>();
};
#[cfg(test)]
mod tests {
use std::time::Duration;
use super::{Event, micros};
#[test]
fn delays_bind_in_microseconds_and_saturate() {
assert_eq!(micros(Duration::from_millis(1500)), 1_500_000);
assert_eq!(micros(Duration::MAX), i64::MAX);
assert_eq!(Event::RetryAfter.name(), "retry_after");
}
#[test]
fn the_advisory_events_name_themselves_as_messages_read_them() {
let names = [Event::Lock, Event::Unlock, Event::Take].map(Event::name);
assert_eq!(names, ["lock", "unlock", "take"]);
}
}