ruststream-sqlx 0.7.0

SQL databases for the RustStream messaging framework through sqlx: a transactional outbox over any RustStream broker and task queues in Postgres, MySQL/MariaDB and SQLite tables.
Documentation
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
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
//! The gate the live suites share, the stands they run on, and the rows they read.
//!
//! A live test skips when its stand's URL is unset, which keeps `cargo test` usable on a laptop
//! with no stand. The same skip in a job that started the stand would be a lie, so
//! `just test-brokers` and CI set `RUSTSTREAM_REQUIRE_LIVE`, and under it a skip fails, naming
//! what it wanted. The SQLite stand runs in memory and needs no URL, so it never skips.
//!
//! A suite writes each test once. [`matrix!`] runs it on every stand a feature turns on, once per
//! form of the queue rows that stand serves; [`fifo_matrix!`] the same for the forms whose claim
//! statement takes a FIFO group's head, the row lock and the lease form, and [`server_matrix!`]
//! those on the stands that run as servers. [`stands!`] runs a test once per stand, for a test
//! whose rows name their own form, [`row_lock_stands!`] once per stand that serves the row lock
//! form, [`lease_stands!`] once per stand of the lease form, [`advisory_stands!`] once per stand
//! of the advisory lock form's suite, and [`advisory_server_stands!`] once per such stand that runs
//! as a server. A stand module gives each
//! test a database of its own, the dialect the broker builds the stand's statements with, and reads
//! the tables in its own SQL; a row module holds the queue rows of one form under the names every
//! form shares.

// Each test target is its own binary and uses the part of this module its suites need, so what
// one of them leaves alone, a macro included, is not dead code.
#![allow(dead_code, unused_macros)]

use std::future::Future;

use sqlx::{Database as Backend, Pool};

#[cfg(feature = "mysql")]
pub(crate) mod mysql;
#[cfg(feature = "postgres")]
pub(crate) mod postgres;
// The shared rows keep times and JSON headers; a suite of other column types runs without them.
#[cfg(all(feature = "chrono", feature = "json"))]
pub(crate) mod rows;
#[cfg(feature = "sqlite")]
pub(crate) mod sqlite;

/// The MariaDB stand: a server of its own, which the MySQL stand's SQL reads and writes.
#[cfg(feature = "mysql")]
pub(crate) mod mariadb {
    pub(crate) use super::mysql::Db;

    use ruststream_sqlx::dialect;

    use super::Database;

    /// The variable that names the stand: a MariaDB URL whose user may create databases.
    pub(crate) const URL: &str = "MARIADB_TEST_URL";

    /// The dialect the broker builds the stand's statements with: the MySQL stand's.
    pub(crate) const DIALECT: dialect::MySql = super::mysql::DIALECT;

    /// A fresh database on the stand, or `None` to skip the test.
    pub(crate) async fn database() -> Option<Database<Db>> {
        super::mysql::database_on(URL).await
    }
}

/// The variable a job sets to say it stood its databases up, so skipping past one is a defect.
pub(crate) const REQUIRE_LIVE: &str = "RUSTSTREAM_REQUIRE_LIVE";

fn required() -> bool {
    std::env::var(REQUIRE_LIVE).is_ok_and(|value| !value.is_empty())
}

/// The stand's URL that `variable` names, or `None` to skip the test.
///
/// # Panics
///
/// Panics when [`REQUIRE_LIVE`] is set and `variable` is not: a job that started a stand and lost
/// its address is a broken job, and the tests behind it would pass without running.
pub(crate) fn url(variable: &str) -> Option<String> {
    match std::env::var(variable) {
        Ok(url) if !url.is_empty() => Some(url),
        _ => {
            assert!(
                !required(),
                "{REQUIRE_LIVE} is set, so this suite must run, but {variable} is unset or empty"
            );
            eprintln!("{variable} is not set; skipping the live suite");
            None
        }
    }
}

