knex/lib/formatter/wrappingFormatter.js

252 lines
6.2 KiB
JavaScript

const transform = require('lodash/transform');
const QueryBuilder = require('../query/querybuilder');
const { compileCallback, wrapAsIdentifier } = require('./formatterUtils');
// Valid values for the `order by` clause generation.
const orderBys = ['asc', 'desc'];
// 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',
'match',
'&',
'|',
'^',
'<<',
'>>',
'~',
'~=',
'~*',
'!~',
'!~*',
'#',
'&&',
'@>',
'<@',
'||',
'&<',
'&>',
'-|-',
'@@',
'!!',
['?', '\\?'],
['?|', '\\?|'],
['?&', '\\?&'],
],
(result, key) => {
if (Array.isArray(key)) {
result[key[0]] = key[1];
} else {
result[key] = key;
}
},
{}
);
// Accepts a string or array of columns to wrap as appropriate. Column can be raw
function columnize(target, builder, client, bindingHolder) {
const columns = Array.isArray(target) ? target : [target];
let str = '',
i = -1;
while (++i < columns.length) {
if (i > 0) str += ', ';
str += wrap(columns[i], undefined, builder, client, bindingHolder);
}
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.
function wrap(value, isParameter, builder, client, bindingHolder) {
const raw = unwrapRaw(value, isParameter, builder, client, bindingHolder);
if (raw) return raw;
switch (typeof value) {
case 'function':
return outputQuery(
compileCallback(value, undefined, client, bindingHolder),
true,
builder,
client
);
case 'object':
return parseObject(value, builder, client, bindingHolder);
case 'number':
return value;
default:
return wrapString(value + '', builder, client);
}
}
function unwrapRaw(value, isParameter, builder, client, bindingsHolder) {
let query;
if (value instanceof QueryBuilder) {
query = client.queryCompiler(value).toSQL();
if (query.bindings) {
bindingsHolder.bindings.push(...query.bindings);
}
return outputQuery(query, isParameter, builder, client);
}
if (value && value.isRawInstance) {
value.client = client;
if (builder._queryContext) {
value.queryContext = () => {
return builder._queryContext;
};
}
query = value.toSQL();
if (query.bindings) {
bindingsHolder.bindings.push(...query.bindings);
}
return query.sql;
}
if (isParameter) {
bindingsHolder.bindings.push(value);
}
}
function operator(value, builder, client, bindingsHolder) {
const raw = unwrapRaw(value, undefined, builder, client, bindingsHolder);
if (raw) return raw;
const operator = operators[(value || '').toLowerCase()];
if (!operator) {
throw new TypeError(`The operator "${value}" is not permitted`);
}
return operator;
}
// Coerce to string to prevent strange errors when it's not a string.
function wrapString(value, builder, client) {
const asIndex = value.toLowerCase().indexOf(' as ');
if (asIndex !== -1) {
const first = value.slice(0, asIndex);
const second = value.slice(asIndex + 4);
return client.alias(
wrapString(first, builder, client),
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) {
wrapped.push(wrapString((value || '').trim(), builder, client));
} 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
ret.push(outputQuery(compiled, true, builder, client));
} else if (queryOrIdentifier instanceof QueryBuilder) {
ret.push(
client.alias(
`(${wrap(queryOrIdentifier, undefined, builder, client, formatter)})`,
wrapAsIdentifier(alias, builder, client)
)
);
} else {
ret.push(
client.alias(
wrap(queryOrIdentifier, undefined, builder, client, formatter),
wrapAsIdentifier(alias, builder, client)
)
);
}
}
return ret.join(', ');
}
// Ensures the query is aliased if necessary.
function outputQuery(compiled, isParameter, builder, client) {
let sql = compiled.sql || '';
if (sql) {
if (
(compiled.method === 'select' || compiled.method === 'first') &&
(isParameter || compiled.as)
) {
sql = `(${sql})`;
if (compiled.as)
return client.alias(sql, wrapString(compiled.as, builder, client));
}
}
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
* @param bindingHolder
*/
function rawOrFn(value, method, builder, client, bindingHolder) {
if (typeof value === 'function') {
return outputQuery(
compileCallback(value, method, client, bindingHolder),
undefined,
builder,
client
);
}
return unwrapRaw(value, undefined, builder, client, bindingHolder) || '';
}
// Specify the direction of the ordering.
function direction(value, builder, client, bindingsHolder) {
const raw = unwrapRaw(value, undefined, builder, client, bindingsHolder);
if (raw) return raw;
return orderBys.indexOf((value || '').toLowerCase()) !== -1 ? value : 'asc';
}
module.exports = {
columnize,
direction,
operator,
outputQuery,
rawOrFn,
unwrapRaw,
wrap,
wrapString,
};