knex/clients/base/grammar.js
Tim Griesser 3c8d08a64b Removing unnecessary UMD
When this eventually enabled on the client side,
we can build this into the build script.
2013-11-27 16:51:01 -05:00

349 lines
11 KiB
JavaScript

// Grammar
// -------
// The "Grammar" is a collection of functions
// which help to reliably compile the various pieces
// of SQL into a valid, escaped query. These functions
// are combined with dialect specific "Grammar" functions
// to keep the interface database agnostic.
var _ = require('lodash');
var Raw = require('../../lib/raw').Raw;
var Helpers = require('../../lib/helpers').Helpers;
var push = [].push;
// The list of different components
var components = [
'columns', 'aggregates', 'from',
'joins', 'wheres', 'groups', 'havings',
'orders', 'limit', 'offset', 'unions'
];
exports.baseGrammar = {
// Compiles the current query builder.
toSql: function(builder) {
builder.type = builder.type || 'select';
return builder.grammar['compile' + Helpers.capitalize(builder.type)](builder);
},
// Gets the cleaned bindings.
getBindings: function(builder) {
var bindings = builder.bindings;
var cleaned = [];
for (var i = 0, l = bindings.length; i < l; i++) {
// if (bindings[i] == void 0) continue;
if (!bindings[i] || bindings[i]._source !== 'Raw') {
cleaned.push(bindings[i]);
} else {
push.apply(cleaned, bindings[i].bindings);
}
}
return cleaned;
},
// 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.
compileSelect: function(qb) {
var sql = [];
if (_.isEmpty(qb.columns) && _.isEmpty(qb.aggregates)) qb.columns = ['*'];
for (var i = 0, l = components.length; i < l; i++) {
var component = components[i];
var result = _.result(qb, component);
if (result != null) {
sql.push(this['compile' + Helpers.capitalize(component)](qb, result));
}
}
// If there is a transaction, and we have either `forUpdate` or `forShare` specified,
// call the appropriate additions to the select statement.
if (qb.transaction && qb.flags.selectMode) {
sql.push(this['compile' + qb.flags.selectMode](qb));
}
return _.compact(sql).join(' ');
},
// Compiles the columns with aggregate functions.
compileAggregates: function(qb) {
var sql = [], segments, column;
for (var i = 0, l = qb.aggregates.length; i < l; i++) {
var aggregate = qb.aggregates[i];
if (aggregate.columns.toLowerCase().indexOf(' as ') !== -1) {
segments = aggregate.columns.split(' ');
column = segments[0];
} else {
column = aggregate.columns;
}
sql.push(aggregate.type + '(' + this.wrap(column) + ')' + (segments ? ' as ' + this.wrap(segments[2]) : ''));
}
return sql.join(', ');
},
// Compiles the columns in the query, specifying if an item was distinct.
compileColumns: function(qb, columns) {
return (qb.flags.distinct ? 'select distinct' : 'select') + ((_.isArray(columns) && _.isEmpty(columns)) ? '' : ' '+this.columnize(columns));
},
// Compiles the `from` tableName portion of the query.
compileFrom: function(qb, table) {
return 'from ' + this.wrapTable(table);
},
// Compiles all each of the `join` clauses on the query,
// including any nested join queries.
compileJoins: function(qb, joins) {
var sql = [];
for (var i = 0, l = joins.length; i < l; i++) {
var join = joins[i];
var clauses = [];
for (var i2 = 0, l2 = join.clauses.length; i2 < l2; i2++) {
var clause = join.clauses[i2];
clauses.push(
[clause['bool'], this.wrap(clause['first']), clause.operator, this.wrap(clause['second'])].join(' ')
);
}
clauses[0] = clauses[0].replace(/and |or /, '');
sql.push(join.joinType + ' join ' + this.wrapTable(join.table) + ' on ' + clauses.join(' '));
}
return sql.join(' ');
},
// Compiles all `where` statements on the query.
compileWheres: function(qb) {
var sql = [];
var wheres = qb.wheres;
if (wheres.length === 0) return '';
for (var i = 0, l = wheres.length; i < l; i++) {
var where = wheres[i];
sql.push(where.bool + ' ' + this['where' + where.type](qb, where));
}
return (sql.length > 0 ? 'where ' + sql.join(' ').replace(/and |or /, '') : '');
},
// Compile the "union" queries attached to the main query.
compileUnions: function(qb) {
var sql = '';
for (var i = 0, l = qb.unions.length; i < l; i++) {
var union = qb.unions[i];
sql += (union.all ? 'union all ' : 'union ') + this.compileSelect(union.query);
}
return sql;
},
// Compiles a nested where clause.
whereNested: function(qb, where) {
return '(' + this.compileWheres(where.query).slice(6) + ')';
},
// Compiles a nested where clause.
whereSub: function(qb, where) {
return this.wrap(where.column) + ' ' + where.operator + ' (' + (this.compileSelect(where.query)) + ')';
},
// Compiles a basic where clause.
whereBasic: function(qb, where) {
return this.wrap(where.column) + ' ' + where.operator + ' ' + this.parameter(where.value);
},
// Compiles a basic exists clause.
whereExists: function(qb, where) {
return 'exists (' + this.compileSelect(where.query) + ')';
},
// Compiles a basic not exists clause.
whereNotExists: function(qb, where) {
return 'not exists (' + this.compileSelect(where.query) + ')';
},
// Compiles a where in clause.
whereIn: function(qb, where) {
return this.wrap(where.column) + ' in (' + this.parameterize(where.value) + ')';
},
// Compiles a where not in clause.
whereNotIn: function(qb, where) {
return this.wrap(where.column) + ' not in (' + this.parameterize(where.value) + ')';
},
// Compiles a sub-where in clause.
whereInSub: function(qb, where) {
return this.wrap(where.column) + ' in (' + this.compileSelect(where.query) + ')';
},
// Compiles a sub-where not in clause.
whereNotInSub: function(qb, where) {
return this.wrap(where.column) + ' not in (' + this.compileSelect(where.query) + ')';
},
// Where between.
whereBetween: function(qb, where) {
return this.wrap(where.column) + ' between ? and ?';
},
whereNull: function(qb, where) {
return this.wrap(where.column) + ' is null';
},
whereNotNull: function(qb, where) {
return this.wrap(where.column) + ' is not null';
},
whereRaw: function(qb, where) {
return where.sql;
},
// Compiles the `group by` columns.
compileGroups: function(qb, groups) {
return 'group by ' + this.columnize(groups);
},
// Compiles the `having` statements.
compileHavings: function(qb, havings) {
if (!havings.length) return;
var h = 'having ' + havings.map(function(having) {
if (having.type === 'Raw') {
return having.bool + ' ' + having.sql;
}
return having.bool + ' ' + this.wrap(having.column) + ' ' + having.operator + ' ' + this.parameter(having['value']);
}, this);
return h.replace(/and |or /, '');
},
// Compiles the `order by` statements.
compileOrders: function(qb, orders) {
if (orders.length > 0) {
return 'order by ' + orders.map(function(order) {
return '' + this.wrap(order.column) + ' ' + order.direction;
}, this).join(', ');
}
},
// Compiles the `limit` statements.
compileLimit: function(qb, limit) {
return 'limit ' + limit;
},
// Compiles an `offset` statement on the query.
compileOffset: function(qb, offset) {
return 'offset ' + offset;
},
// Compiles an `insert` query, allowing for multiple
// inserts using a single query statement.
compileInsert: function(qb) {
var values = qb.values;
var table = this.wrapTable(qb.table);
var columns = _.pluck(values[0], 0);
var paramBlocks = [];
// If there are any "where" clauses, we need to omit
// any bindings that may have been associated with them.
if (qb.wheres.length > 0) this.clearWhereBindings(qb);
for (var i = 0, l = values.length; i < l; ++i) {
paramBlocks.push("(" + this.parameterize(_.pluck(values[i], 1)) + ")");
}
return "insert into " + table + " (" + this.columnize(columns) + ") values " + paramBlocks.join(', ');
},
// Depending on the type of `where` clause, this will appropriately
// remove any binding caused by "where" constraints, allowing the same
// query to be used for `insert` and `update` without issue.
clearWhereBindings: function(qb) {
var wheres = qb.wheres;
var bindingCount = 0;
for (var i = 0, l = wheres.length; i<l; i++) {
var where = wheres[i];
if (_.isArray(where.value)) {
bindingCount += where.value.length;
} else if (where.query) {
bindingCount += where.query.bindings.length;
} else {
bindingCount += 1;
}
}
qb.bindings = qb.bindings.slice(bindingCount);
},
// Compiles an `update` query.
compileUpdate: function(qb) {
var values = qb.values;
var table = this.wrapTable(qb.table), columns = [];
for (var i=0, l = values.length; i < l; i++) {
var value = values[i];
columns.push(this.wrap(value[0]) + ' = ' + this.parameter(value[1]));
}
return 'update ' + table + ' set ' + columns.join(', ') + ' ' + this.compileWheres(qb);
},
// Compiles a `delete` query.
compileDelete: function(qb) {
var table = this.wrapTable(qb.table);
var where = !_.isEmpty(qb.wheres) ? this.compileWheres(qb) : '';
return 'delete from ' + table + ' ' + where;
},
// Compiles a `truncate` query.
compileTruncate: function(qb) {
return 'truncate ' + this.wrapTable(qb.table);
},
// Adds a `for update` clause to the query, relevant with transactions.
compileForUpdate: function() {
return 'for update';
},
// Adds a `for share` clause to the query, relevant with transactions.
compileForShare: function() {
return 'for share';
},
// 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.
wrap: function(value) {
var segments;
if (value instanceof Raw) return value.sql;
if (_.isNumber(value)) return value;
if (value.toLowerCase().indexOf(' as ') !== -1) {
segments = value.split(' ');
return this.wrap(segments[0]) + ' as ' + this.wrap(segments[2]);
}
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) {
wrapped.push(this.wrapTable(value));
} else {
wrapped.push(this.wrapValue(value));
}
}
return wrapped.join('.');
},
wrapArray: function(values) {
return _.map(values, this.wrap, this);
},
wrapTable: function(table) {
if (table instanceof Raw) return table.sql;
return this.wrap(table);
},
columnize: function(columns) {
if (!_.isArray(columns)) columns = [columns];
return _.map(columns, this.wrap, this).join(', ');
},
parameterize: function(values) {
if (!_.isArray(values)) values = [values];
return _.map(values, this.parameter, this).join(', ');
},
parameter: function(value) {
return (value instanceof Raw ? value.sql : '?');
}
};