/// Awaits `read` on a running tokio clock, then pauses the clock again.
///
/// A paused clock jumps to its next timer whenever the runtime waits, and a reply on its way from
/// the database is such a wait, so the pool's acquire timeout can fire under a read. The broker's
/// in-process connection keeps its own calls off a paused clock; a test reads its tables through
/// here.
pub(crate) async fn unpaused<T>(read: impl Future<Output = T>) -> T {
    tokio::time::resume();
    let value = read.await;
    tokio::time::pause();
    value
}

/// A database of the test's own on a stand, with the stand's schema applied.
///
/// Its stand's module creates it, drops it in `finish` and reads its tables.
pub(crate) struct Database<DB: Backend> {
    /// A pool on the database, the one the test's broker takes.
    pub(crate) pool: Pool<DB>,
    /// The database's name on the stand.
    name: String,
    /// The stand's URL, which `finish` connects to again to drop the database.
    url: String,
    /// A connection the stand holds open beside the pool, where the database lives only while one
    /// is open.
    keeper: Option<DB::Connection>,
}

/// One module per stand and per form of the queue rows, each holding `$items`.
///
/// A stand appears when its feature is on; the rows of a form appear when the form exists on that
/// stand. Each module sees the suite's own items, the stand's `Db`, `database` and `DIALECT`, and
/// the rows of its form.
macro_rules! matrix {
    ($($items:item)*) => {
        $crate::live::fifo_matrix! { $($items)* }

        #[cfg(feature = "postgres")]
        mod postgres_advisory {
            #[allow(unused_imports)]
            use super::*;
            #[allow(unused_imports)]
            use crate::live::postgres::{DIALECT, Db, database};
            #[allow(unused_imports)]
            use crate::live::rows::advisory::*;
            $($items)*
        }

        #[cfg(feature = "mysql")]
        mod mysql_advisory {
            #[allow(unused_imports)]
            use super::*;
            #[allow(unused_imports)]
            use crate::live::mysql::{DIALECT, Db, database};
            #[allow(unused_imports)]
            use crate::live::rows::advisory::*;
            $($items)*
        }

        #[cfg(feature = "mysql")]
        mod mariadb_advisory {
            #[allow(unused_imports)]
            use super::*;
            #[allow(unused_imports)]
            use crate::live::mariadb::{DIALECT, Db, database};
            #[allow(unused_imports)]
            use crate::live::rows::advisory::*;
            $($items)*
        }

        #[cfg(feature = "sqlite")]
        mod sqlite_advisory {
            #[allow(unused_imports)]
            use super::*;
            #[allow(unused_imports)]
            use crate::live::sqlite::{DIALECT, Db, database};
            #[allow(unused_imports)]
            use crate::live::rows::advisory::*;
            $($items)*
        }
    };
}

/// One module per stand and per form whose claim statement takes a FIFO group's head, the row lock
/// and the lease form, each holding `$items`: for a test that runs that statement itself.
///
/// A stand appears when its feature is on; SQLite has no row locks, so it runs the lease form
/// alone. Each module sees the suite's own items, the stand's `Db`, `database` and `DIALECT`, and
/// the rows of its form.
macro_rules! fifo_matrix {
    ($($items:item)*) => {
        $crate::live::server_matrix! { $($items)* }

        #[cfg(feature = "sqlite")]
        mod sqlite_lease {
            #[allow(unused_imports)]
            use super::*;
            #[allow(unused_imports)]
            use crate::live::sqlite::{DIALECT, Db, database};
            #[allow(unused_imports)]
            use crate::live::rows::lease::*;
            $($items)*
        }
    };
}

