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.
207 lines
7.0 KiB
207 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'
|
|
import {Scope} from '../Scope.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<T> extends ConnectionExecutable<T> {
|
|
protected _fields: string[] = []
|
|
protected _source?: QuerySource = undefined
|
|
protected _wheres: WhereStatement[] = []
|
|
protected _scopes: Scope[] = []
|
|
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<T> extends WhereBuilder, TableRefBuilder, HavingBuilder {}
|
|
applyMixins(Select, [WhereBuilder, TableRefBuilder, HavingBuilder])
|