2020-04-22 15:52:17 +02:00
|
|
|
import {query, QueryResult} from "./MysqlConnectionManager";
|
|
|
|
import {Connection} from "mysql";
|
|
|
|
|
|
|
|
export default class Query {
|
|
|
|
public static select(table: string, ...fields: string[]): Query {
|
|
|
|
return new Query(QueryType.SELECT, table, fields.length > 0 ? fields : ['*']);
|
|
|
|
}
|
|
|
|
|
|
|
|
public static update(table: string, data: {
|
|
|
|
[key: string]: any
|
2020-06-14 11:43:31 +02:00
|
|
|
}): Query {
|
2020-04-22 15:52:17 +02:00
|
|
|
const fields = [];
|
|
|
|
for (let key in data) {
|
|
|
|
if (data.hasOwnProperty(key)) {
|
2020-06-14 11:43:31 +02:00
|
|
|
fields.push(new UpdateFieldValue(key, data[key], false));
|
2020-04-22 15:52:17 +02:00
|
|
|
}
|
|
|
|
}
|
|
|
|
return new Query(QueryType.UPDATE, table, fields);
|
|
|
|
}
|
|
|
|
|
2020-06-14 11:43:31 +02:00
|
|
|
public static delete(table: string): Query {
|
2020-04-22 15:52:17 +02:00
|
|
|
return new Query(QueryType.DELETE, table);
|
|
|
|
}
|
|
|
|
|
|
|
|
private readonly type: QueryType;
|
|
|
|
private readonly table: string;
|
|
|
|
private readonly fields: (string | SelectFieldValue | UpdateFieldValue)[];
|
2020-06-14 11:43:31 +02:00
|
|
|
private _leftJoin?: string;
|
|
|
|
private _leftJoinOn: WhereFieldValue[] = [];
|
2020-04-22 15:52:17 +02:00
|
|
|
private _where: WhereFieldValue[] = [];
|
|
|
|
private _limit?: number;
|
|
|
|
private _offset?: number;
|
|
|
|
private _sortBy?: string;
|
|
|
|
private _sortDirection?: 'ASC' | 'DESC';
|
|
|
|
private _foundRows: boolean = false;
|
|
|
|
|
|
|
|
private constructor(type: QueryType, table: string, fields?: (string | SelectFieldValue | UpdateFieldValue)[]) {
|
|
|
|
this.type = type;
|
|
|
|
this.table = table;
|
|
|
|
this.fields = fields || [];
|
|
|
|
}
|
|
|
|
|
2020-06-14 11:43:31 +02:00
|
|
|
public leftJoin(table: string): this {
|
|
|
|
this._leftJoin = table;
|
2020-04-22 15:52:17 +02:00
|
|
|
return this;
|
|
|
|
}
|
|
|
|
|
2020-06-14 11:43:31 +02:00
|
|
|
public on(field1: string, field2: string, test: WhereTest = WhereTest.EQ, operator: WhereOperator = WhereOperator.AND): this {
|
|
|
|
this._leftJoinOn.push(new WhereFieldValue(field1, field2, true, test, operator));
|
|
|
|
return this;
|
2020-04-22 15:52:17 +02:00
|
|
|
}
|
|
|
|
|
2020-06-14 11:43:31 +02:00
|
|
|
public where(field: string, value: string | Date | Query | any, test: WhereTest = WhereTest.EQ, operator: WhereOperator = WhereOperator.AND): this {
|
|
|
|
this._where.push(new WhereFieldValue(field, value, false, test, operator));
|
|
|
|
return this;
|
2020-04-22 15:52:17 +02:00
|
|
|
}
|
|
|
|
|
2020-06-14 11:43:31 +02:00
|
|
|
public limit(limit: number, offset: number = 0): this {
|
2020-04-22 15:52:17 +02:00
|
|
|
this._limit = limit;
|
|
|
|
this._offset = offset;
|
|
|
|
return this;
|
|
|
|
}
|
|
|
|
|
2020-06-14 11:43:31 +02:00
|
|
|
public first(): this {
|
2020-04-22 15:52:17 +02:00
|
|
|
return this.limit(1);
|
|
|
|
}
|
|
|
|
|
2020-06-14 11:43:31 +02:00
|
|
|
public sortBy(field: string, direction: 'ASC' | 'DESC' = 'ASC'): this {
|
2020-04-22 15:52:17 +02:00
|
|
|
this._sortBy = field;
|
|
|
|
this._sortDirection = direction;
|
|
|
|
return this;
|
|
|
|
}
|
|
|
|
|
2020-06-14 11:43:31 +02:00
|
|
|
public withTotalRowCount(): this {
|
2020-04-22 15:52:17 +02:00
|
|
|
this._foundRows = true;
|
|
|
|
return this;
|
|
|
|
}
|
|
|
|
|
|
|
|
public toString(final: boolean = false): string {
|
|
|
|
let query = '';
|
|
|
|
|
|
|
|
let fields = this.fields?.join(',');
|
|
|
|
|
2020-06-14 11:43:31 +02:00
|
|
|
let join = '';
|
|
|
|
if (this._leftJoin) {
|
|
|
|
join = `LEFT JOIN ${this._leftJoin} ON ${this._leftJoinOn[0]}`;
|
|
|
|
for (let i = 1; i < this._leftJoinOn.length; i++) {
|
|
|
|
join += this._leftJoinOn[i].toString(false);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2020-04-22 15:52:17 +02:00
|
|
|
let where = '';
|
|
|
|
if (this._where.length > 0) {
|
|
|
|
where = `WHERE ${this._where[0]}`;
|
|
|
|
for (let i = 1; i < this._where.length; i++) {
|
|
|
|
where += this._where[i].toString(false);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
let limit = '';
|
|
|
|
if (typeof this._limit === 'number') {
|
|
|
|
limit = `LIMIT ${this._limit}`;
|
|
|
|
if (typeof this._offset === 'number' && this._offset !== 0) {
|
|
|
|
limit += ` OFFSET ${this._offset}`;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
let orderBy = '';
|
|
|
|
if (typeof this._sortBy === 'string') {
|
|
|
|
orderBy = `ORDER BY ${this._sortBy} ${this._sortDirection}`;
|
|
|
|
}
|
|
|
|
|
|
|
|
switch (this.type) {
|
|
|
|
case QueryType.SELECT:
|
2020-06-14 11:43:31 +02:00
|
|
|
query = `SELECT ${this._foundRows ? 'SQL_CALC_FOUND_ROWS' : ''} ${fields} FROM ${this.table} ${join} ${where} ${orderBy} ${limit}`;
|
2020-04-22 15:52:17 +02:00
|
|
|
break;
|
|
|
|
case QueryType.UPDATE:
|
|
|
|
query = `UPDATE ${this.table} SET ${fields} ${where} ${orderBy} ${limit}`;
|
|
|
|
break;
|
|
|
|
case QueryType.DELETE:
|
|
|
|
query = `DELETE FROM ${this.table} ${where} ${orderBy} ${limit}`;
|
|
|
|
break;
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
return final ? query : `(${query})`;
|
|
|
|
}
|
|
|
|
|
|
|
|
public build(): string {
|
|
|
|
return this.toString(true);
|
|
|
|
}
|
|
|
|
|
|
|
|
public get variables(): any[] {
|
|
|
|
const variables: any[] = [];
|
|
|
|
this.fields?.filter(v => v instanceof FieldValue)
|
|
|
|
.flatMap(v => (<FieldValue>v).variables)
|
|
|
|
.forEach(v => variables.push(v));
|
|
|
|
this._where.flatMap(v => v.variables)
|
|
|
|
.forEach(v => variables.push(v));
|
|
|
|
return variables;
|
|
|
|
}
|
|
|
|
|
|
|
|
public isCacheable(): boolean {
|
|
|
|
return this.type === QueryType.SELECT && this.fields.length === 1 && this.fields[0] === '*';
|
|
|
|
}
|
|
|
|
|
|
|
|
public async execute(connection?: Connection): Promise<QueryResult> {
|
|
|
|
const queryResult = await query(this.build(), this.variables, connection);
|
|
|
|
if (this._foundRows) {
|
|
|
|
const foundRows = await query('SELECT FOUND_ROWS() as r', undefined, connection);
|
|
|
|
queryResult.foundRows = foundRows.results[0].r;
|
|
|
|
}
|
|
|
|
return queryResult;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
export enum QueryType {
|
|
|
|
SELECT,
|
|
|
|
UPDATE,
|
|
|
|
DELETE,
|
|
|
|
}
|
|
|
|
|
2020-06-14 11:43:31 +02:00
|
|
|
export enum WhereOperator {
|
2020-04-22 15:52:17 +02:00
|
|
|
AND = 'AND',
|
|
|
|
OR = 'OR',
|
|
|
|
}
|
|
|
|
|
2020-06-14 11:43:31 +02:00
|
|
|
export enum WhereTest {
|
|
|
|
EQ = '=',
|
|
|
|
NE = '!=',
|
|
|
|
GT = '>',
|
|
|
|
GE = '>=',
|
|
|
|
LT = '<',
|
|
|
|
LE = '<=',
|
2020-04-22 15:52:17 +02:00
|
|
|
IN = ' IN ',
|
|
|
|
}
|
|
|
|
|
|
|
|
class FieldValue {
|
|
|
|
protected readonly field: string;
|
|
|
|
protected value: any;
|
2020-06-14 11:43:31 +02:00
|
|
|
protected raw: boolean;
|
2020-04-22 15:52:17 +02:00
|
|
|
|
2020-06-14 11:43:31 +02:00
|
|
|
constructor(field: string, value: any, raw: boolean) {
|
2020-04-22 15:52:17 +02:00
|
|
|
this.field = field;
|
|
|
|
this.value = value;
|
2020-06-14 11:43:31 +02:00
|
|
|
this.raw = raw;
|
2020-04-22 15:52:17 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
public toString(first: boolean = true): string {
|
2020-06-14 11:43:31 +02:00
|
|
|
return `${!first ? ',' : ''}${this.field}${this.test}${this.raw || this.value instanceof Query ? this.value : (Array.isArray(this.value) ? '(?)' : '?')}`;
|
2020-04-22 15:52:17 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
protected get test(): string {
|
|
|
|
return '=';
|
|
|
|
}
|
|
|
|
|
|
|
|
public get variables(): any[] {
|
|
|
|
return this.value instanceof Query ? this.value.variables : [this.value];
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
class SelectFieldValue extends FieldValue {
|
|
|
|
public toString(first: boolean = true): string {
|
|
|
|
return `(${this.value instanceof Query ? this.value : '?'}) AS ${this.field}`;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
class UpdateFieldValue extends FieldValue {
|
|
|
|
}
|
|
|
|
|
|
|
|
class WhereFieldValue extends FieldValue {
|
|
|
|
private readonly _test: WhereTest;
|
2020-06-14 11:43:31 +02:00
|
|
|
private readonly operator: WhereOperator;
|
2020-04-22 15:52:17 +02:00
|
|
|
|
2020-06-14 11:43:31 +02:00
|
|
|
constructor(field: string, value: any, raw: boolean, test: WhereTest, operator: WhereOperator) {
|
|
|
|
super(field, value, raw);
|
2020-04-22 15:52:17 +02:00
|
|
|
this._test = test;
|
2020-06-14 11:43:31 +02:00
|
|
|
this.operator = operator;
|
2020-04-22 15:52:17 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
public toString(first: boolean = true): string {
|
|
|
|
return (!first ? ` ${this.operator} ` : '') + super.toString(true);
|
|
|
|
}
|
|
|
|
|
|
|
|
protected get test(): string {
|
|
|
|
return this._test;
|
|
|
|
}
|
|
|
|
}
|