@nsilly/repository
Version:
NF Repository is a abstract layer of Sequelize Application, that make application more easy to understand and flexible to maintain.
359 lines (333 loc) • 8.43 kB
JavaScript
import * as _ from 'lodash';
import { Exception } from '@nsilly/exceptions';
import { Op } from 'sequelize';
export class QueryBuilder {
constructor() {
this.wheres = [];
this.scopeQueries = [];
this.scopes = [];
this.offset = 0;
this.limit = undefined;
this.orders = [];
this.group = undefined;
this.includes = [];
this.attributes = [];
}
setModels(models) {
this.models = models;
}
/**
* Add a basic WHERE clause to the query.
*
* @param string column
* @param mixed operator
* @param mixed value
*
* @return this
*/
where(...args) {
const type = Op.and;
let column;
let operation = '=';
let value;
if (args.length === 2) {
[column, value] = [args[0], args[1]];
} else if (args.length === 3) {
[column, operation, value] = args;
} else if (args.length === 1) {
// means raw query
[column, operation, value] = [args[0], null, null];
} else {
throw new Exception('where function expect two or three parameters', 1000);
}
this.wheres.push({ column, operation, value, type });
return this;
}
/**
* Add a basic WHERE clause to the query.
*
* @param string column
* @param mixed operator
* @param mixed value
*
* @return this
*/
scopeQuery(query, type = Op.and) {
this.scopeQueries.push({ query, type });
return this;
}
/**
* Add a basic OR WHERE clause to the query.
*
* @param string column
* @param mixed operator
* @param mixed value
*
* @return this
*/
orWhere(...args) {
const type = Op.or;
let column;
let operation = '=';
let value;
if (args.length === 2) {
[column, value] = [args[0], args[1]];
} else if (args.length === 3) {
[column, operation, value] = args;
} else if (args.length === 1) {
[column, operation, value] = [args[0], null, null];
} else {
throw new Exception('orWhere function expect one, two or three parameters', 1000);
}
this.wheres.push({ column, operation, value, type });
return this;
}
/**
* Add an "WHERE IN" clause to the query.
*
* @param string column
* @param array value
*
* @return this
*/
whereIn(column, value) {
const operation = Op.in;
const type = Op.and;
this.wheres.push({ column, operation, value, type });
return this;
}
/**
* Add an "OR WHERE IN" clause to the query.
*
* @param string column
* @param array value
*
* @return this
*/
orWhereIn(column, value) {
const operation = Op.in;
const type = Op.or;
this.wheres.push({ column, operation, value, type });
return this;
}
/**
* Add an "WHERE NOT IN" clause to the query.
*
* @param string column
* @param array value
*
* @return this
*/
whereNotIn(column, value) {
const operation = Op.notIn;
const type = Op.and;
this.wheres.push({ column, operation, value, type });
return this;
}
/**
* Add a basic where clause with relation to the query.
*
* @param string relation
* @param callable callable
*
* @return this
*/
whereHas(relation, builder, options) {
options = options || {};
let model;
if (this.models !== undefined && this.models[relation] !== undefined) {
model = this.models[relation];
} else {
model = relation;
}
this.includes.push(Object.assign({ model: model, where: builder.buildWhereQuery() }, options));
return this;
}
/**
* include another table that has many to many relationship with it
*
* @param string relation
* @param callable builder
*
* @return this
*/
includeThroughWhere(relation, builder) {
if (this.models !== undefined && this.models[relation] !== undefined) {
this.includes.push({ model: this.models[relation], through: { where: builder.buildWhereQuery() } });
} else {
this.includes.push({ model: relation, through: { where: builder.buildWhereQuery() } });
}
return this;
}
/**
* Alias to set the "offset" value of the query.
*
* @param int value
*
* @return this
*/
skip(offset) {
this.offset = _.isUndefined(offset) ? 1 : parseInt(offset);
}
/**
* Alias to set the "limit" value of the query.
*
* @param int value
*
* @return this
*/
take(limit) {
this.limit = _.isUndefined(limit) ? undefined : parseInt(limit);
}
/**
* Add an "order by" clause to the query.
*
* @param string column
* @param string direction
*
* @return this
*/
orderBy(...args) {
let model;
let field;
let direction = 'ASC';
if (args.length === 2) {
[field, direction] = args;
this.orders.push([field, direction]);
}
if (args.length === 3) {
[model, field, direction] = args;
this.orders.push([model, field, direction]);
}
if (args.length === 1) {
[field] = args;
this.orders.push(field);
}
}
/**
* Add an "GROUP BY" clause to the query.
*
* @param string column
*
* @return this
*/
groupBy(column) {
this.group = column;
}
/**
* Begin querying a model with eager loading.
*
* @param array|string $relations
*
* @return this
*/
with(...args) {
let mainModel = args[0];
const options = args[1] || {};
if (typeof args[0] === 'string') {
if (this.models !== undefined && this.models[args[0]] !== undefined) {
mainModel = this.models[args[0]];
} else {
mainModel = args[0];
}
}
if (options.include !== undefined && typeof options.include === 'string' && this.models !== undefined && this.models[options.include] !== undefined) {
options.include = this.models[options.include];
}
const include = { ...options, ...{ model: mainModel } };
this.includes.push(include);
}
/**
* Add scope to the query
*
* @param string scope
*
* @return this
*/
withScope(scope) {
this.scopes.push(scope);
}
/**
* Set the columns to be selected.
*
* @param array|mixed $columns
*
* @return this
*/
select(columns) {
this.attributes = columns;
return this;
}
resolveOperation(operation) {
switch (operation) {
case '=':
return Op.eq;
case '>':
return Op.gt;
case '<':
return Op.lt;
case '>=':
return Op.gte;
case '<=':
return Op.lte;
case '<>':
case '!=':
return Op.ne;
case 'like':
case 'LIKE':
return Op.like;
case 'notLike':
case 'NOTLIKE':
return Op.notLike;
default:
return operation;
}
}
buildWhereQuery() {
let query = {};
const group = _.groupBy(this.wheres, 'type');
if (this.wheres.length === 0 && this.scopeQueries.length === 0) {
return query;
}
if (!_.isUndefined(group[Op.or]) && group[Op.or].length > 0) {
query = {
[Op.or]: []
};
if (!_.isUndefined(group[Op.and]) && group[Op.and].length > 0) {
const andQuery = {};
_.forEach(group[Op.and], item => {
andQuery[item.column] = { [this.resolveOperation(item.operation)]: item.value };
});
query[Op.or].push({ [Op.and]: andQuery });
}
_.forEach(group[Op.or], item => {
if (item.operation === null && item.value == null) {
query[Op.or].push(item.column);
} else {
query[Op.or].push({ [item.column]: { [this.resolveOperation(item.operation)]: item.value } });
}
});
} else {
query = {
[Op.and]: []
};
_.forEach(group[Op.and], item => {
if (item.operation === null && item.value == null) {
query[Op.and].push(item.column);
} else {
query[Op.and].push({ [item.column]: { [this.resolveOperation(item.operation)]: item.value } });
}
});
}
if (_.isArray(this.scopeQueries) && this.scopeQueries.length > 0) {
_.forEach(this.scopeQueries, item => {
if (query[item.type] !== undefined && _.isArray(query[item.type])) {
query[item.type].push({ [item.type]: item.query.buildWhereQuery() });
} else {
query[item.type] = item.query.buildWhereQuery();
}
});
}
return query;
}
}