UNPKG

@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
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; } }