ReviewOS

stacks/bun-query-builder

browser.ts: orWhere is serialised as AND, and 'not in' is serialised identically to 'in'

#1096
Closed glennmichael123 opened this 23 days ago · 0 comments
23 days ago

Found on main @ 68fd61c. Established by reading buildQueryParams — these are HTTP query-param builders, so confirming end-to-end needs a server, but the code is unambiguous on both counts.

BrowserModelQueryBuilder.buildQueryParams() (packages/bun-query-builder/src/browser.ts, ~970) turns the recorded _wheres into a URLSearchParams. Two filters change meaning in transit.

1. orWhere is indistinguishable from where

orWhere records the disjunction correctly:

this._wheres.push({ column, operator: '=', value: operatorOrValue, boolean: 'or' })

but where.boolean is never readbuildQueryParams iterates every recorded where and appends each as a parameter, which any receiving API will read as a conjunction:

Model.query().where('role', 'admin').orWhere('role', 'moderator')
// role=admin&role=moderator   -> AND, not OR

So the disjunction is silently narrowed to a conjunction. boolean is recorded on every push in this file and read nowhere in it.

2. not in sends the same request as in

else if ((where.operator === 'in' || where.operator === 'not in') && Array.isArray(where.value)) {
  params.append(`${where.column}[]`, where.value.join(','))
}

Both branches produce column[]=a,b. whereNotIn('status', ['banned']) transmits as whereIn('status', ['banned']) — an inverted filter, which is the worst direction for something usually used to exclude.

Note the neighbouring is / is not cases are distinguished (filter[col][is] vs filter[col][is_not]), so the encoding scheme has room for the distinction; not in just wasn't given one.

Impact

Both are silent. Neither throws, neither logs, and the returned rows look plausible — a superset for the first, a complement for the second.

Not addressed here

What the receiving API is expected to accept. If there is a documented contract for the query-param format, not in needs a spelling in it (e.g. filter[col][not_in]) and boolean needs one too, or orWhere should throw on this builder rather than pretend.

Sign in to comment on this issue.