1581 lines
37 KiB
JavaScript
1581 lines
37 KiB
JavaScript
|
// Query Compiler
|
||
|
// -------
|
||
|
const helpers = require('../util/helpers');
|
||
|
const Raw = require('../raw');
|
||
|
const QueryBuilder = require('./querybuilder');
|
||
|
const JoinClause = require('./joinclause');
|
||
|
const debug = require('debug');
|
||
|
|
||
|
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');
|
||
|
const {
|
||
|
columnize: columnize_,
|
||
|
direction: direction_,
|
||
|
operator: operator_,
|
||
|
wrap: wrap_,
|
||
|
unwrapRaw: unwrapRaw_,
|
||
|
rawOrFn: rawOrFn_,
|
||
|
} = require('../formatter/wrappingFormatter');
|
||
|
|
||
|
const debugBindings = debug('knex:bindings');
|
||
|
|
||
|
const components = [
|
||
|
'columns',
|
||
|
'join',
|
||
|
'where',
|
||
|
'union',
|
||
|
'group',
|
||
|
'having',
|
||
|
'order',
|
||
|
'limit',
|
||
|
'offset',
|
||
|
'lock',
|
||
|
'waitMode',
|
||
|
];
|
||
|
|
||
|
// 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;
|
||
|
|
||
|
this.bindings = bindings || [];
|
||
|
this.formatter.bindings = this.bindings;
|
||
|
this.bindingsHolder = this;
|
||
|
this.builder = this.formatter.builder;
|
||
|
}
|
||
|
|
||
|
// 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,
|
||
|
options: reduce(this.options, assign, {}),
|
||
|
timeout: this.timeout,
|
||
|
cancelOnTimeout: this.cancelOnTimeout,
|
||
|
bindings: this.bindingsHolder.bindings || [],
|
||
|
__knexQueryUid: nanoid(),
|
||
|
};
|
||
|
|
||
|
Object.defineProperties(query, {
|
||
|
toNative: {
|
||
|
value: () => {
|
||
|
return {
|
||
|
sql: this.client.positionBindings(query.sql),
|
||
|
bindings: this.client.prepBindings(query.bindings),
|
||
|
};
|
||
|
},
|
||
|
enumerable: false,
|
||
|
},
|
||
|
});
|
||
|
|
||
|
if (isString(val)) {
|
||
|
query.sql = val;
|
||
|
} else {
|
||
|
assign(query, val);
|
||
|
}
|
||
|
|
||
|
if (method === 'select' || method === 'first') {
|
||
|
if (this.single.as) {
|
||
|
query.as = this.single.as;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
if (this._undefinedInWhereClause) {
|
||
|
debugBindings(query.bindings);
|
||
|
throw new Error(
|
||
|
`Undefined binding(s) detected when compiling ` +
|
||
|
`${method.toUpperCase()}. Undefined column(s): [${this.undefinedBindingsInfo.join(
|
||
|
', '
|
||
|
)}] query: ${query.sql}`
|
||
|
);
|
||
|
}
|
||
|
|
||
|
return query;
|
||
|
}
|
||
|
|
||
|
// 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();
|
||
|
|
||
|
let unionStatement = '';
|
||
|
|
||
|
const firstStatements = [];
|
||
|
const endStatements = [];
|
||
|
|
||
|
components.forEach((component) => {
|
||
|
const statement = this[component](this);
|
||
|
// We store the 'union' statement to append it at the end.
|
||
|
// We still need to call the component sequentially because of
|
||
|
// order of bindings.
|
||
|
switch (component) {
|
||
|
case 'union':
|
||
|
unionStatement = statement;
|
||
|
break;
|
||
|
case 'columns':
|
||
|
case 'join':
|
||
|
case 'where':
|
||
|
firstStatements.push(statement);
|
||
|
break;
|
||
|
default:
|
||
|
endStatements.push(statement);
|
||
|
break;
|
||
|
}
|
||
|
});
|
||
|
|
||
|
// Check if we need to wrap the main query.
|
||
|
// We need to wrap main query if one of union have wrap options to true
|
||
|
// to avoid error syntax (in PostgreSQL for example).
|
||
|
const wrapMainQuery =
|
||
|
this.grouped.union &&
|
||
|
this.grouped.union.map((u) => u.wrap).some((u) => u);
|
||
|
|
||
|
if (this.onlyUnions()) {
|
||
|
const statements = compact(firstStatements.concat(endStatements)).join(
|
||
|
' '
|
||
|
);
|
||
|
sql += unionStatement + (statements ? ' ' + statements : '');
|
||
|
} else {
|
||
|
const allStatements =
|
||
|
(wrapMainQuery ? '(' : '') +
|
||
|
compact(firstStatements).join(' ') +
|
||
|
(wrapMainQuery ? ')' : '');
|
||
|
const endStat = compact(endStatements).join(' ');
|
||
|
sql +=
|
||
|
allStatements +
|
||
|
(unionStatement ? ' ' + unionStatement : '') +
|
||
|
(endStat ? ' ' + endStat : endStat);
|
||
|
}
|
||
|
return sql;
|
||
|
}
|
||
|
|
||
|
pluck() {
|
||
|
let toPluck = this.single.pluck;
|
||
|
if (toPluck.indexOf('.') !== -1) {
|
||
|
toPluck = toPluck.split('.').slice(-1)[0];
|
||
|
}
|
||
|
return {
|
||
|
sql: this.select(),
|
||
|
pluck: toPluck,
|
||
|
};
|
||
|
}
|
||
|
|
||
|
// Compiles an "insert" query, allowing for multiple
|
||
|
// inserts using a single query statement.
|
||
|
insert() {
|
||
|
const insertValues = this.single.insert || [];
|
||
|
const sql = this.with() + `insert into ${this.tableName} `;
|
||
|
const body = this._insertBody(insertValues);
|
||
|
return body === '' ? '' : sql + body;
|
||
|
}
|
||
|
|
||
|
_onConflictClause(columns) {
|
||
|
return columns instanceof Raw
|
||
|
? this.formatter.wrap(columns)
|
||
|
: `(${this.formatter.columnize(columns)})`;
|
||
|
}
|
||
|
|
||
|
_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 = '';
|
||
|
if (Array.isArray(insertValues)) {
|
||
|
if (insertValues.length === 0) {
|
||
|
return '';
|
||
|
}
|
||
|
} else if (typeof insertValues === 'object' && isEmpty(insertValues)) {
|
||
|
return sql + this._emptyInsertValue;
|
||
|
}
|
||
|
|
||
|
const insertData = this._prepInsert(insertValues);
|
||
|
if (typeof insertData === 'string') {
|
||
|
sql += insertData;
|
||
|
} else {
|
||
|
if (insertData.columns.length) {
|
||
|
sql += `(${columnize_(
|
||
|
insertData.columns,
|
||
|
this.builder,
|
||
|
this.client,
|
||
|
this.bindingsHolder
|
||
|
)}`;
|
||
|
sql += ') values (' + this._buildInsertValues(insertData) + ')';
|
||
|
} else if (insertValues.length === 1 && insertValues[0]) {
|
||
|
sql += this._emptyInsertValue;
|
||
|
} else {
|
||
|
sql = '';
|
||
|
}
|
||
|
}
|
||
|
return sql;
|
||
|
}
|
||
|
|
||
|
// Compiles the "update" query.
|
||
|
update() {
|
||
|
// 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}` : '')
|
||
|
);
|
||
|
}
|
||
|
|
||
|
_hintComments() {
|
||
|
let hints = this.grouped.hintComments || [];
|
||
|
hints = hints.map((hint) => compact(hint.value).join(' '));
|
||
|
hints = compact(hints).join(' ');
|
||
|
return hints ? `/*+ ${hints} */ ` : '';
|
||
|
}
|
||
|
|
||
|
// 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 = [];
|
||
|
if (columns) {
|
||
|
while (++i < columns.length) {
|
||
|
const stmt = columns[i];
|
||
|
if (stmt.distinct) distinctClause = 'distinct ';
|
||
|
if (stmt.distinctOn) {
|
||
|
distinctClause = this.distinctOn(stmt.value);
|
||
|
continue;
|
||
|
}
|
||
|
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));
|
||
|
} else if (stmt.type === 'json') {
|
||
|
sql.push(this.json(stmt));
|
||
|
} else if (stmt.value && stmt.value.length > 0) {
|
||
|
sql.push(
|
||
|
columnize_(
|
||
|
stmt.value,
|
||
|
this.builder,
|
||
|
this.client,
|
||
|
this.bindingsHolder
|
||
|
)
|
||
|
);
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
if (sql.length === 0) sql = ['*'];
|
||
|
const select = this.onlyJson() ? '' : 'select ';
|
||
|
return (
|
||
|
`${select}${hints}${distinctClause}` +
|
||
|
sql.join(', ') +
|
||
|
(this.tableName
|
||
|
? ` from ${this.single.only ? 'only ' : ''}${this.tableName}`
|
||
|
: '')
|
||
|
);
|
||
|
}
|
||
|
|
||
|
_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) {
|
||
|
throw new Error('When using an object explicit alias can not be used');
|
||
|
}
|
||
|
return Object.entries(value).map(([alias, column]) => {
|
||
|
if (Array.isArray(column)) {
|
||
|
return aggregateArray(column, alias);
|
||
|
}
|
||
|
return aggregateString(column, alias);
|
||
|
});
|
||
|
}
|
||
|
|
||
|
// Allows us to speciy an alias for the aggregate types.
|
||
|
const splitOn = value.toLowerCase().indexOf(' as ');
|
||
|
let column = value;
|
||
|
let { alias } = stmt;
|
||
|
if (splitOn !== -1) {
|
||
|
column = value.slice(0, splitOn);
|
||
|
if (alias) {
|
||
|
throw new Error(`Found multiple aliases for same column: ${column}`);
|
||
|
}
|
||
|
alias = value.slice(splitOn + 4);
|
||
|
}
|
||
|
return [aggregateString(column, alias)];
|
||
|
}
|
||
|
|
||
|
aggregate(stmt) {
|
||
|
return this._aggregate(stmt);
|
||
|
}
|
||
|
|
||
|
aggregateRaw(stmt) {
|
||
|
const distinct = stmt.aggregateDistinct ? 'distinct ' : '';
|
||
|
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;
|
||
|
}
|
||
|
|
||
|
// 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;
|
||
|
if (!joins) return '';
|
||
|
while (++i < joins.length) {
|
||
|
const join = joins[i];
|
||
|
const table = this._joinTable(join);
|
||
|
if (i > 0) sql += ' ';
|
||
|
if (join.joinType === 'raw') {
|
||
|
sql += unwrapRaw_(
|
||
|
join.table,
|
||
|
undefined,
|
||
|
this.builder,
|
||
|
this.client,
|
||
|
this.bindingsHolder
|
||
|
);
|
||
|
} else {
|
||
|
sql +=
|
||
|
join.joinType +
|
||
|
' join ' +
|
||
|
wrap_(
|
||
|
table,
|
||
|
undefined,
|
||
|
this.builder,
|
||
|
this.client,
|
||
|
this.bindingsHolder
|
||
|
);
|
||
|
let ii = -1;
|
||
|
while (++ii < join.clauses.length) {
|
||
|
const clause = join.clauses[ii];
|
||
|
if (ii > 0) {
|
||
|
sql += ` ${clause.bool} `;
|
||
|
} else {
|
||
|
sql += ` ${clause.type === 'onUsing' ? 'using' : 'on'} `;
|
||
|
}
|
||
|
const val = this[clause.type](clause);
|
||
|
if (val) {
|
||
|
sql += val;
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
return sql;
|
||
|
}
|
||
|
|
||
|
onBetween(statement) {
|
||
|
return (
|
||
|
wrap_(
|
||
|
statement.column,
|
||
|
undefined,
|
||
|
this.builder,
|
||
|
this.client,
|
||
|
this.bindingsHolder
|
||
|
) +
|
||
|
' ' +
|
||
|
this._not(statement, 'between') +
|
||
|
' ' +
|
||
|
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') +
|
||
|
' (' +
|
||
|
rawOrFn_(
|
||
|
statement.value,
|
||
|
undefined,
|
||
|
this.builder,
|
||
|
this.client,
|
||
|
this.bindingsHolder
|
||
|
) +
|
||
|
')'
|
||
|
);
|
||
|
}
|
||
|
|
||
|
onIn(statement) {
|
||
|
if (Array.isArray(statement.column)) return this.multiOnIn(statement);
|
||
|
|
||
|
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 ') +
|
||
|
this.wrap(values)
|
||
|
);
|
||
|
}
|
||
|
|
||
|
multiOnIn(statement) {
|
||
|
let i = -1,
|
||
|
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 + '))';
|
||
|
}
|
||
|
|
||
|
// Compiles all `where` statements on the query.
|
||
|
where() {
|
||
|
const wheres = this.grouped.where;
|
||
|
if (!wheres) return;
|
||
|
const sql = [];
|
||
|
let i = -1;
|
||
|
while (++i < wheres.length) {
|
||
|
const stmt = wheres[i];
|
||
|
if (
|
||
|
Object.prototype.hasOwnProperty.call(stmt, 'value') &&
|
||
|
helpers.containsUndefined(stmt.value)
|
||
|
) {
|
||
|
this.undefinedBindingsInfo.push(stmt.column);
|
||
|
this._undefinedInWhereClause = true;
|
||
|
}
|
||
|
const val = this[stmt.type](stmt);
|
||
|
if (val) {
|
||
|
if (sql.length === 0) {
|
||
|
sql[0] = 'where';
|
||
|
} else {
|
||
|
sql.push(stmt.bool);
|
||
|
}
|
||
|
sql.push(val);
|
||
|
}
|
||
|
}
|
||
|
return sql.length > 1 ? sql.join(' ') : '';
|
||
|
}
|
||
|
|
||
|
group() {
|
||
|
return this._groupsOrders('group');
|
||
|
}
|
||
|
|
||
|
order() {
|
||
|
return this._groupsOrders('order');
|
||
|
}
|
||
|
|
||
|
// Compiles the `having` statements.
|
||
|
having() {
|
||
|
const havings = this.grouped.having;
|
||
|
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);
|
||
|
}
|
||
|
sql.push(val);
|
||
|
}
|
||
|
}
|
||
|
return sql.length > 1 ? sql.join(' ') : '';
|
||
|
}
|
||
|
|
||
|
havingRaw(statement) {
|
||
|
return (
|
||
|
this._not(statement, '') +
|
||
|
unwrapRaw_(
|
||
|
statement.value,
|
||
|
undefined,
|
||
|
this.builder,
|
||
|
this.client,
|
||
|
this.bindingsHolder
|
||
|
)
|
||
|
);
|
||
|
}
|
||
|
|
||
|
havingWrapped(statement) {
|
||
|
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') +
|
||
|
' (' +
|
||
|
rawOrFn_(
|
||
|
statement.value,
|
||
|
undefined,
|
||
|
this.builder,
|
||
|
this.client,
|
||
|
this.bindingsHolder
|
||
|
) +
|
||
|
')'
|
||
|
);
|
||
|
}
|
||
|
|
||
|
havingBetween(statement) {
|
||
|
return (
|
||
|
wrap_(
|
||
|
statement.column,
|
||
|
undefined,
|
||
|
this.builder,
|
||
|
this.client,
|
||
|
this.bindingsHolder
|
||
|
) +
|
||
|
' ' +
|
||
|
this._not(statement, 'between') +
|
||
|
' ' +
|
||
|
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) {
|
||
|
return this.multiOnIn(statement);
|
||
|
}
|
||
|
|
||
|
// Compile the "union" queries attached to the main query.
|
||
|
union() {
|
||
|
const onlyUnions = this.onlyUnions();
|
||
|
const unions = this.grouped.union;
|
||
|
if (!unions) return '';
|
||
|
let sql = '';
|
||
|
for (let i = 0, l = unions.length; i < l; i++) {
|
||
|
const union = unions[i];
|
||
|
if (i > 0) sql += ' ';
|
||
|
if (i > 0 || !onlyUnions) sql += union.clause + ' ';
|
||
|
const statement = rawOrFn_(
|
||
|
union.value,
|
||
|
undefined,
|
||
|
this.builder,
|
||
|
this.client,
|
||
|
this.bindingsHolder
|
||
|
);
|
||
|
if (statement) {
|
||
|
const wrap = union.wrap;
|
||
|
if (wrap) sql += '(';
|
||
|
sql += statement;
|
||
|
if (wrap) sql += ')';
|
||
|
}
|
||
|
}
|
||
|
return sql;
|
||
|
}
|
||
|
|
||
|
// 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
|
||
|
);
|
||
|
}
|
||
|
|
||
|
_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
|
||
|
);
|
||
|
}
|
||
|
|
||
|
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;
|
||
|
if (noLimit) return '';
|
||
|
return `limit ${this._getValueOrParameterFromAttribute('limit')}`;
|
||
|
}
|
||
|
|
||
|
offset() {
|
||
|
if (!this.single.offset) return '';
|
||
|
return `offset ${this._getValueOrParameterFromAttribute('offset')}`;
|
||
|
}
|
||
|
|
||
|
// Compiles a `delete` query.
|
||
|
del() {
|
||
|
// 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}` : '')
|
||
|
);
|
||
|
}
|
||
|
|
||
|
// Compiles a `truncate` query.
|
||
|
truncate() {
|
||
|
return `truncate ${this.tableName}`;
|
||
|
}
|
||
|
|
||
|
// Compiles the "locks".
|
||
|
lock() {
|
||
|
if (this.single.lock) {
|
||
|
return this[this.single.lock]();
|
||
|
}
|
||
|
}
|
||
|
|
||
|
// 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');
|
||
|
}
|
||
|
|
||
|
// On Clause
|
||
|
// ------
|
||
|
|
||
|
onWrapped(clause) {
|
||
|
const self = this;
|
||
|
|
||
|
const wrapJoin = new JoinClause();
|
||
|
clause.value.call(wrapJoin, wrapJoin);
|
||
|
|
||
|
let sql = '';
|
||
|
|
||
|
for (let ii = 0; ii < wrapJoin.clauses.length; ii++) {
|
||
|
const wrapClause = wrapJoin.clauses[ii];
|
||
|
if (ii > 0) {
|
||
|
sql += ` ${wrapClause.bool} `;
|
||
|
}
|
||
|
const val = self[wrapClause.type](wrapClause);
|
||
|
if (val) {
|
||
|
sql += val;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
if (sql.length) {
|
||
|
return `(${sql})`;
|
||
|
}
|
||
|
return '';
|
||
|
}
|
||
|
|
||
|
onBasic(clause) {
|
||
|
const toWrap = clause.value instanceof QueryBuilder;
|
||
|
return (
|
||
|
wrap_(
|
||
|
clause.column,
|
||
|
undefined,
|
||
|
this.builder,
|
||
|
this.client,
|
||
|
this.bindingsHolder
|
||
|
) +
|
||
|
' ' +
|
||
|
operator_(
|
||
|
clause.operator,
|
||
|
this.builder,
|
||
|
this.client,
|
||
|
this.bindingsHolder
|
||
|
) +
|
||
|
' ' +
|
||
|
(toWrap ? '(' : '') +
|
||
|
wrap_(
|
||
|
clause.value,
|
||
|
undefined,
|
||
|
this.builder,
|
||
|
this.client,
|
||
|
this.bindingsHolder
|
||
|
) +
|
||
|
(toWrap ? ')' : '')
|
||
|
);
|
||
|
}
|
||
|
|
||
|
onVal(clause) {
|
||
|
return (
|
||
|
wrap_(
|
||
|
clause.column,
|
||
|
undefined,
|
||
|
this.builder,
|
||
|
this.client,
|
||
|
this.bindingsHolder
|
||
|
) +
|
||
|
' ' +
|
||
|
operator_(
|
||
|
clause.operator,
|
||
|
this.builder,
|
||
|
this.client,
|
||
|
this.bindingsHolder
|
||
|
) +
|
||
|
' ' +
|
||
|
this.client.parameter(clause.value, this.builder, this.bindingsHolder)
|
||
|
);
|
||
|
}
|
||
|
|
||
|
onRaw(clause) {
|
||
|
return unwrapRaw_(
|
||
|
clause.value,
|
||
|
undefined,
|
||
|
this.builder,
|
||
|
this.client,
|
||
|
this.bindingsHolder
|
||
|
);
|
||
|
}
|
||
|
|
||
|
onUsing(clause) {
|
||
|
return (
|
||
|
'(' +
|
||
|
columnize_(
|
||
|
clause.column,
|
||
|
this.builder,
|
||
|
this.client,
|
||
|
this.bindingsHolder
|
||
|
) +
|
||
|
')'
|
||
|
);
|
||
|
}
|
||
|
|
||
|
// 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) {
|
||
|
let columns;
|
||
|
if (Array.isArray(statement.column)) {
|
||
|
columns = `(${columnize_(
|
||
|
statement.column,
|
||
|
this.builder,
|
||
|
this.client,
|
||
|
this.bindingsHolder
|
||
|
)})`;
|
||
|
} else {
|
||
|
columns = wrap_(
|
||
|
statement.column,
|
||
|
undefined,
|
||
|
this.builder,
|
||
|
this.client,
|
||
|
this.bindingsHolder
|
||
|
);
|
||
|
}
|
||
|
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)}`;
|
||
|
}
|
||
|
|
||
|
whereNull(statement) {
|
||
|
return (
|
||
|
wrap_(
|
||
|
statement.column,
|
||
|
undefined,
|
||
|
this.builder,
|
||
|
this.client,
|
||
|
this.bindingsHolder
|
||
|
) +
|
||
|
' is ' +
|
||
|
this._not(statement, 'null')
|
||
|
);
|
||
|
}
|
||
|
|
||
|
// Compiles a basic "where" clause.
|
||
|
whereBasic(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._valueClause(statement)
|
||
|
);
|
||
|
}
|
||
|
|
||
|
whereExists(statement) {
|
||
|
return (
|
||
|
this._not(statement, 'exists') +
|
||
|
' (' +
|
||
|
rawOrFn_(
|
||
|
statement.value,
|
||
|
undefined,
|
||
|
this.builder,
|
||
|
this.client,
|
||
|
this.bindingsHolder
|
||
|
) +
|
||
|
')'
|
||
|
);
|
||
|
}
|
||
|
|
||
|
whereWrapped(statement) {
|
||
|
const val = rawOrFn_(
|
||
|
statement.value,
|
||
|
'where',
|
||
|
this.builder,
|
||
|
this.client,
|
||
|
this.bindingsHolder
|
||
|
);
|
||
|
return (val && this._not(statement, '') + '(' + val.slice(6) + ')') || '';
|
||
|
}
|
||
|
|
||
|
whereBetween(statement) {
|
||
|
return (
|
||
|
wrap_(
|
||
|
statement.column,
|
||
|
undefined,
|
||
|
this.builder,
|
||
|
this.client,
|
||
|
this.bindingsHolder
|
||
|
) +
|
||
|
' ' +
|
||
|
this._not(statement, 'between') +
|
||
|
' ' +
|
||
|
statement.value
|
||
|
.map((value) =>
|
||
|
this.client.parameter(value, this.builder, this.bindingsHolder)
|
||
|
)
|
||
|
.join(' and ')
|
||
|
);
|
||
|
}
|
||
|
|
||
|
// Compiles a "whereRaw" query.
|
||
|
whereRaw(statement) {
|
||
|
return (
|
||
|
this._not(statement, '') +
|
||
|
unwrapRaw_(
|
||
|
statement.value,
|
||
|
undefined,
|
||
|
this.builder,
|
||
|
this.client,
|
||
|
this.bindingsHolder
|
||
|
)
|
||
|
);
|
||
|
}
|
||
|
|
||
|
_jsonWrapValue(jsonValue) {
|
||
|
if (!this.builder._isJsonObject(jsonValue)) {
|
||
|
try {
|
||
|
return JSON.stringify(JSON.parse(jsonValue.replace(/\n|\t/g, '')));
|
||
|
} catch (e) {
|
||
|
return jsonValue;
|
||
|
}
|
||
|
}
|
||
|
return JSON.stringify(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})`;
|
||
|
return str;
|
||
|
}
|
||
|
|
||
|
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) {
|
||
|
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 &&
|
||
|
columnize_(
|
||
|
statement.alias,
|
||
|
this.builder,
|
||
|
this.client,
|
||
|
this.bindingsHolder
|
||
|
) +
|
||
|
columnList +
|
||
|
' as ' +
|
||
|
materialized +
|
||
|
'(' +
|
||
|
val +
|
||
|
')') ||
|
||
|
''
|
||
|
);
|
||
|
}
|
||
|
|
||
|
// Determines whether to add a "not" prefix to the where clause.
|
||
|
_not(statement, str) {
|
||
|
if (statement.not) return `not ${str}`;
|
||
|
return str;
|
||
|
}
|
||
|
|
||
|
_prepInsert(data) {
|
||
|
const isRaw = rawOrFn_(
|
||
|
data,
|
||
|
undefined,
|
||
|
this.builder,
|
||
|
this.client,
|
||
|
this.bindingsHolder
|
||
|
);
|
||
|
if (isRaw) return isRaw;
|
||
|
let columns = [];
|
||
|
const values = [];
|
||
|
if (!Array.isArray(data)) data = data ? [data] : [];
|
||
|
let i = -1;
|
||
|
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;
|
||
|
while (++j < keys.length) {
|
||
|
const key = keys[j];
|
||
|
let idx = columns.indexOf(key);
|
||
|
if (idx === -1) {
|
||
|
columns = columns.concat(key).sort();
|
||
|
idx = columns.indexOf(key);
|
||
|
let k = -1;
|
||
|
while (++k < values.length) {
|
||
|
values[k].splice(idx, 0, undefined);
|
||
|
}
|
||
|
row.splice(idx, 0, undefined);
|
||
|
}
|
||
|
row[idx] = data[i][key];
|
||
|
}
|
||
|
values.push(row);
|
||
|
}
|
||
|
return {
|
||
|
columns,
|
||
|
values,
|
||
|
};
|
||
|
}
|
||
|
|
||
|
// "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
|
||
|
) +
|
||
|
' = ' +
|
||
|
this.client.parameter(
|
||
|
data[columns[i]],
|
||
|
this.builder,
|
||
|
this.bindingsHolder
|
||
|
)
|
||
|
);
|
||
|
}
|
||
|
|
||
|
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(' ')
|
||
|
);
|
||
|
}
|
||
|
|
||
|
return vals;
|
||
|
}
|
||
|
|
||
|
_formatGroupsItemValue(value, nulls) {
|
||
|
const { formatter } = this;
|
||
|
let nullOrder = '';
|
||
|
if (nulls === 'last') {
|
||
|
nullOrder = ' is null';
|
||
|
} else if (nulls === 'first') {
|
||
|
nullOrder = ' is not null';
|
||
|
}
|
||
|
|
||
|
let groupOrder;
|
||
|
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 + ')';
|
||
|
} else {
|
||
|
groupOrder = formatter.columnize(value);
|
||
|
}
|
||
|
return groupOrder;
|
||
|
}
|
||
|
|
||
|
_basicGroupOrder(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;
|
||
|
}
|
||
|
|
||
|
_groupOrder(item, type) {
|
||
|
return this._basicGroupOrder(item, type);
|
||
|
}
|
||
|
|
||
|
_groupOrderNulls(item, type) {
|
||
|
const column = this._formatGroupsItemValue(item.value);
|
||
|
const direction =
|
||
|
type === 'order' && item.type !== 'orderByRaw'
|
||
|
? ` ${direction_(
|
||
|
item.direction,
|
||
|
this.builder,
|
||
|
this.client,
|
||
|
this.bindingsHolder
|
||
|
)}`
|
||
|
: '';
|
||
|
if (item.nulls && !(item.value instanceof Raw)) {
|
||
|
return `${column}${direction ? direction : ''} nulls ${item.nulls}`;
|
||
|
}
|
||
|
return column + direction;
|
||
|
}
|
||
|
|
||
|
// Compiles the `order by` statements.
|
||
|
_groupsOrders(type) {
|
||
|
const items = this.grouped[type];
|
||
|
if (!items) return '';
|
||
|
const sql = items.map((item) => {
|
||
|
return this._groupOrder(item, type);
|
||
|
});
|
||
|
return sql.length ? type + ' by ' + sql.join(', ') : '';
|
||
|
}
|
||
|
|
||
|
// 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) {
|
||
|
// Only call this.formatter.wrap() the first time this property is accessed.
|
||
|
let tableName = this.single.table;
|
||
|
const schemaName = this.single.schema;
|
||
|
|
||
|
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}`;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
this._tableName = tableName
|
||
|
? // Wrap subQuery with parenthesis, #3485
|
||
|
wrap_(
|
||
|
tableName,
|
||
|
tableName instanceof QueryBuilder,
|
||
|
this.builder,
|
||
|
this.client,
|
||
|
this.bindingsHolder
|
||
|
)
|
||
|
: '';
|
||
|
}
|
||
|
return this._tableName;
|
||
|
}
|
||
|
|
||
|
_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
|
||
|
) +
|
||
|
')'
|
||
|
);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
module.exports = QueryCompiler;
|