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.

205 lines
7.0 KiB

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])