knex/lib/query/querycompiler.js

1500 lines
35 KiB
JavaScript
Raw Normal View History

2015-05-09 13:58:18 -04:00
// Query Compiler
// -------
const helpers = require('../util/helpers');
const Raw = require('../raw');
2021-01-09 17:59:53 +02:00
const QueryBuilder = require('./querybuilder');
const JoinClause = require('./joinclause');
const debug = require('debug');
2016-05-11 15:26:53 +02:00
const assign = require('lodash/assign');
const compact = require('lodash/compact');
const groupBy = require('lodash/groupBy');
const has = require('lodash/has');
const isEmpty = require('lodash/isEmpty');
const map = require('lodash/map');
const omitBy = require('lodash/omitBy');
const reduce = require('lodash/reduce');
const { nanoid } = require('../util/nanoid');
const { isString, isUndefined } = require('../util/is');
2021-01-07 23:34:46 +02:00
const {
columnize: columnize_,
direction: direction_,
operator: operator_,
wrap: wrap_,
2021-01-11 00:38:15 +02:00
unwrapRaw: unwrapRaw_,
rawOrFn: rawOrFn_,
2021-01-07 23:34:46 +02:00
} = require('../formatter/wrappingFormatter');
2016-06-17 09:19:20 -07:00
const debugBindings = debug('knex:bindings');
2016-09-16 16:04:11 -04:00
const components = [
'columns',
'join',
'where',
'union',
'group',
'having',
'order',
'limit',
'offset',
'lock',
'waitMode',
2015-05-09 13:58:18 -04:00
];
// The "QueryCompiler" takes all of the query statements which
// have been gathered in the "QueryBuilder" and turns them into a
// properly formatted / bound query string.
class QueryCompiler {
constructor(client, builder, bindings) {
this.client = client;
this.method = builder._method || 'select';
this.options = builder._options;
this.single = builder._single;
this.timeout = builder._timeout || false;
this.cancelOnTimeout = builder._cancelOnTimeout || false;
this.grouped = groupBy(builder._statements, 'grouping');
this.formatter = client.formatter(builder);
// Used when the insert call is empty.
this._emptyInsertValue = 'default values';
this.first = this.select;
2021-01-07 23:34:46 +02:00
this.bindings = bindings || [];
this.formatter.bindings = this.bindings;
this.bindingsHolder = this;
2021-01-07 23:34:46 +02:00
this.builder = this.formatter.builder;
}
2015-05-09 13:58:18 -04:00
// Collapse the builder into a single object
toSQL(method, tz) {
this._undefinedInWhereClause = false;
this.undefinedBindingsInfo = [];
method = method || this.method;
const val = this[method]() || '';
const query = {
method,
2015-05-09 13:58:18 -04:00
options: reduce(this.options, assign, {}),
2016-02-15 17:06:08 +01:00
timeout: this.timeout,
2016-05-26 11:06:33 -07:00
cancelOnTimeout: this.cancelOnTimeout,
bindings: this.bindingsHolder.bindings || [],
__knexQueryUid: nanoid(),
2020-03-05 21:43:57 +01:00
};
Object.defineProperties(query, {
toNative: {
value: () => {
return {
sql: this.client.positionBindings(query.sql),
bindings: this.client.prepBindings(query.bindings),
};
},
enumerable: false,
},
});
2016-03-02 16:52:32 +01:00
if (isString(val)) {
query.sql = val;
} else {
assign(query, val);
2015-05-09 13:58:18 -04:00
}
if (method === 'select' || method === 'first') {
if (this.single.as) {
query.as = this.single.as;
}
}
if (this._undefinedInWhereClause) {
debugBindings(query.bindings);
2016-06-14 19:50:51 +10:00
throw new Error(
`Undefined binding(s) detected when compiling ` +
2020-03-05 21:43:57 +01:00
`${method.toUpperCase()}. Undefined column(s): [${this.undefinedBindingsInfo.join(
', '
)}] query: ${query.sql}`
2016-06-14 19:50:51 +10:00
);
2015-05-09 13:58:18 -04:00
}
return query;
}
2015-05-09 13:58:18 -04:00
// Compiles the `select` statement, or nested sub-selects by calling each of
// the component compilers, trimming out the empties, and returning a
// generated query string.
select() {
let sql = this.with();
const statements = components.map((component) => this[component](this));
sql += compact(statements).join(' ');
return sql;
}
pluck() {
let toPluck = this.single.pluck;
2016-09-13 09:56:53 -04:00
if (toPluck.indexOf('.') !== -1) {
toPluck = toPluck.split('.').slice(-1)[0];
2016-09-13 09:56:53 -04:00
}
2015-05-09 13:58:18 -04:00
return {
sql: this.select(),
pluck: toPluck,
2015-05-09 13:58:18 -04:00
};
}
2015-05-09 13:58:18 -04:00
// Compiles an "insert" query, allowing for multiple
// inserts using a single query statement.
insert() {
const insertValues = this.single.insert || [];
2021-11-09 09:34:19 +01:00
const sql = this.with() + `insert into ${this.tableName} `;
const body = this._insertBody(insertValues);
return body === '' ? '' : sql + body;
}
_buildInsertValues(insertData) {
let sql = '';
let i = -1;
while (++i < insertData.values.length) {
if (i !== 0) sql += '), (';
sql += this.client.parameterize(
insertData.values[i],
this.client.valueForUndefined,
this.builder,
this.bindingsHolder
);
}
return sql;
}
_insertBody(insertValues) {
let sql = '';
2015-05-09 13:58:18 -04:00
if (Array.isArray(insertValues)) {
if (insertValues.length === 0) {
return '';
2015-05-09 13:58:18 -04:00
}
2016-03-02 16:52:32 +01:00
} else if (typeof insertValues === 'object' && isEmpty(insertValues)) {
return sql + this._emptyInsertValue;
2015-05-09 13:58:18 -04:00
}
const insertData = this._prepInsert(insertValues);
2015-05-09 13:58:18 -04:00
if (typeof insertData === 'string') {
sql += insertData;
} else {
2015-05-09 13:58:18 -04:00
if (insertData.columns.length) {
2021-01-07 23:34:46 +02:00
sql += `(${columnize_(
insertData.columns,
this.builder,
this.client,
this.bindingsHolder
2021-01-07 23:34:46 +02:00
)}`;
2021-11-09 09:34:19 +01:00
sql += ') values (' + this._buildInsertValues(insertData) + ')';
2015-05-09 13:58:18 -04:00
} else if (insertValues.length === 1 && insertValues[0]) {
sql += this._emptyInsertValue;
2015-05-09 13:58:18 -04:00
} else {
sql = '';
2015-05-09 13:58:18 -04:00
}
}
return sql;
}
2015-05-09 13:58:18 -04:00
// Compiles the "update" query.
update() {
2015-05-09 13:58:18 -04:00
// Make sure tableName is processed by the formatter first.
const withSQL = this.with();
const { tableName } = this;
const updateData = this._prepUpdate(this.single.update);
const wheres = this.where();
return (
withSQL +
`update ${this.single.only ? 'only ' : ''}${tableName}` +
' set ' +
updateData.join(', ') +
(wheres ? ` ${wheres}` : '')
);
}
2015-05-09 13:58:18 -04:00
2021-01-28 00:57:25 +01:00
_hintComments() {
let hints = this.grouped.hintComments || [];
hints = hints.map((hint) => compact(hint.value).join(' '));
hints = compact(hints).join(' ');
return hints ? `/*+ ${hints} */ ` : '';
2021-01-28 00:57:25 +01:00
}
2015-05-09 13:58:18 -04:00
// Compiles the columns in the query, specifying if an item was distinct.
columns() {
let distinctClause = '';
if (this.onlyUnions()) return '';
const hints = this._hintComments();
const columns = this.grouped.columns || [];
let i = -1,
sql = [];
2015-05-09 13:58:18 -04:00
if (columns) {
while (++i < columns.length) {
const stmt = columns[i];
if (stmt.distinct) distinctClause = 'distinct ';
if (stmt.distinctOn) {
distinctClause = this.distinctOn(stmt.value);
continue;
}
2015-05-09 13:58:18 -04:00
if (stmt.type === 'aggregate') {
sql.push(...this.aggregate(stmt));
} else if (stmt.type === 'aggregateRaw') {
sql.push(this.aggregateRaw(stmt));
} else if (stmt.type === 'analytic') {
sql.push(this.analytic(stmt));
2021-12-22 10:47:16 +01:00
} else if (stmt.type === 'json') {
sql.push(this.json(stmt));
} else if (stmt.value && stmt.value.length > 0) {
2021-01-11 00:38:15 +02:00
sql.push(
columnize_(
stmt.value,
this.builder,
this.client,
this.bindingsHolder
)
);
2015-05-09 13:58:18 -04:00
}
}
}
if (sql.length === 0) sql = ['*'];
2021-12-22 10:47:16 +01:00
const select = this.onlyJson() ? '' : 'select ';
return (
2021-12-22 10:47:16 +01:00
`${select}${hints}${distinctClause}` +
sql.join(', ') +
(this.tableName
? ` from ${this.single.only ? 'only ' : ''}${this.tableName}`
: '')
);
}
2015-05-09 13:58:18 -04:00
_aggregate(stmt, { aliasSeparator = ' as ', distinctParentheses } = {}) {
const value = stmt.value;
const method = stmt.method;
const distinct = stmt.aggregateDistinct ? 'distinct ' : '';
const wrap = (identifier) =>
wrap_(
identifier,
undefined,
this.builder,
this.client,
this.bindingsHolder
);
const addAlias = (value, alias) => {
if (alias) {
return value + aliasSeparator + wrap(alias);
}
return value;
};
const aggregateArray = (value, alias) => {
let columns = value.map(wrap).join(', ');
if (distinct) {
const openParen = distinctParentheses ? '(' : ' ';
const closeParen = distinctParentheses ? ')' : '';
columns = distinct.trim() + openParen + columns + closeParen;
}
const aggregated = `${method}(${columns})`;
return addAlias(aggregated, alias);
};
const aggregateString = (value, alias) => {
const aggregated = `${method}(${distinct + wrap(value)})`;
return addAlias(aggregated, alias);
};
if (Array.isArray(value)) {
return [aggregateArray(value)];
}
if (typeof value === 'object') {
if (stmt.alias) {
2019-07-23 18:00:13 +02:00
throw new Error('When using an object explicit alias can not be used');
}
return Object.entries(value).map(([alias, column]) => {
2019-07-23 18:00:13 +02:00
if (Array.isArray(column)) {
return aggregateArray(column, alias);
}
return aggregateString(column, alias);
});
}
2015-05-09 13:58:18 -04:00
// Allows us to speciy an alias for the aggregate types.
const splitOn = value.toLowerCase().indexOf(' as ');
let column = value;
2019-07-23 18:00:13 +02:00
let { alias } = stmt;
2015-05-09 13:58:18 -04:00
if (splitOn !== -1) {
column = value.slice(0, splitOn);
if (alias) {
2019-07-23 18:00:13 +02:00
throw new Error(`Found multiple aliases for same column: ${column}`);
}
alias = value.slice(splitOn + 4);
2015-05-09 13:58:18 -04:00
}
return [aggregateString(column, alias)];
}
aggregate(stmt) {
return this._aggregate(stmt);
}
2015-05-09 13:58:18 -04:00
aggregateRaw(stmt) {
const distinct = stmt.aggregateDistinct ? 'distinct ' : '';
2021-01-11 00:38:15 +02:00
return `${stmt.method}(${
distinct +
unwrapRaw_(
stmt.value,
undefined,
this.builder,
this.client,
this.bindingsHolder
)
})`;
}
_joinTable(join) {
return join.schema && !(join.table instanceof Raw)
? `${join.schema}.${join.table}`
: join.table;
}
2015-05-09 13:58:18 -04:00
// Compiles all each of the `join` clauses on the query,
// including any nested join queries.
join() {
let sql = '';
let i = -1;
const joins = this.grouped.join;
2015-05-09 13:58:18 -04:00
if (!joins) return '';
while (++i < joins.length) {
const join = joins[i];
const table = this._joinTable(join);
if (i > 0) sql += ' ';
2015-05-09 13:58:18 -04:00
if (join.joinType === 'raw') {
sql += unwrapRaw_(
join.table,
undefined,
this.builder,
this.client,
this.bindingsHolder
);
2015-05-09 13:58:18 -04:00
} else {
sql +=
join.joinType +
' join ' +
wrap_(
table,
undefined,
this.builder,
this.client,
this.bindingsHolder
);
let ii = -1;
2015-05-09 13:58:18 -04:00
while (++ii < join.clauses.length) {
const clause = join.clauses[ii];
2016-05-11 16:22:15 +02:00
if (ii > 0) {
sql += ` ${clause.bool} `;
2016-05-11 16:22:15 +02:00
} else {
sql += ` ${clause.type === 'onUsing' ? 'using' : 'on'} `;
2016-05-11 16:22:15 +02:00
}
2021-01-11 00:38:15 +02:00
const val = this[clause.type](clause);
2016-05-11 15:26:53 +02:00
if (val) {
sql += val;
}
2015-05-09 13:58:18 -04:00
}
}
}
return sql;
}
2015-05-09 13:58:18 -04:00
onBetween(statement) {
return (
wrap_(
statement.column,
undefined,
this.builder,
this.client,
2021-01-11 00:38:15 +02:00
this.bindingsHolder
) +
' ' +
this._not(statement, 'between') +
' ' +
2021-01-11 00:38:15 +02:00
statement.value
.map((value) =>
this.client.parameter(value, this.builder, this.bindingsHolder)
)
.join(' and ')
);
}
onNull(statement) {
return (
wrap_(
statement.column,
undefined,
this.builder,
this.client,
this.bindingsHolder
) +
' is ' +
this._not(statement, 'null')
);
}
onExists(statement) {
return (
this._not(statement, 'exists') +
' (' +
2021-01-11 00:38:15 +02:00
rawOrFn_(
statement.value,
undefined,
this.builder,
this.client,
this.bindingsHolder
) +
')'
);
}
onIn(statement) {
if (Array.isArray(statement.column)) return this.multiOnIn(statement);
2021-11-16 09:25:29 +01:00
let values;
if (statement.value instanceof Raw) {
values = this.client.parameter(
statement.value,
this.builder,
this.formatter
);
} else {
values = this.client.parameterize(
statement.value,
undefined,
this.builder,
this.bindingsHolder
);
}
return (
wrap_(
statement.column,
undefined,
this.builder,
this.client,
this.bindingsHolder
) +
' ' +
this._not(statement, 'in ') +
2021-11-16 09:25:29 +01:00
this.wrap(values)
);
}
multiOnIn(statement) {
let i = -1,
2021-01-11 00:38:15 +02:00
sql = `(${columnize_(
statement.column,
this.builder,
this.client,
this.bindingsHolder
)}) `;
sql += this._not(statement, 'in ') + '((';
while (++i < statement.value.length) {
if (i !== 0) sql += '),(';
sql += this.client.parameterize(
statement.value[i],
undefined,
this.builder,
this.bindingsHolder
);
}
return sql + '))';
}
2015-05-09 13:58:18 -04:00
// Compiles all `where` statements on the query.
where() {
const wheres = this.grouped.where;
2015-05-09 13:58:18 -04:00
if (!wheres) return;
const sql = [];
let i = -1;
2015-05-09 13:58:18 -04:00
while (++i < wheres.length) {
const stmt = wheres[i];
if (
Object.prototype.hasOwnProperty.call(stmt, 'value') &&
helpers.containsUndefined(stmt.value)
) {
2019-10-06 20:21:32 +02:00
this.undefinedBindingsInfo.push(stmt.column);
this._undefinedInWhereClause = true;
}
const val = this[stmt.type](stmt);
2015-05-09 13:58:18 -04:00
if (val) {
if (sql.length === 0) {
sql[0] = 'where';
2015-05-09 13:58:18 -04:00
} else {
sql.push(stmt.bool);
2015-05-09 13:58:18 -04:00
}
sql.push(val);
2015-05-09 13:58:18 -04:00
}
}
return sql.length > 1 ? sql.join(' ') : '';
}
2015-05-09 13:58:18 -04:00
group() {
2015-05-09 13:58:18 -04:00
return this._groupsOrders('group');
}
2015-05-09 13:58:18 -04:00
order() {
2015-05-09 13:58:18 -04:00
return this._groupsOrders('order');
}
2015-05-09 13:58:18 -04:00
// Compiles the `having` statements.
having() {
const havings = this.grouped.having;
2015-05-09 13:58:18 -04:00
if (!havings) return '';
const sql = ['having'];
for (let i = 0, l = havings.length; i < l; i++) {
const s = havings[i];
const val = this[s.type](s);
if (val) {
if (sql.length === 0) {
sql[0] = 'where';
}
if (sql.length > 1 || (sql.length === 1 && sql[0] !== 'having')) {
sql.push(s.bool);
2015-05-09 13:58:18 -04:00
}
sql.push(val);
2015-05-09 13:58:18 -04:00
}
}
return sql.length > 1 ? sql.join(' ') : '';
}
2015-05-09 13:58:18 -04:00
havingRaw(statement) {
2021-01-11 00:38:15 +02:00
return (
this._not(statement, '') +
unwrapRaw_(
statement.value,
undefined,
this.builder,
this.client,
this.bindingsHolder
)
);
}
havingWrapped(statement) {
2021-01-11 00:38:15 +02:00
const val = rawOrFn_(
statement.value,
'where',
this.builder,
this.client,
this.bindingsHolder
);
return (val && this._not(statement, '') + '(' + val.slice(6) + ')') || '';
}
havingBasic(statement) {
return (
this._not(statement, '') +
wrap_(
statement.column,
undefined,
this.builder,
this.client,
this.bindingsHolder
) +
' ' +
operator_(
statement.operator,
this.builder,
this.client,
this.bindingsHolder
) +
' ' +
this.client.parameter(statement.value, this.builder, this.bindingsHolder)
);
}
havingNull(statement) {
return (
wrap_(
statement.column,
undefined,
this.builder,
this.client,
this.bindingsHolder
) +
' is ' +
this._not(statement, 'null')
);
}
havingExists(statement) {
return (
this._not(statement, 'exists') +
' (' +
2021-01-11 00:38:15 +02:00
rawOrFn_(
statement.value,
undefined,
this.builder,
this.client,
this.bindingsHolder
) +
')'
);
}
havingBetween(statement) {
return (
wrap_(
statement.column,
undefined,
this.builder,
this.client,
2021-01-11 00:38:15 +02:00
this.bindingsHolder
) +
' ' +
this._not(statement, 'between') +
' ' +
2021-01-11 00:38:15 +02:00
statement.value
.map((value) =>
this.client.parameter(value, this.builder, this.bindingsHolder)
)
.join(' and ')
);
}
havingIn(statement) {
if (Array.isArray(statement.column)) return this.multiHavingIn(statement);
return (
wrap_(
statement.column,
undefined,
this.builder,
this.client,
this.bindingsHolder
) +
' ' +
this._not(statement, 'in ') +
this.wrap(
this.client.parameterize(
statement.value,
undefined,
this.builder,
this.bindingsHolder
)
)
);
}
multiHavingIn(statement) {
2021-11-09 09:34:19 +01:00
return this.multiOnIn(statement);
}
2015-05-09 13:58:18 -04:00
// Compile the "union" queries attached to the main query.
union() {
const onlyUnions = this.onlyUnions();
const unions = this.grouped.union;
2015-05-09 13:58:18 -04:00
if (!unions) return '';
let sql = '';
for (let i = 0, l = unions.length; i < l; i++) {
const union = unions[i];
2015-05-09 13:58:18 -04:00
if (i > 0) sql += ' ';
if (i > 0 || !onlyUnions) sql += union.clause + ' ';
2021-01-11 00:38:15 +02:00
const statement = rawOrFn_(
union.value,
undefined,
this.builder,
this.client,
this.bindingsHolder
);
2015-05-09 13:58:18 -04:00
if (statement) {
if (union.wrap) sql += '(';
sql += statement;
if (union.wrap) sql += ')';
}
}
return sql;
}
2015-05-09 13:58:18 -04:00
// If we haven't specified any columns or a `tableName`, we're assuming this
// is only being used for unions.
onlyUnions() {
return (
(!this.grouped.columns || !!this.grouped.columns[0].value) &&
this.grouped.union &&
!this.tableName
);
}
2015-05-09 13:58:18 -04:00
_getValueOrParameterFromAttribute(attribute, rawValue) {
if (this.single.skipBinding[attribute] === true) {
return rawValue !== undefined && rawValue !== null
? rawValue
: this.single[attribute];
}
return this.client.parameter(
this.single[attribute],
this.builder,
this.bindingsHolder
);
}
2021-12-22 10:47:16 +01:00
onlyJson() {
return (
!this.tableName &&
this.grouped.columns &&
this.grouped.columns.length === 1 &&
this.grouped.columns[0].type === 'json'
);
}
limit() {
const noLimit = !this.single.limit && this.single.limit !== 0;
2015-05-09 13:58:18 -04:00
if (noLimit) return '';
return `limit ${this._getValueOrParameterFromAttribute('limit')}`;
}
2015-05-09 13:58:18 -04:00
offset() {
2015-05-09 13:58:18 -04:00
if (!this.single.offset) return '';
return `offset ${this._getValueOrParameterFromAttribute('offset')}`;
}
2015-05-09 13:58:18 -04:00
// Compiles a `delete` query.
del() {
2015-05-09 13:58:18 -04:00
// Make sure tableName is processed by the formatter first.
const { tableName } = this;
const withSQL = this.with();
const wheres = this.where();
const joins = this.join();
// When using joins, delete the "from" table values as a default
const deleteSelector = joins ? tableName + ' ' : '';
return (
withSQL +
`delete ${deleteSelector}from ${
this.single.only ? 'only ' : ''
}${tableName}` +
(joins ? ` ${joins}` : '') +
(wheres ? ` ${wheres}` : '')
);
}
2015-05-09 13:58:18 -04:00
// Compiles a `truncate` query.
truncate() {
return `truncate ${this.tableName}`;
}
2015-05-09 13:58:18 -04:00
// Compiles the "locks".
lock() {
2015-05-09 13:58:18 -04:00
if (this.single.lock) {
return this[this.single.lock]();
2015-05-09 13:58:18 -04:00
}
}
2015-05-09 13:58:18 -04:00
// Compiles the wait mode on the locks.
waitMode() {
if (this.single.waitMode) {
return this[this.single.waitMode]();
}
}
// Fail on unsupported databases
skipLocked() {
throw new Error(
'.skipLocked() is currently only supported on MySQL 8.0+ and PostgreSQL 9.5+'
);
}
// Fail on unsupported databases
noWait() {
throw new Error(
'.noWait() is currently only supported on MySQL 8.0+, MariaDB 10.3.0+ and PostgreSQL 9.5+'
);
}
distinctOn(value) {
throw new Error('.distinctOn() is currently only supported on PostgreSQL');
}
2016-05-11 15:26:53 +02:00
// On Clause
// ------
onWrapped(clause) {
const self = this;
2016-05-11 15:26:53 +02:00
const wrapJoin = new JoinClause();
2016-05-12 09:09:42 +02:00
clause.value.call(wrapJoin, wrapJoin);
2016-05-11 15:26:53 +02:00
let sql = '';
2021-01-11 00:38:15 +02:00
for (let ii = 0; ii < wrapJoin.clauses.length; ii++) {
const wrapClause = wrapJoin.clauses[ii];
2016-05-11 15:26:53 +02:00
if (ii > 0) {
sql += ` ${wrapClause.bool} `;
2016-05-11 15:26:53 +02:00
}
const val = self[wrapClause.type](wrapClause);
2016-05-11 15:26:53 +02:00
if (val) {
sql += val;
}
2021-01-11 00:38:15 +02:00
}
2016-05-11 15:26:53 +02:00
if (sql.length) {
return `(${sql})`;
2016-05-11 15:26:53 +02:00
}
return '';
}
2016-05-11 15:26:53 +02:00
onBasic(clause) {
return (
wrap_(
clause.column,
undefined,
this.builder,
this.client,
this.bindingsHolder
) +
' ' +
2021-01-11 00:38:15 +02:00
operator_(
clause.operator,
this.builder,
this.client,
this.bindingsHolder
) +
' ' +
wrap_(
clause.value,
undefined,
this.builder,
this.client,
this.bindingsHolder
)
);
}
2016-05-11 15:26:53 +02:00
onVal(clause) {
return (
wrap_(
clause.column,
undefined,
this.builder,
this.client,
this.bindingsHolder
) +
' ' +
2021-01-11 00:38:15 +02:00
operator_(
clause.operator,
this.builder,
this.client,
this.bindingsHolder
) +
' ' +
this.client.parameter(clause.value, this.builder, this.bindingsHolder)
);
}
2016-05-11 15:26:53 +02:00
onRaw(clause) {
return unwrapRaw_(
clause.value,
undefined,
this.builder,
this.client,
this.bindingsHolder
);
}
2016-05-11 15:26:53 +02:00
2016-05-11 16:22:15 +02:00
onUsing(clause) {
2021-01-11 00:38:15 +02:00
return (
'(' +
columnize_(
clause.column,
this.builder,
this.client,
this.bindingsHolder
) +
')'
);
}
2016-05-11 16:22:15 +02:00
2015-05-09 13:58:18 -04:00
// Where Clause
// ------
_valueClause(statement) {
return statement.asColumn
? wrap_(
statement.value,
undefined,
this.builder,
this.client,
this.bindingsHolder
)
: this.client.parameter(
statement.value,
this.builder,
this.bindingsHolder
);
}
_columnClause(statement) {
2021-01-11 00:38:15 +02:00
let columns;
if (Array.isArray(statement.column)) {
2021-01-11 00:38:15 +02:00
columns = `(${columnize_(
statement.column,
this.builder,
this.client,
this.bindingsHolder
)})`;
} else {
columns = wrap_(
statement.column,
undefined,
this.builder,
this.client,
this.bindingsHolder
);
2015-05-09 13:58:18 -04:00
}
return columns;
}
whereIn(statement) {
const values = this.client.values(
statement.value,
this.builder,
this.bindingsHolder
);
return `${this._columnClause(statement)} ${this._not(
statement,
'in '
)}${values}`;
}
whereLike(statement) {
return `${this._columnClause(statement)} ${this._not(
statement,
'like '
)}${this._valueClause(statement)}`;
}
whereILike(statement) {
return `${this._columnClause(statement)} ${this._not(
statement,
'ilike '
)}${this._valueClause(statement)}`;
}
2015-05-09 13:58:18 -04:00
whereNull(statement) {
return (
wrap_(
statement.column,
undefined,
this.builder,
this.client,
this.bindingsHolder
) +
' is ' +
this._not(statement, 'null')
);
}
2015-05-09 13:58:18 -04:00
// Compiles a basic "where" clause.
whereBasic(statement) {
return (
this._not(statement, '') +
wrap_(
statement.column,
undefined,
this.builder,
this.client,
this.bindingsHolder
) +
' ' +
2021-01-11 00:38:15 +02:00
operator_(
statement.operator,
this.builder,
this.client,
this.bindingsHolder
) +
' ' +
this._valueClause(statement)
);
}
2015-05-09 13:58:18 -04:00
whereExists(statement) {
return (
this._not(statement, 'exists') +
' (' +
2021-01-11 00:38:15 +02:00
rawOrFn_(
statement.value,
undefined,
this.builder,
this.client,
this.bindingsHolder
) +
')'
);
}
2015-05-09 13:58:18 -04:00
whereWrapped(statement) {
2021-01-11 00:38:15 +02:00
const val = rawOrFn_(
statement.value,
'where',
this.builder,
this.client,
this.bindingsHolder
);
return (val && this._not(statement, '') + '(' + val.slice(6) + ')') || '';
}
2015-05-09 13:58:18 -04:00
whereBetween(statement) {
return (
wrap_(
statement.column,
undefined,
this.builder,
this.client,
this.bindingsHolder
) +
' ' +
this._not(statement, 'between') +
' ' +
2021-01-11 00:38:15 +02:00
statement.value
.map((value) =>
this.client.parameter(value, this.builder, this.bindingsHolder)
)
.join(' and ')
);
}
2015-05-09 13:58:18 -04:00
// Compiles a "whereRaw" query.
whereRaw(statement) {
2021-01-11 00:38:15 +02:00
return (
this._not(statement, '') +
unwrapRaw_(
statement.value,
undefined,
this.builder,
this.client,
this.bindingsHolder
)
);
}
2015-05-09 13:58:18 -04:00
2021-12-22 10:47:16 +01:00
_jsonWrapValue(jsonValue) {
if (this.builder._isJsonObject(jsonValue)) {
return JSON.stringify(jsonValue);
}
return jsonValue;
}
_jsonValueClause(statement) {
statement.value = this._jsonWrapValue(statement.value);
return this._valueClause(statement);
}
whereJsonObject(statement) {
return `${this._columnClause(statement)} ${
statement.not ? '!=' : '='
} ${this._jsonValueClause(statement)}`;
}
wrap(str) {
if (str.charAt(0) !== '(') return `(${str})`;
2015-05-09 13:58:18 -04:00
return str;
}
2015-05-09 13:58:18 -04:00
2021-12-22 10:47:16 +01:00
json(stmt) {
return this[stmt.method](stmt.params);
}
analytic(stmt) {
let sql = '';
const self = this;
sql += stmt.method + '() over (';
if (stmt.raw) {
sql += stmt.raw;
} else {
if (stmt.partitions.length) {
sql += 'partition by ';
sql +=
map(stmt.partitions, function (partition) {
if (isString(partition)) {
return self.formatter.columnize(partition);
} else return self.formatter.columnize(partition.column) + (partition.order ? ' ' + partition.order : '');
}).join(', ') + ' ';
}
sql += 'order by ';
sql += map(stmt.order, function (order) {
if (isString(order)) {
return self.formatter.columnize(order);
} else return self.formatter.columnize(order.column) + (order.order ? ' ' + order.order : '');
}).join(', ');
}
sql += ')';
if (stmt.alias) {
sql += ' as ' + stmt.alias;
}
return sql;
}
// Compiles all `with` statements on the query.
with() {
if (!this.grouped.with || !this.grouped.with.length) {
return '';
}
const withs = this.grouped.with;
if (!withs) return;
const sql = [];
let i = -1;
let isRecursive = false;
while (++i < withs.length) {
const stmt = withs[i];
if (stmt.recursive) {
isRecursive = true;
}
const val = this[stmt.type](stmt);
sql.push(val);
}
return `with ${isRecursive ? 'recursive ' : ''}${sql.join(', ')} `;
}
withWrapped(statement) {
2021-01-11 00:38:15 +02:00
const val = rawOrFn_(
statement.value,
undefined,
this.builder,
this.client,
this.bindingsHolder
);
const columnList = statement.columnList
? '(' +
columnize_(
statement.columnList,
this.builder,
this.client,
this.bindingsHolder
) +
')'
: '';
const materialized =
statement.materialized === undefined
? ''
: statement.materialized
? 'materialized '
: 'not materialized ';
return (
(val &&
2021-01-11 00:38:15 +02:00
columnize_(
statement.alias,
this.builder,
this.client,
this.bindingsHolder
) +
columnList +
' as ' +
materialized +
'(' +
2021-01-11 00:38:15 +02:00
val +
')') ||
''
);
}
2015-05-09 13:58:18 -04:00
// Determines whether to add a "not" prefix to the where clause.
_not(statement, str) {
if (statement.not) return `not ${str}`;
2015-05-09 13:58:18 -04:00
return str;
}
_prepInsert(data) {
2021-01-11 00:38:15 +02:00
const isRaw = rawOrFn_(
data,
undefined,
this.builder,
this.client,
this.bindingsHolder
);
2015-05-09 13:58:18 -04:00
if (isRaw) return isRaw;
let columns = [];
const values = [];
2015-05-09 13:58:18 -04:00
if (!Array.isArray(data)) data = data ? [data] : [];
let i = -1;
2015-05-09 13:58:18 -04:00
while (++i < data.length) {
if (data[i] == null) break;
if (i === 0) columns = Object.keys(data[i]).sort();
const row = new Array(columns.length);
const keys = Object.keys(data[i]);
let j = -1;
2015-05-09 13:58:18 -04:00
while (++j < keys.length) {
const key = keys[j];
let idx = columns.indexOf(key);
2015-05-09 13:58:18 -04:00
if (idx === -1) {
columns = columns.concat(key).sort();
idx = columns.indexOf(key);
let k = -1;
2015-05-09 13:58:18 -04:00
while (++k < values.length) {
values[k].splice(idx, 0, undefined);
2015-05-09 13:58:18 -04:00
}
row.splice(idx, 0, undefined);
2015-05-09 13:58:18 -04:00
}
row[idx] = data[i][key];
2015-05-09 13:58:18 -04:00
}
values.push(row);
2015-05-09 13:58:18 -04:00
}
return {
columns,
values,
2015-05-09 13:58:18 -04:00
};
}
2015-05-09 13:58:18 -04:00
// "Preps" the update.
_prepUpdate(data = {}) {
const { counter = {} } = this.single;
for (const column of Object.keys(counter)) {
//Skip?
if (has(data, column)) {
//Needed?
this.client.logger.warn(
`increment/decrement called for a column that has already been specified in main .update() call. Ignoring increment/decrement and using value from .update() call.`
);
continue;
}
let value = counter[column];
const symbol = value < 0 ? '-' : '+';
if (symbol === '-') {
value = -value;
}
data[column] = this.client.raw(`?? ${symbol} ?`, [column, value]);
}
data = omitBy(data, isUndefined);
const vals = [];
const columns = Object.keys(data);
let i = -1;
while (++i < columns.length) {
vals.push(
wrap_(
columns[i],
undefined,
this.builder,
this.client,
this.bindingsHolder
) +
2020-03-05 21:43:57 +01:00
' = ' +
this.client.parameter(
data[columns[i]],
this.builder,
this.bindingsHolder
)
);
2015-05-09 13:58:18 -04:00
}
if (isEmpty(vals)) {
throw new Error(
[
'Empty .update() call detected!',
'Update data does not contain any values to update.',
'This will result in a faulty query.',
this.single.table ? `Table: ${this.single.table}.` : '',
this.single.update
? `Columns: ${Object.keys(this.single.update)}.`
: '',
].join(' ')
);
}
2015-05-09 13:58:18 -04:00
return vals;
}
2015-05-09 13:58:18 -04:00
_formatGroupsItemValue(value, nulls) {
2019-10-24 23:08:12 +09:00
const { formatter } = this;
let nullOrder = '';
if (nulls === 'last') {
nullOrder = ' is null';
} else if (nulls === 'first') {
nullOrder = ' is not null';
}
let groupOrder;
2019-10-24 23:08:12 +09:00
if (value instanceof Raw) {
groupOrder = unwrapRaw_(
value,
undefined,
this.builder,
this.client,
this.bindingsHolder
);
} else if (value instanceof QueryBuilder || nulls) {
groupOrder = '(' + formatter.columnize(value) + nullOrder + ')';
2019-10-24 23:08:12 +09:00
} else {
groupOrder = formatter.columnize(value);
2019-10-24 23:08:12 +09:00
}
return groupOrder;
}
_groupOrder(item, type) {
const column = this._formatGroupsItemValue(item.value, item.nulls);
const direction =
type === 'order' && item.type !== 'orderByRaw'
? ` ${direction_(
item.direction,
this.builder,
this.client,
this.bindingsHolder
)}`
: '';
return column + direction;
}
2019-10-24 23:08:12 +09:00
2015-05-09 13:58:18 -04:00
// Compiles the `order by` statements.
_groupsOrders(type) {
const items = this.grouped[type];
2015-05-09 13:58:18 -04:00
if (!items) return '';
const sql = items.map((item) => {
return this._groupOrder(item, type);
2015-05-09 13:58:18 -04:00
});
return sql.length ? type + ' by ' + sql.join(', ') : '';
}
2015-05-09 13:58:18 -04:00
// Get the table name, wrapping it if necessary.
// Implemented as a property to prevent ordering issues as described in #704.
get tableName() {
if (!this._tableName) {
2015-05-09 13:58:18 -04:00
// Only call this.formatter.wrap() the first time this property is accessed.
let tableName = this.single.table;
const schemaName = this.single.schema;
2015-08-09 20:22:39 -03:00
if (tableName && schemaName) {
const isQueryBuilder = tableName instanceof QueryBuilder;
const isRawQuery = tableName instanceof Raw;
const isFunction = typeof tableName === 'function';
if (!isQueryBuilder && !isRawQuery && !isFunction) {
tableName = `${schemaName}.${tableName}`;
}
}
2015-08-09 20:22:39 -03:00
this._tableName = tableName
? // Wrap subQuery with parenthesis, #3485
wrap_(
tableName,
tableName instanceof QueryBuilder,
this.builder,
this.client,
this.bindingsHolder
)
: '';
2015-05-09 13:58:18 -04:00
}
return this._tableName;
}
2021-12-22 10:47:16 +01:00
_jsonPathWrap(extraction) {
return this.client.parameter(
extraction.path || extraction[1],
this.builder,
this.bindingsHolder
);
}
// Json common functions
_jsonExtract(nameFunction, params) {
let extractions;
if (Array.isArray(params.column)) {
extractions = params.column;
} else {
extractions = [params];
}
if (!Array.isArray(nameFunction)) {
nameFunction = [nameFunction];
}
return extractions
.map((extraction) => {
let jsonCol = `${columnize_(
extraction.column || extraction[0],
this.builder,
this.client,
this.bindingsHolder
)}, ${this._jsonPathWrap(extraction)}`;
nameFunction.forEach((f) => {
jsonCol = f + '(' + jsonCol + ')';
});
const alias = extraction.alias || extraction[2];
return alias
? this.client.alias(jsonCol, this.formatter.wrap(alias))
: jsonCol;
})
.join(', ');
}
_jsonSet(nameFunction, params) {
const jsonSet = `${nameFunction}(${columnize_(
params.column,
this.builder,
this.client,
this.bindingsHolder
)}, ${this.client.parameter(
params.path,
this.builder,
this.bindingsHolder
)}, ${this.client.parameter(
params.value,
this.builder,
this.bindingsHolder
)})`;
return params.alias
? this.client.alias(jsonSet, this.formatter.wrap(params.alias))
: jsonSet;
}
_whereJsonPath(nameFunction, statement) {
return `${nameFunction}(${this._columnClause(
statement
)}, ${this._jsonPathWrap({ path: statement.jsonPath })}) ${operator_(
statement.operator,
this.builder,
this.client,
this.bindingsHolder
)} ${this._jsonValueClause(statement)}`;
}
_onJsonPathEquals(nameJoinFunction, clause) {
return (
nameJoinFunction +
'(' +
wrap_(
clause.columnFirst,
undefined,
this.builder,
this.client,
this.bindingsHolder
) +
', ' +
this.client.parameter(
clause.jsonPathFirst,
this.builder,
this.bindingsHolder
) +
') = ' +
nameJoinFunction +
'(' +
wrap_(
clause.columnSecond,
undefined,
this.builder,
this.client,
this.bindingsHolder
) +
', ' +
this.client.parameter(
clause.jsonPathSecond,
this.builder,
this.bindingsHolder
) +
')'
);
}
}
2015-05-09 13:58:18 -04:00
module.exports = QueryCompiler;