ReviewOS

also looking at this

stacks/bun-query-builder

fix(browser)!: refuse orWhere rather than sending it as an AND

#1131
Merged glennmichael123 wants to merge fix/1096-browser-orwhere into main
2 files +136 -34
packages/bun-query-builder/src/browser.tsmodified+24-34
Changes to packages/bun-query-builder/src/browser.ts
@@ -401,23 +401,19 @@ export class BrowserQueryBuilder<T = any> {
401401 */
402402 orWhere(column: string, value: unknown): this
403403 orWhere(column: string, operator: WhereOperator, value: unknown): this
404 orWhere(column: string, operatorOrValue: unknown, value?: unknown): this {
405 if (value === undefined) {
406 this.state.wheres.push({
407 column,
408 operator: '=',
409 value: operatorOrValue,
410 boolean: 'or',
411 })
412 }
413 else {
414 this.state.wheres.push({
415 column,
416 operator: operatorOrValue as WhereOperator,
417 value,
418 boolean: 'or',
419 })
420 }
404 orWhere(_column: string, _operatorOrValue: unknown, _value?: unknown): this {
405 // Refused rather than recorded. `boolean: 'or'` was pushed onto the where
406 // list and buildQueryParams never read it, so the term went out as a plain
407 // filter and any receiving API read the whole set as a conjunction:
408 //
409 // .where('role', 'admin').orWhere('role', 'moderator')
410 // -> role=admin&role=moderator (AND, not OR)
411 //
412 // That is a superset of the intended rows, returned without complaint. A
413 // wire spelling for OR would have to be agreed with whatever serves these
414 // requests, and inventing one here would ship a format nothing reads —
415 // the same silent failure, one layer further away. See #1096.
416 throw new TypeError('[browser] orWhere() is not supported by this client. The query-param format it builds has no spelling for a disjunction, so the term would be sent as a plain filter and the server would read it as AND \u2014 silently returning a superset of the rows you asked for. Express the alternatives as a single filter (whereIn), or issue one request per branch and merge the results.')
421417 return this
422418 }
423419
@@ -536,8 +532,8 @@ export class BrowserQueryBuilder<T = any> {
536532 private buildQueryParams(): URLSearchParams {
537533 const params = new URLSearchParams()
538534
539 // As in BrowserModelQueryBuilder: `boolean` is not read, so an `orWhere`
540 // serialises as an AND. Left alone deliberately — see #1096.
535 // Every recorded where is a conjunct: orWhere() now throws rather than
536 // recording a disjunction this format cannot express. See #1096.
541537 // Add where clauses as query params
542538 for (const where of this.state.wheres) {
543539 if (where.operator === '=') {
@@ -887,17 +883,13 @@ else {
887883 }
888884
889885 orWhere<K extends BrowserColumnName<TDef>>(
890 column: K,
891 operatorOrValue: WhereOperator | (K extends keyof BrowserModelAttributes<TDef> ? BrowserModelAttributes<TDef>[K] : unknown),
892 value?: K extends keyof BrowserModelAttributes<TDef> ? BrowserModelAttributes<TDef>[K] : unknown
886 _column: K,
887 _operatorOrValue: WhereOperator | (K extends keyof BrowserModelAttributes<TDef> ? BrowserModelAttributes<TDef>[K] : unknown),
888 _value?: K extends keyof BrowserModelAttributes<TDef> ? BrowserModelAttributes<TDef>[K] : unknown
893889 ): BrowserModelQueryBuilder<TDef, TSelected> {
894 if (value === undefined) {
895 this._wheres.push({ column: column as string, operator: '=', value: operatorOrValue, boolean: 'or' })
896 }
897else {
898 this._wheres.push({ column: column as string, operator: operatorOrValue as WhereOperator, value, boolean: 'or' })
899 }
900 return this
890 // See BrowserQueryBuilder.orWhere: the recorded `boolean` was never read,
891 // so a disjunction was transmitted as a conjunction. #1096.
892 throw new TypeError('[browser] orWhere() is not supported by this client. The query-param format it builds has no spelling for a disjunction, so the term would be sent as a plain filter and the server would read it as AND \u2014 silently returning a superset of the rows you asked for. Express the alternatives as a single filter (whereIn), or issue one request per branch and merge the results.')
901893 }
902894
903895 whereIn<K extends BrowserColumnName<TDef>>(
@@ -985,11 +977,9 @@ else {
985977 private buildQueryParams(): URLSearchParams {
986978 const params = new URLSearchParams()
987979
988 // `where.boolean` is not read here, so an `orWhere` serialises exactly like
989 // a `where` and the server reads it as AND. Representing OR needs a wire
990 // format that this file has no precedent for, so it is left alone rather
991 // than invented — see #1096. BrowserQueryBuilder.buildQueryParams has the
992 // same gap.
980 // Every recorded where is a conjunct, which is what this format can say.
981 // orWhere() throws rather than recording a disjunction that would be
982 // transmitted — and read by the server — as one more AND term. See #1096.
993983 for (const where of this._wheres) {
994984 if (where.operator === '=') {
995985 params.append(where.column, String(where.value))