import { FieldSet, QuerySource, WhereStatement, TableRef, OrderStatement, OrderDirection, HavingStatement } from '../types.ts' import {WhereBuilder} from './WhereBuilder.ts' import {applyMixins} from '../../../../lib/src/support/mixins.ts' import {TableRefBuilder} from './TableRefBuilder.ts' import {Join} from './join/Join.ts' import {LeftJoin} from './join/LeftJoin.ts' import {LeftOuterJoin} from './join/LeftOuterJoin.ts' import {CrossJoin} from './join/CrossJoin.ts' import {InnerJoin} from './join/InnerJoin.ts' import {RightJoin} from './join/RightJoin.ts' import {RightOuterJoin} from './join/RightOuterJoin.ts' import {FullOuterJoin} from './join/FullOuterJoin.ts' import {HavingBuilder} from './HavingBuilder.ts' import ConnectionExecutable from './ConnectionExecutable.ts' export type WhereBuilderFunction = (group: WhereBuilder) => any export type HavingBuilderFunction = (group: HavingBuilder) => any export type JoinFunction = (join: Join) => any export class MalformedSQLGrammarError extends Error {} export class Select extends ConnectionExecutable { protected _fields: string[] = [] protected _source?: QuerySource = undefined protected _wheres: WhereStatement[] = [] protected _havings: HavingStatement[] = [] protected _limit?: number protected _offset?: number protected _joins: Join[] = [] protected _distinct = false protected _group_by: string[] = [] protected _order: OrderStatement[] = [] distinct() { this._distinct = true return this } sql(level = 0): string { const indent = Array(level * 2).fill(' ').join('') if ( typeof this._source === 'undefined' ) throw new MalformedSQLGrammarError(`No table reference has been provided.`) const table_ref = this.source_alias_to_table_ref(this._source) let order = '' if ( this._order.length > 0 ) { order = this._order.map(x => `${x.field} ${x.direction}`).join(', ') } const wheres = this.wheres_to_sql(this._wheres, level + 1) const havings = this.havings_to_sql(this._havings, level + 1) return [ `SELECT ${this._distinct ? 'DISTINCT ' : ''}${this._fields.join(', ')}`, `FROM ${this.serialize_table_ref(table_ref)}`, ...this._joins.map(join => join.sql(level + 1)), ...(wheres.trim() ? ['WHERE', wheres] : []), ...[typeof this._limit !== 'undefined' ? [`LIMIT ${this._limit}`] : []], ...[typeof this._offset !== 'undefined' ? [`OFFSET ${this._offset}`] : []], ...[this._group_by.length > 0 ? 'GROUP BY ' + this._group_by.join(', ') : []], ...[order ? [`ORDER BY ${order}`] : []], ...(havings.trim() ? ['HAVING', havings] : []), ].filter(x => String(x).trim()).join(`\n${indent}`) } field(field: string) { if ( !this._fields.includes(field) ) { this._fields.push(field) } return this } group_by(...groupings: string[]) { this._group_by = groupings return this } fields(...fields: FieldSet[]) { for ( const field_set of fields ) { if ( typeof field_set === 'string' ) { if ( !this._fields.includes(field_set) ) this._fields.push(field_set) } else { for ( const field of field_set ) { if ( !this._fields.includes(field) ) this._fields.push(field) } } } return this } from(source: QuerySource, alias?: string) { if ( !alias ) this._source = source else this._source = { ref: source, alias } return this } limit(num: number) { this._limit = Number(num) return this } offset(num: number) { this._offset = Number(num) return this } skip(num: number) { this._offset = Number(num) return this } take(num: number) { this._limit = Number(num) return this } join(source: QuerySource, alias_or_func: string | JoinFunction, func?: JoinFunction) { this._createJoin(Join, source, alias_or_func, func) return this } left_join(source: QuerySource, alias_or_func: string | JoinFunction, func?: JoinFunction) { this._createJoin(LeftJoin, source, alias_or_func, func) return this } left_outer_join(source: QuerySource, alias_or_func: string | JoinFunction, func?: JoinFunction) { this._createJoin(LeftOuterJoin, source, alias_or_func, func) return this } cross_join(source: QuerySource, alias_or_func: string | JoinFunction, func?: JoinFunction) { this._createJoin(CrossJoin, source, alias_or_func, func) return this } inner_join(source: QuerySource, alias_or_func: string | JoinFunction, func?: JoinFunction) { this._createJoin(InnerJoin, source, alias_or_func, func) return this } right_join(source: QuerySource, alias_or_func: string | JoinFunction, func?: JoinFunction) { this._createJoin(RightJoin, source, alias_or_func, func) return this } right_outer_join(source: QuerySource, alias_or_func: string | JoinFunction, func?: JoinFunction) { this._createJoin(RightOuterJoin, source, alias_or_func, func) return this } full_outer_join(source: QuerySource, alias_or_func: string | JoinFunction, func?: JoinFunction) { this._createJoin(FullOuterJoin, source, alias_or_func, func) return this } private _createJoin(Class: typeof Join, source: QuerySource, alias_or_func: string | JoinFunction, func?: JoinFunction) { const [table_ref, join_func] = this.join_ref_to_join_args(source, alias_or_func, func) const join = new Class(table_ref) this._joins.push(join) join_func(join) } join_ref_to_join_args(source: QuerySource, alias_or_func: string | JoinFunction, func?: JoinFunction): [TableRef, JoinFunction] { let alias = undefined if ( typeof alias_or_func === 'string' ) alias = alias_or_func let join_func = undefined if ( func ) join_func = func else if ( typeof alias_or_func === 'function' ) join_func = alias_or_func else { throw new TypeError('Missing required join function handler!') } return [this.source_alias_to_table_ref(source, alias), join_func] } order_by(field: string, direction: OrderDirection = 'ASC') { this._order.push({ field, direction }) return this } order_asc(field: string) { return this.order_by(field, 'ASC') } order_desc(field: string) { return this.order_by(field, 'DESC') } // TODO subquery support - https://www.sqlservertutorial.net/sql-server-basics/sql-server-subquery/ // TODO raw() } export interface Select extends WhereBuilder, TableRefBuilder, HavingBuilder {} applyMixins(Select, [WhereBuilder, TableRefBuilder, HavingBuilder])