import { EscapedValue, HavingPreOperator, HavingStatement, SQLHavingOperator, escape, isHavingClause, isHavingGroup } from '../types.ts' import {HavingBuilderFunction} from './Select.ts' /** * Mixin class for queries supporting HAVING clauses. */ export class HavingBuilder { /** * Having clauses to apply to the query. * @type Array */ protected _havings: HavingStatement[] = [] /** * Get the having clauses applied to the query. * @type Array */ get having_items() { return this._havings } /** * Cast the having statements to SQL. * @param {HavingStatement} [havings] * @param {number} [level = 0] - the indentation level * @return string */ havings_to_sql(havings?: HavingStatement[], level = 0): string { const indent = Array(level * 2).fill(' ').join('') let statements = [] for ( const having of havings || this._havings ) { if ( isHavingClause(having) ) { statements.push(`${indent}${statements.length < 1 ? '' : having.preop + ' '}${having.field} ${having.operator} ${having.operand}`) } else if ( isHavingGroup(having) ) { statements.push(`${indent}${statements.length < 1 ? '' : having.preop + ' '}(\n${this.havings_to_sql(having.items, level + 1)}\n${indent})`) } } return statements.filter(Boolean).join('\n') } /** * Internal helper for creating a HAVING clause. * @param {HavingPreOperator} preop * @param {string | HavingBuilderFunction} field * @param {SQLHavingOperator} [operator] * @param [operand] * @private */ private _createHaving(preop: HavingPreOperator, field: string | HavingBuilderFunction, operator?: SQLHavingOperator, operand?: any) { if ( typeof field === 'function' ) { const having_builder = new HavingBuilder() field(having_builder) this._havings.push({ preop, items: having_builder.having_items, }) } else if ( field && operator && typeof operand !== 'undefined' ) { this._havings.push({ field, operator, operand: escape(operand), preop }) } } /** * Add a basic HAVING clause to the query. * @param {string | HavingBuilderFunction} field * @param {SQLHavingOperator} [operator] * @param [operand] * @return HavingBuilder */ having(field: string | HavingBuilderFunction, operator?: SQLHavingOperator, operand?: any) { this._createHaving('AND', field, operator, operand) return this } /** * Add a HAVING ... IN (...) clause to the query. * @param {string} field * @param {EscapedValue} values * @return HavingBuilder */ havingIn(field: string, values: EscapedValue) { this._havings.push({ field, operator: 'IN', operand: escape(values), preop: 'AND', }) return this } /** * Add an HAVING NOT ... clause to the query. * @param {string | HavingBuilderFunction} field * @param {SQLHavingOperator} operator * @param [operand] * @return HavingBuilder */ havingNot(field: string | HavingBuilderFunction, operator?: SQLHavingOperator, operand?: EscapedValue) { this._createHaving('AND NOT', field, operator, operand) return this } /** * Add an HAVING NOT ... IN (...) clause to the query. * @param {string} field * @param {EscapedValue} values * @return HavingBuilder */ havingNotIn(field: string, values: EscapedValue) { this._havings.push({ field, operator: 'NOT IN', operand: escape(values), preop: 'AND' }) return this } /** * Add an OR HAVING ... clause to the query. * @param {string | HavingBuilderFunction} field * @param {SQLHavingOperator} [operator] * @param [operand] * @return HavingBuilder */ orHaving(field: string | HavingBuilderFunction, operator?: SQLHavingOperator, operand?: EscapedValue) { this._createHaving('OR', field, operator, operand) return this } /** * Add an HAVING OR NOT ... clause to the query. * @param {string | HavingBuilderFunction} field * @param {SQLHavingOperator} [operator] * @param [operand] * @return HavingBuilder */ orHavingNot(field: string | HavingBuilderFunction, operator?: SQLHavingOperator, operand?: EscapedValue) { this._createHaving('OR NOT', field, operator, operand) return this } /** * Add an OR HAVING ... IN (...) clause to the query. * @param {string} field * @param {EscapedValue} values * @return HavingBuilder */ orHavingIn(field: string, values: EscapedValue) { this._havings.push({ field, operator: 'IN', operand: escape(values), preop: 'OR', }) return this } /** * Add an OR HAVING NOT ... IN (...) clause to the query. * @param {string} field * @param {EscapedValue} values * @return HavingBuilder */ orHavingNotIn(field: string, values: EscapedValue) { this._havings.push({ field, operator: 'NOT IN', operand: escape(values), preop: 'OR', }) return this } }