UNPKG

silvie

Version:

Typescript Back-end Framework

1,026 lines (973 loc) 28.4 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.default = void 0; var _where = _interopRequireDefault(require("../condition/where")); var _having = _interopRequireDefault(require("../condition/having")); var _join = _interopRequireDefault(require("../condition/join")); var _ = _interopRequireDefault(require("../..")); var _cloneDeep = _interopRequireDefault(require("lodash/cloneDeep")); function _interopRequireDefault(e) { return e && e.__esModule ? e : { default: e }; } function _defineProperty(e, r, t) { return (r = _toPropertyKey(r)) in e ? Object.defineProperty(e, r, { value: t, enumerable: !0, configurable: !0, writable: !0 }) : e[r] = t, e; } function _toPropertyKey(t) { var i = _toPrimitive(t, "string"); return "symbol" == typeof i ? i : i + ""; } function _toPrimitive(t, r) { if ("object" != typeof t || !t) return t; var e = t[Symbol.toPrimitive]; if (void 0 !== e) { var i = e.call(t, r || "default"); if ("object" != typeof i) return i; throw new TypeError("@@toPrimitive must return a primitive value."); } return ("string" === r ? String : Number)(t); } class QueryBuilder { constructor(tableName) { _defineProperty(this, "options", void 0); this.options = { select: [], where: [], having: [], order: [], group: [], union: [], join: [], processData: data => data, useTimestamps: true, createTimestamp: 'created_at', updateTimestamp: 'updated_at', useSoftDeletes: false, softDeleteTimestamp: 'deleted_at', withTrashed: false, onlyTrashed: false, fetchingRelations: [], alongQueries: [] }; if (tableName) { this.options.table = tableName; } } /** * Extend the query builder options with a custom options object * @param opts */ extend(opts) { Object.assign(this.options, opts); return this; } /** * Create a new copy of the current query builder */ clone() { const qb = new QueryBuilder(); qb.options = (0, _cloneDeep.default)(this.options); return qb; } /** * Return all matching rows of this query */ async get() { return this.options.processData(await _.default.select(this), this); } /** * Return the first matching row of this query */ async first() { const qb = this.clone(); qb.options.limit = 1; return this.options.processData(await _.default.select(qb), this)[0] || null; } /** * Query database to see if there are any records matching this query */ exists() { return _.default.exists(this); } /** * Query database to see if there are no records matching this query */ async doesntExist() { return !(await this.exists()); } /** * Query the database and return with an array of single field or a hashmap * @param keyColumn The column name to use for the keys * @param valueColumn If provided, it will be used to determine key, value pairs * @param overwrite Override the duplicate keys in a hashmap */ async pluck(keyColumn, valueColumn = null, overwrite = true) { const results = await this.get(); if (!valueColumn) { return results.map(row => row[keyColumn]); } if (overwrite) { return results.reduce((group, row) => { group[row[keyColumn]] = row[valueColumn]; return group; }, {}); } return results.reduce((group, row) => { const key = row[keyColumn]; const value = row[valueColumn]; if (group[key] !== undefined) { throw new Error(`Pluck found a duplicate key '${keyColumn}': ${key}'`); } group[key] = value; return group; }, {}); } /** * Return the count of records matching this query */ count() { return _.default.count(this); } /** * Calculate the average of the specified column * @param column */ average(column) { if (!column) { throw new Error("Column is not specified for 'average' method"); } return _.default.average(this, column); } /** * Calculate the summation of the specified column * @param column */ sum(column) { if (!column) { throw new Error("Column is not specified for 'sum' method"); } return _.default.sum(this, column); } /** * Find the minimum value of the specified column * @param column */ min(column) { if (!column) { throw new Error("Column is not specified for 'min' method"); } return _.default.min(this, column); } /** * Find the maximum value of the specified column * @param column */ max(column) { if (!column) { throw new Error("Column is not specified for 'max' method"); } return _.default.max(this, column); } /** * Insert the provided data into the table * @param data Inserting data * @param ignore Weather to ignore duplicate keys or not */ insert(data, ignore) { if (data.length === 0) { throw new Error('You should provide one or more objects to insert'); } this.options.insert = data; this.options.ignoreDuplicates = ignore; return _.default.insert(this).then(results => { delete this.options.insert; delete this.options.ignoreDuplicates; return results; }); } /** * Updates the table with the provided data * @param data Updating data * @param silent Weather to keep the update time field or not */ update(data, silent = false) { if (!data) { throw new Error('You should provide an object to the update method'); } if (Object.keys(data).length === 0) { throw new Error('An empty object cannot be used in update method'); } this.options.update = data; this.options.silentUpdate = silent; return _.default.update(this).then(results => { delete this.options.update; delete this.options.silentUpdate; return results; }); } /** * Update multiple records in the table * @param data Updating data * @param keys Keys used to find the matching row * @param silent Weather to keep the update time or not */ bulkUpdate(data, keys = [], silent = false) { if (data.length === 0) { throw new Error('You should provide one or more data to bulk update'); } if (keys.length === 0) { throw new Error('You should provide one or more keys to bulk update'); } this.options.bulkUpdateData = data; this.options.bulkUpdateKeys = keys; this.options.silentUpdate = silent; return _.default.bulkUpdate(this).then(results => { delete this.options.bulkUpdateData; delete this.options.bulkUpdateKeys; delete this.options.silentUpdate; return results; }); } /** * Delete the rows matching this query * @param soft Weather to use soft deletes or not */ delete(soft) { if (this.options.useSoftDeletes && soft === undefined || soft) { return this.softDelete(); } return _.default.delete(this); } /** * Enabled soft deletes for the current query builder instance */ useSoftDeletes() { this.options.useSoftDeletes = true; return this; } /** * Soft delete the rows matching this query */ softDelete() { if (!this.options.useSoftDeletes) { throw new Error(`Soft deletes are not enabled for this query builder`); } if (!this.options.softDeleteTimestamp) { throw new Error('Soft delete timestamp is not specified in this query builder'); } return _.default.softDelete(this); } /** * Undelete the soft deleted rows matching this query */ restore() { if (!this.options.useSoftDeletes) { throw new Error(`Soft deletes are not enabled for this query builder`); } if (!this.options.softDeleteTimestamp) { throw new Error('Soft delete timestamp is not specified in this query builder'); } return _.default.restore(this); } /** * Include soft deleted records in the result */ withTrashed() { if (!this.options.useSoftDeletes) { throw new Error(`Soft deletes are not enabled for this query builder`); } if (!this.options.softDeleteTimestamp) { throw new Error('Soft delete timestamp is not specified in this query builder'); } this.options.withTrashed = true; this.options.onlyTrashed = false; return this; } /** * Filter results to soft deleted records */ onlyTrashed() { if (!this.options.useSoftDeletes) { throw new Error(`Soft deletes are not enabled for this query builder`); } if (!this.options.softDeleteTimestamp) { throw new Error('Soft delete timestamp is not specified in this query builder'); } this.options.withTrashed = false; this.options.onlyTrashed = true; return this; } /** * Exclude soft deleted records from the result */ withoutTrashed() { if (!this.options.useSoftDeletes) { throw new Error(`Soft deletes are not enabled for this query builder`); } if (!this.options.softDeleteTimestamp) { throw new Error('Soft delete timestamp is not specified in this query builder'); } this.options.withTrashed = false; this.options.onlyTrashed = false; return this; } /** * Set a shared lock on this query */ sharedLock() { this.options.lock = 'shared'; return this; } /** * Set a lock for update on this query */ lockForUpdate() { this.options.lock = 'update'; return this; } /** * Remove the previously set locks */ clearLock() { this.options.lock = null; return this; } /** * Set the result of this query into a variable * @param variableName */ into(variableName) { this.options.selectInto = variableName; return this; } /** * Select the current query fields from another query builder * @param queryBuilder Query builder to select from * @param alias Alias name of the resulting table */ fromAliasTable(queryBuilder, alias) { this.options.aliasTable = { queryBuilder, alias }; return this; } /** * Select a set of columns from the table * @param columns */ select(...columns) { this.options.select.push(...columns.map(column => ({ column, type: 'column' }))); return this; } /** * Select a query builder as a sub selection * @param queryBuilder Query builder to use in selection * @param alias Alias name for the sub selection */ selectSub(queryBuilder, alias) { this.options.select.push({ queryBuilder, alias, type: 'query' }); return this; } /** * Add a raw query as a selection * @param query Query string * @param params Binding parameters */ selectRaw(query, params) { this.options.select.push({ query, params: params || [], type: 'raw' }); return this; } /** * Order the results by a column * @param column * @param direction */ orderBy(column, direction) { const order = { direction: direction || 'ASC', type: 'column' }; if (column instanceof QueryBuilder) { order.queryBuilder = column; order.type = 'query'; } else { order.column = column; } this.options.order.push(order); return this; } /** * Add a raw query to the order * @param query * @param params */ orderByRaw(query, params) { this.options.order.push({ query, params, type: 'raw' }); return this; } /** * Clear previously set orders or set a fresh order * @param column * @param direction */ reorder(column, direction) { this.options.order = []; if (column) { return this.orderBy(column, direction); } return this; } /** * Randomize the result order * @param seed Seed to use in the random function */ shuffle(seed) { this.options.randomOrder = true; this.options.randomSeed = seed || ''; return this; } /** * Skip the first n rows * @param count */ offset(count) { this.options.offset = count; return this; } /** * Skip the first n rows * @param count */ skip(count) { return this.offset(count); } /** * Take the next m rows * @param count */ limit(count) { this.options.limit = count; return this; } /** * Take the next m rows * @param count */ take(count) { return this.limit(count); } baseAggregate(type, column, alias, meta) { this.options.select.push({ type: 'aggregate', aggregation: type, column, alias, meta }); return this; } /** * Select the count of matching rows in the field set * @param alias */ selectCount(alias) { return this.baseAggregate('count', '*', alias); } /** * Select the average of specified column in the field set * @param column Column to get average * @param alias Alias name for the new field */ selectAverage(column, alias) { return this.baseAggregate('average', column, alias); } /** * Select the average of specified column in the field set * @param column Column to get summation * @param alias Alias name for the new field */ selectSum(column, alias) { return this.baseAggregate('summation', column, alias); } /** * Select the minimum value of specified column in the field set * @param column Column to get summation * @param alias Alias name for the new field */ selectMin(column, alias) { return this.baseAggregate('minimum', column, alias); } /** * Select the maximum value of specified column in the field set * @param column Column to get summation * @param alias Alias name for the new field */ selectMax(column, alias) { return this.baseAggregate('maximum', column, alias); } /** * Group the results by the specified columns * @param columns */ groupBy(...columns) { this.options.group.push(...columns.map(column => ({ column, type: 'column' }))); return this; } /** * Group the results by the specified raw query * @param query * @param params */ groupByRaw(query, params) { this.options.group.push({ query, params, type: 'raw' }); return this; } /** * Union the selection with a query builder instance * @param queryBuilder * @param all */ union(queryBuilder, all = false) { this.options.union.push({ queryBuilder, all, type: 'query' }); return this; } /** * Union the selection with a raw query * @param query * @param params * @param all */ unionRaw(query, params, all = false) { this.options.union.push({ query, params, all, type: 'raw' }); return this; } baseJoin(type, table, column1, operator, column2, alias) { const join = { type }; if (column1 instanceof Function) { const conditionBuilder = new _join.default(); column1(conditionBuilder); join.conditions = conditionBuilder.conditions; if (typeof operator === 'string') { join.alias = operator; } } else { join.column1 = column1; if (alias === undefined) { if (column2 === undefined) { if (operator === undefined) { throw new Error(`Invalid usage of ${type} join`); } else { join.operator = '='; join.column2 = operator; } } else { join.operator = operator; join.column2 = column2; } } else { join.operator = operator; join.column2 = column2; join.alias = alias; } } if (table instanceof QueryBuilder) { join.queryBuilder = table; if (alias === undefined) { if (column2 === undefined) { throw new Error(`Invalid usage of ${type} join with query builder`); } else { join.operator = '='; join.column2 = operator; join.alias = column2; } } else { join.operator = operator; join.column2 = column2; join.alias = alias; } } else { join.table = table; } this.options.join.push(join); return this; } /** * Inner join the current query with another table or query builder * @param table Joining table or QueryBuilder instance * @param column1 Condition's first column or condition builder callback function * @param operator Custom operator or condition's second column * @param column2 Condition's second column or alias name * @param alias Alias name for the joining table */ join(table, column1, operator, column2, alias) { return this.baseJoin('inner', table, column1, operator, column2, alias); } /** * Left join the current query with another table or query builder * @param table Joining table or QueryBuilder instance * @param column1 Condition's first column or condition builder callback function * @param operator Custom operator or condition's second column * @param column2 Condition's second column or alias name * @param alias Alias name for the joining table */ leftJoin(table, column1, operator, column2, alias) { return this.baseJoin('left', table, column1, operator, column2, alias); } /** * Right join the current query with another table or query builder * @param table Joining table or QueryBuilder instance * @param column1 Condition's first column or condition builder callback function * @param operator Custom operator or condition's second column * @param column2 Condition's second column or alias name * @param alias Alias name for the joining table */ rightJoin(table, column1, operator, column2, alias) { return this.baseJoin('right', table, column1, operator, column2, alias); } /** * Cross join the current query with another table or query builder * @param table Joining table or QueryBuilder instance * @param column1 Condition's first column or condition builder callback function * @param operator Custom operator or condition's second column * @param column2 Condition's second column or alias name * @param alias Alias name for the joining table */ crossJoin(table, column1, operator, column2, alias) { return this.baseJoin('cross', table, column1, operator, column2, alias); } /** * Outer join the current query with another table or query builder * @param table Joining table or QueryBuilder instance * @param column1 Condition's first column or condition builder callback function * @param operator Custom operator or condition's second column * @param column2 Condition's second column or alias name * @param alias Alias name for the joining table */ outerJoin(table, column1, operator, column2, alias) { return this.baseJoin('outer', table, column1, operator, column2, alias); } baseWhere(type, relation, lhs, operator, rhs) { const condition = { type, relation }; if (lhs instanceof Function) { const conditionBuilder = new _where.default(); lhs(conditionBuilder); condition.type = 'group'; condition.conditions = conditionBuilder.conditions; } else { condition.leftHandSide = lhs; if (rhs === undefined) { if (operator === undefined) { if (!type.endsWith('null')) { throw new Error(`Invalid usage of ${type} where`); } } else { if (!['between', 'not between', 'in', 'not in', 'like', 'not like'].includes(type)) { condition.operator = '='; } condition.rightHandSide = operator; } } else { if (operator) condition.operator = operator; condition.rightHandSide = rhs; } } this.options.where.push(condition); return this; } where(column, operator, value) { return this.baseWhere('value', 'and', column, operator, value); } orWhere(column, operator, value) { return this.baseWhere('value', 'or', column, operator, value); } whereNull(column) { return this.baseWhere('null', 'and', column); } orWhereNull(column) { return this.baseWhere('null', 'or', column); } whereNotNull(column) { return this.baseWhere('not null', 'and', column); } orWhereNotNull(column) { return this.baseWhere('not null', 'or', column); } whereBetween(column, values) { return this.baseWhere('between', 'and', column, values); } orWhereBetween(column, values) { return this.baseWhere('between', 'and', column, values); } whereNotBetween(column, values) { return this.baseWhere('not between', 'and', column, values); } orWhereNotBetween(column, values) { return this.baseWhere('not between', 'and', column, values); } whereIn(column, values) { return this.baseWhere('in', 'and', column, values); } orWhereIn(column, values) { return this.baseWhere('in', 'or', column, values); } whereNotIn(column, values) { return this.baseWhere('not in', 'and', column, values); } orWhereNotIn(column, values) { return this.baseWhere('not in', 'or', column, values); } whereLike(column, value) { return this.baseWhere('like', 'and', column, value); } orWhereLike(column, value) { return this.baseWhere('like', 'or', column, value); } whereNotLike(column, value) { return this.baseWhere('not like', 'and', column, value); } orWhereNotLike(column, value) { return this.baseWhere('not like', 'or', column, value); } whereColumn(firstColumn, operator, secondColumn) { return this.baseWhere('column', 'and', firstColumn, operator, secondColumn); } orWhereColumn(firstColumn, operator, secondColumn) { return this.baseWhere('column', 'or', firstColumn, operator, secondColumn); } whereDate(column, operator, value) { return this.baseWhere('date', 'and', column, operator, value); } orWhereDate(column, operator, value) { return this.baseWhere('date', 'or', column, operator, value); } whereYear(column, operator, value) { return this.baseWhere('year', 'and', column, operator, value); } orWhereYear(column, operator, value) { return this.baseWhere('year', 'or', column, operator, value); } whereMonth(column, operator, value) { return this.baseWhere('month', 'and', column, operator, value); } orWhereMonth(column, operator, value) { return this.baseWhere('month', 'or', column, operator, value); } whereDay(column, operator, value) { return this.baseWhere('day', 'and', column, operator, value); } orWhereDay(column, operator, value) { return this.baseWhere('day', 'or', column, operator, value); } whereTime(column, operator, value) { return this.baseWhere('time', 'and', column, operator, value); } orWhereTime(column, operator, value) { return this.baseWhere('time', 'or', column, operator, value); } whereRaw(query, params) { this.options.where.push({ type: 'raw', relation: 'and', query, params: params || [] }); return this; } orWhereRaw(query, params) { this.options.where.push({ type: 'raw', relation: 'or', query, params: params || [] }); return this; } groupWhereConditions() { if (this.options.where.length > 0) { const condition = { type: 'group', relation: 'and', conditions: this.options.where }; this.options.where = [condition]; } return this; } baseHaving(type, relation, lhs, operator, rhs) { const condition = { type, relation }; if (lhs instanceof Function) { const conditionBuilder = new _having.default(); lhs(conditionBuilder); condition.type = 'group'; condition.conditions = conditionBuilder.conditions; } else { condition.leftHandSide = lhs; if (rhs === undefined) { if (operator === undefined) { if (!type.endsWith('null')) { throw new Error(`Invalid usage of ${type} having`); } } else { if (!['between', 'not between', 'in', 'not in', 'like', 'not like'].includes(type)) { condition.operator = '='; } condition.rightHandSide = operator; } } else { if (operator) condition.operator = operator; condition.rightHandSide = rhs; } } this.options.having.push(condition); return this; } having(column, operator, value) { return this.baseHaving('value', 'and', column, operator, value); } orHaving(column, operator, value) { return this.baseHaving('value', 'or', column, operator, value); } havingNull(column) { return this.baseHaving('null', 'and', column); } orHavingNull(column) { return this.baseHaving('null', 'or', column); } havingNotNull(column) { return this.baseHaving('not null', 'and', column); } orHavingNotNull(column) { return this.baseHaving('not null', 'or', column); } havingBetween(column, values) { return this.baseHaving('between', 'and', column, values); } orHavingBetween(column, values) { return this.baseHaving('between', 'and', column, values); } havingNotBetween(column, values) { return this.baseHaving('not between', 'and', column, values); } orHavingNotBetween(column, values) { return this.baseHaving('not between', 'and', column, values); } havingIn(column, values) { return this.baseHaving('in', 'and', column, values); } orHavingIn(column, values) { return this.baseHaving('in', 'or', column, values); } havingNotIn(column, values) { return this.baseHaving('not in', 'and', column, values); } orHavingNotIn(column, values) { return this.baseHaving('not in', 'or', column, values); } havingLike(column, value) { return this.baseHaving('like', 'and', column, value); } orHavingLike(column, value) { return this.baseHaving('like', 'or', column, value); } havingNotLike(column, value) { return this.baseHaving('not like', 'and', column, value); } orHavingNotLike(column, value) { return this.baseHaving('not like', 'or', column, value); } havingColumn(firstColumn, operator, secondColumn) { return this.baseHaving('column', 'and', firstColumn, operator, secondColumn); } orHavingColumn(firstColumn, operator, secondColumn) { return this.baseHaving('column', 'or', firstColumn, operator, secondColumn); } havingDate(column, operator, value) { return this.baseHaving('date', 'and', column, operator, value); } orHavingDate(column, operator, value) { return this.baseHaving('date', 'or', column, operator, value); } havingYear(column, operator, value) { return this.baseHaving('year', 'and', column, operator, value); } orHavingYear(column, operator, value) { return this.baseHaving('year', 'or', column, operator, value); } havingMonth(column, operator, value) { return this.baseHaving('month', 'and', column, operator, value); } orHavingMonth(column, operator, value) { return this.baseHaving('month', 'or', column, operator, value); } havingDay(column, operator, value) { return this.baseHaving('day', 'and', column, operator, value); } orHavingDay(column, operator, value) { return this.baseHaving('day', 'or', column, operator, value); } havingTime(column, operator, value) { return this.baseHaving('time', 'and', column, operator, value); } orHavingTime(column, operator, value) { return this.baseHaving('time', 'or', column, operator, value); } havingRaw(query, params) { this.options.having.push({ type: 'raw', relation: 'and', query, params: params || [] }); return this; } orHavingRaw(query, params) { this.options.having.push({ type: 'raw', relation: 'or', query, params: params || [] }); return this; } groupHavingConditions() { if (this.options.having.length > 0) { const condition = { type: 'group', relation: 'and', conditions: this.options.having }; this.options.having = [condition]; } return this; } alongWith(queryBuilder) { this.options.alongQueries.push(queryBuilder); return this; } } exports.default = QueryBuilder;