2021-01-07 02:04:10 +02:00
|
|
|
const transform = require('lodash/transform');
|
|
|
|
const Raw = require('../raw');
|
2021-01-09 17:59:53 +02:00
|
|
|
const QueryBuilder = require('../query/querybuilder');
|
2021-01-07 17:31:56 +02:00
|
|
|
const { compileCallback, wrapAsIdentifier } = require('./formatterUtils');
|
2021-01-07 02:04:10 +02:00
|
|
|
|
2021-01-07 17:48:14 +02:00
|
|
|
// Valid values for the `order by` clause generation.
|
|
|
|
const orderBys = ['asc', 'desc'];
|
|
|
|
|
2021-01-07 02:04:10 +02:00
|
|
|
// Turn this into a lookup map
|
|
|
|
const operators = transform(
|
|
|
|
[
|
|
|
|
'=',
|
|
|
|
'<',
|
|
|
|
'>',
|
|
|
|
'<=',
|
|
|
|
'>=',
|
|
|
|
'<>',
|
|
|
|
'!=',
|
|
|
|
'like',
|
|
|
|
'not like',
|
|
|
|
'between',
|
|
|
|
'not between',
|
|
|
|
'ilike',
|
|
|
|
'not ilike',
|
|
|
|
'exists',
|
|
|
|
'not exist',
|
|
|
|
'rlike',
|
|
|
|
'not rlike',
|
|
|
|
'regexp',
|
|
|
|
'not regexp',
|
2021-01-13 08:58:23 -08:00
|
|
|
'match',
|
2021-01-07 02:04:10 +02:00
|
|
|
'&',
|
|
|
|
'|',
|
|
|
|
'^',
|
|
|
|
'<<',
|
|
|
|
'>>',
|
|
|
|
'~',
|
|
|
|
'~*',
|
|
|
|
'!~',
|
|
|
|
'!~*',
|
|
|
|
'#',
|
|
|
|
'&&',
|
|
|
|
'@>',
|
|
|
|
'<@',
|
|
|
|
'||',
|
|
|
|
'&<',
|
|
|
|
'&>',
|
|
|
|
'-|-',
|
|
|
|
'@@',
|
|
|
|
'!!',
|
|
|
|
['?', '\\?'],
|
|
|
|
['?|', '\\?|'],
|
|
|
|
['?&', '\\?&'],
|
|
|
|
],
|
|
|
|
(result, key) => {
|
|
|
|
if (Array.isArray(key)) {
|
|
|
|
result[key[0]] = key[1];
|
|
|
|
} else {
|
|
|
|
result[key] = key;
|
|
|
|
}
|
|
|
|
},
|
|
|
|
{}
|
|
|
|
);
|
|
|
|
|
2021-01-11 00:38:15 +02:00
|
|
|
// Accepts a string or array of columns to wrap as appropriate. Column can be raw
|
|
|
|
function columnize(target, builder, client, bindingHolder) {
|
2021-01-07 02:04:10 +02:00
|
|
|
const columns = Array.isArray(target) ? target : [target];
|
|
|
|
let str = '',
|
|
|
|
i = -1;
|
|
|
|
while (++i < columns.length) {
|
|
|
|
if (i > 0) str += ', ';
|
2021-01-11 00:38:15 +02:00
|
|
|
str += wrap(columns[i], undefined, builder, client, bindingHolder);
|
2021-01-07 02:04:10 +02:00
|
|
|
}
|
|
|
|
return str;
|
|
|
|
}
|
|
|
|
|
|
|
|
// Puts the appropriate wrapper around a value depending on the database
|
|
|
|
// engine, unless it's a knex.raw value, in which case it's left alone.
|
2021-01-09 17:40:30 +02:00
|
|
|
function wrap(value, isParameter, builder, client, bindingHolder) {
|
|
|
|
const raw = unwrapRaw(value, isParameter, builder, client, bindingHolder);
|
2021-01-07 02:04:10 +02:00
|
|
|
if (raw) return raw;
|
|
|
|
switch (typeof value) {
|
|
|
|
case 'function':
|
|
|
|
return outputQuery(
|
2021-01-09 17:40:30 +02:00
|
|
|
compileCallback(value, undefined, client, bindingHolder),
|
2021-01-07 02:04:10 +02:00
|
|
|
true,
|
|
|
|
builder,
|
2021-01-07 22:44:03 +02:00
|
|
|
client
|
2021-01-07 02:04:10 +02:00
|
|
|
);
|
|
|
|
case 'object':
|
2021-01-09 17:40:30 +02:00
|
|
|
return parseObject(value, builder, client, bindingHolder);
|
2021-01-07 02:04:10 +02:00
|
|
|
case 'number':
|
|
|
|
return value;
|
|
|
|
default:
|
2021-01-07 22:44:03 +02:00
|
|
|
return wrapString(value + '', builder, client);
|
2021-01-07 02:04:10 +02:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2021-01-09 17:40:30 +02:00
|
|
|
function unwrapRaw(value, isParameter, builder, client, bindingsHolder) {
|
2021-01-07 02:04:10 +02:00
|
|
|
let query;
|
|
|
|
if (value instanceof QueryBuilder) {
|
|
|
|
query = client.queryCompiler(value).toSQL();
|
|
|
|
if (query.bindings) {
|
2021-01-09 17:40:30 +02:00
|
|
|
bindingsHolder.bindings.push(...query.bindings);
|
2021-01-07 02:04:10 +02:00
|
|
|
}
|
2021-01-07 22:44:03 +02:00
|
|
|
return outputQuery(query, isParameter, builder, client);
|
2021-01-07 02:04:10 +02:00
|
|
|
}
|
|
|
|
if (value instanceof Raw) {
|
|
|
|
value.client = client;
|
|
|
|
if (builder._queryContext) {
|
|
|
|
value.queryContext = () => {
|
2021-01-09 17:40:30 +02:00
|
|
|
return builder._queryContext;
|
2021-01-07 02:04:10 +02:00
|
|
|
};
|
|
|
|
}
|
|
|
|
|
|
|
|
query = value.toSQL();
|
|
|
|
if (query.bindings) {
|
2021-01-09 17:40:30 +02:00
|
|
|
bindingsHolder.bindings.push(...query.bindings);
|
2021-01-07 02:04:10 +02:00
|
|
|
}
|
|
|
|
return query.sql;
|
|
|
|
}
|
|
|
|
if (isParameter) {
|
2021-01-09 17:40:30 +02:00
|
|
|
bindingsHolder.bindings.push(value);
|
2021-01-07 02:04:10 +02:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2021-01-09 17:40:30 +02:00
|
|
|
function operator(value, builder, client, bindingsHolder) {
|
|
|
|
const raw = unwrapRaw(value, undefined, builder, client, bindingsHolder);
|
2021-01-07 02:04:10 +02:00
|
|
|
if (raw) return raw;
|
|
|
|
const operator = operators[(value || '').toLowerCase()];
|
|
|
|
if (!operator) {
|
|
|
|
throw new TypeError(`The operator "${value}" is not permitted`);
|
|
|
|
}
|
|
|
|
return operator;
|
|
|
|
}
|
|
|
|
|
2021-01-07 17:31:56 +02:00
|
|
|
// Coerce to string to prevent strange errors when it's not a string.
|
2021-01-07 22:44:03 +02:00
|
|
|
function wrapString(value, builder, client) {
|
2021-01-07 02:04:10 +02:00
|
|
|
const asIndex = value.toLowerCase().indexOf(' as ');
|
|
|
|
if (asIndex !== -1) {
|
|
|
|
const first = value.slice(0, asIndex);
|
|
|
|
const second = value.slice(asIndex + 4);
|
2021-01-07 22:44:03 +02:00
|
|
|
return client.alias(
|
|
|
|
wrapString(first, builder, client),
|
2021-01-07 02:04:10 +02:00
|
|
|
wrapAsIdentifier(second, builder, client)
|
|
|
|
);
|
|
|
|
}
|
|
|
|
const wrapped = [];
|
|
|
|
let i = -1;
|
|
|
|
const segments = value.split('.');
|
|
|
|
while (++i < segments.length) {
|
|
|
|
value = segments[i];
|
|
|
|
if (i === 0 && segments.length > 1) {
|
2021-01-07 22:44:03 +02:00
|
|
|
wrapped.push(wrapString((value || '').trim(), builder, client));
|
2021-01-07 02:04:10 +02:00
|
|
|
} else {
|
|
|
|
wrapped.push(wrapAsIdentifier(value, builder, client));
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return wrapped.join('.');
|
|
|
|
}
|
|
|
|
|
|
|
|
// Key-value notation for alias
|
|
|
|
function parseObject(obj, builder, client, formatter) {
|
|
|
|
const ret = [];
|
|
|
|
for (const alias in obj) {
|
|
|
|
const queryOrIdentifier = obj[alias];
|
|
|
|
// Avoids double aliasing for subqueries
|
|
|
|
if (typeof queryOrIdentifier === 'function') {
|
|
|
|
const compiled = compileCallback(
|
|
|
|
queryOrIdentifier,
|
|
|
|
undefined,
|
|
|
|
client,
|
|
|
|
formatter
|
|
|
|
);
|
|
|
|
compiled.as = alias; // enforces the object's alias
|
2021-01-07 22:44:03 +02:00
|
|
|
ret.push(outputQuery(compiled, true, builder, client));
|
2021-01-07 02:04:10 +02:00
|
|
|
} else if (queryOrIdentifier instanceof QueryBuilder) {
|
|
|
|
ret.push(
|
2021-01-07 22:44:03 +02:00
|
|
|
client.alias(
|
2021-01-07 02:04:10 +02:00
|
|
|
`(${wrap(queryOrIdentifier, undefined, builder, client, formatter)})`,
|
|
|
|
wrapAsIdentifier(alias, builder, client)
|
|
|
|
)
|
|
|
|
);
|
|
|
|
} else {
|
|
|
|
ret.push(
|
2021-01-07 22:44:03 +02:00
|
|
|
client.alias(
|
2021-01-07 02:04:10 +02:00
|
|
|
wrap(queryOrIdentifier, undefined, builder, client, formatter),
|
|
|
|
wrapAsIdentifier(alias, builder, client)
|
|
|
|
)
|
|
|
|
);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return ret.join(', ');
|
|
|
|
}
|
|
|
|
|
|
|
|
// Ensures the query is aliased if necessary.
|
2021-01-07 22:44:03 +02:00
|
|
|
function outputQuery(compiled, isParameter, builder, client) {
|
2021-01-07 02:04:10 +02:00
|
|
|
let sql = compiled.sql || '';
|
|
|
|
if (sql) {
|
|
|
|
if (
|
|
|
|
(compiled.method === 'select' || compiled.method === 'first') &&
|
|
|
|
(isParameter || compiled.as)
|
|
|
|
) {
|
|
|
|
sql = `(${sql})`;
|
|
|
|
if (compiled.as)
|
2021-01-07 22:44:03 +02:00
|
|
|
return client.alias(sql, wrapString(compiled.as, builder, client));
|
2021-01-07 02:04:10 +02:00
|
|
|
}
|
|
|
|
}
|
|
|
|
return sql;
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Creates SQL for a parameter, which might be passed to where() or .with() or
|
|
|
|
* pretty much anywhere in API.
|
|
|
|
*
|
|
|
|
* @param value
|
|
|
|
* @param method Optional at least 'select' or 'update' are valid
|
|
|
|
* @param builder
|
|
|
|
* @param client
|
2021-01-09 17:40:30 +02:00
|
|
|
* @param bindingHolder
|
2021-01-07 02:04:10 +02:00
|
|
|
*/
|
2021-01-09 17:40:30 +02:00
|
|
|
function rawOrFn(value, method, builder, client, bindingHolder) {
|
2021-01-07 02:04:10 +02:00
|
|
|
if (typeof value === 'function') {
|
|
|
|
return outputQuery(
|
2021-01-09 17:40:30 +02:00
|
|
|
compileCallback(value, method, client, bindingHolder),
|
2021-01-07 02:04:10 +02:00
|
|
|
undefined,
|
|
|
|
builder,
|
|
|
|
client,
|
2021-01-09 17:40:30 +02:00
|
|
|
bindingHolder
|
2021-01-07 02:04:10 +02:00
|
|
|
);
|
|
|
|
}
|
2021-01-09 17:40:30 +02:00
|
|
|
return unwrapRaw(value, undefined, builder, client, bindingHolder) || '';
|
2021-01-07 02:04:10 +02:00
|
|
|
}
|
|
|
|
|
2021-01-07 17:48:14 +02:00
|
|
|
// Specify the direction of the ordering.
|
2021-01-09 17:40:30 +02:00
|
|
|
function direction(value, builder, client, bindingsHolder) {
|
|
|
|
const raw = unwrapRaw(value, undefined, builder, client, bindingsHolder);
|
2021-01-07 17:48:14 +02:00
|
|
|
if (raw) return raw;
|
|
|
|
return orderBys.indexOf((value || '').toLowerCase()) !== -1 ? value : 'asc';
|
|
|
|
}
|
|
|
|
|
2021-01-07 02:04:10 +02:00
|
|
|
module.exports = {
|
|
|
|
columnize,
|
2021-01-07 17:48:14 +02:00
|
|
|
direction,
|
2021-01-07 02:04:10 +02:00
|
|
|
operator,
|
|
|
|
outputQuery,
|
|
|
|
rawOrFn,
|
|
|
|
unwrapRaw,
|
|
|
|
wrap,
|
|
|
|
wrapString,
|
|
|
|
};
|