// Knex.js 0.1.4 // // (c) 2013 Tim Griesser // Knex may be freely distributed under the MIT license. // For details and documentation: // http://knexjs.org (function() { "use strict"; // Required dependencies. var _ = require('underscore'); var When = require('when'); var push = Array.prototype.push; // `Knex` is the root namespace and a chainable function: `Knex('tableName')` var Knex = function(table) { if (!Knex.Instances['main']) { throw new Error('The Knex instance has not been initialized yet.'); } return Knex.Instances['main'](table); }; // Keep in sync with package.json Knex.VERSION = '0.1.4'; // Methods common to both the `Grammar` and `SchemaGrammar` interfaces, // used to generate the sql in one form or another. var Common = { _debug: false, _promise: null, debug: function() { this._debug = true; return this; }, // For those who dislike promise interfaces. // Multiple calls to `exec` will resolve with the same value // if called more than once. exec: function(callback) { this._promise || (this._promise = this.runQuery()); return this._promise.then(function(resp) { callback(null, resp); }, function(err) { callback(err, null); }); }, // The promise interface for the query builder. then: function(onFulfilled, onRejected) { this._promise || (this._promise = this.runQuery()); return this._promise.then(onFulfilled, onRejected); }, // Returns an array of query strings filled out with the // correct values based on bindings, etc. Useful for debugging. toString: function() { this.type || (this.type = 'select'); var data = this.toSql(); var builder = this; if (!_.isArray(data)) data = [data]; return _.map(data, function(str) { var questionCount = 0; return str.replace(/\?/g, function() { return builder.bindings[questionCount++]; }); }).join('; '); }, // Explicitly sets the connection. connection: function(connection) { this._connection = connection; return this; }, // The connection the current query is being run on, optionally // specified by the `connection` method. _connection: false, // Sets the "type" of the current query, so we can potentially place // `select`, `update`, `del`, etc. anywhere in the query statement // and have it come out fine. _setType: function(type) { if (this.type) { throw new Error('The query type has already been set to ' + this.type); } this.type = type; return this; }, // Returns all bindings excluding the `Knex.Raw` types. _cleanBindings: function() { var bindings = this.bindings; var cleaned = []; for (var i = 0, l = bindings.length; i < l; i++) { if (!(bindings[i] instanceof Raw)) cleaned.push(bindings[i]); } return cleaned; }, // Runs the query on the current builder instance and returns a promise. runQuery: function() { if (this.transaction) { if (!this.transaction.connection) return When.reject(new Error('The transaction has already completed.')); this._connection = this.transaction.connection; } // Prep the SQL associated with the this. this.sql = this.toSql(); this.bindings = this._cleanBindings(); if (!_.isArray(this.sql)) this.sql = [this.sql]; var chain; for (var i = 0, l = this.sql.length; i < l; i++) { if (chain) { chain.then(multiQuery(this, i, chain)); } else { chain = multiQuery(this, i); } } return chain; } }; // Grammar // ------- // The list of different components var components = [ 'aggregate', 'columns', 'from', 'joins', 'wheres', 'groups', 'havings', 'orders', 'limit', 'offset', 'unions' ]; Knex.Grammar = { // 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)) 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[component] = this['compile' + capitalize(component)](qb, result); } } return _.compact(sql).join(' '); }, // Compiles an aggregate query. compileAggregate: function(qb) { var column = this.columnize(qb.aggregate.columns); if (qb.isDistinct && column !== '*') { column = 'distinct ' + column; } return 'select ' + qb.aggregate.type + '(' + column + ') as aggregate'; }, // Compiles the columns in the query, specifying if an item was distinct. compileColumns: function(qb, columns) { if (qb.aggregate != null) return; return (qb.isDistinct ? 'select distinct ' : 'select ') + 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.type + ' 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) { return 'having ' + havings.map(function(having) { if (having.type === 'raw') { return having.bool + ' ' + having.sql; } return '' + this.wrap(having.column) + ' ' + having.operator + ' ' + this.parameter(having['value']); }, this).join('and ').replace(/and /, ''); }, // 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 = this.columnize(_.keys(values[0]).sort()); var parameters = this.parameterize(_.values(values[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("(" + parameters + ")"); } return "insert into " + table + " (" + 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 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.value; return this.wrap(table); }, columnize: function(columns) { return _.map(columns, this.wrap, this).join(', '); }, parameterize: function(values) { return _.map(values, this.parameter, this).join(', '); }, parameter: function(value) { return (value instanceof Raw ? value.value : '?'); } }; // Knex.Builder // ------- var Builder = Knex.Builder = function(table) { // We use this logic to create sub-builders // for the advanced query statements. if (table) { if (_.isString(table)) { this.table = table; } else { this.client = table.client; this.grammar = table.grammar; } } this.reset(); }; // All operators used in the `where` clause generation. var operators = ['=', '<', '>', '<=', '>=', '<>', 'like', 'not like', 'between', 'ilike']; _.extend(Builder.prototype, Common, { _source: 'Builder', _idAttribute: 'id', // Sets the `returning` for the query - only necessary // to set the "returning" value for the postgres insert, // defaults to `id`. idAttribute: function(val) { this._idAttribute = val; return this; }, // Sets the `tableName` on the query. from: function(tableName) { if (!tableName) return this.table; this.table = tableName; return this; }, // Adds a column to the list of "columns" being selected // on the query. column: function(value) { this.columns.push(value); return this; }, // Adds a `distinct` clause to the query. distinct: function(column) { this.column(column); this.isDistinct = true; return this; }, // Compiles the current query builder. toSql: function() { this.type || (this.type = 'select'); return this.grammar['compile' + capitalize(this.type)](this); }, // Clones the current query builder, including any // pieces that have been set thus far. clone: function() { var item = new Builder(this.table); item.client = this.client; item.grammar = this.grammar; var items = [ '_idAttribute', 'isDistinct', 'joins', 'wheres', 'orders', 'columns', 'bindings', 'grammar', 'transaction', 'unions' ]; 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.isDistinct = false; }, // 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, bool) { bool || (bool = 'and'); if (_.isFunction(column)) { return this._whereNested(column, bool); } if (_.isObject(column)) { for (var key in column) { value = column[key]; this[bool + 'Where'](key, '=', value); } return this; } if (!_.contains(operators, operator)) { value = operator; operator = '='; } 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() { return this.where.apply(this, arguments); }, // Adds an `or where` clause to the query. orWhere: function(column, operator, value) { return this.where(column, operator, value, 'or'); }, // Adds a raw `where` clause to the query. whereRaw: function(sql, bindings, bool) { bindings || (bindings = []); bool || (bool = 'and'); this.wheres.push({type: 'Raw', sql:sql, bool:bool}); 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); 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) { this.havings.push({column: column, operator: (operator || ''), value: (value || '')}); this.bindings.push(value); return this; }, havingRaw: function(sql, bindings) { this.havings.push({type: 'Raw', sql: sql, bool: 'and'}); this.bindings.push(bindings); return this; }, orHavingRaw: function(sql, bindings) { this.havings.push({type: 'Raw', sql: sql, bool: 'or'}); this.bindings.push(bindings); return this; }, // ---------------------------------------------------------------------- offset: function(value) { if (value == null) return this.isOffset; this.isOffset = value; return this; }, limit: function(value) { if (value == null) return this.isLimit; this.isLimit = 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) { this.values = this._prepValues(values); return this._setType('insert'); }, // Sets the values for an `update` query. update: function(values) { var obj = 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'); }, // Truncate truncate: function() { return this._setType('truncate'); }, // Set by `transacting` - contains the object with the connection // needed to execute a transaction transaction: false, // Sets the current Builder connection to that of the // the currently running transaction transacting: function(t) { if (!t) throw new Error('A transaction object must be passed to "transacting".'); this.transaction = t; return this; }, // ---------------------------------------------------------------------- _prepValues: function(values) { if (!_.isArray(values)) values = values ? [values] : []; for (var i = 0, l = values.length; i 0 && !this.creating()) { this.commands.unshift(new Chainable({name: 'add'})); } // Add indicies for (var i = 0, l = this.columns.length; i < l; i++) { var column = this.columns[i]; var indices = ['primary', 'unique', 'index']; continueIndex: for (var i2 = 0, l2 = indices.length; i2 < l2; i2++) { var index = indices[i2]; var indexVar = 'is' + capitalize(index); // If the index has been specified on the given column, but is simply // equal to "true" (boolean), no name has been specified for this // index, so we will simply call the index methods without one. if (column[indexVar] === true) { this[index](column); continue continueIndex; // If the index has been specified on the column and it is something // other than boolean true, we will assume a name was provided on // the index specification, and pass in the name to the method. } else if (_.has(column, indexVar)) { this[index](column.name, column[indexVar]); continue continueIndex; } } } var statements = []; // Each type of command has a corresponding compiler function on the schema // grammar which is used to build the necessary SQL statements to build // the blueprint element, so we'll just call that compilers function. for (i = 0, l = this.commands.length; i < l; i++) { var command = this.commands[i]; var method = 'compile' + capitalize(command.name); if (_.has(this.grammar, method)) { var sql = this.grammar[method](this, command); statements = statements.concat(sql); } } return statements; }, // Determine if the blueprint has a create command. creating: function() { for (var i = 0, l = this.commands.length; i < l; i++) { if (this.commands[i].name == 'createTable') return true; } return false; }, // Sets the engine to use when creating the table in MySql engine: function(name) { if (!this.creating()) throw new Error('The `engine` modifier may only be used while creating a table.'); this.isEngine = name; return this; }, // Indicate that the given columns should be dropped. dropColumn: function(columns) { if (!_.isArray(columns)) columns = columns ? [columns] : []; return this._addCommand('dropColumn', {columns: columns}); }, // Indicate that the given columns should be dropped. dropColumns: function() { return this.dropColumn(arguments); }, // Indicate that the given primary key should be dropped. dropPrimary: function(index) { return this._dropIndexCommand('dropPrimary', index); }, // Indicate that the given unique key should be dropped. dropUnique: function(index) { return this._dropIndexCommand('dropUnique', index); }, // Indicate that the given index should be dropped. dropIndex: function(index) { return this._dropIndexCommand('dropIndex', index); }, // Indicate that the given foreign key should be dropped. dropForeign: function(index) { return this._dropIndexCommand('dropForeign', index); }, // Specify the primary key(s) for the table. primary: function(columns, name) { return this._indexCommand('primary', columns, name); }, // Specify a unique index for the table. unique: function(columns, name) { return this._indexCommand('unique', columns, name); }, // Specify an index for the table. index: function(columns, name) { return this._indexCommand('index', columns, name); }, // Specify a foreign key for the table. foreign: function(columns, name) { return this._indexCommand('foreign', columns, name); }, // Create a new auto-incrementing column on the table. increments: function(column) { return this._addColumn('integer', (column || 'id'), {autoIncrement: true, length: 11}); }, // Create a new string column on the table. string: function(column, length) { return this._addColumn('string', column, {length: (length || 255)}); }, // Alias varchar to string varchar: function(column, length) { return this.string(column, length); }, // Create a new text column on the table. text: function(column, length) { return this._addColumn('text', column, {length: (length || false)}); }, // Create a new integer column on the table. integer: function(column, length) { return this._addColumn('integer', column, {length: (length || 11)}); }, // Create a new tinyinteger column on the table. tinyInteger: function(column) { return this._addColumn('tinyInteger', column); }, // Alias for tinyinteger column. tinyint: function(column) { return this.tinyInteger(column); }, // Create a new float column on the table. float: function(column, precision, scale) { return this._addColumn('float', column, { precision: (precision == null ? 8 : precision), scale: (scale == null ? 2 : scale) }); }, // Create a new decimal column on the table. decimal: function(column, precision, scale) { return this._addColumn('decimal', column, { precision: (precision == null ? 8 : precision), scale: (scale == null ? 2 : scale) }); }, // Alias to "bool" boolean: function(column) { return this.bool(column); }, // Create a new boolean column on the table bool: function(column) { return this._addColumn('boolean', column); }, // Create a new date column on the table. date: function(column) { return this._addColumn('date', column); }, // Create a new date-time column on the table. dateTime: function(column) { return this._addColumn('dateTime', column); }, // Create a new time column on the table. time: function(column) { return this._addColumn('time', column); }, // Create a new timestamp column on the table. timestamp: function(column) { return this._addColumn('timestamp', column); }, // Add creation and update timestamps to the table. timestamps: function() { this.timestamp('created_at'); this.timestamp('updated_at'); }, // Alias to enum. "enum": function(column, allowed) { return this.enu(column, allowed); }, // Create a new enum column on the table. enu: function(column, allowed) { return this._addColumn('enum', column, {allowed: allowed}); }, // Create a new bit column on the table. bit: function(column, length) { return this._addColumn('bit', column, {length: (length || false)}); }, // Create a new binary column on the table. binary: function(column) { return this._addColumn('binary', column); }, // ---------------------------------------------------------------------- // Create a new drop index command on the blueprint. // If the index is an array of columns, the developer means // to drop an index merely by specifying the columns involved. _dropIndexCommand: function(type, index) { var columns = []; if (_.isArray(index)) { columns = index; index = null; } return this._indexCommand(type, columns, index); }, // Add a new index command to the blueprint. // If no name was specified for this index, we will create one using a basic // convention of the table name, followed by the columns, followed by an // index type, such as primary or index, which makes the index unique. _indexCommand: function(type, columns, index) { index || (index = null); if (!_.isArray(columns)) columns = columns ? [columns] : []; if (index === null) { var table = this.table.replace(/\.|-/g, '_'); index = (table + '_' + _.map(columns, function(col) { return col.name; }).join('_') + '_' + type).toLowerCase(); } return this._addCommand(type, {index: index, columns: columns}); }, // Add a new column to the blueprint. _addColumn: function(type, name, parameters) { if (!name) throw new Error('A `name` must be defined to add a column'); var attrs = _.extend({type: type, name: name}, parameters); var column = new Chainable(attrs); this.columns.push(column); return column; }, // Add a new command to the blueprint. _addCommand: function(name, parameters) { var command = new Chainable(_.extend({name: name}, parameters)); this.commands.push(command); return command; } }); // Chainable object used in creating SchemaBuilder commands. var Chainable = function(obj) { _.extend(this, obj); }; Chainable.prototype = { // Sets the default value for a column. // For `boolean` columns, we'll permit 'false' // to be used as default values. defaultTo: function(value) { if (this.type === 'boolean') { if (value === 'false') value = 0; value = (value ? 1 : 0); } this.defaultValue = value; return this; }, // Sets an integer as unsigned, is a no-op // if the column type is not an integer. unsigned: function() { this.isUnsigned = true; return this; }, // Allows the column to contain null values. nullable: function() { this.isNullable = true; return this; }, // Adds an index on the specified column. index: function(name) { this.isIndex = name || true; return this; }, // Sets this column as the primary key. primary: function(name) { this.isPrimary = name || true; return this; }, // Sets this column as unique. unique: function(name) { this.isUnique = name || true; return this; }, // Sets the column to be inserted after another, // used in MySql alter tables. after: function(name) { this.isAfter = name; return this; } }; Knex.SchemaGrammar = { // Compile a foreign key command. compileForeign: function(blueprint, command) { var table = this.wrapTable(blueprint); var on = this.wrapTable(command.on); // We need to prepare several of the elements of the foreign key definition // before we can create the SQL, such as wrapping the tables and convert // an array of columns to comma-delimited strings for the SQL queries. var columns = this.columnize(command.columns); var onColumns = this.columnize(command.references); var sql = "alter table " + table + " add constraint " + command.index + " "; sql += "foreign key (" + columns + ") references " + on + " (" + onColumns + ")"; // Once we have the basic foreign key creation statement constructed we can // build out the syntax for what should happen on an update or delete of // the affected columns, which will get something like "cascade", etc. if (command.onDelete) sql += " on delete " + command.onDelete; if (command.onUpdate) sql += " on update " + command.onUpdate; return sql; }, // Each of the column types have their own compiler functions which are // responsible for turning the column definition into its SQL format // for the platform. Then column modifiers are compiled and added. getColumns: function(blueprint) { var columns = []; for (var i = 0, l = blueprint.columns.length; i < l; i++) { var column = blueprint.columns[i]; var sql = this.wrap(column) + ' ' + this.getType(column); columns.push(this.addModifiers(sql, blueprint, column)); } return columns; }, // Add the column modifiers to the definition. addModifiers: function(sql, blueprint, column) { for (var i = 0, l = this.modifiers.length; i < l; i++) { var modifier = this.modifiers[i]; var method = "modify" + modifier; if (_.has(this, method)) { sql += this[method](blueprint, column) || ''; } } return sql; }, // Get the SQL for the column data type. getType: function(column) { return this["type" + capitalize(column.type)](column); }, // Add a prefix to an array of values, utilized in the client libs. prefixArray: function(prefix, values) { return _.map(values, function(value) { return prefix + ' ' + value; }); }, // Wrap a table in keyword identifiers. wrapTable: function(table) { if (table instanceof SchemaBuilder) table = table.table; return Knex.Grammar.wrapTable.call(this, table); }, // Wrap a value in keyword identifiers. wrap: function(value) { if (value instanceof Chainable) value = value.name; return Knex.Grammar.wrap.call(this, value); }, // Format a value so that it can be used in "default" clauses. getDefaultValue: function(value) { if (value instanceof Raw) return value.value; if (value === true || value === false) { return parseInt(value, 10); } return '' + value; } }; // Knex.Raw // ------- // Helpful for injecting a snippet of raw SQL into a // `Knex` block... in most cases, we'll check if the value // is an instanceof Raw, and if it is, use the supplied value. Knex.Raw = function(value) { if (!Knex.Instances['main']) { throw new Error('The Knex instance has not been initialized yet.'); } return Knex.Instances['main'].Raw(value); }; var Raw = function(value) { this.value = value; }; _.extend(Raw.prototype, Common, { _source: 'Raw', // Returns the raw sql for the query. toSql: function() { return this.value; }, // Returns the bindings for a raw query. _cleanBindings: function() { return []; } }); // Simple capitalization of a word. var capitalize = function(word) { return word.charAt(0).toUpperCase() + word.slice(1); }; // Sorts an object based on the names. var sortObject = function(obj) { return _.sortBy(_.pairs(obj), function(a) { return a[0]; }); }; // Sets up a multi-query to be executed with serial promises. var multiQuery = function(builder, i, chain) { if (chain) { return function() { return multiQuery(builder, i); }; } return builder.client.query(_.extend({}, builder, {sql: builder.sql[i]})); }; // Knex.Initialize // ------- // Takes a hash of options to initialize the database // connection. The `client` is required to choose which client // path above is loaded, or to specify a custom path to a client. // Other options, such as `connection` or `pool` are passed // into `client.initialize`. Knex.Initialize = function(name, options) { var Target, ClientCtor, client; // A name for the connection isn't required in // cases where there is only a single connection. if (_.isObject(name)) { options = name; name = 'main'; } // Don't try to initialize the same `name` twice... If necessary, // delete the instance from `Knex.Instances`. if (Knex.Instances[name]) { throw new Error('An instance named ' + name + ' already exists'); } client = options.client; if (!client) throw new Error('The client is required to use Knex.'); // Checks if this is a default client. If it's not, // that means it's a custom lib, set the object to the client. if (_.isString(client)) { client = client.toLowerCase(); ClientCtor = require(Clients[client]); } else { ClientCtor = client; } // Creates a new instance of the db client, passing the name and options. client = new ClientCtor(name, _.omit(options, 'client')); // Setup the grammars specific to the client. client.grammar = _.extend({}, Knex.Grammar, client.grammar); client.schemaGrammar = _.extend({}, client.grammar, Knex.SchemaGrammar, client.schemaGrammar); // If this is named "default" then we're setting this on the Knex Target = function(table) { var builder = new Knex.Builder(table); builder.client = client; builder.grammar = client.grammar; return builder; }; // Inherit static properties, without any that don't apply except // on the "root" `Knex`. _.extend(Target, _.omit(Knex, 'Initialize', 'Instances', 'VERSION')); // Initialize the schema builder methods. if (name === 'main') { initSchema(Knex, client); } initSchema(Target, client); // Specifically set the client on the current target. Target.client = client; Target.instanceName = name; // Setup the transacting function properly for this connection. Target.Transaction = function(handler) { return transaction.call(this, handler); }; // Executes a Raw query. Target.Raw = function(value) { var raw = new Raw(value); raw.client = client; return raw; }; // Add this instance to the global `Knex` instances, and return. Knex.Instances[name] = Target; return Target; }; // Default client paths, located in the `./clients` directory. var Clients = { 'mysql' : './clients/mysql.js', 'pg' : './clients/postgres.js', 'postgres' : './clients/postgres.js', 'sqlite' : './clients/sqlite3.js', 'sqlite3' : './clients/sqlite3.js' }; // Named instances of Knex, presumably with different database // connections, the main instance being named "main"... Knex.Instances = {}; // Export the Knex module module.exports = Knex; }).call(this);