/// One module per stand that runs as a server and per form whose claim statement takes a FIFO
/// group's head, each holding `$items`: for a test of what such claims on separate connections do
/// side by side, which SQLite's one writer keeps apart.
///
/// A stand appears when its feature is on. Each module sees the suite's own items, the stand's
/// `Db`, `database` and `DIALECT`, and the rows of its form.
macro_rules! server_matrix {
    ($($items:item)*) => {
        #[cfg(feature = "postgres")]
        mod postgres_row_lock {
            #[allow(unused_imports)]
            use super::*;
            #[allow(unused_imports)]
            use crate::live::postgres::{DIALECT, Db, database};
            #[allow(unused_imports)]
            use crate::live::rows::row_lock::*;
            $($items)*
        }

        #[cfg(feature = "postgres")]
        mod postgres_lease {
            #[allow(unused_imports)]
            use super::*;
            #[allow(unused_imports)]
            use crate::live::postgres::{DIALECT, Db, database};
            #[allow(unused_imports)]
            use crate::live::rows::lease::*;
            $($items)*
        }

        #[cfg(feature = "mysql")]
        mod mysql_row_lock {
            #[allow(unused_imports)]
            use super::*;
            #[allow(unused_imports)]
            use crate::live::mysql::{DIALECT, Db, database};
            #[allow(unused_imports)]
            use crate::live::rows::row_lock::*;
            $($items)*
        }

        #[cfg(feature = "mysql")]
        mod mysql_lease {
            #[allow(unused_imports)]
            use super::*;
            #[allow(unused_imports)]
            use crate::live::mysql::{DIALECT, Db, database};
            #[allow(unused_imports)]
            use crate::live::rows::lease::*;
            $($items)*
        }

        #[cfg(feature = "mysql")]
        mod mariadb_row_lock {
            #[allow(unused_imports)]
            use super::*;
            #[allow(unused_imports)]
            use crate::live::mariadb::{DIALECT, Db, database};
            #[allow(unused_imports)]
            use crate::live::rows::row_lock::*;
            $($items)*
        }

        #[cfg(feature = "mysql")]
        mod mariadb_lease {
            #[allow(unused_imports)]
            use super::*;
            #[allow(unused_imports)]
            use crate::live::mariadb::{DIALECT, Db, database};
            #[allow(unused_imports)]
            use crate::live::rows::lease::*;
            $($items)*
        }
    };
}

/// One module per stand, each holding `$items`: for a test whose rows name their own form, one
/// every stand serves.
///
/// A stand appears when its feature is on. Each module sees the suite's own items and the stand's
/// `Db` and `database`.
macro_rules! stands {
    ($($items:item)*) => {
        $crate::live::row_lock_stands! { $($items)* }

        #[cfg(feature = "sqlite")]
        mod sqlite {
            #[allow(unused_imports)]
            use super::*;
            #[allow(unused_imports)]
            use crate::live::sqlite::{Db, database};
            $($items)*
        }
    };
}

/// One module per stand of the lease form, each holding `$items`: for a test of what a lease does.
///
/// A stand appears when its feature is on. Each module sees the suite's own items, the stand's
/// `Db`, `database` and `DIALECT`, and the rows of the lease form.
macro_rules! lease_stands {
    ($($items:item)*) => {
        #[cfg(feature = "postgres")]
        mod postgres_lease {
            #[allow(unused_imports)]
            use super::*;
            #[allow(unused_imports)]
            use crate::live::postgres::{DIALECT, Db, database};
            #[allow(unused_imports)]
            use crate::live::rows::lease::*;
            $($items)*
        }

        #[cfg(feature = "mysql")]
        mod mysql_lease {
            #[allow(unused_imports)]
            use super::*;
            #[allow(unused_imports)]
            use crate::live::mysql::{DIALECT, Db, database};
            #[allow(unused_imports)]
            use crate::live::rows::lease::*;
            $($items)*
        }

        #[cfg(feature = "mysql")]
        mod mariadb_lease {
            #[allow(unused_imports)]
            use super::*;
            #[allow(unused_imports)]
            use crate::live::mariadb::{DIALECT, Db, database};
            #[allow(unused_imports)]
            use crate::live::rows::lease::*;
            $($items)*
        }

        #[cfg(feature = "sqlite")]
        mod sqlite_lease {
            #[allow(unused_imports)]
            use super::*;
            #[allow(unused_imports)]
            use crate::live::sqlite::{DIALECT, Db, database};
            #[allow(unused_imports)]
            use crate::live::rows::lease::*;
            $($items)*
        }
    };
}

