2014-09-01 17:18:45 +02:00
|
|
|
'use strict';
|
|
|
|
|
2014-03-14 16:56:55 -04:00
|
|
|
// Mixed into the query compiler & schema pieces. Assumes a `grammar`
|
|
|
|
// property exists on the current object.
|
2014-04-08 16:25:57 -04:00
|
|
|
var _ = require('lodash');
|
|
|
|
var QueryBuilder = require('./query/builder');
|
|
|
|
|
|
|
|
var Raw = require('./raw');
|
|
|
|
var push = Array.prototype.push;
|
|
|
|
|
|
|
|
// Valid values for the `order by` clause generation.
|
|
|
|
var orderBys = ['asc', 'desc'];
|
|
|
|
|
|
|
|
// A "formatter" instance is used to both determine how wrap, bind, and
|
|
|
|
// parameterize values within a query, keeping track of all bindings
|
|
|
|
// added to the query. This allows us to easily keep track of raw statements
|
|
|
|
// arbitrarily added to queries.
|
2014-04-27 19:36:40 -04:00
|
|
|
function Formatter() {
|
2014-04-08 16:25:57 -04:00
|
|
|
this.bindings = [];
|
2014-04-27 19:36:40 -04:00
|
|
|
}
|
2014-04-08 16:25:57 -04:00
|
|
|
|
|
|
|
// Turns a list of values into a list of ?'s, joining them with commas unless
|
|
|
|
// a "joining" value is specified (e.g. ' and ')
|
2014-04-16 01:23:50 -04:00
|
|
|
Formatter.prototype.parameterize = function(values) {
|
2014-04-08 16:25:57 -04:00
|
|
|
if (_.isFunction(values)) return this.parameter(values);
|
|
|
|
values = _.isArray(values) ? values : [values];
|
2014-04-16 01:23:50 -04:00
|
|
|
return _.map(values, this.parameter, this).join(', ');
|
2014-04-08 16:25:57 -04:00
|
|
|
};
|
|
|
|
|
|
|
|
// Checks whether a value is a function... if it is, we compile it
|
|
|
|
// otherwise we check whether it's a raw
|
|
|
|
Formatter.prototype.parameter = function(value) {
|
|
|
|
if (_.isFunction(value)) {
|
2014-06-14 16:26:01 -04:00
|
|
|
return this.outputQuery(this.compileCallback(value), true);
|
2014-04-08 16:25:57 -04:00
|
|
|
}
|
2014-04-15 11:43:47 -04:00
|
|
|
return this.checkRaw(value, true) || '?';
|
2014-04-08 16:25:57 -04:00
|
|
|
};
|
2014-03-14 16:56:55 -04:00
|
|
|
|
2014-04-08 16:25:57 -04:00
|
|
|
Formatter.prototype.checkRaw = function(value, parameter) {
|
|
|
|
if (value instanceof QueryBuilder) {
|
2014-04-15 11:43:47 -04:00
|
|
|
var query = value.toSQL();
|
|
|
|
if (query.bindings) push.apply(this.bindings, query.bindings);
|
2014-06-14 16:26:01 -04:00
|
|
|
return this.outputQuery(query);
|
2014-04-08 16:25:57 -04:00
|
|
|
}
|
|
|
|
if (value instanceof Raw) {
|
|
|
|
if (value.bindings) push.apply(this.bindings, value.bindings);
|
|
|
|
return value.sql;
|
|
|
|
}
|
|
|
|
if (parameter) this.bindings.push(value);
|
|
|
|
};
|
|
|
|
|
2014-06-14 09:58:38 -04:00
|
|
|
Formatter.prototype.rawOrFn = function(value, method) {
|
2014-04-08 16:25:57 -04:00
|
|
|
if (_.isFunction(value)) {
|
2014-06-14 16:26:01 -04:00
|
|
|
return this.outputQuery(this.compileCallback(value, method));
|
2014-04-08 16:25:57 -04:00
|
|
|
}
|
2014-06-14 09:58:38 -04:00
|
|
|
return this.checkRaw(value) || '';
|
2014-04-08 16:25:57 -04:00
|
|
|
};
|
|
|
|
|
|
|
|
// 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.
|
2014-06-14 16:26:01 -04:00
|
|
|
Formatter.prototype.wrap = function(value) {
|
2014-04-15 11:43:47 -04:00
|
|
|
var raw;
|
2014-06-14 16:26:01 -04:00
|
|
|
if (_.isFunction(value)) {
|
|
|
|
return this.outputQuery(this.compileCallback(value), true);
|
|
|
|
}
|
|
|
|
if (raw = this.checkRaw(value)) return raw;
|
|
|
|
if (_.isNumber(value)) return value;
|
|
|
|
return this._wrap(value + '');
|
2014-04-08 16:25:57 -04:00
|
|
|
};
|
|
|
|
|
|
|
|
// Coerce to string to prevent strange errors when it's not a string.
|
2014-04-15 11:43:47 -04:00
|
|
|
Formatter.prototype._wrapString = function(value) {
|
|
|
|
var segments, asIndex = value.toLowerCase().indexOf(' as ');
|
2014-04-08 16:25:57 -04:00
|
|
|
if (asIndex !== -1) {
|
2014-04-16 10:41:16 -04:00
|
|
|
var first = value.slice(0, asIndex);
|
|
|
|
var second = value.slice(asIndex + 4);
|
|
|
|
return this.wrap(first) + ' as ' + this.wrap(second);
|
2014-04-08 16:25:57 -04:00
|
|
|
}
|
|
|
|
var wrapped = [];
|
|
|
|
segments = value.split('.');
|
|
|
|
for (var i = 0, l = segments.length; i < l; i = ++i) {
|
|
|
|
value = segments[i];
|
|
|
|
if (i === 0 && segments.length > 1) {
|
2014-07-14 15:19:03 -04:00
|
|
|
wrapped.push(this.wrap((value || '').trim()));
|
2014-04-08 16:25:57 -04:00
|
|
|
} else {
|
2014-07-14 15:19:03 -04:00
|
|
|
wrapped.push(this.wrapValue((value || '').trim()));
|
2014-03-14 16:56:55 -04:00
|
|
|
}
|
|
|
|
}
|
2014-04-08 16:25:57 -04:00
|
|
|
return wrapped.join('.');
|
|
|
|
};
|
|
|
|
|
|
|
|
// Accepts a string or array of columns to wrap as appropriate.
|
|
|
|
Formatter.prototype.columnize = function(target) {
|
|
|
|
var columns = (_.isString(target) ? [target] : target);
|
|
|
|
return _.map(columns, this.wrap, this).join(', ');
|
|
|
|
};
|
|
|
|
|
|
|
|
// The operator method takes a value and returns something or other.
|
|
|
|
Formatter.prototype.operator = function(value) {
|
|
|
|
var raw;
|
|
|
|
if (raw = this.checkRaw(value)) return raw;
|
2014-06-30 12:42:33 -04:00
|
|
|
if (!_.contains(this.operators, (value || '').toLowerCase())) {
|
2014-05-09 14:42:29 -04:00
|
|
|
throw new TypeError('The operator "' + value + '" is not permitted');
|
2014-04-08 16:25:57 -04:00
|
|
|
}
|
|
|
|
return value;
|
|
|
|
};
|
|
|
|
|
2014-04-15 13:10:32 -04:00
|
|
|
// Specify the direction of the ordering.
|
2014-04-08 16:25:57 -04:00
|
|
|
Formatter.prototype.direction = function(value) {
|
|
|
|
var raw;
|
|
|
|
if (raw = this.checkRaw(value)) return raw;
|
|
|
|
return _.contains(orderBys, (value || '').toLowerCase()) ? value : 'asc';
|
|
|
|
};
|
|
|
|
|
2014-06-14 16:26:01 -04:00
|
|
|
// Compiles a callback using the query builder.
|
2014-04-08 16:25:57 -04:00
|
|
|
Formatter.prototype.compileCallback = function(callback, method) {
|
|
|
|
var client = this.client;
|
|
|
|
|
|
|
|
// Build the callback
|
|
|
|
var builder = new client.QueryBuilder();
|
|
|
|
callback.call(builder, builder);
|
|
|
|
|
|
|
|
// Compile the callback, using the current formatter (to track all bindings).
|
|
|
|
var compiler = new client.QueryCompiler(builder);
|
|
|
|
compiler.formatter = this;
|
2014-03-14 16:56:55 -04:00
|
|
|
|
2014-04-08 16:25:57 -04:00
|
|
|
// Return the compiled & parameterized sql.
|
2014-06-14 16:26:01 -04:00
|
|
|
return compiler.toSQL(method || 'select');
|
|
|
|
};
|
|
|
|
|
|
|
|
// Ensures the query is aliased if necessary.
|
|
|
|
Formatter.prototype.outputQuery = function(compiled, alwaysWrapped) {
|
|
|
|
var sql = compiled.sql || '';
|
|
|
|
if (sql) {
|
|
|
|
if (compiled.method === 'select' && alwaysWrapped || compiled.as) {
|
|
|
|
sql = '(' + sql + ')';
|
|
|
|
if (compiled.as) sql += ' as ' + this.wrap(compiled.as);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return sql;
|
2014-04-08 16:25:57 -04:00
|
|
|
};
|
2014-03-14 16:56:55 -04:00
|
|
|
|
2014-04-08 16:25:57 -04:00
|
|
|
module.exports = Formatter;
|