You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
**A nested render does not pick a bucket (#432).** An `Exists(...)`, a projected `Subquery(...)` or an `__@in` subquery renders inside the PARENT's clause, so its values are marked, lifted and re-emitted as one clause-ordered run at the parent's marker position (`nested_parameter_mark` / `detach_nested_run!`), with `own_contexts=true` so the inner build files its values under its own clauses first. Binding order is not text order: a build binds joins last and renders them first, which is what the buckets exist to reconcile.
45
+
44
46
Parameter collector model:
45
47
46
48
-`AbstractPormGParam`: base abstraction for all collectors
47
49
-`PormGPostgresParam`: linear collector for `$1`, `$2`, ... placeholders
48
-
-`PormGPositionalParam`: bucketed collector for positional `?` placeholders
50
+
-`PormGSQLiteParam`: bucketed collector for positional `?` placeholders (concrete type `SQLiteParameterizedQuery`)
49
51
50
52
When changing parameter behavior, verify:
51
53
@@ -54,7 +56,7 @@ When changing parameter behavior, verify:
54
56
- parent and subquery inheritance behavior
55
57
- HAVING alias promotion placement
56
58
- custom join parameter routing into the join bucket
57
-
- flattening through `get_final_parameters(::PormGPositionalParam)` in SQL-clause order
59
+
- flattening through `get_final_parameters(::PormGSQLiteParam)` in SQL-clause order
58
60
59
61
Query-building context rules:
60
62
@@ -235,7 +237,8 @@ When introducing a new parameterized SQL clause or changing clause order, update
235
237
236
238
- bucket struct fields in `parameters.jl`
237
239
-`set_context!` call sites in builder modules
238
-
-`get_final_parameters` flatten order
240
+
-`_BUCKET_ORDER` in `parameters.jl` — the single list both `get_final_parameters` and
241
+
`detach_nested_run!` (#432) read; there is no second copy to keep in sync
239
242
- unit coverage in the canonical alignment tests
240
243
- integration coverage if the behavior is user-visible
-**Positional-parameter buckets (deliberate, contained)** — for SQLite the positional `?` markers
182
182
are collected into per-clause buckets and flattened in SQL-clause order
183
183
(`:cte → :select → :update → :join → :where → :having`). The flatten order is single-sourced in
184
-
`get_final_parameters` and guarded by `test_alignment_sqlite.jl` / `test_parameters.jl`; the only
185
-
cost is that *adding a new bucket* is a documented multi-site edit (see the QueryBuilder skill's
186
-
maintenance checklist). Noted here so it is not mistaken for accidental coupling.
184
+
`_BUCKET_ORDER` (which `get_final_parameters` and the nested-run machinery both read) and guarded by `test_alignment_sqlite.jl` / `test_parameters.jl`. Noted here
185
+
so it is not mistaken for accidental coupling.
186
+
187
+
Two costs, not one. *Adding a new bucket* is a documented multi-site edit (see the QueryBuilder
188
+
skill's maintenance checklist). The subtler one: a bucket is chosen by the builder phase that binds
189
+
a value, while correctness depends on where that value's `?` is **emitted** — and those diverge
190
+
whenever a fragment renders somewhere other than its own clause. Three bugs came out of that gap —
191
+
#421 a relocated fragment and #432 a nested render, both SQLite-only, plus #441 a discarded
192
+
projection, whose *parameter* desync was SQLite-only but which dropped the column itself on **both**
193
+
backends. All three were silent. A nested render now re-emits its values as one clause-ordered run at its own marker
194
+
position (`nested_parameter_mark` / `detach_nested_run!`). All four such sites in the **read**
195
+
builder go through it — `Exists`, a projected `Subquery`, an `__@in` subquery, and a CTE body.
196
+
197
+
`deletion.jl` splices subqueries into hand-built `DELETE`/`UPDATE` clauses without it, and is
198
+
correct today only by coincidence: it builds a fresh collector per statement, and a lone
199
+
subquery's own text order *is*`_BUCKET_ORDER`, so the flatten happens to agree. That is a
200
+
narrower claim than "contained", and deliberately so.
187
201
188
202
!!! note "\"Async-first\" is backend-specific"
189
203
For **PostgreSQL** the async path is genuine: the pool lock is released before the round-trip and
0 commit comments