ReviewOS

also looking at this

stacks/bun-query-builder

fix(sqlite): a nested query's bindings were being thrown away

#1085
Merged glennmichael123 wants to merge fix/nested-fragment-params into main
2 files +156 -6
packages/bun-query-builder/src/db.tsmodified+29-6
Changes to packages/bun-query-builder/src/db.ts
@@ -263,16 +263,39 @@ function createSQLiteSQL(filename: string): SQL {
263263 for (let i = 0; i < values.length; i++) {
264264 const value = values[i]
265265
266 // Handle query objects (nested queries) FIRST.
267 //
268 // Order matters, and getting it wrong is stacksjs/bun-query-builder#1084.
269 // The queries this wrapper hands back (see the object returned below)
270 // carry `raw: () => sql` so callers can read their text — which means
271 // they satisfy the raw-marker test underneath as well. With that branch
272 // first, interpolating one query into another spliced its SQL TEXT and
273 // silently threw away its `values`, so the placeholders arrived with no
274 // bindings behind them:
275 //
276 // db.selectFrom('t').where('id', '>', 0).paginate(5)
277 // -> SQLite query expected 3 values, received 2
278 //
279 // and the COUNT half of the same call failed the other way: bun:sqlite
280 // only arity-checks when some values are passed, so `WHERE id > ?` bound
281 // the placeholder to NULL, matched nothing, and reported total 0 rather
282 // than throwing. `paginate` nests twice, which is why it was the loudest
283 // victim, but every method that composes a query into a template — the
284 // pagination family, exists/first/value, the soft-delete filter — was
285 // dropping bindings the same way.
286 //
287 // The discriminator is `values` being an ARRAY: raw markers carry
288 // `__raw` and no `values`, and Bun's native query objects expose `values`
289 // as a method, so neither is captured here.
290 if (value && typeof value === 'object' && typeof value.sql === 'string' && Array.isArray(value.values)) {
291 sql += value.sql + (strings[i + 1] || '')
292 params.push(...value.values)
293 }
266294 // Handle raw SQL markers (from sql('identifier') or sql.raw())
267 if (value && typeof value === 'object' && (value.__raw || value.raw)) {
295 else if (value && typeof value === 'object' && (value.__raw || value.raw)) {
268296 const rawValue = value.__raw ? value.value : (typeof value.raw === 'string' ? value.raw : value.raw())
269297 sql += rawValue + (strings[i + 1] || '')
270298 }
271 // Handle query objects (nested queries)
272 else if (value && typeof value === 'object' && 'sql' in value && 'values' in value) {
273 sql += value.sql + (strings[i + 1] || '')
274 params.push(...value.values)
275 }
276299 // Handle arrays - expand to placeholders
277300 else if (Array.isArray(value)) {
278301 const placeholders = value.map(() => '?').join(', ')