Add left join to query builder

This commit is contained in:
Alice Gaudon 2020-06-14 11:43:31 +02:00
parent e965303777
commit e63b5d21fe
3 changed files with 46 additions and 31 deletions

View File

@ -5,6 +5,7 @@ import AuthProof from "../AuthProof";
import Validator from "../../db/Validator"; import Validator from "../../db/Validator";
import User from "./User"; import User from "./User";
import argon2 from "argon2"; import argon2 from "argon2";
import {WhereTest} from "../../db/Query";
export default class MagicLink extends Model implements AuthProof { export default class MagicLink extends Model implements AuthProof {
public static async bySessionID(sessionID: string, actionType?: string | string[]): Promise<MagicLink | null> { public static async bySessionID(sessionID: string, actionType?: string | string[]): Promise<MagicLink | null> {
@ -13,7 +14,7 @@ export default class MagicLink extends Model implements AuthProof {
if (typeof actionType === 'string') { if (typeof actionType === 'string') {
query = query.where('action_type', actionType); query = query.where('action_type', actionType);
} else { } else {
query = query.whereIn('action_type', actionType); query = query.where('action_type', actionType, WhereTest.IN);
} }
} }
const links = await this.models<MagicLink>(query.first()); const links = await this.models<MagicLink>(query.first());

View File

@ -8,23 +8,25 @@ export default class Query {
public static update(table: string, data: { public static update(table: string, data: {
[key: string]: any [key: string]: any
}) { }): Query {
const fields = []; const fields = [];
for (let key in data) { for (let key in data) {
if (data.hasOwnProperty(key)) { if (data.hasOwnProperty(key)) {
fields.push(new UpdateFieldValue(key, data[key])); fields.push(new UpdateFieldValue(key, data[key], false));
} }
} }
return new Query(QueryType.UPDATE, table, fields); return new Query(QueryType.UPDATE, table, fields);
} }
public static delete(table: string) { public static delete(table: string): Query {
return new Query(QueryType.DELETE, table); return new Query(QueryType.DELETE, table);
} }
private readonly type: QueryType; private readonly type: QueryType;
private readonly table: string; private readonly table: string;
private readonly fields: (string | SelectFieldValue | UpdateFieldValue)[]; private readonly fields: (string | SelectFieldValue | UpdateFieldValue)[];
private _leftJoin?: string;
private _leftJoinOn: WhereFieldValue[] = [];
private _where: WhereFieldValue[] = []; private _where: WhereFieldValue[] = [];
private _limit?: number; private _limit?: number;
private _offset?: number; private _offset?: number;
@ -38,40 +40,38 @@ export default class Query {
this.fields = fields || []; this.fields = fields || [];
} }
public where(field: string, value: string | Date | Query | any, operator: WhereOperator = WhereOperator.AND, test: WhereTest = WhereTest.EQUALS): Query { public leftJoin(table: string): this {
this._where.push(new WhereFieldValue(field, value, operator, test)); this._leftJoin = table;
return this; return this;
} }
public whereNot(field: string, value: string | Date | Query | any, operator: WhereOperator = WhereOperator.AND): Query { public on(field1: string, field2: string, test: WhereTest = WhereTest.EQ, operator: WhereOperator = WhereOperator.AND): this {
return this.where(field, value, operator, WhereTest.DIFFERENT); this._leftJoinOn.push(new WhereFieldValue(field1, field2, true, test, operator));
return this;
} }
public orWhere(field: string, value: string | Date | Query | any): Query { public where(field: string, value: string | Date | Query | any, test: WhereTest = WhereTest.EQ, operator: WhereOperator = WhereOperator.AND): this {
return this.where(field, value, WhereOperator.OR); this._where.push(new WhereFieldValue(field, value, false, test, operator));
return this;
} }
public whereIn(field: string, value: any[]): Query { public limit(limit: number, offset: number = 0): this {
return this.where(field, value, WhereOperator.AND, WhereTest.IN);
}
public limit(limit: number, offset: number = 0): Query {
this._limit = limit; this._limit = limit;
this._offset = offset; this._offset = offset;
return this; return this;
} }
public first(): Query { public first(): this {
return this.limit(1); return this.limit(1);
} }
public sortBy(field: string, direction: 'ASC' | 'DESC' = 'ASC'): Query { public sortBy(field: string, direction: 'ASC' | 'DESC' = 'ASC'): this {
this._sortBy = field; this._sortBy = field;
this._sortDirection = direction; this._sortDirection = direction;
return this; return this;
} }
public withTotalRowCount(): Query { public withTotalRowCount(): this {
this._foundRows = true; this._foundRows = true;
return this; return this;
} }
@ -81,6 +81,14 @@ export default class Query {
let fields = this.fields?.join(','); let fields = this.fields?.join(',');
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);
}
}
let where = ''; let where = '';
if (this._where.length > 0) { if (this._where.length > 0) {
where = `WHERE ${this._where[0]}`; where = `WHERE ${this._where[0]}`;
@ -104,7 +112,7 @@ export default class Query {
switch (this.type) { switch (this.type) {
case QueryType.SELECT: case QueryType.SELECT:
query = `SELECT ${this._foundRows ? 'SQL_CALC_FOUND_ROWS' : ''} ${fields} FROM ${this.table} ${where} ${orderBy} ${limit}`; query = `SELECT ${this._foundRows ? 'SQL_CALC_FOUND_ROWS' : ''} ${fields} FROM ${this.table} ${join} ${where} ${orderBy} ${limit}`;
break; break;
case QueryType.UPDATE: case QueryType.UPDATE:
query = `UPDATE ${this.table} SET ${fields} ${where} ${orderBy} ${limit}`; query = `UPDATE ${this.table} SET ${fields} ${where} ${orderBy} ${limit}`;
@ -152,28 +160,34 @@ export enum QueryType {
DELETE, DELETE,
} }
enum WhereOperator { export enum WhereOperator {
AND = 'AND', AND = 'AND',
OR = 'OR', OR = 'OR',
} }
enum WhereTest { export enum WhereTest {
EQUALS = '=', EQ = '=',
DIFFERENT = '!=', NE = '!=',
GT = '>',
GE = '>=',
LT = '<',
LE = '<=',
IN = ' IN ', IN = ' IN ',
} }
class FieldValue { class FieldValue {
protected readonly field: string; protected readonly field: string;
protected value: any; protected value: any;
protected raw: boolean;
constructor(field: string, value: any) { constructor(field: string, value: any, raw: boolean) {
this.field = field; this.field = field;
this.value = value; this.value = value;
this.raw = raw;
} }
public toString(first: boolean = true): string { public toString(first: boolean = true): string {
return `${!first ? ',' : ''}${this.field}${this.test}${this.value instanceof Query ? this.value : (Array.isArray(this.value) ? '(?)' : '?')}`; return `${!first ? ',' : ''}${this.field}${this.test}${this.raw || this.value instanceof Query ? this.value : (Array.isArray(this.value) ? '(?)' : '?')}`;
} }
protected get test(): string { protected get test(): string {
@ -195,13 +209,13 @@ class UpdateFieldValue extends FieldValue {
} }
class WhereFieldValue extends FieldValue { class WhereFieldValue extends FieldValue {
private readonly operator: WhereOperator;
private readonly _test: WhereTest; private readonly _test: WhereTest;
private readonly operator: WhereOperator;
constructor(field: string, value: any, operator: WhereOperator, test: WhereTest) { constructor(field: string, value: any, raw: boolean, test: WhereTest, operator: WhereOperator) {
super(field, value); super(field, value, raw);
this.operator = operator;
this._test = test; this._test = test;
this.operator = operator;
} }
public toString(first: boolean = true): string { public toString(first: boolean = true): string {

View File

@ -1,5 +1,5 @@
import Model from "./Model"; import Model from "./Model";
import Query from "./Query"; import Query, {WhereTest} from "./Query";
import {Connection} from "mysql"; import {Connection} from "mysql";
import {Type} from "../Utils"; import {Type} from "../Utils";
@ -184,7 +184,7 @@ export default class Validator<T> {
} else { } else {
query = (model instanceof Model ? <any>model.constructor : model).select('1').where(foreignKey, val); query = (model instanceof Model ? <any>model.constructor : model).select('1').where(foreignKey, val);
} }
if (model instanceof Model && typeof model.id === 'number') query = query.whereNot('id', model.id); if (model instanceof Model && typeof model.id === 'number') query = query.where('id', model.id, WhereTest.NE);
return (await query.execute(c)).results.length === 0; return (await query.execute(c)).results.length === 0;
}, },
throw: () => new AlreadyExistsValidationError((<any>model).table), throw: () => new AlreadyExistsValidationError((<any>model).table),