silvie
Version:
Typescript Back-end Framework
1,026 lines (973 loc) • 28.4 kB
JavaScript
;
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;