You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
323 lines
9.5 KiB
323 lines
9.5 KiB
import {EscapedValue, isWhereClause, isWhereGroup, WhereStatement} from '../types.ts'
|
|
import {escape, SQLWhereOperator, WherePreOperator} from '../types.ts'
|
|
import {WhereBuilderFunction} from './Select.ts'
|
|
import {apply_filter_to_where, QueryFilter} from '../../model/filter.ts'
|
|
import {Scope} from '../Scope.ts'
|
|
import {FunctionScope, ScopeFunction} from '../scope/FunctionScope.ts'
|
|
import RawValue from '../RawValue.ts'
|
|
|
|
/**
|
|
* Query builder mixin for queries that have WHERE clauses.
|
|
*/
|
|
export class WhereBuilder {
|
|
/**
|
|
* The where clauses to be applied.
|
|
* @type Array<WhereStatement>
|
|
*/
|
|
protected _wheres: WhereStatement[] = []
|
|
|
|
/**
|
|
* The scopes to be applied.
|
|
* @type Array<Scope>
|
|
*/
|
|
protected _scopes: Scope[] = []
|
|
|
|
/**
|
|
* Get the where clauses applied to the query.
|
|
* @type Array<WhereStatement>
|
|
*/
|
|
get where_items() {
|
|
return this._wheres
|
|
}
|
|
|
|
/**
|
|
* Remove a scope from this query.
|
|
* @param {typeof Scope} scope
|
|
* @return self
|
|
*/
|
|
without_scope(scope: typeof Scope) {
|
|
this._scopes = this._scopes.filter(x => !(x instanceof Scope))
|
|
return this
|
|
}
|
|
|
|
/**
|
|
* Add a scope to this query.
|
|
* @param {Scope | ScopeFunction} scope
|
|
* @return self
|
|
*/
|
|
with_scope(scope: Scope | ScopeFunction) {
|
|
if ( scope instanceof Scope ) {
|
|
this._scopes.push(scope)
|
|
} else {
|
|
this._scopes.push(new FunctionScope(scope))
|
|
}
|
|
return this
|
|
}
|
|
|
|
/**
|
|
* Add multiple scopes to this query.
|
|
* @param {Array<Scope | ScopeFunction>} scopes
|
|
* @return self
|
|
*/
|
|
with_scopes(scopes: (Scope | ScopeFunction)[]) {
|
|
scopes.forEach(scope => this.with_scope(scope))
|
|
return this
|
|
}
|
|
|
|
/**
|
|
* Cast the where clause to raw SQL.
|
|
* @param {Array<WhereStatement>} [wheres]
|
|
* @param {number} [level = 0] - the indentation level
|
|
*/
|
|
wheres_to_sql(wheres?: WhereStatement[], level = 0): string {
|
|
this._scopes.forEach(scope => scope.apply(this))
|
|
const indent = Array(level * 2).fill(' ').join('')
|
|
let statements = []
|
|
for ( const where of wheres || this._wheres ) {
|
|
if ( isWhereClause(where) ) {
|
|
statements.push(`${indent}${statements.length < 1 ? '' : where.preop + ' '}${where.field} ${where.operator} ${where.operand}`)
|
|
} else if ( isWhereGroup(where) ) {
|
|
statements.push(`${indent}${statements.length < 1 ? '' : where.preop + ' '}(\n${this.wheres_to_sql(where.items, level + 1)}\n${indent})`)
|
|
}
|
|
}
|
|
|
|
return statements.filter(Boolean).join('\n')
|
|
}
|
|
|
|
/**
|
|
* Internal helper method for creating where clauses.
|
|
* @param {WherePreOperator} preop
|
|
* @param {string | WhereBuilderFunction} field
|
|
* @param {SQLWhereOperator} [operator]
|
|
* @param [operand]
|
|
* @private
|
|
*/
|
|
private _createWhere(preop: WherePreOperator, field: string | WhereBuilderFunction, operator?: SQLWhereOperator, operand?: any) {
|
|
if ( typeof field === 'function' ) {
|
|
const where_builder = new WhereBuilder()
|
|
field(where_builder)
|
|
this._wheres.push({
|
|
preop,
|
|
items: where_builder.where_items,
|
|
})
|
|
} else if ( field && operator && typeof operand !== 'undefined' ) {
|
|
this._wheres.push({
|
|
field, operator, operand: escape(operand), preop
|
|
})
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Add a basic where clause to the query.
|
|
* @param {string | WhereBuilderFunction} field
|
|
* @param {SQLWhereOperator} [operator]
|
|
* @param [operand]
|
|
* @return self
|
|
*/
|
|
where(field: string | WhereBuilderFunction, operator?: SQLWhereOperator, operand?: any) {
|
|
this._createWhere('AND', field, operator, operand)
|
|
return this
|
|
}
|
|
|
|
/**
|
|
* Add a where clause to the query, without escaping the operand.
|
|
* @param {string | WhereBuilderFunction} field
|
|
* @param {SQLWhereOperator} [operator]
|
|
* @param [operand]
|
|
* @return self
|
|
*/
|
|
whereRaw(field: string, operator: SQLWhereOperator, operand: string) {
|
|
this._createWhere('AND', field, operator, new RawValue(operand))
|
|
return this
|
|
}
|
|
|
|
/**
|
|
* Add an OR WHERE clause to the query, without escaping the operand.
|
|
* @param {string | WhereBuilderFunction} field
|
|
* @param {SQLWhereOperator} [operator]
|
|
* @param [operand]
|
|
* @return self
|
|
*/
|
|
orWhereRaw(field: string, operator: SQLWhereOperator, operand: string) {
|
|
this._createWhere('OR', field, operator, new RawValue(operand))
|
|
return this
|
|
}
|
|
|
|
/**
|
|
* Add a WHERE ... IN (...) clause to the query.
|
|
* @param {string} field
|
|
* @param {EscapedValue} values
|
|
* @return self
|
|
*/
|
|
whereIn(field: string, values: EscapedValue) {
|
|
this._wheres.push({
|
|
field,
|
|
operator: 'IN',
|
|
operand: escape(values),
|
|
preop: 'AND',
|
|
})
|
|
return this
|
|
}
|
|
|
|
/**
|
|
* Add a WHERE NOT ... clause to the query.
|
|
* @param {string | WhereBuilderFunction} field
|
|
* @param {SQLWhereOperator} [operator]
|
|
* @param [operand]
|
|
* @return self
|
|
*/
|
|
whereNot(field: string | WhereBuilderFunction, operator?: SQLWhereOperator, operand?: EscapedValue) {
|
|
this._createWhere('AND NOT', field, operator, operand)
|
|
return this
|
|
}
|
|
|
|
/**
|
|
* Add a WHERE ... NOT IN (...) clause to the query.
|
|
* @param {string} field
|
|
* @param {EscapedValue} values
|
|
* @return self
|
|
*/
|
|
whereNotIn(field: string, values: EscapedValue) {
|
|
this._wheres.push({
|
|
field,
|
|
operator: 'NOT IN',
|
|
operand: escape(values),
|
|
preop: 'AND'
|
|
})
|
|
return this
|
|
}
|
|
|
|
/**
|
|
* Add an OR WHERE ... clause to the query.
|
|
* @param {string | WhereBuilderFunction} field
|
|
* @param {SQLWhereOperator} [operator]
|
|
* @param [operand]
|
|
* @return self
|
|
*/
|
|
orWhere(field: string | WhereBuilderFunction, operator?: SQLWhereOperator, operand?: EscapedValue) {
|
|
this._createWhere('OR', field, operator, operand)
|
|
return this
|
|
}
|
|
|
|
/**
|
|
* Add an OR WHERE NOT clause to the query.
|
|
* @param {string | WhereBuilderFunction} field
|
|
* @param {SQLWhereOperator} [operator]
|
|
* @param [operand]
|
|
* @return self
|
|
*/
|
|
orWhereNot(field: string | WhereBuilderFunction, operator?: SQLWhereOperator, operand?: EscapedValue) {
|
|
this._createWhere('OR NOT', field, operator, operand)
|
|
return this
|
|
}
|
|
|
|
/**
|
|
* Add an OR WHERE ... IN (...) clause to the query.
|
|
* @param {string} field
|
|
* @param {EscapedValue} values
|
|
* @return self
|
|
*/
|
|
orWhereIn(field: string, values: EscapedValue) {
|
|
this._wheres.push({
|
|
field,
|
|
operator: 'IN',
|
|
operand: escape(values),
|
|
preop: 'OR',
|
|
})
|
|
return this
|
|
}
|
|
|
|
/**
|
|
* Add an OR WHERE ... NOT IN (...) clause to the query.
|
|
* @param {string} field
|
|
* @param {EscapedValue} values
|
|
* @return self
|
|
*/
|
|
orWhereNotIn(field: string, values: EscapedValue) {
|
|
this._wheres.push({
|
|
field,
|
|
operator: 'NOT IN',
|
|
operand: escape(values),
|
|
preop: 'OR',
|
|
})
|
|
return this
|
|
}
|
|
|
|
/**
|
|
* Add a WHERE ... BETWEEN ... AND ... clause to the query.
|
|
* @param {string} field
|
|
* @param {EscapedValue} lower_bound
|
|
* @param {EscapedValue} upper_bound
|
|
* @return self
|
|
*/
|
|
whereBetween(field: string, lower_bound: EscapedValue, upper_bound: EscapedValue) {
|
|
this._wheres.push({
|
|
field,
|
|
operator: 'BETWEEN',
|
|
operand: `${escape(lower_bound)} AND ${escape(upper_bound)}`,
|
|
preop: 'AND',
|
|
})
|
|
return this
|
|
}
|
|
|
|
/**
|
|
* Add an OR WHERE ... BETWEEN ... AND ... clause to the query.
|
|
* @param {string} field
|
|
* @param {EscapedValue} lower_bound
|
|
* @param {EscapedValue} upper_bound
|
|
* @return self
|
|
*/
|
|
orWhereBetween(field: string, lower_bound: EscapedValue, upper_bound: EscapedValue) {
|
|
this._wheres.push({
|
|
field,
|
|
operator: 'BETWEEN',
|
|
operand: `${escape(lower_bound)} AND ${escape(upper_bound)}`,
|
|
preop: 'OR',
|
|
})
|
|
return this
|
|
}
|
|
|
|
/**
|
|
* Add a WHERE ... NOT BETWEEN ... AND ... clause to the query.
|
|
* @param {string} field
|
|
* @param {EscapedValue} lower_bound
|
|
* @param {EscapedValue} upper_bound
|
|
* @return self
|
|
*/
|
|
whereNotBetween(field: string, lower_bound: EscapedValue, upper_bound: EscapedValue) {
|
|
this._wheres.push({
|
|
field,
|
|
operator: 'NOT BETWEEN',
|
|
operand: `${escape(lower_bound)} AND ${escape(upper_bound)}`,
|
|
preop: 'AND',
|
|
})
|
|
return this
|
|
}
|
|
|
|
/**
|
|
* Add an OR WHERE ... NOT BETWEEN ... AND ... clause to the query.
|
|
* @param {string} field
|
|
* @param {EscapedValue} lower_bound
|
|
* @param {EscapedValue} upper_bound
|
|
* @return self
|
|
*/
|
|
orWhereNotBetween(field: string, lower_bound: EscapedValue, upper_bound: EscapedValue) {
|
|
this._wheres.push({
|
|
field,
|
|
operator: 'NOT BETWEEN',
|
|
operand: `${escape(lower_bound)} AND ${escape(upper_bound)}`,
|
|
preop: 'OR',
|
|
})
|
|
return this
|
|
}
|
|
|
|
/**
|
|
* Apply a filter object to the query.
|
|
* @param {QueryFilter} filter
|
|
* @return self
|
|
*/
|
|
filter(filter: QueryFilter) {
|
|
return apply_filter_to_where(filter, this)
|
|
}
|
|
}
|