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
//! `JsonbSchema` — typed compile-time path tree for JSONB columns.
//! # What
//! [`JsonbSchema`] is a marker trait that opts a struct into the typed
//! JSONB path API. Implementing it (via `#[derive(JsonbSchema)]`) causes
//! the proc macro to emit a `{T}Path<M>` struct with one method per field:
//! - Scalar fields (from the cast-matrix allowlist: `i16`, `i32`, `i64`,
//! `f32`, `f64`, `bool`, `String`, `time::OffsetDateTime`, etc.) return a
//! [`JsonbPathRef<M, FieldType>`](crate::jsonb::JsonbPathRef) that carries
//! the accumulated JSONB path and exposes `eq` / `gt` / `lt` / ... comparisons.
//! - Nested fields whose types also implement `JsonbSchema` return the nested
//! type's `Path<M>`, extending the path accumulator by one segment.
//! # Why
//! The flat escape hatch (`FieldRef<M, Jsonb<T>>::path::<V>("a.b.c")`) is
//! correct but bypasses the type system — a string typo produces a silent
//! runtime miss. `JsonbSchema` brings the path into the type system so
//! `f.specs.typed().engine.cylinders.gt(4)` compiles only when `VehicleSpecs`
//! has an `engine: EngineSpecs` field and `EngineSpecs` has a `cylinders: i32`
//! field.
//! # Deref vs `.typed()` — decision
//! The plan considered a `Deref<Target = T::Path<M>>` impl on
//! `FieldRef<M, Jsonb<T>>`. Deref requires returning `&T::Path<M>`, which
//! means storing the path tree inside `FieldRef` or using a thread-local. Both
//! approaches add runtime storage to a `Copy` ZST, harming the zero-overhead
//! design goal.
//! The explicit `.typed()` method avoids any storage:
//! ```ignore
//! f.specs.typed().engine.cylinders.gt(4)
//! ```
//! The extra `.typed()` call also makes the schema boundary visible to readers,
//! and the flat escape hatch (`f.specs().path::<i32>("engine.cylinders")`) is
//! still available for dynamic paths or types not covered by the allowlist.
//! # How to opt in
//! ```rust
//! use djogi::JsonbSchema;
//! use serde::{Serialize, Deserialize};
//!
//! #[derive(JsonbSchema, Serialize, Deserialize, Default)]
//! pub struct EngineSpecs {
//! pub cylinders: i32,
//! pub displacement_cc: f32,
//! }
//!
//! #[derive(JsonbSchema, Serialize, Deserialize, Default)]
//! pub struct VehicleSpecs {
//! pub engine: EngineSpecs,
//! pub weight_kg: f32,
//! }
//! ```
//! Then, with a model that has `specs: Jsonb<VehicleSpecs>`, you can write:
//! ```ignore
//! Vehicle::objects()
//! .filter(|f| f.specs().typed().engine.cylinders.gt(4))
//! .fetch_all(&mut ctx).await?
//! ```
//! This emits: `(specs->'engine'->>'cylinders')::int4 > $1`
use crateModel;
use HashMap;
use Mutex;
// ── String path interner ─────────────────────────────────────────────────────
// Typed path construction (`{T}Path<M>` methods) must produce
// `JsonbPathRef<M, V>` which carries a `&'static str` for the dotted path.
// Since the set of unique paths in a compiled binary is bounded (one per
// leaf field per schema type), leaking each unique dotted string once is
// acceptable — the total allocation is proportional to the number of
// distinct path queries in the program, not the number of rows.
// The interner is a `Mutex<HashMap>` rather than a thread-local because
// `JsonbPathRef` must be `Send` (it escapes the filter closure into the
// async executor). A thread-local would give a different reference for the
// same path string on each thread, which would be incorrect for
// multi-threaded use.
// # Performance
// The lock is taken once per unique `(column, path_segments)` combination
// at filter-build time. Identical paths on subsequent calls are served by
// the interner without allocation (they hit the HashMap entry and return the
// already-leaked `&'static str`). For single-threaded access (the overwhelm-
// ingly common case for filter closures), the lock is always uncontended.
static PATH_INTERNER: = new;
// ── Segment-slice interner ────────────────────────────────────────────────────
// The `__new_from_slice` method on derived `{T}Path<M>` types needs a
// `&'static [&'static str]` slice of path segments. Without interning,
// every call to a nested accessor would `Box::leak` a fresh slice, producing
// unbounded allocation growth proportional to query volume rather than
// schema shape — a memory leak in long-running web servers.
// `intern_path_slice` caches slices keyed by their content (as a
// `Vec<&'static str>`), so the same path traversal reuses the identical
// slice on every call. Growth is bounded by unique paths, same as
// `PATH_INTERNER` above.
static PATH_SLICE_INTERNER: =
new;
/// Intern a dotted JSONB path string.
/// Joins `segments` with `.` to form the dotted path string, then either
/// returns the previously-interned `&'static str` for that string, or
/// leaks a new `Box<str>` and stores the reference in the global interner.
/// # Panics
/// Panics if the interner mutex is poisoned (which only happens if a previous
/// holder panicked while holding the lock — an unrecoverable situation).
/// # Validity
/// Each segment must satisfy the plain-identifier rules enforced by
/// `crate::jsonb::path::is_plain_ident` — the derive macro only emits calls
/// to `intern_path` with literal field name strings, so this is guaranteed
/// at derive time. The resulting dotted string is validated by
/// `JsonbPathRef::new` anyway.
/// Intern a `&'static [&'static str]` segment slice.
/// Returns the previously-interned slice for the given segment sequence, or
/// leaks a new `Box<[&'static str]>` on first encounter and caches it.
/// # Why this is needed
/// `{T}Path<M>::__new` carries a `&'static [&'static str]` base_path. Without
/// interning, every call to a nested accessor would `Box::leak` a fresh slice,
/// producing unbounded allocation growth proportional to query-build volume.
/// With interning, the slice is allocated once per unique segment sequence,
/// bounding growth by the number of distinct JSONB paths in the program.
/// # Panics
/// Panics if the interner mutex is poisoned.
/// # Pointer stability
/// Two calls with equal segment sequences return the same `&'static` pointer
/// pointer equality can be used in tests to confirm caching is active.
/// Marker trait for structs that participate in the typed JSONB deep-path API.
/// Implementing this trait (via `#[derive(JsonbSchema)]`) causes the proc macro
/// to emit a `{T}Path<M>` struct providing one method per field. Scalar fields
/// (from the cast-matrix allowlist) return a
/// [`JsonbPathRef<M, V>`](crate::jsonb::JsonbPathRef) ready for comparison;
/// nested fields return the nested type's `Path<M>` with the path accumulator
/// extended by one segment.
/// # Implementing manually
/// Manual implementation is possible but strongly discouraged — the derive
/// handles path-accumulator bookkeeping correctly. If you implement manually,
/// the `Path<M>` type must accept `(base_column: &'static str,
/// base_path: &'static [&'static str])` and propagate those through every
/// nested accessor.
/// # The `Path` associated type
/// `type Path<M: Model>` is generic over the model so that `JsonbPathRef<M, V>`
/// nodes carry the model marker — mismatching a `VehicleSpecsPath<Post>` with
/// a `Vehicle`-targeted `QuerySet` is a compile error, not a runtime bug.
/// # Bounded-leak guarantee
/// All interning (both string paths via [`intern_path`] and segment slices via
/// [`intern_path_slice`]) is bounded by the product of (distinct JSONB schema
/// types x field counts x nesting depth) across the compiled program — typically
/// hundreds of entries for a real app. Every unique path is leaked at most once,
/// so memory growth reaches zero at steady state once every unique path has been
/// observed for the first time.