// Builder // ------- (function(define) { "use strict"; // The `Builder` is the interface for constructing a regular // `select`, `insert`, `update`, or `delete` query, as well // as some aggregate helpers. define(function(require, exports) { var _ = require('underscore'); var Raw = require('./raw').Raw; var Common = require('./common').Common; var Helpers = require('./helpers').Helpers; var JoinClause = require('./builder/joinclause').JoinClause; var array = []; var push = array.push; // Constructor for the builder instance, typically called from // `knex.builder`, accepting the current `knex` instance, // and pulling out the `client` and `grammar` from the current // knex instance. var Builder = function(knex) { this.knex = knex; this.client = knex.client; this.grammar = knex.grammar; this.reset(); _.bindAll(this, 'handleResponse'); }; // All operators used in the `where` clause generation. var operators = ['=', '<', '>', '<=', '>=', '<>', '!=', 'like', 'not like', 'between', 'ilike']; _.extend(Builder.prototype, Common, { _source: 'Builder', // Sets the `tableName` on the query. from: function(tableName) { if (!tableName) return this.table; this.table = tableName; return this; }, // Alias to from, for "insert" statements // e.g. builder.insert({a: value}).into('tableName') into: function(tableName) { this.table = tableName; return this; }, // Adds a column or columns to the list of "columns" // being selected on the query. column: function(columns) { if (columns) { push.apply(this.columns, _.isArray(columns) ? columns : _.toArray(arguments)); } return this; }, // Adds a `distinct` clause to the query. distinct: function(column) { this.column(column); this.flags.distinct = true; return this; }, // Clones the current query builder, including any // pieces that have been set thus far. clone: function() { var item = new Builder(this.knex); item.table = this.table; var items = [ 'joins', 'wheres', 'orders', 'columns', 'bindings', 'grammar', 'transaction', 'unions', 'flags', 'type' ]; for (var i = 0, l = items.length; i < l; i++) { var k = items[i]; item[k] = this[k]; } return item; }, // Resets all attributes on the query builder. reset: function() { this.joins = []; this.values = []; this.unions = []; this.wheres = []; this.orders = []; this.columns = []; this.bindings = []; this.flags = {}; }, // Adds a join clause to the query, allowing for advanced joins // with an anonymous function as the second argument. join: function(table, first, operator, second, type) { var join; if (_.isFunction(first)) { type = operator; join = new JoinClause(type || 'inner', table); first.call(join, join); } else { join = new JoinClause(type || 'inner', table); join.on(first, operator, second); } this.joins.push(join); return this; }, // The where function can be used in several ways: // The most basic is `where(key, value)`, which expands to // where key = value. where: function(column, operator, value) { var bool = this._boolFlag || 'and'; this._boolFlag = 'and'; // Check if the column is a function, in which case it's // a grouped where statement (wrapped in parens). if (_.isFunction(column)) { return this._whereNested(column, bool); } // Allow a raw statement to be passed along to the query. if (column instanceof Raw) { return this.whereRaw(column.sql, column.bindings, bool); } // Allows `where({id: 2})` syntax. if (_.isObject(column)) { for (var key in column) { value = column[key]; this[bool + 'Where'](key, '=', value); } return this; } // Enable the where('key', value) syntax, only when there // are explicitly two arguments passed, so it's not possible to // do where('key', '!=') and have that turn into where key != null if (arguments.length === 2) { value = operator; operator = '='; } // If the value is null, and the operator is equals, assume that we're // going for a `whereNull` statement here. if (value == null && operator === '=') { return this.whereNull(column, bool); } // If the value is a function, assume it's for building a sub-select. if (_.isFunction(value)) { return this._whereSub(column, operator, value, bool); } this.wheres.push({ type: 'Basic', column: column, operator: operator, value: value, bool: bool }); this.bindings.push(value); return this; }, // Alias to `where`, for internal builder consistency. andWhere: function(column, operator, value) { return this.where.apply(this, arguments); }, // Adds an `or where` clause to the query. orWhere: function(column, operator, value) { this._boolFlag = 'or'; return this.where.apply(this, arguments); }, // Adds a raw `where` clause to the query. whereRaw: function(sql, bindings, bool) { bindings = _.isArray(bindings) ? bindings : (bindings ? [bindings] : []); this.wheres.push({type: 'Raw', sql: sql, bool: bool || 'and'}); push.apply(this.bindings, bindings); return this; }, // Adds a raw `or where` clause to the query. orWhereRaw: function(sql, bindings) { return this.whereRaw(sql, bindings, 'or'); }, // Adds a `where exists` clause to the query. whereExists: function(callback, bool, type) { var query = new Builder(this.knex); callback.call(query, query); this.wheres.push({ type: (type || 'Exists'), query: query, bool: (bool || 'and') }); push.apply(this.bindings, query.bindings); return this; }, // Adds an `or where exists` clause to the query. orWhereExists: function(callback) { return this.whereExists(callback, 'or'); }, // Adds a `where not exists` clause to the query. whereNotExists: function(callback) { return this.whereExists(callback, 'and', 'NotExists'); }, // Adds a `or where not exists` clause to the query. orWhereNotExists: function(callback) { return this.whereExists(callback, 'or', 'NotExists'); }, // Adds a `where in` clause to the query. whereIn: function(column, values, bool, condition) { bool || (bool = 'and'); if (_.isFunction(values)) { return this._whereInSub(column, values, bool, (condition || 'In')); } this.wheres.push({ type: (condition || 'In'), column: column, value: values, bool: bool }); push.apply(this.bindings, values); return this; }, // Adds a `or where in` clause to the query. orWhereIn: function(column, values) { return this.whereIn(column, values, 'or'); }, // Adds a `where not in` clause to the query. whereNotIn: function(column, values) { return this.whereIn(column, values, 'and', 'NotIn'); }, // Adds a `or where not in` clause to the query. orWhereNotIn: function(column, values) { return this.whereIn(column, values, 'or', 'NotIn'); }, // Adds a `where null` clause to the query. whereNull: function(column, bool, type) { this.wheres.push({type: (type || 'Null'), column: column, bool: (bool || 'and')}); return this; }, // Adds a `or where null` clause to the query. orWhereNull: function(column) { return this.whereNull(column, 'or', 'Null'); }, // Adds a `where not null` clause to the query. whereNotNull: function(column) { return this.whereNull(column, 'and', 'NotNull'); }, // Adds a `or where not null` clause to the query. orWhereNotNull: function(column) { return this.whereNull(column, 'or', 'NotNull'); }, // Adds a `where between` clause to the query. whereBetween: function(column, values) { this.wheres.push({column: column, type: 'Between', bool: 'and'}); push.apply(this.bindings, values); return this; }, // Adds a `or where between` clause to the query. orWhereBetween: function(column, values) { this.wheres.push({column: column, type: 'Between', bool: 'or'}); push.apply(this.bindings, values); return this; }, // Adds a `group by` clause to the query. groupBy: function() { this.groups = (this.groups || []).concat(_.toArray(arguments)); return this; }, // Adds a `order by` clause to the query. orderBy: function(column, direction) { this.orders.push({column: column, direction: (direction || 'asc')}); return this; }, // Add a union statement to the query. union: function(callback) { this._union(callback, false); return this; }, // Adds a union all statement to the query. unionAll: function(callback) { this._union(callback, true); return this; }, // Adds a `having` clause to the query. having: function(column, operator, value, bool) { if (column instanceof Raw) { return this.havingRaw(column.value, bool); } this.havings.push({column: column, operator: (operator || ''), value: (value || ''), bool: bool || 'and'}); this.bindings.push(value); return this; }, // Adds an `or having` clause to the query. orHaving: function(column, operator, value) { return this.having(column, operator, value, 'or'); }, // Adds a raw `having` clause to the query. havingRaw: function(sql, bool) { this.havings.push({type: 'Raw', sql: sql, bool: bool || 'and'}); return this; }, // Adds a raw `or having` clause to the query. orHavingRaw: function(sql) { return this.havingRaw(sql, 'or'); }, offset: function(value) { if (value == null) return this.flags.offset; this.flags.offset = value; return this; }, limit: function(value) { if (value == null) return this.flags.limit; this.flags.limit = value; return this; }, // Retrieve the "count" result of the query. count: function(column) { return this._aggregate('count', column); }, // Retrieve the minimum value of a given column. min: function(column) { return this._aggregate('min', column); }, // Retrieve the maximum value of a given column. max: function(column) { return this._aggregate('max', column); }, // Retrieve the sum of the values of a given column. sum: function(column) { return this._aggregate('sum', column); }, // Increments a column's value by the specified amount. increment: function(column, amount) { return this._counter(column, amount); }, // Decrements a column's value by the specified amount. decrement: function(column, amount) { return this._counter(column, amount, '-'); }, // Sets the values for a `select` query. select: function(columns) { if (columns) { push.apply(this.columns, _.isArray(columns) ? columns : _.toArray(arguments)); } return this._setType('select'); }, // Sets the values for an `insert` query. insert: function(values, returning) { if (returning) this.returning(returning); this.values = this.prepValues(_.clone(values)); return this._setType('insert'); }, // Sets the returning value for the query. returning: function(returning) { this.flags.returning = returning; return this; }, // Sets the values for an `update` query. update: function(values) { var obj = Helpers.sortObject(values); var bindings = []; for (var i = 0, l = obj.length; i < l; i++) { bindings[i] = obj[i][1]; } this.bindings = bindings.concat(this.bindings || []); this.values = obj; return this._setType('update'); }, // Alias to del. "delete": function() { return this._setType('delete'); }, // Executes a delete statement on the query; del: function() { return this._setType('delete'); }, option: function(opts) { this.opts = _.extend(this.opts, opts); return this; }, // Truncate truncate: function() { return this._setType('truncate'); }, // Set by `transacting` - contains the object with the connection // needed to execute a transaction transaction: false, // Preps the values for `insert` or `update`. prepValues: function(values) { if (!_.isArray(values)) values = values ? [values] : []; for (var i = 0, l = values.length; i