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
//! Pool-scoped tenant isolation: set the configured GUC (default
//! `app.tenant_id`) once when a connection is checked out of the pool,
//! reset it when it goes back. Every query made in the scoped async call
//! chain is auto-isolated, with no per-call-site boilerplate.
//!
//! This is the pattern most production codebases want. It pairs with:
//!
//! 1. The [`TENANT_ID`] task-local — set per-request by the
//! [`tenant_scope`] middleware below.
//! 2. The [`with_tenant_hooks`] free fn (default-config) or the
//! [`crate::Tenancy::with_tenant_hooks`] method (custom config) — wires
//! `before_acquire` / `after_release` / `after_connect` hooks that
//! read the task-local and run `set_config` / `RESET` on the
//! connection.
//!
//! ## Default-config wiring (most apps)
//!
//! ```no_run
//! # async fn run() -> sqlx::Result<()> {
//! use sqlx::postgres::PgPoolOptions;
//! use tenaxum::pool;
//!
//! let pool = pool::with_tenant_hooks(PgPoolOptions::new().max_connections(8))
//! .connect("postgres://...").await?;
//! # Ok(()) }
//! ```
//!
//! ## Custom-config wiring (different GUC name, e.g. `app.org_id`)
//!
//! ```no_run
//! # async fn run() -> sqlx::Result<()> {
//! use sqlx::postgres::PgPoolOptions;
//! use tenaxum::Tenancy;
//!
//! let pool = Tenancy::new()
//! .guc("app.org_id")
//! .with_tenant_hooks(PgPoolOptions::new().max_connections(8))
//! .connect("postgres://...").await?;
//! # Ok(()) }
//! ```
//!
//! ## Axum middleware
//!
//! ```ignore
//! use axum::{Router, middleware};
//! use tenaxum::pool::tenant_scope;
//!
//! let app = Router::new()
//! // ...your routes...
//! .layer(middleware::from_fn(tenant_scope));
//! ```
//!
//! `tenant_scope` reads `Extension<TenantId>` (set by your auth layer) and
//! scopes the [`TENANT_ID`] task-local for the rest of the request. The
//! pool hooks then pick it up automatically on every connection checkout.
//!
//! The middleware is GUC-agnostic — it only sets the task-local. The hooks
//! (which know the GUC name) read from the task-local. So a single
//! `tenant_scope` middleware works for any [`crate::Tenancy`] configuration.
//!
//! ## Spawned tasks
//!
//! Tokio task-locals do **not** propagate across [`tokio::spawn`]. If a
//! handler fans DB work out into spawned tasks, use [`spawn_with_tenant`]
//! (or manually wrap the future in [`scope_tenant`]) so the child task
//! inherits the current tenant binding.
//!
//! ## When to use this vs [`crate::PgPoolExt::begin_tenant`]
//!
//! - **Pool hooks** (this module): for the common case where every
//! handler in your app touches tenant-scoped data and you want
//! isolation by default.
//! - **`begin_tenant`**: for explicit, scoped uses — background jobs
//! that operate on a specific tenant outside of an HTTP request,
//! one-off scripts, or admin paths where the pool hooks aren't wired.
//!
//! ## Composing with your own `after_connect`
//!
//! `with_tenant_hooks` installs three hooks: `before_acquire`,
//! `after_release`, and `after_connect`. `after_connect` is a scalar
//! setter on `PgPoolOptions`, so if **you** call `.after_connect(..)`
//! after the hooks are installed, **your** hook replaces tenaxum's — and
//! a fresh connection will not have the GUC set when its first query
//! runs (sqlx 0.8 only fires `before_acquire` for connections already in
//! the idle pool, not for newly-opened ones).
//!
//! Use [`tenant_after_connect_hook`] (default config) or
//! [`crate::Tenancy::after_connect`] (custom) from inside your own
//! `after_connect` closure to keep tenaxum's behaviour:
//!
//! ```no_run
//! # async fn run() -> sqlx::Result<()> {
//! use sqlx::postgres::PgPoolOptions;
//! use tenaxum::pool;
//!
//! let opts = pool::with_tenant_hooks(PgPoolOptions::new())
//! .after_connect(|conn, _meta| Box::pin(async move {
//! sqlx::query("SET statement_timeout = '5s'")
//! .execute(&mut *conn).await?;
//! pool::tenant_after_connect_hook(conn).await?;
//! Ok(())
//! }));
//! # let _ = opts;
//! # Ok(()) }
//! ```
use crateTenancy;
use crateTenantId;
use ;
use ;
use Future;
use JoinHandle;
task_local!
/// Apply tenaxum pool hooks to a [`PgPoolOptions`] builder using the
/// default [`Tenancy`] (`app.tenant_id`).
///
/// Equivalent to `Tenancy::default().with_tenant_hooks(opts)`. Use the
/// [`Tenancy`] builder directly if you need a custom GUC name.
/// Free function that mirrors the `before_acquire` SET on a fresh
/// connection, using the default GUC (`app.tenant_id`).
///
/// Public so callers who need their own `after_connect` callback (e.g.
/// to set `statement_timeout`) can compose tenaxum's behaviour into
/// theirs — sqlx 0.8 stores `after_connect` as a single callback, so
/// multiple `.after_connect(..)` calls overwrite each other.
///
/// If [`TENANT_ID`] is set, runs `set_config('app.tenant_id', <id>, false)`.
/// If unset, leaves the connection untouched. For a custom GUC name, use
/// [`crate::Tenancy::after_connect`].
pub async
/// `PgPoolOptions` pre-wired with [`with_tenant_hooks`] (default config)
/// and a small connection cap, suitable for integration tests.
///
/// Equivalent to:
///
/// ```ignore
/// with_tenant_hooks(PgPoolOptions::new().max_connections(2))
/// ```
///
/// For custom-config tests, use `Tenancy::new().<knobs>.with_tenant_hooks(...)`
/// directly.
/// Returns the tenant currently bound in [`TENANT_ID`], if any.
///
/// Useful when request-scoped code needs to propagate the current tenant
/// into another future or task explicitly.
/// Run `future` inside a [`TENANT_ID`] scope.
///
/// This is the manual form of what [`tenant_scope`] does for an Axum
/// request. Use it when tenant-scoped work starts outside the middleware
/// path, or when you need to re-bind the tenant around a child future.
pub async
/// Spawn a task that inherits the current [`TENANT_ID`] binding, if any.
///
/// Tokio task-locals do not cross [`tokio::spawn`] boundaries on their
/// own. This helper captures the current tenant and re-scopes the child
/// future so pool hooks continue to see the expected binding.
/// Axum middleware that scopes the [`TENANT_ID`] task-local for the
/// duration of the request.
///
/// Reads `Extension<TenantId>` off the request — your auth layer is
/// expected to have inserted it. If absent, the request runs with no
/// tenant binding (the pool hooks will run `RESET` on every checkout,
/// which is the correct behavior for unauthenticated or admin paths).
///
/// GUC-agnostic: this middleware sets the task-local, the pool hooks
/// (which know the GUC name) read it. A single `tenant_scope` works for
/// any [`crate::Tenancy`] configuration.
pub async
async
async