|
|
|
@ -23,24 +23,105 @@ import ConnectionExecutable from './ConnectionExecutable.ts'
|
|
|
|
|
import {Scope} from '../Scope.ts'
|
|
|
|
|
import {isInstantiable} from "../../../../di/src/type/Instantiable.ts";
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Base type for functions that operate on WhereBuilders.
|
|
|
|
|
*/
|
|
|
|
|
export type WhereBuilderFunction = (group: WhereBuilder) => any
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Base type for functions that operate on HavingBuilders.
|
|
|
|
|
*/
|
|
|
|
|
export type HavingBuilderFunction = (group: HavingBuilder) => any
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Base type for functions that operate on Joins.
|
|
|
|
|
*/
|
|
|
|
|
export type JoinFunction = (join: Join) => any
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Error class thrown when the SQL generated will be invalid.
|
|
|
|
|
* @extends Error
|
|
|
|
|
*/
|
|
|
|
|
export class MalformedSQLGrammarError extends Error {}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Query builder base class for SELECT queries.
|
|
|
|
|
* @extends ConnectionExecutable
|
|
|
|
|
* @extends TableRefBuilder
|
|
|
|
|
* @extends WhereBuilder
|
|
|
|
|
* @extends HavingBuilder
|
|
|
|
|
*/
|
|
|
|
|
export class Select<T> extends ConnectionExecutable<T> {
|
|
|
|
|
/**
|
|
|
|
|
* The fields to select.
|
|
|
|
|
* @type Array<string>
|
|
|
|
|
*/
|
|
|
|
|
protected _fields: string[] = []
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* The source to select from.
|
|
|
|
|
* @type QuerySource
|
|
|
|
|
*/
|
|
|
|
|
protected _source?: QuerySource = undefined
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Where clauses to apply.
|
|
|
|
|
* @type Array<WhereStatement>
|
|
|
|
|
*/
|
|
|
|
|
protected _wheres: WhereStatement[] = []
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* The scopes to apply.
|
|
|
|
|
* @type Array<Scope>
|
|
|
|
|
*/
|
|
|
|
|
protected _scopes: Scope[] = []
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Having clauses to apply.
|
|
|
|
|
* @type Array<HavingStatement>
|
|
|
|
|
*/
|
|
|
|
|
protected _havings: HavingStatement[] = []
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Max number of rows to return.
|
|
|
|
|
* @type number
|
|
|
|
|
*/
|
|
|
|
|
protected _limit?: number
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Number of rows to skip.
|
|
|
|
|
* @type number
|
|
|
|
|
*/
|
|
|
|
|
protected _offset?: number
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Join clauses to apply.
|
|
|
|
|
* @type Array<Join>
|
|
|
|
|
*/
|
|
|
|
|
protected _joins: Join[] = []
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Include the DISTINCT operator?
|
|
|
|
|
* @type boolean
|
|
|
|
|
*/
|
|
|
|
|
protected _distinct = false
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Group by clauses to apply.
|
|
|
|
|
* @type Array<string>
|
|
|
|
|
*/
|
|
|
|
|
protected _group_by: string[] = []
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Order by clauses to apply.
|
|
|
|
|
* @type Array<OrderStatement>
|
|
|
|
|
*/
|
|
|
|
|
protected _order: OrderStatement[] = []
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Include the DISTINCT operator.
|
|
|
|
|
* @return self
|
|
|
|
|
*/
|
|
|
|
|
distinct() {
|
|
|
|
|
this._distinct = true
|
|
|
|
|
return this
|
|
|
|
@ -73,6 +154,12 @@ export class Select<T> extends ConnectionExecutable<T> {
|
|
|
|
|
].filter(x => String(x).trim()).join(`\n${indent}`)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Include a field in the results.
|
|
|
|
|
* @param {string | Select} field
|
|
|
|
|
* @param {string} [as] - alias
|
|
|
|
|
* @return self
|
|
|
|
|
*/
|
|
|
|
|
field(field: string | Select<any>, as?: string) {
|
|
|
|
|
if ( field instanceof Select ) {
|
|
|
|
|
this._fields.push(`${escape(field)}${as ? ' AS '+as : ''}`)
|
|
|
|
@ -83,11 +170,19 @@ export class Select<T> extends ConnectionExecutable<T> {
|
|
|
|
|
return this
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Clear the selected fields.
|
|
|
|
|
* @return self
|
|
|
|
|
*/
|
|
|
|
|
clear_fields() {
|
|
|
|
|
this._fields = []
|
|
|
|
|
return this
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Get a copy of this query.
|
|
|
|
|
* @return Select
|
|
|
|
|
*/
|
|
|
|
|
clone(): Select<T> {
|
|
|
|
|
const constructor = this.constructor as typeof Select
|
|
|
|
|
if ( !isInstantiable<Select<T>>(constructor) ) {
|
|
|
|
@ -114,11 +209,21 @@ export class Select<T> extends ConnectionExecutable<T> {
|
|
|
|
|
return select
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Add group by clauses to the query.
|
|
|
|
|
* @param {...string} groupings
|
|
|
|
|
* @return self
|
|
|
|
|
*/
|
|
|
|
|
group_by(...groupings: string[]) {
|
|
|
|
|
this._group_by = groupings
|
|
|
|
|
return this
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Include the given fields in the result set.
|
|
|
|
|
* @param {...FieldSet} fields
|
|
|
|
|
* @return self
|
|
|
|
|
*/
|
|
|
|
|
fields(...fields: FieldSet[]) {
|
|
|
|
|
for ( const field_set of fields ) {
|
|
|
|
|
if ( typeof field_set === 'string' ) {
|
|
|
|
@ -134,72 +239,162 @@ export class Select<T> extends ConnectionExecutable<T> {
|
|
|
|
|
return this
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Set the source to select from.
|
|
|
|
|
* @param {QuerySource} source
|
|
|
|
|
* @param {string} [alias]
|
|
|
|
|
* @return self
|
|
|
|
|
*/
|
|
|
|
|
from(source: QuerySource, alias?: string) {
|
|
|
|
|
if ( !alias ) this._source = source
|
|
|
|
|
else this._source = { ref: source, alias }
|
|
|
|
|
return this
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Limit the returned rows.
|
|
|
|
|
* @param {number} num
|
|
|
|
|
* @return self
|
|
|
|
|
*/
|
|
|
|
|
limit(num: number) {
|
|
|
|
|
this._limit = Number(num)
|
|
|
|
|
return this
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Skip the first num rows.
|
|
|
|
|
* @param {number} num
|
|
|
|
|
* @return self
|
|
|
|
|
*/
|
|
|
|
|
offset(num: number) {
|
|
|
|
|
this._offset = Number(num)
|
|
|
|
|
return this
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Skip the first num rows.
|
|
|
|
|
* @param {number} num
|
|
|
|
|
* @return self
|
|
|
|
|
*/
|
|
|
|
|
skip(num: number) {
|
|
|
|
|
this._offset = Number(num)
|
|
|
|
|
return this
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Return only the first num rows.
|
|
|
|
|
* @param {number} num
|
|
|
|
|
* @return self
|
|
|
|
|
*/
|
|
|
|
|
take(num: number) {
|
|
|
|
|
this._limit = Number(num)
|
|
|
|
|
return this
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Add a JOIN clause to the query by alias, or using a function to build the clause.
|
|
|
|
|
* @param {QuerySource} source
|
|
|
|
|
* @param {string | JoinFunction} alias_or_func
|
|
|
|
|
* @param {JoinFunction} [func]
|
|
|
|
|
* @return self
|
|
|
|
|
*/
|
|
|
|
|
join(source: QuerySource, alias_or_func: string | JoinFunction, func?: JoinFunction) {
|
|
|
|
|
this._createJoin(Join, source, alias_or_func, func)
|
|
|
|
|
return this
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Add a LEFT JOIN clause to the query by alias, or using a function to build the clause.
|
|
|
|
|
* @param {QuerySource} source
|
|
|
|
|
* @param {string | JoinFunction} alias_or_func
|
|
|
|
|
* @param {JoinFunction} [func]
|
|
|
|
|
* @return self
|
|
|
|
|
*/
|
|
|
|
|
left_join(source: QuerySource, alias_or_func: string | JoinFunction, func?: JoinFunction) {
|
|
|
|
|
this._createJoin(LeftJoin, source, alias_or_func, func)
|
|
|
|
|
return this
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Add a LEFT OUTER JOIN clause to the query by alias, or using a function to build the clause.
|
|
|
|
|
* @param {QuerySource} source
|
|
|
|
|
* @param {string | JoinFunction} alias_or_func
|
|
|
|
|
* @param {JoinFunction} [func]
|
|
|
|
|
* @return self
|
|
|
|
|
*/
|
|
|
|
|
left_outer_join(source: QuerySource, alias_or_func: string | JoinFunction, func?: JoinFunction) {
|
|
|
|
|
this._createJoin(LeftOuterJoin, source, alias_or_func, func)
|
|
|
|
|
return this
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Add a CROSS JOIN clause to the query by alias, or using a function to build the clause.
|
|
|
|
|
* @param {QuerySource} source
|
|
|
|
|
* @param {string | JoinFunction} alias_or_func
|
|
|
|
|
* @param {JoinFunction} [func]
|
|
|
|
|
* @return self
|
|
|
|
|
*/
|
|
|
|
|
cross_join(source: QuerySource, alias_or_func: string | JoinFunction, func?: JoinFunction) {
|
|
|
|
|
this._createJoin(CrossJoin, source, alias_or_func, func)
|
|
|
|
|
return this
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Add an INNER JOIN clause to the query by alias, or using a function to build the clause.
|
|
|
|
|
* @param {QuerySource} source
|
|
|
|
|
* @param {string | JoinFunction} alias_or_func
|
|
|
|
|
* @param {JoinFunction} [func]
|
|
|
|
|
* @return self
|
|
|
|
|
*/
|
|
|
|
|
inner_join(source: QuerySource, alias_or_func: string | JoinFunction, func?: JoinFunction) {
|
|
|
|
|
this._createJoin(InnerJoin, source, alias_or_func, func)
|
|
|
|
|
return this
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Add a RIGHT JOIN clause to the query by alias, or using a function to build the clause.
|
|
|
|
|
* @param {QuerySource} source
|
|
|
|
|
* @param {string | JoinFunction} alias_or_func
|
|
|
|
|
* @param {JoinFunction} [func]
|
|
|
|
|
* @return self
|
|
|
|
|
*/
|
|
|
|
|
right_join(source: QuerySource, alias_or_func: string | JoinFunction, func?: JoinFunction) {
|
|
|
|
|
this._createJoin(RightJoin, source, alias_or_func, func)
|
|
|
|
|
return this
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Add a RIGHT OUTER JOIN clause to the query by alias, or using a function to build the clause.
|
|
|
|
|
* @param {QuerySource} source
|
|
|
|
|
* @param {string | JoinFunction} alias_or_func
|
|
|
|
|
* @param {JoinFunction} [func]
|
|
|
|
|
* @return self
|
|
|
|
|
*/
|
|
|
|
|
right_outer_join(source: QuerySource, alias_or_func: string | JoinFunction, func?: JoinFunction) {
|
|
|
|
|
this._createJoin(RightOuterJoin, source, alias_or_func, func)
|
|
|
|
|
return this
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Add a FULL OUTER JOIN clause to the query by alias, or using a function to build the clause.
|
|
|
|
|
* @param {QuerySource} source
|
|
|
|
|
* @param {string | JoinFunction} alias_or_func
|
|
|
|
|
* @param {JoinFunction} [func]
|
|
|
|
|
* @return self
|
|
|
|
|
*/
|
|
|
|
|
full_outer_join(source: QuerySource, alias_or_func: string | JoinFunction, func?: JoinFunction) {
|
|
|
|
|
this._createJoin(FullOuterJoin, source, alias_or_func, func)
|
|
|
|
|
return this
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Internal helper for creating join clauses using query builder classes.
|
|
|
|
|
* @param {typeof Join} Class
|
|
|
|
|
* @param {QuerySource} source
|
|
|
|
|
* @param {string | JoinFunction} alias_or_func
|
|
|
|
|
* @param {JoinFunction} [func]
|
|
|
|
|
* @private
|
|
|
|
|
*/
|
|
|
|
|
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)
|
|
|
|
@ -207,6 +402,13 @@ export class Select<T> extends ConnectionExecutable<T> {
|
|
|
|
|
join_func(join)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Cast a join reference to the arguments required for the JOIN query builder.
|
|
|
|
|
* @param {QuerySource} source
|
|
|
|
|
* @param {string | JoinFunction} alias_or_func
|
|
|
|
|
* @param {JoinFunction} [func]
|
|
|
|
|
* @return Array
|
|
|
|
|
*/
|
|
|
|
|
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
|
|
|
|
@ -221,21 +423,34 @@ export class Select<T> extends ConnectionExecutable<T> {
|
|
|
|
|
return [this.source_alias_to_table_ref(source, alias), join_func]
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Add an order by clause to the query.
|
|
|
|
|
* @param {string} field
|
|
|
|
|
* @param {string} [direction = 'ASC']
|
|
|
|
|
* @return self
|
|
|
|
|
*/
|
|
|
|
|
order_by(field: string, direction: OrderDirection = 'ASC') {
|
|
|
|
|
this._order.push({ field, direction })
|
|
|
|
|
return this
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Add an ORDER BY ... ASC clause to the query.
|
|
|
|
|
* @param {string} field
|
|
|
|
|
* @return self
|
|
|
|
|
*/
|
|
|
|
|
order_asc(field: string) {
|
|
|
|
|
return this.order_by(field, 'ASC')
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Add an ORDER BY ... DESC clause to the query.
|
|
|
|
|
* @param {string} field
|
|
|
|
|
* @return self
|
|
|
|
|
*/
|
|
|
|
|
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 {}
|
|
|
|
|