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
//! Per-sim `SQLite` database substrate (sim-testing W4, issue #1797).
//!
//! This is the **DB lane** the sim builds its app on: a fresh, migrated,
//! in-process `SQLite` database, unique per simulation, ready to be handed to a
//! [`Sim`](crate::sim::Sim)-mounted app. It is deliberately **self-contained**
//! and additive — W2 owns `Sim::build(TestApp)` / the `SimApp` mount and will
//! consume this substrate there in a follow-up; nothing here defines or pre-empts
//! that public API.
//!
//! # Consumption shape (W2 seam)
//!
//! W2's `Sim::build` takes a [`crate::test::TestApp`] (not a raw `AppBuilder`),
//! and the endorsed pattern attaches the DB to the `TestApp` *before* build. So
//! the substrate hands its pool to a `TestApp` via
//! [`TestApp::with_db`](crate::test::TestApp::with_db):
//! `TestApp::new()…with_db(substrate.pool())`, and W2 then calls `sim.build(app)`.
//! [`SqliteSubstrate::pool`] returns exactly the [`crate::db::RuntimeConnection`]
//! pool that seam consumes; the substrate value itself must outlive the app,
//! because it holds the kept-alive guard connection.
//!
//! # Why shared-cache in-memory + a kept-alive guard
//!
//! A pure `:memory:` / `file::memory:` `SQLite` database is **private per
//! connection** and is destroyed the moment its *last* connection closes. So the
//! naive "migrate on a transient connection, then build a pool" sequence loses
//! the schema before the pool's first checkout, and every DB-backed request then
//! 500s with "no such table". That is exactly why the framework's startup
//! migration path *rejects* any in-memory target with registered migrations (see
//! [`crate::migrate::reject_in_memory_migrations`] /
//! [`crate::db::sqlite_target_is_any_in_memory`]).
//!
//! The substrate honours the literal "in-memory" DoD without hitting that
//! failure by using a **named shared-cache** in-memory database
//! (`file:<unique>?mode=memory&cache=shared`) plus a **kept-alive guard
//! connection** held for the whole sim lifetime:
//!
//! 1. Open the guard connection *first*, so the shared in-memory database exists
//! and cannot be reclaimed.
//! 2. Apply the caller's migration set directly through diesel's
//! `MigrationHarness` on that same guard connection. This deliberately
//! bypasses [`crate::migrate::run_pending_sqlite`]'s in-memory reject — that
//! reject is a *conservative* guard for the "no one is keeping the DB alive"
//! case, and here the guard connection is precisely what makes applying
//! migrations to a shared in-memory DB safe.
//! 3. Build the async runtime pool ([`crate::db::create_pool`]) over the *same*
//! URL. Because the guard connection stays open, the shared in-memory database
//! (and the migrated schema) survives for every pooled checkout.
//!
//! The unique database name (a fresh UUID per substrate) guarantees two sims
//! never share state: `SQLite` scopes a shared-cache in-memory database by its
//! name within the process, so distinct names are fully isolated databases.
//!
//! The framework's `SQLite` **repository-commit-hook** migration set
//! ([`crate::repository_commit_hooks::REPOSITORY_COMMIT_HOOK_MIGRATIONS`]) is
//! applied *first*, before any caller-registered migrations, so the
//! `autumn_repository_commit_hooks` control-plane table always exists on the
//! substrate. This is load-bearing: an app mounted on a substrate that has a DB
//! pool is drained by [`Sim::run_to_idle`](crate::sim::Sim::run_to_idle), whose
//! drain does an unconditional `COUNT(*)` on that table
//! ([`crate::test::drain_ready_repository_commit_hooks`]) — so without the table
//! `run_to_idle` panics with "no such table". Because `substrate.rs` lives inside
//! the `autumn` crate it can reference that `pub(crate)`-reachable migration set
//! directly; a sim author outside the crate cannot, which is exactly why the
//! substrate provisions it rather than leaving it to the caller. Other caller
//! migrations then apply on top.
//!
//! The Postgres control-plane [`crate::migrate::FRAMEWORK_MIGRATIONS`] are **not**
//! applied here: they are Postgres DDL, and the sim's representative scheduler +
//! job paths (below) need no other DB-side control tables.
//!
//! # Feature-unification hazard resolution (the representative path)
//!
//! Under `--features sqlite` two Postgres-only orchestration backends are
//! compiled **out**, so the sim exercises their local, in-process substitutes —
//! which are the real, default-configured paths a single-node `SQLite` app runs:
//!
//! * **Scheduler** — the sim runs the **`InProcessSchedulerCoordinator`**
//! ([`crate::scheduler::coordinator_from_config`], the
//! [`SchedulerBackend::InProcess`](crate::config::SchedulerBackend::InProcess)
//! `#[default]`). `coordinator_from_config` *rejects*
//! `scheduler.backend = "postgres"` under the `sqlite` feature, because the
//! Postgres advisory-lock coordinator leases via `pg_advisory_lock`, which
//! `SQLite` has no primitive for.
//! * **Jobs** — the sim runs the **local `JobAdminMemoryBackend`**
//! ([`crate::job::start_runtime`] with the `"local"` backend default);
//! `jobs.backend = "postgres"` is a hard-error stub under the `sqlite` feature
//! because `SQLite` has no `LISTEN`/`NOTIFY` + `SKIP LOCKED` durable queue.
//!
//! **Documented divergence** (consistent with the RFC §12 scope): a green sim
//! proves the *orchestration, timing, and ordering* of the local scheduler + job
//! paths. It does **not** validate the Postgres advisory-lock scheduler leasing
//! or the durable Postgres `LISTEN`/`NOTIFY` + `SKIP LOCKED` job-queue
//! claim/lock semantics — those are compiled out under `sqlite` and remain the
//! province of the Postgres-backed integration tests. The sim is a determinism /
//! orchestration harness, not a Postgres queue-semantics conformance suite.
use ;
use ;
use crateDatabaseConfig;
use crateRuntimeConnection;
use crateEmbeddedMigrationsRef;
use Pool;
/// A monotonic counter folded into each substrate's database name so that, even
/// within a single process and a single wall-clock instant, two substrates never
/// collide on a name (belt-and-suspenders alongside the per-substrate UUID).
static SUBSTRATE_SEQ: AtomicU64 = new;
/// An error building the `SQLite` sim substrate.
///
/// Deliberately small and string-backed: the substrate is a test/sim seam, so a
/// self-describing message is more useful to a sim author than a typed error
/// tree, and it keeps the type free of a `diesel`/`deadpool` dependency in its
/// public shape.
/// Holds the kept-alive connection that anchors the shared-cache in-memory
/// database for the substrate's lifetime.
///
/// `SQLite` reclaims a shared-cache in-memory database when its **last** connection
/// closes; this guard is that last connection. It is never used for queries after
/// migrations — it exists purely so the async pool's checkouts keep seeing the
/// migrated schema. Dropping the [`SqliteSubstrate`] drops this guard, releasing
/// the in-memory database.
/// A fresh, migrated, in-process `SQLite` database for one simulation.
///
/// Construct one per sim via [`SqliteSubstrate::new`] (no migrations) or
/// [`SqliteSubstrate::with_migrations`] (apply a caller-supplied set), then hand
/// [`SqliteSubstrate::pool`] to the app the sim builds. The substrate owns the
/// kept-alive guard connection, so it must outlive any app that uses its pool.
/// Mint a process-unique shared-cache in-memory `SQLite` URL for one substrate.
///
/// The name combines a fresh UUID with a monotonic sequence number so two
/// substrates never collide, and `mode=memory&cache=shared` makes it an in-memory
/// database shareable across the guard connection and the pool's connections
/// within this process. A distinct name is a fully isolated database, which is
/// what keeps two sims from ever sharing state.