Skip to content

Commit

Permalink
feat: add orWhere method to QueryBuilder for flexible query conditions
Browse files Browse the repository at this point in the history
  • Loading branch information
zfben committed Feb 14, 2025
1 parent 400b850 commit 3dd7bf7
Show file tree
Hide file tree
Showing 2 changed files with 101 additions and 6 deletions.
29 changes: 29 additions & 0 deletions src/__tests__/query-builder/query.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -198,6 +198,35 @@ describe('QueryBuilder/query', () => {
})
})

describe('orWhere', () => {
it('column and value', async () => {
const result = await new QueryBuilder(client, 'query')
.where('name', 'Alice')
.orWhere('name', 'Bob')
.pluck('id')

expect(result).toEqual([1, 2])
})

it('column, operator, and value', async () => {
const result = await new QueryBuilder(client, 'query')
.where('name', 'Alice')
.orWhere('name', '!=', 'Bob')
.pluck('id')

expect(result).toEqual([1])
})

it('multiple conditions', async () => {
const result = await new QueryBuilder(client, 'query')
.where('name', 'Alice')
.orWhere('id', '>', 1)
.pluck('id')

expect(result).toEqual([1, 2])
})
})

describe('limit', () => {
it('limits the number of results', async () => {
const result = await new QueryBuilder(client, 'query')
Expand Down
78 changes: 72 additions & 6 deletions src/query-builder.ts
Original file line number Diff line number Diff line change
Expand Up @@ -85,6 +85,7 @@ export class QueryBuilder<
private table: T
private selectColumns: (ColumnName<T> | JsonSelectField<T>)[] = []
private whereConditions: {
type: 'AND' | 'OR'
column: ColumnName<T> | string
operator: Operator
value: any
Expand Down Expand Up @@ -168,6 +169,7 @@ export class QueryBuilder<
!['IS NULL', 'IS NOT NULL'].includes(operatorOrValue)
) {
this.whereConditions.push({
type: 'AND',
column,
operator: '=',
value: operatorOrValue,
Expand All @@ -180,6 +182,68 @@ export class QueryBuilder<
throw new Error(`Invalid operator: ${operatorOrValue}`)

this.whereConditions.push({
type: 'AND',
column,
operator: operatorOrValue,
value,
})

return this
}

/**
* Applies an OR WHERE condition to the query builder.
* @param column - The column to filter on.
* @param operator - The operator to use for comparison.
* @param value - The value to compare against.
* @example
* ```ts
* await query('users').where('id', 1).orWhere('id', 2) // WHERE id = 1 OR id = 2
* ```
*/
orWhere<C extends ColumnName<T>>(
column: C,
operator: (typeof NormalOperators)[number],
value?: ColumnValue<T, C>
): QueryBuilder<T, TResult>
orWhere<C extends ColumnName<T>>(
column: C,
operator: (typeof ArrayOperators)[number],
value: ColumnValue<T, C>[]
): QueryBuilder<T, TResult>
orWhere<C extends ColumnName<T>>(
column: C,
operator: (typeof NullOperators)[number]
): QueryBuilder<T, TResult>
orWhere<C extends ColumnName<T>>(
column: C,
operator: (typeof JsonOperators)[number],
value: Partial<ColumnValue<T, C>>
): QueryBuilder<T, TResult>
orWhere<C extends ColumnName<T>>(
column: C,
value: ColumnValue<T, C>
): QueryBuilder<T, TResult>
orWhere(column: ColumnName<T>, operatorOrValue: Operator | any, value?: any) {
if (
typeof value === 'undefined' &&
!['IS NULL', 'IS NOT NULL'].includes(operatorOrValue)
) {
this.whereConditions.push({
type: 'OR',
column,
operator: '=',
value: operatorOrValue,
})

return this
}

if (!Operators.includes(operatorOrValue))
throw new Error(`Invalid operator: ${operatorOrValue}`)

this.whereConditions.push({
type: 'OR',
column,
operator: operatorOrValue,
value,
Expand Down Expand Up @@ -249,23 +313,25 @@ export class QueryBuilder<
sql.push(
'WHERE',
this.whereConditions
.map(({ column, operator, value }) => {
.map(({ type, column, operator, value }, index) => {
const prefix = index > 0 ? `${type} ` : ''

if (operator === 'IS NULL' || operator === 'IS NOT NULL')
return `${escapeIdentifier(column)} ${operator}`
return `${prefix}${escapeIdentifier(column)} ${operator}`

if (operator === 'IN' || operator === 'NOT IN') {
params.push(value)

if (mode === 'update')
return `${escapeIdentifier(column)} = ANY(?)`
return `${prefix}${escapeIdentifier(column)} = ANY(?)`

return `${escapeIdentifier(column)} ${operator} (${value.map(() => '?').join(',')})`
return `${prefix}${escapeIdentifier(column)} ${operator} (${value.map(() => '?').join(',')})`
}

params.push(value)
return `${escapeIdentifier(column)} ${operator} ?`
return `${prefix}${escapeIdentifier(column)} ${operator} ?`
})
.join(' AND ')
.join(' ')
)
}

Expand Down

0 comments on commit 3dd7bf7

Please sign in to comment.