also looking at this
fix(sqlite): a nested query's bindings were being thrown away
#1085
2 files
+156
-6
| @@ -263,16 +263,39 @@ function createSQLiteSQL(filename: string): SQL { | ||
| 263 | 263 | for (let i = 0; i < values.length; i++) { |
| 264 | 264 | const value = values[i] |
| 265 | 265 | |
| 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 | } | |
| 266 | 294 | // 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)) { | |
| 268 | 296 | const rawValue = value.__raw ? value.value : (typeof value.raw === 'string' ? value.raw : value.raw()) |
| 269 | 297 | sql += rawValue + (strings[i + 1] || '') |
| 270 | 298 | } |
| 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 | } | |
| 276 | 299 | // Handle arrays - expand to placeholders |
| 277 | 300 | else if (Array.isArray(value)) { |
| 278 | 301 | const placeholders = value.map(() => '?').join(', ') |