1795 lines
47 KiB
JavaScript
1795 lines
47 KiB
JavaScript
// Builder
|
|
// -------
|
|
const assert = require('assert');
|
|
const { EventEmitter } = require('events');
|
|
const assign = require('lodash/assign');
|
|
const clone = require('lodash/clone');
|
|
const each = require('lodash/each');
|
|
const isEmpty = require('lodash/isEmpty');
|
|
const isPlainObject = require('lodash/isPlainObject');
|
|
const last = require('lodash/last');
|
|
const reject = require('lodash/reject');
|
|
const tail = require('lodash/tail');
|
|
const toArray = require('lodash/toArray');
|
|
|
|
const { addQueryContext, normalizeArr } = require('../util/helpers');
|
|
const JoinClause = require('./joinclause');
|
|
const Analytic = require('./analytic');
|
|
const saveAsyncStack = require('../util/save-async-stack');
|
|
const {
|
|
isBoolean,
|
|
isNumber,
|
|
isObject,
|
|
isString,
|
|
isFunction,
|
|
} = require('../util/is');
|
|
|
|
const { lockMode, waitMode } = require('./constants');
|
|
const {
|
|
augmentWithBuilderInterface,
|
|
} = require('../builder-interface-augmenter');
|
|
|
|
const SELECT_COMMANDS = new Set(['pluck', 'first', 'select']);
|
|
const CLEARABLE_STATEMENTS = new Set([
|
|
'with',
|
|
'select',
|
|
'columns',
|
|
'hintComments',
|
|
'where',
|
|
'union',
|
|
'join',
|
|
'group',
|
|
'order',
|
|
'having',
|
|
'limit',
|
|
'offset',
|
|
'counter',
|
|
'counters',
|
|
]);
|
|
const LOCK_MODES = new Set([
|
|
lockMode.forShare,
|
|
lockMode.forUpdate,
|
|
lockMode.forNoKeyUpdate,
|
|
lockMode.forKeyShare,
|
|
]);
|
|
|
|
// Typically called from `knex.builder`,
|
|
// start a new query building chain.
|
|
class Builder extends EventEmitter {
|
|
constructor(client) {
|
|
super();
|
|
this.client = client;
|
|
this.and = this;
|
|
this._single = {};
|
|
this._statements = [];
|
|
this._method = 'select';
|
|
if (client.config) {
|
|
saveAsyncStack(this, 5);
|
|
this._debug = client.config.debug;
|
|
}
|
|
// Internal flags used in the builder.
|
|
this._joinFlag = 'inner';
|
|
this._boolFlag = 'and';
|
|
this._notFlag = false;
|
|
this._asColumnFlag = false;
|
|
}
|
|
|
|
toString() {
|
|
return this.toQuery();
|
|
}
|
|
|
|
// Convert the current query "toSQL"
|
|
toSQL(method, tz) {
|
|
return this.client.queryCompiler(this).toSQL(method || this._method, tz);
|
|
}
|
|
|
|
// Create a shallow clone of the current query builder.
|
|
clone() {
|
|
const cloned = new this.constructor(this.client);
|
|
cloned._method = this._method;
|
|
cloned._single = clone(this._single);
|
|
cloned._statements = clone(this._statements);
|
|
cloned._debug = this._debug;
|
|
|
|
// `_option` is assigned by the `Interface` mixin.
|
|
if (this._options !== undefined) {
|
|
cloned._options = clone(this._options);
|
|
}
|
|
if (this._queryContext !== undefined) {
|
|
cloned._queryContext = clone(this._queryContext);
|
|
}
|
|
if (this._connection !== undefined) {
|
|
cloned._connection = this._connection;
|
|
}
|
|
|
|
return cloned;
|
|
}
|
|
|
|
timeout(ms, { cancel } = {}) {
|
|
if (isNumber(ms) && ms > 0) {
|
|
this._timeout = ms;
|
|
if (cancel) {
|
|
this.client.assertCanCancelQuery();
|
|
this._cancelOnTimeout = true;
|
|
}
|
|
}
|
|
return this;
|
|
}
|
|
|
|
// With
|
|
// ------
|
|
isValidStatementArg(statement) {
|
|
return (
|
|
typeof statement === 'function' ||
|
|
statement instanceof Builder ||
|
|
(statement && statement.isRawInstance)
|
|
);
|
|
}
|
|
|
|
_validateWithArgs(alias, statementOrColumnList, nothingOrStatement, method) {
|
|
const [query, columnList] =
|
|
typeof nothingOrStatement === 'undefined'
|
|
? [statementOrColumnList, undefined]
|
|
: [nothingOrStatement, statementOrColumnList];
|
|
if (typeof alias !== 'string') {
|
|
throw new Error(`${method}() first argument must be a string`);
|
|
}
|
|
|
|
if (this.isValidStatementArg(query) && typeof columnList === 'undefined') {
|
|
// Validated as two-arg variant (alias, statement).
|
|
return;
|
|
}
|
|
|
|
// Attempt to interpret as three-arg variant (alias, columnList, statement).
|
|
const isNonEmptyNameList =
|
|
Array.isArray(columnList) &&
|
|
columnList.length > 0 &&
|
|
columnList.every((it) => typeof it === 'string');
|
|
if (!isNonEmptyNameList) {
|
|
throw new Error(
|
|
`${method}() second argument must be a statement or non-empty column name list.`
|
|
);
|
|
}
|
|
|
|
if (this.isValidStatementArg(query)) {
|
|
return;
|
|
}
|
|
throw new Error(
|
|
`${method}() third argument must be a function / QueryBuilder or a raw when its second argument is a column name list`
|
|
);
|
|
}
|
|
|
|
with(alias, statementOrColumnList, nothingOrStatement) {
|
|
this._validateWithArgs(
|
|
alias,
|
|
statementOrColumnList,
|
|
nothingOrStatement,
|
|
'with'
|
|
);
|
|
return this.withWrapped(alias, statementOrColumnList, nothingOrStatement);
|
|
}
|
|
|
|
withMaterialized(alias, statementOrColumnList, nothingOrStatement) {
|
|
throw new Error('With materialized is not supported by this dialect');
|
|
}
|
|
|
|
withNotMaterialized(alias, statementOrColumnList, nothingOrStatement) {
|
|
throw new Error('With materialized is not supported by this dialect');
|
|
}
|
|
|
|
// Helper for compiling any advanced `with` queries.
|
|
withWrapped(alias, statementOrColumnList, nothingOrStatement, materialized) {
|
|
const [query, columnList] =
|
|
typeof nothingOrStatement === 'undefined'
|
|
? [statementOrColumnList, undefined]
|
|
: [nothingOrStatement, statementOrColumnList];
|
|
const statement = {
|
|
grouping: 'with',
|
|
type: 'withWrapped',
|
|
alias: alias,
|
|
columnList,
|
|
value: query,
|
|
};
|
|
if (materialized !== undefined) {
|
|
statement.materialized = materialized;
|
|
}
|
|
this._statements.push(statement);
|
|
return this;
|
|
}
|
|
|
|
// With Recursive
|
|
// ------
|
|
|
|
withRecursive(alias, statementOrColumnList, nothingOrStatement) {
|
|
this._validateWithArgs(
|
|
alias,
|
|
statementOrColumnList,
|
|
nothingOrStatement,
|
|
'withRecursive'
|
|
);
|
|
return this.withRecursiveWrapped(
|
|
alias,
|
|
statementOrColumnList,
|
|
nothingOrStatement
|
|
);
|
|
}
|
|
|
|
// Helper for compiling any advanced `withRecursive` queries.
|
|
withRecursiveWrapped(alias, statementOrColumnList, nothingOrStatement) {
|
|
this.withWrapped(alias, statementOrColumnList, nothingOrStatement);
|
|
this._statements[this._statements.length - 1].recursive = true;
|
|
return this;
|
|
}
|
|
|
|
// Select
|
|
// ------
|
|
|
|
// Adds a column or columns to the list of "columns"
|
|
// being selected on the query.
|
|
columns(column) {
|
|
if (!column && column !== 0) return this;
|
|
this._statements.push({
|
|
grouping: 'columns',
|
|
value: normalizeArr(...arguments),
|
|
});
|
|
return this;
|
|
}
|
|
|
|
// Allow for a sub-select to be explicitly aliased as a column,
|
|
// without needing to compile the query in a where.
|
|
as(column) {
|
|
this._single.as = column;
|
|
return this;
|
|
}
|
|
|
|
// Adds a single hint or an array of hits to the list of "hintComments" on the query.
|
|
hintComment(hints) {
|
|
hints = Array.isArray(hints) ? hints : [hints];
|
|
if (hints.some((hint) => !isString(hint))) {
|
|
throw new Error('Hint comment must be a string');
|
|
}
|
|
if (hints.some((hint) => hint.includes('/*') || hint.includes('*/'))) {
|
|
throw new Error('Hint comment cannot include "/*" or "*/"');
|
|
}
|
|
if (hints.some((hint) => hint.includes('?'))) {
|
|
throw new Error('Hint comment cannot include "?"');
|
|
}
|
|
this._statements.push({
|
|
grouping: 'hintComments',
|
|
value: hints,
|
|
});
|
|
return this;
|
|
}
|
|
|
|
// Prepends the `schemaName` on `tableName` defined by `.table` and `.join`.
|
|
withSchema(schemaName) {
|
|
this._single.schema = schemaName;
|
|
return this;
|
|
}
|
|
|
|
// Sets the `tableName` on the query.
|
|
// Alias to "from" for select and "into" for insert statements
|
|
// e.g. builder.insert({a: value}).into('tableName')
|
|
// `options`: options object containing keys:
|
|
// - `only`: whether the query should use SQL's ONLY to not return
|
|
// inheriting table data. Defaults to false.
|
|
table(tableName, options = {}) {
|
|
this._single.table = tableName;
|
|
this._single.only = options.only === true;
|
|
return this;
|
|
}
|
|
|
|
// Adds a `distinct` clause to the query.
|
|
distinct(...args) {
|
|
this._statements.push({
|
|
grouping: 'columns',
|
|
value: normalizeArr(...args),
|
|
distinct: true,
|
|
});
|
|
return this;
|
|
}
|
|
|
|
distinctOn(...args) {
|
|
if (isEmpty(args)) {
|
|
throw new Error('distinctOn requires at least on argument');
|
|
}
|
|
this._statements.push({
|
|
grouping: 'columns',
|
|
value: normalizeArr(...args),
|
|
distinctOn: true,
|
|
});
|
|
return this;
|
|
}
|
|
|
|
// Adds a join clause to the query, allowing for advanced joins
|
|
// with an anonymous function as the second argument.
|
|
join(table, first, ...args) {
|
|
let join;
|
|
const schema =
|
|
table instanceof Builder || typeof table === 'function'
|
|
? undefined
|
|
: this._single.schema;
|
|
const joinType = this._joinType();
|
|
if (typeof first === 'function') {
|
|
join = new JoinClause(table, joinType, schema);
|
|
first.call(join, join);
|
|
} else if (joinType === 'raw') {
|
|
join = new JoinClause(this.client.raw(table, first), 'raw');
|
|
} else {
|
|
join = new JoinClause(table, joinType, schema);
|
|
if (first) {
|
|
join.on(first, ...args);
|
|
}
|
|
}
|
|
this._statements.push(join);
|
|
return this;
|
|
}
|
|
|
|
using(tables) {
|
|
throw new Error(
|
|
"'using' function is only available in PostgreSQL dialect with Delete statements."
|
|
);
|
|
}
|
|
|
|
// JOIN blocks:
|
|
innerJoin(...args) {
|
|
return this._joinType('inner').join(...args);
|
|
}
|
|
|
|
leftJoin(...args) {
|
|
return this._joinType('left').join(...args);
|
|
}
|
|
|
|
leftOuterJoin(...args) {
|
|
return this._joinType('left outer').join(...args);
|
|
}
|
|
|
|
rightJoin(...args) {
|
|
return this._joinType('right').join(...args);
|
|
}
|
|
|
|
rightOuterJoin(...args) {
|
|
return this._joinType('right outer').join(...args);
|
|
}
|
|
|
|
outerJoin(...args) {
|
|
return this._joinType('outer').join(...args);
|
|
}
|
|
|
|
fullOuterJoin(...args) {
|
|
return this._joinType('full outer').join(...args);
|
|
}
|
|
|
|
crossJoin(...args) {
|
|
return this._joinType('cross').join(...args);
|
|
}
|
|
|
|
joinRaw(...args) {
|
|
return this._joinType('raw').join(...args);
|
|
}
|
|
|
|
// Where modifiers:
|
|
get or() {
|
|
return this._bool('or');
|
|
}
|
|
|
|
get not() {
|
|
return this._not(true);
|
|
}
|
|
|
|
// The where function can be used in several ways:
|
|
// The most basic is `where(key, value)`, which expands to
|
|
// where key = value.
|
|
where(column, operator, value) {
|
|
const argsLength = arguments.length;
|
|
|
|
// Support "where true || where false"
|
|
if (column === false || column === true) {
|
|
return this.where(1, '=', column ? 1 : 0);
|
|
}
|
|
|
|
// Check if the column is a function, in which case it's
|
|
// a where statement wrapped in parens.
|
|
if (typeof column === 'function') {
|
|
return this.whereWrapped(column);
|
|
}
|
|
|
|
// Allows `where({id: 2})` syntax.
|
|
if (isObject(column) && !column.isRawInstance)
|
|
return this._objectWhere(column);
|
|
|
|
// Allow a raw statement to be passed along to the query.
|
|
if (column && column.isRawInstance && argsLength === 1)
|
|
return this.whereRaw(column);
|
|
|
|
// Enable the where('key', value) syntax, only when there
|
|
// are explicitly two arguments passed, so it's not possible to
|
|
// do where('key', '!=') and have that turn into where key != null
|
|
if (argsLength === 2) {
|
|
value = operator;
|
|
operator = '=';
|
|
|
|
// If the value is null, and it's a two argument query,
|
|
// we assume we're going for a `whereNull`.
|
|
if (value === null) {
|
|
return this.whereNull(column);
|
|
}
|
|
}
|
|
|
|
// lower case the operator for comparison purposes
|
|
const checkOperator = `${operator}`.toLowerCase().trim();
|
|
|
|
// If there are 3 arguments, check whether 'in' is one of them.
|
|
if (argsLength === 3) {
|
|
if (checkOperator === 'in' || checkOperator === 'not in') {
|
|
return this._not(checkOperator === 'not in').whereIn(column, value);
|
|
}
|
|
if (checkOperator === 'between' || checkOperator === 'not between') {
|
|
return this._not(checkOperator === 'not between').whereBetween(
|
|
column,
|
|
value
|
|
);
|
|
}
|
|
}
|
|
|
|
// If the value is still null, check whether they're meaning
|
|
// where value is null
|
|
if (value === null) {
|
|
// Check for .where(key, 'is', null) or .where(key, 'is not', 'null');
|
|
if (checkOperator === 'is' || checkOperator === 'is not') {
|
|
return this._not(checkOperator === 'is not').whereNull(column);
|
|
}
|
|
}
|
|
|
|
// Push onto the where statement stack.
|
|
this._statements.push({
|
|
grouping: 'where',
|
|
type: 'whereBasic',
|
|
column,
|
|
operator,
|
|
value,
|
|
not: this._not(),
|
|
bool: this._bool(),
|
|
asColumn: this._asColumnFlag,
|
|
});
|
|
return this;
|
|
}
|
|
|
|
whereColumn(...args) {
|
|
this._asColumnFlag = true;
|
|
this.where(...args);
|
|
this._asColumnFlag = false;
|
|
return this;
|
|
}
|
|
|
|
// Adds an `or where` clause to the query.
|
|
orWhere(column, ...args) {
|
|
this._bool('or');
|
|
const obj = column;
|
|
if (isObject(obj) && !obj.isRawInstance) {
|
|
return this.whereWrapped(function () {
|
|
for (const key in obj) {
|
|
this.andWhere(key, obj[key]);
|
|
}
|
|
});
|
|
}
|
|
return this.where(column, ...args);
|
|
}
|
|
|
|
orWhereColumn(column, ...args) {
|
|
this._bool('or');
|
|
const obj = column;
|
|
if (isObject(obj) && !obj.isRawInstance) {
|
|
return this.whereWrapped(function () {
|
|
for (const key in obj) {
|
|
this.andWhereColumn(key, '=', obj[key]);
|
|
}
|
|
});
|
|
}
|
|
return this.whereColumn(column, ...args);
|
|
}
|
|
|
|
// Adds an `not where` clause to the query.
|
|
whereNot(column, ...args) {
|
|
if (args.length >= 2) {
|
|
if (args[0] === 'in' || args[0] === 'between') {
|
|
this.client.logger.warn(
|
|
'whereNot is not suitable for "in" and "between" type subqueries. You should use "not in" and "not between" instead.'
|
|
);
|
|
}
|
|
}
|
|
return this._not(true).where(column, ...args);
|
|
}
|
|
|
|
whereNotColumn(...args) {
|
|
return this._not(true).whereColumn(...args);
|
|
}
|
|
|
|
// Adds an `or not where` clause to the query.
|
|
orWhereNot(...args) {
|
|
return this._bool('or').whereNot(...args);
|
|
}
|
|
|
|
orWhereNotColumn(...args) {
|
|
return this._bool('or').whereNotColumn(...args);
|
|
}
|
|
|
|
// Processes an object literal provided in a "where" clause.
|
|
_objectWhere(obj) {
|
|
const boolVal = this._bool();
|
|
const notVal = this._not() ? 'Not' : '';
|
|
for (const key in obj) {
|
|
this[boolVal + 'Where' + notVal](key, obj[key]);
|
|
}
|
|
return this;
|
|
}
|
|
|
|
// Adds a raw `where` clause to the query.
|
|
whereRaw(sql, bindings) {
|
|
const raw = sql.isRawInstance ? sql : this.client.raw(sql, bindings);
|
|
|
|
this._statements.push({
|
|
grouping: 'where',
|
|
type: 'whereRaw',
|
|
value: raw,
|
|
not: this._not(),
|
|
bool: this._bool(),
|
|
});
|
|
return this;
|
|
}
|
|
|
|
orWhereRaw(sql, bindings) {
|
|
return this._bool('or').whereRaw(sql, bindings);
|
|
}
|
|
|
|
// Helper for compiling any advanced `where` queries.
|
|
whereWrapped(callback) {
|
|
this._statements.push({
|
|
grouping: 'where',
|
|
type: 'whereWrapped',
|
|
value: callback,
|
|
not: this._not(),
|
|
bool: this._bool(),
|
|
});
|
|
return this;
|
|
}
|
|
|
|
// Adds a `where exists` clause to the query.
|
|
whereExists(callback) {
|
|
this._statements.push({
|
|
grouping: 'where',
|
|
type: 'whereExists',
|
|
value: callback,
|
|
not: this._not(),
|
|
bool: this._bool(),
|
|
});
|
|
return this;
|
|
}
|
|
|
|
// Adds an `or where exists` clause to the query.
|
|
orWhereExists(callback) {
|
|
return this._bool('or').whereExists(callback);
|
|
}
|
|
|
|
// Adds a `where not exists` clause to the query.
|
|
whereNotExists(callback) {
|
|
return this._not(true).whereExists(callback);
|
|
}
|
|
|
|
// Adds a `or where not exists` clause to the query.
|
|
orWhereNotExists(callback) {
|
|
return this._bool('or').whereNotExists(callback);
|
|
}
|
|
|
|
// Adds a `where in` clause to the query.
|
|
whereIn(column, values) {
|
|
if (Array.isArray(values) && isEmpty(values))
|
|
return this.where(this._not());
|
|
this._statements.push({
|
|
grouping: 'where',
|
|
type: 'whereIn',
|
|
column,
|
|
value: values,
|
|
not: this._not(),
|
|
bool: this._bool(),
|
|
});
|
|
return this;
|
|
}
|
|
|
|
// Adds a `or where in` clause to the query.
|
|
orWhereIn(column, values) {
|
|
return this._bool('or').whereIn(column, values);
|
|
}
|
|
|
|
// Adds a `where not in` clause to the query.
|
|
whereNotIn(column, values) {
|
|
return this._not(true).whereIn(column, values);
|
|
}
|
|
|
|
// Adds a `or where not in` clause to the query.
|
|
orWhereNotIn(column, values) {
|
|
return this._bool('or')._not(true).whereIn(column, values);
|
|
}
|
|
|
|
// Adds a `where null` clause to the query.
|
|
whereNull(column) {
|
|
this._statements.push({
|
|
grouping: 'where',
|
|
type: 'whereNull',
|
|
column,
|
|
not: this._not(),
|
|
bool: this._bool(),
|
|
});
|
|
return this;
|
|
}
|
|
|
|
// Adds a `or where null` clause to the query.
|
|
orWhereNull(column) {
|
|
return this._bool('or').whereNull(column);
|
|
}
|
|
|
|
// Adds a `where not null` clause to the query.
|
|
whereNotNull(column) {
|
|
return this._not(true).whereNull(column);
|
|
}
|
|
|
|
// Adds a `or where not null` clause to the query.
|
|
orWhereNotNull(column) {
|
|
return this._bool('or').whereNotNull(column);
|
|
}
|
|
|
|
// Adds a `where between` clause to the query.
|
|
whereBetween(column, values) {
|
|
assert(
|
|
Array.isArray(values),
|
|
'The second argument to whereBetween must be an array.'
|
|
);
|
|
assert(
|
|
values.length === 2,
|
|
'You must specify 2 values for the whereBetween clause'
|
|
);
|
|
this._statements.push({
|
|
grouping: 'where',
|
|
type: 'whereBetween',
|
|
column,
|
|
value: values,
|
|
not: this._not(),
|
|
bool: this._bool(),
|
|
});
|
|
return this;
|
|
}
|
|
|
|
// Adds a `where not between` clause to the query.
|
|
whereNotBetween(column, values) {
|
|
return this._not(true).whereBetween(column, values);
|
|
}
|
|
|
|
// Adds a `or where between` clause to the query.
|
|
orWhereBetween(column, values) {
|
|
return this._bool('or').whereBetween(column, values);
|
|
}
|
|
|
|
// Adds a `or where not between` clause to the query.
|
|
orWhereNotBetween(column, values) {
|
|
return this._bool('or').whereNotBetween(column, values);
|
|
}
|
|
|
|
_whereLike(type, column, value) {
|
|
this._statements.push({
|
|
grouping: 'where',
|
|
type: type,
|
|
column,
|
|
value: value,
|
|
not: this._not(),
|
|
bool: this._bool(),
|
|
asColumn: this._asColumnFlag,
|
|
});
|
|
return this;
|
|
}
|
|
|
|
// Adds a `where like` clause to the query.
|
|
whereLike(column, value) {
|
|
return this._whereLike('whereLike', column, value);
|
|
}
|
|
|
|
// Adds a `or where like` clause to the query.
|
|
orWhereLike(column, value) {
|
|
return this._bool('or')._whereLike('whereLike', column, value);
|
|
}
|
|
|
|
// Adds a `where ilike` clause to the query.
|
|
whereILike(column, value) {
|
|
return this._whereLike('whereILike', column, value);
|
|
}
|
|
|
|
// Adds a `or where ilike` clause to the query.
|
|
orWhereILike(column, value) {
|
|
return this._bool('or')._whereLike('whereILike', column, value);
|
|
}
|
|
|
|
// Adds a `group by` clause to the query.
|
|
groupBy(item) {
|
|
if (item && item.isRawInstance) {
|
|
return this.groupByRaw.apply(this, arguments);
|
|
}
|
|
this._statements.push({
|
|
grouping: 'group',
|
|
type: 'groupByBasic',
|
|
value: normalizeArr(...arguments),
|
|
});
|
|
return this;
|
|
}
|
|
|
|
// Adds a raw `group by` clause to the query.
|
|
groupByRaw(sql, bindings) {
|
|
const raw = sql.isRawInstance ? sql : this.client.raw(sql, bindings);
|
|
this._statements.push({
|
|
grouping: 'group',
|
|
type: 'groupByRaw',
|
|
value: raw,
|
|
});
|
|
return this;
|
|
}
|
|
|
|
// Adds a `order by` clause to the query.
|
|
orderBy(column, direction, nulls = '') {
|
|
if (Array.isArray(column)) {
|
|
return this._orderByArray(column);
|
|
}
|
|
this._statements.push({
|
|
grouping: 'order',
|
|
type: 'orderByBasic',
|
|
value: column,
|
|
direction,
|
|
nulls,
|
|
});
|
|
return this;
|
|
}
|
|
|
|
// Adds a `order by` with multiple columns to the query.
|
|
_orderByArray(columnDefs) {
|
|
for (let i = 0; i < columnDefs.length; i++) {
|
|
const columnInfo = columnDefs[i];
|
|
if (isObject(columnInfo)) {
|
|
this._statements.push({
|
|
grouping: 'order',
|
|
type: 'orderByBasic',
|
|
value: columnInfo['column'],
|
|
direction: columnInfo['order'],
|
|
nulls: columnInfo['nulls'],
|
|
});
|
|
} else if (isString(columnInfo)) {
|
|
this._statements.push({
|
|
grouping: 'order',
|
|
type: 'orderByBasic',
|
|
value: columnInfo,
|
|
});
|
|
}
|
|
}
|
|
return this;
|
|
}
|
|
|
|
// Add a raw `order by` clause to the query.
|
|
orderByRaw(sql, bindings) {
|
|
const raw = sql.isRawInstance ? sql : this.client.raw(sql, bindings);
|
|
this._statements.push({
|
|
grouping: 'order',
|
|
type: 'orderByRaw',
|
|
value: raw,
|
|
});
|
|
return this;
|
|
}
|
|
|
|
_union(clause, args) {
|
|
let callbacks = args[0];
|
|
let wrap = args[1];
|
|
if (args.length === 1 || (args.length === 2 && isBoolean(wrap))) {
|
|
if (!Array.isArray(callbacks)) {
|
|
callbacks = [callbacks];
|
|
}
|
|
for (let i = 0, l = callbacks.length; i < l; i++) {
|
|
this._statements.push({
|
|
grouping: 'union',
|
|
clause: clause,
|
|
value: callbacks[i],
|
|
wrap: wrap || false,
|
|
});
|
|
}
|
|
} else {
|
|
callbacks = toArray(args).slice(0, args.length - 1);
|
|
wrap = args[args.length - 1];
|
|
if (!isBoolean(wrap)) {
|
|
callbacks.push(wrap);
|
|
wrap = false;
|
|
}
|
|
this._union(clause, [callbacks, wrap]);
|
|
}
|
|
return this;
|
|
}
|
|
|
|
// Add a union statement to the query.
|
|
union(...args) {
|
|
return this._union('union', args);
|
|
}
|
|
|
|
// Adds a union all statement to the query.
|
|
unionAll(...args) {
|
|
return this._union('union all', args);
|
|
}
|
|
|
|
// Adds an intersect statement to the query
|
|
intersect(callbacks, wrap) {
|
|
if (arguments.length === 1 || (arguments.length === 2 && isBoolean(wrap))) {
|
|
if (!Array.isArray(callbacks)) {
|
|
callbacks = [callbacks];
|
|
}
|
|
for (let i = 0, l = callbacks.length; i < l; i++) {
|
|
this._statements.push({
|
|
grouping: 'union',
|
|
clause: 'intersect',
|
|
value: callbacks[i],
|
|
wrap: wrap || false,
|
|
});
|
|
}
|
|
} else {
|
|
callbacks = toArray(arguments).slice(0, arguments.length - 1);
|
|
wrap = arguments[arguments.length - 1];
|
|
if (!isBoolean(wrap)) {
|
|
callbacks.push(wrap);
|
|
wrap = false;
|
|
}
|
|
this.intersect(callbacks, wrap);
|
|
}
|
|
return this;
|
|
}
|
|
|
|
// Adds a `having` clause to the query.
|
|
having(column, operator, value) {
|
|
if (column.isRawInstance && arguments.length === 1) {
|
|
return this.havingRaw(column);
|
|
}
|
|
|
|
// Check if the column is a function, in which case it's
|
|
// a having statement wrapped in parens.
|
|
if (typeof column === 'function') {
|
|
return this.havingWrapped(column);
|
|
}
|
|
|
|
this._statements.push({
|
|
grouping: 'having',
|
|
type: 'havingBasic',
|
|
column,
|
|
operator,
|
|
value,
|
|
bool: this._bool(),
|
|
not: this._not(),
|
|
});
|
|
return this;
|
|
}
|
|
|
|
orHaving(column, ...args) {
|
|
this._bool('or');
|
|
const obj = column;
|
|
if (isObject(obj) && !obj.isRawInstance) {
|
|
return this.havingWrapped(function () {
|
|
for (const key in obj) {
|
|
this.andHaving(key, obj[key]);
|
|
}
|
|
});
|
|
}
|
|
return this.having(column, ...args);
|
|
}
|
|
|
|
// Helper for compiling any advanced `having` queries.
|
|
havingWrapped(callback) {
|
|
this._statements.push({
|
|
grouping: 'having',
|
|
type: 'havingWrapped',
|
|
value: callback,
|
|
bool: this._bool(),
|
|
not: this._not(),
|
|
});
|
|
return this;
|
|
}
|
|
|
|
havingNull(column) {
|
|
this._statements.push({
|
|
grouping: 'having',
|
|
type: 'havingNull',
|
|
column,
|
|
not: this._not(),
|
|
bool: this._bool(),
|
|
});
|
|
return this;
|
|
}
|
|
|
|
orHavingNull(callback) {
|
|
return this._bool('or').havingNull(callback);
|
|
}
|
|
|
|
havingNotNull(callback) {
|
|
return this._not(true).havingNull(callback);
|
|
}
|
|
|
|
orHavingNotNull(callback) {
|
|
return this._not(true)._bool('or').havingNull(callback);
|
|
}
|
|
|
|
havingExists(callback) {
|
|
this._statements.push({
|
|
grouping: 'having',
|
|
type: 'havingExists',
|
|
value: callback,
|
|
not: this._not(),
|
|
bool: this._bool(),
|
|
});
|
|
return this;
|
|
}
|
|
|
|
orHavingExists(callback) {
|
|
return this._bool('or').havingExists(callback);
|
|
}
|
|
|
|
havingNotExists(callback) {
|
|
return this._not(true).havingExists(callback);
|
|
}
|
|
|
|
orHavingNotExists(callback) {
|
|
return this._not(true)._bool('or').havingExists(callback);
|
|
}
|
|
|
|
havingBetween(column, values) {
|
|
assert(
|
|
Array.isArray(values),
|
|
'The second argument to havingBetween must be an array.'
|
|
);
|
|
assert(
|
|
values.length === 2,
|
|
'You must specify 2 values for the havingBetween clause'
|
|
);
|
|
this._statements.push({
|
|
grouping: 'having',
|
|
type: 'havingBetween',
|
|
column,
|
|
value: values,
|
|
not: this._not(),
|
|
bool: this._bool(),
|
|
});
|
|
return this;
|
|
}
|
|
|
|
orHavingBetween(column, values) {
|
|
return this._bool('or').havingBetween(column, values);
|
|
}
|
|
|
|
havingNotBetween(column, values) {
|
|
return this._not(true).havingBetween(column, values);
|
|
}
|
|
|
|
orHavingNotBetween(column, values) {
|
|
return this._not(true)._bool('or').havingBetween(column, values);
|
|
}
|
|
|
|
havingIn(column, values) {
|
|
if (Array.isArray(values) && isEmpty(values))
|
|
return this.where(this._not());
|
|
this._statements.push({
|
|
grouping: 'having',
|
|
type: 'havingIn',
|
|
column,
|
|
value: values,
|
|
not: this._not(),
|
|
bool: this._bool(),
|
|
});
|
|
return this;
|
|
}
|
|
|
|
// Adds a `or where in` clause to the query.
|
|
orHavingIn(column, values) {
|
|
return this._bool('or').havingIn(column, values);
|
|
}
|
|
|
|
// Adds a `where not in` clause to the query.
|
|
havingNotIn(column, values) {
|
|
return this._not(true).havingIn(column, values);
|
|
}
|
|
|
|
// Adds a `or where not in` clause to the query.
|
|
orHavingNotIn(column, values) {
|
|
return this._bool('or')._not(true).havingIn(column, values);
|
|
}
|
|
|
|
// Adds a raw `having` clause to the query.
|
|
havingRaw(sql, bindings) {
|
|
const raw = sql.isRawInstance ? sql : this.client.raw(sql, bindings);
|
|
this._statements.push({
|
|
grouping: 'having',
|
|
type: 'havingRaw',
|
|
value: raw,
|
|
bool: this._bool(),
|
|
not: this._not(),
|
|
});
|
|
return this;
|
|
}
|
|
|
|
orHavingRaw(sql, bindings) {
|
|
return this._bool('or').havingRaw(sql, bindings);
|
|
}
|
|
|
|
// set the skip binding parameter (= insert the raw value in the query) for an attribute.
|
|
_setSkipBinding(attribute, options) {
|
|
let skipBinding = options;
|
|
if (isObject(options)) {
|
|
skipBinding = options.skipBinding;
|
|
}
|
|
this._single.skipBinding = this._single.skipBinding || {};
|
|
this._single.skipBinding[attribute] = skipBinding;
|
|
}
|
|
|
|
// Only allow a single "offset" to be set for the current query.
|
|
offset(value, options) {
|
|
if (value == null || value.isRawInstance || value instanceof Builder) {
|
|
// Builder for backward compatibility
|
|
this._single.offset = value;
|
|
} else {
|
|
const val = parseInt(value, 10);
|
|
if (isNaN(val)) {
|
|
this.client.logger.warn('A valid integer must be provided to offset');
|
|
} else if (val < 0) {
|
|
throw new Error(`A non-negative integer must be provided to offset.`);
|
|
} else {
|
|
this._single.offset = val;
|
|
}
|
|
}
|
|
this._setSkipBinding('offset', options);
|
|
return this;
|
|
}
|
|
|
|
// Only allow a single "limit" to be set for the current query.
|
|
limit(value, options) {
|
|
const val = parseInt(value, 10);
|
|
if (isNaN(val)) {
|
|
this.client.logger.warn('A valid integer must be provided to limit');
|
|
} else {
|
|
this._single.limit = val;
|
|
this._setSkipBinding('limit', options);
|
|
}
|
|
return this;
|
|
}
|
|
|
|
// Retrieve the "count" result of the query.
|
|
count(column, options) {
|
|
return this._aggregate('count', column || '*', options);
|
|
}
|
|
|
|
// Retrieve the minimum value of a given column.
|
|
min(column, options) {
|
|
return this._aggregate('min', column, options);
|
|
}
|
|
|
|
// Retrieve the maximum value of a given column.
|
|
max(column, options) {
|
|
return this._aggregate('max', column, options);
|
|
}
|
|
|
|
// Retrieve the sum of the values of a given column.
|
|
sum(column, options) {
|
|
return this._aggregate('sum', column, options);
|
|
}
|
|
|
|
// Retrieve the average of the values of a given column.
|
|
avg(column, options) {
|
|
return this._aggregate('avg', column, options);
|
|
}
|
|
|
|
// Retrieve the "count" of the distinct results of the query.
|
|
countDistinct(...columns) {
|
|
let options;
|
|
if (columns.length > 1 && isPlainObject(last(columns))) {
|
|
[options] = columns.splice(columns.length - 1, 1);
|
|
}
|
|
|
|
if (!columns.length) {
|
|
columns = '*';
|
|
} else if (columns.length === 1) {
|
|
columns = columns[0];
|
|
}
|
|
|
|
return this._aggregate('count', columns, { ...options, distinct: true });
|
|
}
|
|
|
|
// Retrieve the sum of the distinct values of a given column.
|
|
sumDistinct(column, options) {
|
|
return this._aggregate('sum', column, { ...options, distinct: true });
|
|
}
|
|
|
|
// Retrieve the vg of the distinct results of the query.
|
|
avgDistinct(column, options) {
|
|
return this._aggregate('avg', column, { ...options, distinct: true });
|
|
}
|
|
|
|
// Increments a column's value by the specified amount.
|
|
increment(column, amount = 1) {
|
|
if (isObject(column)) {
|
|
for (const key in column) {
|
|
this._counter(key, column[key]);
|
|
}
|
|
|
|
return this;
|
|
}
|
|
|
|
return this._counter(column, amount);
|
|
}
|
|
|
|
// Decrements a column's value by the specified amount.
|
|
decrement(column, amount = 1) {
|
|
if (isObject(column)) {
|
|
for (const key in column) {
|
|
this._counter(key, -column[key]);
|
|
}
|
|
|
|
return this;
|
|
}
|
|
|
|
return this._counter(column, -amount);
|
|
}
|
|
|
|
// Clears increments/decrements
|
|
clearCounters() {
|
|
this._single.counter = {};
|
|
return this;
|
|
}
|
|
|
|
// Sets the values for a `select` query, informing that only the first
|
|
// row should be returned (limit 1).
|
|
first(...args) {
|
|
if (this._method && this._method !== 'select') {
|
|
throw new Error(`Cannot chain .first() on "${this._method}" query`);
|
|
}
|
|
|
|
this.select(normalizeArr(...args));
|
|
this._method = 'first';
|
|
this.limit(1);
|
|
return this;
|
|
}
|
|
|
|
// Use existing connection to execute the query
|
|
// Same value that client.acquireConnection() for an according client returns should be passed
|
|
connection(_connection) {
|
|
this._connection = _connection;
|
|
this.client.processPassedConnection(_connection);
|
|
return this;
|
|
}
|
|
|
|
// Pluck a column from a query.
|
|
pluck(column) {
|
|
if (this._method && this._method !== 'select') {
|
|
throw new Error(`Cannot chain .pluck() on "${this._method}" query`);
|
|
}
|
|
|
|
this._method = 'pluck';
|
|
this._single.pluck = column;
|
|
this._statements.push({
|
|
grouping: 'columns',
|
|
type: 'pluck',
|
|
value: column,
|
|
});
|
|
return this;
|
|
}
|
|
|
|
// Deprecated. Remove everything from select clause
|
|
clearSelect() {
|
|
this._clearGrouping('columns');
|
|
return this;
|
|
}
|
|
|
|
// Deprecated. Remove everything from where clause
|
|
clearWhere() {
|
|
this._clearGrouping('where');
|
|
return this;
|
|
}
|
|
|
|
// Deprecated. Remove everything from group clause
|
|
clearGroup() {
|
|
this._clearGrouping('group');
|
|
return this;
|
|
}
|
|
|
|
// Deprecated. Remove everything from order clause
|
|
clearOrder() {
|
|
this._clearGrouping('order');
|
|
return this;
|
|
}
|
|
|
|
// Deprecated. Remove everything from having clause
|
|
clearHaving() {
|
|
this._clearGrouping('having');
|
|
return this;
|
|
}
|
|
|
|
// Remove everything from statement clause
|
|
clear(statement) {
|
|
if (!CLEARABLE_STATEMENTS.has(statement))
|
|
throw new Error(`Knex Error: unknown statement '${statement}'`);
|
|
if (statement.startsWith('counter')) return this.clearCounters();
|
|
if (statement === 'select') {
|
|
statement = 'columns';
|
|
}
|
|
this._clearGrouping(statement);
|
|
return this;
|
|
}
|
|
|
|
// Insert & Update
|
|
// ------
|
|
|
|
// Sets the values for an `insert` query.
|
|
insert(values, returning, options) {
|
|
this._method = 'insert';
|
|
if (!isEmpty(returning)) this.returning(returning, options);
|
|
this._single.insert = values;
|
|
return this;
|
|
}
|
|
|
|
// Sets the values for an `update`, allowing for both
|
|
// `.update(key, value, [returning])` and `.update(obj, [returning])` syntaxes.
|
|
update(values, returning, options) {
|
|
let ret;
|
|
const obj = this._single.update || {};
|
|
this._method = 'update';
|
|
if (isString(values)) {
|
|
if (isPlainObject(returning)) {
|
|
obj[values] = JSON.stringify(returning);
|
|
} else {
|
|
obj[values] = returning;
|
|
}
|
|
if (arguments.length > 2) {
|
|
ret = arguments[2];
|
|
}
|
|
} else {
|
|
const keys = Object.keys(values);
|
|
if (this._single.update) {
|
|
this.client.logger.warn('Update called multiple times with objects.');
|
|
}
|
|
let i = -1;
|
|
while (++i < keys.length) {
|
|
obj[keys[i]] = values[keys[i]];
|
|
}
|
|
ret = arguments[1];
|
|
}
|
|
if (!isEmpty(ret)) this.returning(ret, options);
|
|
this._single.update = obj;
|
|
return this;
|
|
}
|
|
|
|
// Sets the returning value for the query.
|
|
returning(returning, options) {
|
|
this._single.returning = returning;
|
|
this._single.options = options;
|
|
return this;
|
|
}
|
|
|
|
onConflict(columns) {
|
|
if (typeof columns === 'string') {
|
|
columns = [columns];
|
|
}
|
|
return new OnConflictBuilder(this, columns || true);
|
|
}
|
|
|
|
// Delete
|
|
// ------
|
|
|
|
// Executes a delete statement on the query;
|
|
delete(ret, options) {
|
|
this._method = 'del';
|
|
if (!isEmpty(ret)) this.returning(ret, options);
|
|
return this;
|
|
}
|
|
|
|
// Truncates a table, ends the query chain.
|
|
truncate(tableName) {
|
|
this._method = 'truncate';
|
|
if (tableName) {
|
|
this._single.table = tableName;
|
|
}
|
|
return this;
|
|
}
|
|
|
|
// Retrieves columns for the table specified by `knex(tableName)`
|
|
columnInfo(column) {
|
|
this._method = 'columnInfo';
|
|
this._single.columnInfo = column;
|
|
return this;
|
|
}
|
|
|
|
// Set a lock for update constraint.
|
|
forUpdate(...tables) {
|
|
this._single.lock = lockMode.forUpdate;
|
|
if (tables.length === 1 && Array.isArray(tables[0])) {
|
|
this._single.lockTables = tables[0];
|
|
} else {
|
|
this._single.lockTables = tables;
|
|
}
|
|
return this;
|
|
}
|
|
|
|
// Set a lock for share constraint.
|
|
forShare(...tables) {
|
|
this._single.lock = lockMode.forShare;
|
|
this._single.lockTables = tables;
|
|
return this;
|
|
}
|
|
|
|
// Set a lock for no key update constraint.
|
|
forNoKeyUpdate(...tables) {
|
|
this._single.lock = lockMode.forNoKeyUpdate;
|
|
this._single.lockTables = tables;
|
|
return this;
|
|
}
|
|
|
|
// Set a lock for key share constraint.
|
|
forKeyShare(...tables) {
|
|
this._single.lock = lockMode.forKeyShare;
|
|
this._single.lockTables = tables;
|
|
return this;
|
|
}
|
|
|
|
// Skips locked rows when using a lock constraint.
|
|
skipLocked() {
|
|
if (!this._isSelectQuery()) {
|
|
throw new Error(`Cannot chain .skipLocked() on "${this._method}" query!`);
|
|
}
|
|
if (!this._hasLockMode()) {
|
|
throw new Error(
|
|
'.skipLocked() can only be used after a call to .forShare() or .forUpdate()!'
|
|
);
|
|
}
|
|
if (this._single.waitMode === waitMode.noWait) {
|
|
throw new Error('.skipLocked() cannot be used together with .noWait()!');
|
|
}
|
|
this._single.waitMode = waitMode.skipLocked;
|
|
return this;
|
|
}
|
|
|
|
// Causes error when acessing a locked row instead of waiting for it to be released.
|
|
noWait() {
|
|
if (!this._isSelectQuery()) {
|
|
throw new Error(`Cannot chain .noWait() on "${this._method}" query!`);
|
|
}
|
|
if (!this._hasLockMode()) {
|
|
throw new Error(
|
|
'.noWait() can only be used after a call to .forShare() or .forUpdate()!'
|
|
);
|
|
}
|
|
if (this._single.waitMode === waitMode.skipLocked) {
|
|
throw new Error('.noWait() cannot be used together with .skipLocked()!');
|
|
}
|
|
this._single.waitMode = waitMode.noWait;
|
|
return this;
|
|
}
|
|
|
|
// Takes a JS object of methods to call and calls them
|
|
fromJS(obj) {
|
|
each(obj, (val, key) => {
|
|
if (typeof this[key] !== 'function') {
|
|
this.client.logger.warn(`Knex Error: unknown key ${key}`);
|
|
}
|
|
if (Array.isArray(val)) {
|
|
this[key].apply(this, val);
|
|
} else {
|
|
this[key](val);
|
|
}
|
|
});
|
|
return this;
|
|
}
|
|
|
|
fromRaw(sql, bindings) {
|
|
const raw = sql.isRawInstance ? sql : this.client.raw(sql, bindings);
|
|
return this.from(raw);
|
|
}
|
|
|
|
// Passes query to provided callback function, useful for e.g. composing
|
|
// domain-specific helpers
|
|
modify(callback) {
|
|
callback.apply(this, [this].concat(tail(arguments)));
|
|
return this;
|
|
}
|
|
|
|
upsert(values, returning, options) {
|
|
throw new Error(
|
|
`Upsert is not yet supported for dialect ${this.client.dialect}`
|
|
);
|
|
}
|
|
|
|
// JSON support functions
|
|
_json(nameFunction, params) {
|
|
this._statements.push({
|
|
grouping: 'columns',
|
|
type: 'json',
|
|
method: nameFunction,
|
|
params: params,
|
|
});
|
|
return this;
|
|
}
|
|
|
|
jsonExtract() {
|
|
const column = arguments[0];
|
|
let path;
|
|
let alias;
|
|
let singleValue = true;
|
|
|
|
// We use arguments to have the signatures :
|
|
// - column (string or array)
|
|
// - column + path
|
|
// - column + path + alias
|
|
// - column + path + alias + singleValue
|
|
// - column array + singleValue
|
|
if (arguments.length >= 2) {
|
|
path = arguments[1];
|
|
}
|
|
if (arguments.length >= 3) {
|
|
alias = arguments[2];
|
|
}
|
|
if (arguments.length === 4) {
|
|
singleValue = arguments[3];
|
|
}
|
|
if (
|
|
arguments.length === 2 &&
|
|
Array.isArray(arguments[0]) &&
|
|
isBoolean(arguments[1])
|
|
) {
|
|
singleValue = arguments[1];
|
|
}
|
|
return this._json('jsonExtract', {
|
|
column: column,
|
|
path: path,
|
|
alias: alias,
|
|
singleValue, // boolean used only in MSSQL to use function for extract value instead of object/array.
|
|
});
|
|
}
|
|
|
|
jsonSet(column, path, value, alias) {
|
|
return this._json('jsonSet', {
|
|
column: column,
|
|
path: path,
|
|
value: value,
|
|
alias: alias,
|
|
});
|
|
}
|
|
|
|
jsonInsert(column, path, value, alias) {
|
|
return this._json('jsonInsert', {
|
|
column: column,
|
|
path: path,
|
|
value: value,
|
|
alias: alias,
|
|
});
|
|
}
|
|
|
|
jsonRemove(column, path, alias) {
|
|
return this._json('jsonRemove', {
|
|
column: column,
|
|
path: path,
|
|
alias: alias,
|
|
});
|
|
}
|
|
|
|
// Wheres for JSON
|
|
_isJsonObject(jsonValue) {
|
|
return isObject(jsonValue) && !(jsonValue instanceof Builder);
|
|
}
|
|
|
|
_whereJsonWrappedValue(type, column, value) {
|
|
const whereJsonClause = {
|
|
grouping: 'where',
|
|
type: type,
|
|
column,
|
|
value: value,
|
|
not: this._not(),
|
|
bool: this._bool(),
|
|
asColumn: this._asColumnFlag,
|
|
};
|
|
if (arguments[3]) {
|
|
whereJsonClause.operator = arguments[3];
|
|
}
|
|
if (arguments[4]) {
|
|
whereJsonClause.jsonPath = arguments[4];
|
|
}
|
|
this._statements.push(whereJsonClause);
|
|
}
|
|
|
|
whereJsonObject(column, value) {
|
|
this._whereJsonWrappedValue('whereJsonObject', column, value);
|
|
return this;
|
|
}
|
|
|
|
orWhereJsonObject(column, value) {
|
|
return this._bool('or').whereJsonObject(column, value);
|
|
}
|
|
|
|
whereNotJsonObject(column, value) {
|
|
return this._not(true).whereJsonObject(column, value);
|
|
}
|
|
|
|
orWhereNotJsonObject(column, value) {
|
|
return this._bool('or').whereNotJsonObject(column, value);
|
|
}
|
|
|
|
whereJsonPath(column, path, operator, value) {
|
|
this._whereJsonWrappedValue('whereJsonPath', column, value, operator, path);
|
|
return this;
|
|
}
|
|
|
|
orWhereJsonPath(column, path, operator, value) {
|
|
return this._bool('or').whereJsonPath(column, path, operator, value);
|
|
}
|
|
|
|
// Json superset wheres
|
|
whereJsonSupersetOf(column, value) {
|
|
this._whereJsonWrappedValue('whereJsonSupersetOf', column, value);
|
|
return this;
|
|
}
|
|
|
|
whereJsonNotSupersetOf(column, value) {
|
|
return this._not(true).whereJsonSupersetOf(column, value);
|
|
}
|
|
|
|
orWhereJsonSupersetOf(column, value) {
|
|
return this._bool('or').whereJsonSupersetOf(column, value);
|
|
}
|
|
|
|
orWhereJsonNotSupersetOf(column, value) {
|
|
return this._bool('or').whereJsonNotSupersetOf(column, value);
|
|
}
|
|
|
|
// Json subset wheres
|
|
whereJsonSubsetOf(column, value) {
|
|
this._whereJsonWrappedValue('whereJsonSubsetOf', column, value);
|
|
return this;
|
|
}
|
|
|
|
whereJsonNotSubsetOf(column, value) {
|
|
return this._not(true).whereJsonSubsetOf(column, value);
|
|
}
|
|
|
|
orWhereJsonSubsetOf(column, value) {
|
|
return this._bool('or').whereJsonSubsetOf(column, value);
|
|
}
|
|
|
|
orWhereJsonNotSubsetOf(column, value) {
|
|
return this._bool('or').whereJsonNotSubsetOf(column, value);
|
|
}
|
|
|
|
whereJsonHasNone(column, values) {
|
|
this._not(true).whereJsonHasAll(column, values);
|
|
return this;
|
|
}
|
|
|
|
// end of wheres for JSON
|
|
|
|
_analytic(alias, second, third) {
|
|
let analytic;
|
|
const { schema } = this._single;
|
|
const method = this._analyticMethod();
|
|
alias = typeof alias === 'string' ? alias : null;
|
|
|
|
assert(
|
|
typeof second === 'function' ||
|
|
second.isRawInstance ||
|
|
Array.isArray(second) ||
|
|
typeof second === 'string' ||
|
|
typeof second === 'object',
|
|
`The second argument to an analytic function must be either a function, a raw,
|
|
an array of string or object, an object or a single string.`
|
|
);
|
|
|
|
if (third) {
|
|
assert(
|
|
Array.isArray(third) ||
|
|
typeof third === 'string' ||
|
|
typeof third === 'object',
|
|
'The third argument to an analytic function must be either a string, an array of string or object or an object.'
|
|
);
|
|
}
|
|
|
|
if (isFunction(second)) {
|
|
analytic = new Analytic(method, schema, alias);
|
|
second.call(analytic, analytic);
|
|
} else if (second.isRawInstance) {
|
|
const raw = second;
|
|
analytic = {
|
|
grouping: 'columns',
|
|
type: 'analytic',
|
|
method: method,
|
|
raw: raw,
|
|
alias: alias,
|
|
};
|
|
} else {
|
|
const order = !Array.isArray(second) ? [second] : second;
|
|
let partitions = third || [];
|
|
partitions = !Array.isArray(partitions) ? [partitions] : partitions;
|
|
analytic = {
|
|
grouping: 'columns',
|
|
type: 'analytic',
|
|
method: method,
|
|
order: order,
|
|
alias: alias,
|
|
partitions: partitions,
|
|
};
|
|
}
|
|
this._statements.push(analytic);
|
|
return this;
|
|
}
|
|
|
|
rank(...args) {
|
|
return this._analyticMethod('rank')._analytic(...args);
|
|
}
|
|
|
|
denseRank(...args) {
|
|
return this._analyticMethod('dense_rank')._analytic(...args);
|
|
}
|
|
|
|
rowNumber(...args) {
|
|
return this._analyticMethod('row_number')._analytic(...args);
|
|
}
|
|
|
|
// ----------------------------------------------------------------------
|
|
|
|
// Helper for the incrementing/decrementing queries.
|
|
_counter(column, amount) {
|
|
amount = parseFloat(amount);
|
|
|
|
this._method = 'update';
|
|
|
|
this._single.counter = this._single.counter || {};
|
|
|
|
this._single.counter[column] = amount;
|
|
|
|
return this;
|
|
}
|
|
|
|
// Helper to get or set the "boolFlag" value.
|
|
_bool(val) {
|
|
if (arguments.length === 1) {
|
|
this._boolFlag = val;
|
|
return this;
|
|
}
|
|
const ret = this._boolFlag;
|
|
this._boolFlag = 'and';
|
|
return ret;
|
|
}
|
|
|
|
// Helper to get or set the "notFlag" value.
|
|
_not(val) {
|
|
if (arguments.length === 1) {
|
|
this._notFlag = val;
|
|
return this;
|
|
}
|
|
const ret = this._notFlag;
|
|
this._notFlag = false;
|
|
return ret;
|
|
}
|
|
|
|
// Helper to get or set the "joinFlag" value.
|
|
_joinType(val) {
|
|
if (arguments.length === 1) {
|
|
this._joinFlag = val;
|
|
return this;
|
|
}
|
|
const ret = this._joinFlag || 'inner';
|
|
this._joinFlag = 'inner';
|
|
return ret;
|
|
}
|
|
|
|
_analyticMethod(val) {
|
|
if (arguments.length === 1) {
|
|
this._analyticFlag = val;
|
|
return this;
|
|
}
|
|
return this._analyticFlag || 'row_number';
|
|
}
|
|
|
|
// Helper for compiling any aggregate queries.
|
|
_aggregate(method, column, options = {}) {
|
|
this._statements.push({
|
|
grouping: 'columns',
|
|
type: column.isRawInstance ? 'aggregateRaw' : 'aggregate',
|
|
method,
|
|
value: column,
|
|
aggregateDistinct: options.distinct || false,
|
|
alias: options.as,
|
|
});
|
|
return this;
|
|
}
|
|
|
|
// Helper function for clearing or reseting a grouping type from the builder
|
|
_clearGrouping(grouping) {
|
|
if (grouping in this._single) {
|
|
this._single[grouping] = undefined;
|
|
} else {
|
|
this._statements = reject(this._statements, { grouping });
|
|
}
|
|
}
|
|
|
|
// Helper function that checks if the builder will emit a select query
|
|
_isSelectQuery() {
|
|
return SELECT_COMMANDS.has(this._method);
|
|
}
|
|
|
|
// Helper function that checks if the query has a lock mode set
|
|
_hasLockMode() {
|
|
return LOCK_MODES.has(this._single.lock);
|
|
}
|
|
}
|
|
|
|
Builder.prototype.select = Builder.prototype.columns;
|
|
Builder.prototype.column = Builder.prototype.columns;
|
|
Builder.prototype.andWhereNot = Builder.prototype.whereNot;
|
|
Builder.prototype.andWhereNotColumn = Builder.prototype.whereNotColumn;
|
|
Builder.prototype.andWhere = Builder.prototype.where;
|
|
Builder.prototype.andWhereColumn = Builder.prototype.whereColumn;
|
|
Builder.prototype.andWhereRaw = Builder.prototype.whereRaw;
|
|
Builder.prototype.andWhereBetween = Builder.prototype.whereBetween;
|
|
Builder.prototype.andWhereNotBetween = Builder.prototype.whereNotBetween;
|
|
Builder.prototype.andWhereJsonObject = Builder.prototype.whereJsonObject;
|
|
Builder.prototype.andWhereNotJsonObject = Builder.prototype.whereJsonObject;
|
|
Builder.prototype.andWhereJsonPath = Builder.prototype.whereJsonPath;
|
|
Builder.prototype.andWhereLike = Builder.prototype.whereLike;
|
|
Builder.prototype.andWhereILike = Builder.prototype.whereILike;
|
|
Builder.prototype.andHaving = Builder.prototype.having;
|
|
Builder.prototype.andHavingIn = Builder.prototype.havingIn;
|
|
Builder.prototype.andHavingNotIn = Builder.prototype.havingNotIn;
|
|
Builder.prototype.andHavingNull = Builder.prototype.havingNull;
|
|
Builder.prototype.andHavingNotNull = Builder.prototype.havingNotNull;
|
|
Builder.prototype.andHavingExists = Builder.prototype.havingExists;
|
|
Builder.prototype.andHavingNotExists = Builder.prototype.havingNotExists;
|
|
Builder.prototype.andHavingBetween = Builder.prototype.havingBetween;
|
|
Builder.prototype.andHavingNotBetween = Builder.prototype.havingNotBetween;
|
|
Builder.prototype.from = Builder.prototype.table;
|
|
Builder.prototype.into = Builder.prototype.table;
|
|
Builder.prototype.del = Builder.prototype.delete;
|
|
|
|
// Attach all of the top level promise methods that should be chainable.
|
|
augmentWithBuilderInterface(Builder);
|
|
addQueryContext(Builder);
|
|
|
|
Builder.extend = (methodName, fn) => {
|
|
if (Object.prototype.hasOwnProperty.call(Builder.prototype, methodName)) {
|
|
throw new Error(
|
|
`Can't extend QueryBuilder with existing method ('${methodName}').`
|
|
);
|
|
}
|
|
|
|
assign(Builder.prototype, { [methodName]: fn });
|
|
};
|
|
|
|
// Sub-builder for onConflict clauses
|
|
class OnConflictBuilder {
|
|
constructor(builder, columns) {
|
|
this.builder = builder;
|
|
this._columns = columns;
|
|
}
|
|
|
|
// Sets insert query to ignore conflicts
|
|
ignore() {
|
|
this.builder._single.onConflict = this._columns;
|
|
this.builder._single.ignore = true;
|
|
return this.builder;
|
|
}
|
|
|
|
// Sets insert query to update on conflict
|
|
merge(updates) {
|
|
this.builder._single.onConflict = this._columns;
|
|
this.builder._single.merge = { updates };
|
|
return this.builder;
|
|
}
|
|
|
|
// Prevent
|
|
then() {
|
|
throw new Error(
|
|
'Incomplete onConflict clause. .onConflict() must be directly followed by either .merge() or .ignore()'
|
|
);
|
|
}
|
|
}
|
|
|
|
module.exports = Builder;
|