swaf/src/db/Query.ts

214 lines
6.3 KiB
TypeScript

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
}) {
const fields = [];
for (let key in data) {
if (data.hasOwnProperty(key)) {
fields.push(new UpdateFieldValue(key, data[key]));
}
}
return new Query(QueryType.UPDATE, table, fields);
}
public static delete(table: string) {
return new Query(QueryType.DELETE, table);
}
private readonly type: QueryType;
private readonly table: string;
private readonly fields: (string | SelectFieldValue | UpdateFieldValue)[];
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 || [];
}
public where(field: string, value: string | Date | Query | any, operator: WhereOperator = WhereOperator.AND, test: WhereTest = WhereTest.EQUALS): Query {
this._where.push(new WhereFieldValue(field, value, operator, test));
return this;
}
public whereNot(field: string, value: string | Date | Query | any, operator: WhereOperator = WhereOperator.AND): Query {
return this.where(field, value, operator, WhereTest.DIFFERENT);
}
public orWhere(field: string, value: string | Date | Query | any): Query {
return this.where(field, value, WhereOperator.OR);
}
public whereIn(field: string, value: any[]): Query {
return this.where(field, value, WhereOperator.AND, WhereTest.IN);
}
public limit(limit: number, offset: number = 0): Query {
this._limit = limit;
this._offset = offset;
return this;
}
public first(): Query {
return this.limit(1);
}
public sortBy(field: string, direction: 'ASC' | 'DESC' = 'ASC'): Query {
this._sortBy = field;
this._sortDirection = direction;
return this;
}
public withTotalRowCount(): Query {
this._foundRows = true;
return this;
}
public toString(final: boolean = false): string {
let query = '';
let fields = this.fields?.join(',');
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:
query = `SELECT ${this._foundRows ? 'SQL_CALC_FOUND_ROWS' : ''} ${fields} FROM ${this.table} ${where} ${orderBy} ${limit}`;
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,
}
enum WhereOperator {
AND = 'AND',
OR = 'OR',
}
enum WhereTest {
EQUALS = '=',
DIFFERENT = '!=',
IN = ' IN ',
}
class FieldValue {
protected readonly field: string;
protected value: any;
constructor(field: string, value: any) {
this.field = field;
this.value = value;
}
public toString(first: boolean = true): string {
return `${!first ? ',' : ''}${this.field}${this.test}${this.value instanceof Query ? this.value : (Array.isArray(this.value) ? '(?)' : '?')}`;
}
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 operator: WhereOperator;
private readonly _test: WhereTest;
constructor(field: string, value: any, operator: WhereOperator, test: WhereTest) {
super(field, value);
this.operator = operator;
this._test = test;
}
public toString(first: boolean = true): string {
return (!first ? ` ${this.operator} ` : '') + super.toString(true);
}
protected get test(): string {
return this._test;
}
}