/// One module per stand of the advisory lock form's suite, each holding `$items`.
///
/// A stand appears when its feature is on. Each module sees the suite's own items, the stand's
/// `Db`, `database` and `DIALECT`, and the rows of the advisory lock form.
macro_rules! advisory_stands {
    ($($items:item)*) => {
        $crate::live::advisory_server_stands! { $($items)* }

        #[cfg(feature = "sqlite")]
        mod sqlite {
            #[allow(unused_imports)]
            use super::*;
            #[allow(unused_imports)]
            use crate::live::sqlite::{DIALECT, Db, database};
            #[allow(unused_imports)]
            use crate::live::rows::advisory::*;
            $($items)*
        }
    };
}

/// One module per stand of the advisory lock form that runs as a server, each holding `$items`:
/// the stands whose databases keep the locks.
///
/// A stand appears when its feature is on. Each module sees the suite's own items, the stand's
/// `Db`, `database` and `DIALECT`, and the rows of the advisory lock form.
macro_rules! advisory_server_stands {
    ($($items:item)*) => {
        #[cfg(feature = "postgres")]
        mod postgres {
            #[allow(unused_imports)]
            use super::*;
            #[allow(unused_imports)]
            use crate::live::postgres::{DIALECT, Db, database};
            #[allow(unused_imports)]
            use crate::live::rows::advisory::*;
            $($items)*
        }

        #[cfg(feature = "mysql")]
        mod mysql {
            #[allow(unused_imports)]
            use super::*;
            #[allow(unused_imports)]
            use crate::live::mysql::{DIALECT, Db, database};
            #[allow(unused_imports)]
            use crate::live::rows::advisory::*;
            $($items)*
        }

        #[cfg(feature = "mysql")]
        mod mariadb {
            #[allow(unused_imports)]
            use super::*;
            #[allow(unused_imports)]
            use crate::live::mariadb::{DIALECT, Db, database};
            #[allow(unused_imports)]
            use crate::live::rows::advisory::*;
            $($items)*
        }
    };
}

/// One module per stand whose database locks rows, each holding `$items`: for a test whose rows
/// take the row lock form, which SQLite does not serve.
///
/// A stand appears when its feature is on. Each module sees the suite's own items and the stand's
/// `Db` and `database`.
macro_rules! row_lock_stands {
    ($($items:item)*) => {
        #[cfg(feature = "postgres")]
        mod postgres {
            #[allow(unused_imports)]
            use super::*;
            #[allow(unused_imports)]
            use crate::live::postgres::{Db, database};
            $($items)*
        }

        $crate::live::mysql_stands! { $($items)* }
    };
}

/// One module per stand that speaks MySQL, each holding `$items`: for a test of what MySQL and
/// MariaDB do alike.
///
/// The stands appear with the `mysql` feature. Each module sees the suite's own items and the
/// stand's `Db` and `database`.
macro_rules! mysql_stands {
    ($($items:item)*) => {
        #[cfg(feature = "mysql")]
        mod mysql {
            #[allow(unused_imports)]
            use super::*;
            #[allow(unused_imports)]
            use crate::live::mysql::{Db, database};
            $($items)*
        }

        #[cfg(feature = "mysql")]
        mod mariadb {
            #[allow(unused_imports)]
            use super::*;
            #[allow(unused_imports)]
            use crate::live::mariadb::{Db, database};
            $($items)*
        }
    };
}

#[allow(unused_imports)]
pub(crate) use {
    advisory_server_stands, advisory_stands, fifo_matrix, lease_stands, matrix, mysql_stands,
    row_lock_stands, server_matrix, stands,
};