knex/knex.js

1568 lines
47 KiB
JavaScript
Raw Normal View History

// Knex.js 0.0.0
2013-03-12 11:52:08 -04:00
//
// (c) 2013 Tim Griesser
2013-04-30 18:29:53 -04:00
// Knex may be freely distributed under the MIT license.
2013-03-12 11:52:08 -04:00
// For details and documentation:
// http://knexjs.org
(function() {
2013-03-19 20:28:03 -04:00
"use strict";
// Required dependencies.
2013-03-12 11:52:08 -04:00
var _ = require('underscore');
var Q = require('q');
2013-04-30 18:29:53 -04:00
var push = Array.prototype.push;
2013-03-12 11:52:08 -04:00
2013-04-30 18:29:53 -04:00
// `Knex` is the root namespace and a chainable function: `Knex('tableName')`
var Knex = function(table) {
return new Knex.Builder(table);
2013-03-12 11:52:08 -04:00
};
// Keep in sync with package.json
2013-03-19 20:28:03 -04:00
Knex.VERSION = '0.0.0';
2013-03-12 11:52:08 -04:00
// Methods common to both the `Grammar` and `SchemaGrammar` interfaces,
// used to generate the sql in one form or another.
var Common = {
_debug: false,
debug: function(val) {
this._debug = val;
return this;
},
// For those who dislike promise interfaces.
exec: function(callback) {
2013-05-04 02:57:12 -04:00
var run = Knex.runQuery(this);
return run.nodeify(callback);
},
// The promise interface for the query builder.
then: function(onFulfilled, onRejected) {
var run = Knex.runQuery(this);
return run.then(onFulfilled, onRejected);
},
// Specifies to resolve the statement with the `data` rather
// than a promise... useful in testing/debugging.
toString: function() {
if (!this.type) {
throw new Error('Cannot be converted to string');
}
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('; ');
},
// 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;
}
};
// Grammar
// -------
2013-03-12 11:52:08 -04:00
// The list of different components
var components = [
'aggregate', 'columns', 'from',
'joins', 'wheres', 'groups', 'havings',
'orders', 'limit', 'offset', 'unions'
2013-03-12 11:52:08 -04:00
];
Knex.Grammar = {
2013-03-12 11:52:08 -04:00
dateFormat: 'Y-m-d H:i:s',
// 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 = {};
2013-04-30 19:08:47 -04:00
if (_.isEmpty(qb.columns)) qb.columns = ['*'];
2013-03-12 11:52:08 -04:00
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);
2013-03-12 11:52:08 -04:00
}
}
return _.compact(sql).join(' ');
},
// Compiles an aggregate query.
compileAggregate: function(qb) {
var column = this.columnize(qb.aggregate.columns);
2013-03-12 11:52:08 -04:00
if (qb.isDistinct && column !== '*') {
column = 'distinct ' + column;
}
return 'select ' + qb.aggregate.type + '(' + column + ') as aggregate';
2013-03-12 11:52:08 -04:00
},
// 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 /, '');
2013-03-19 20:28:03 -04:00
sql.push(join.type + ' join ' + this.wrapTable(join.table) + ' on ' + clauses.join(' '));
2013-03-12 11:52:08 -04:00
}
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;
},
2013-03-12 11:52:08 -04:00
// 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) {
2013-04-26 11:28:50 -04:00
return this.wrap(where.column) + ' in (' + this.parameterize(where.value) + ')';
2013-03-12 11:52:08 -04:00
},
// Compiles a where not in clause.
whereNotIn: function(qb, where) {
2013-04-26 11:28:50 -04:00
return this.wrap(where.column) + ' not in (' + this.parameterize(where.value) + ')';
2013-03-12 11:52:08 -04:00
},
// 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) + ')';
},
2013-03-19 20:28:03 -04:00
// Where between.
whereBetween: function(qb, where) {
return this.wrap(where.column) + ' between ? and ?';
},
2013-03-12 11:52:08 -04:00
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;
}
2013-03-12 11:52:08 -04:00
return '' + this.wrap(having.column) + ' ' + having.operator + ' ' + this.parameter(having['value']);
}, this).join('and ').replace(/and /, '');
2013-03-12 11:52:08 -04:00
},
// 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;
2013-03-12 11:52:08 -04:00
var table = this.wrapTable(qb.table);
var columns = this.columnize(_.keys(values[0]).sort());
var parameters = this.parameterize(_.values(values[0]));
2013-03-12 11:52:08 -04:00
var paramBlocks = [];
2013-04-26 11:28:50 -04:00
// 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);
2013-03-12 11:52:08 -04:00
for (var i = 0, l = values.length; i < l; ++i) {
paramBlocks.push("(" + parameters + ")");
}
return "insert into " + table + " (" + columns + ") values " + paramBlocks.join(', ');
},
2013-04-26 11:28:50 -04:00
// 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);
2013-03-12 11:52:08 -04:00
},
// Compiles an `update` query.
compileUpdate: function(qb) {
var values = qb.values;
2013-03-12 11:52:08 -04:00
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]));
2013-03-12 11:52:08 -04:00
}
return 'update ' + table + ' set ' + columns.join(', ') + ' ' + this.compileWheres(qb);
},
2013-03-19 20:28:03 -04:00
// Compiles a `delete` query.
2013-03-12 11:52:08 -04:00
compileDelete: function(qb) {
var table = this.wrapTable(qb.table);
var where = !_.isEmpty(qb.wheres) ? this.compileWheres(qb) : '';
return 'delete from ' + table + ' ' + where;
},
2013-03-19 20:28:03 -04:00
// Compiles a `truncate` query.
2013-03-12 11:52:08 -04:00
compileTruncate: function(qb) {
2013-05-04 20:06:39 -04:00
return 'truncate ' + this.wrapTable(qb.table);
2013-03-12 11:52:08 -04:00
},
wrap: function(value) {
var segments;
if (value instanceof Raw) return value.value;
2013-03-12 11:52:08 -04:00
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.value;
2013-04-30 19:08:47 -04:00
return this.wrapValue(table);
2013-03-12 11:52:08 -04:00
},
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 : '?');
2013-03-12 11:52:08 -04:00
}
};
// Knex.Builder
// -------
var Builder = Knex.Builder = function(table) {
// We use this logic to create sub-builders
// for the advanced query statements.
if (_.isString(table)) {
this.table = table;
} else {
this.client = table.client;
this.grammar = table.grammar;
}
2013-03-12 11:52:08 -04:00
this.reset();
};
// All operators used in the `where` clause generation.
var operators = ['=', '<', '>', '<=', '>=', 'like', 'not like', 'between', 'ilike'];
_.extend(Builder.prototype, Common, {
2013-03-12 11:52:08 -04:00
_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;
},
2013-03-12 11:52:08 -04:00
// Sets the `tableName` on the query.
from: function(tableName) {
if (!tableName) return this.table;
this.table = tableName;
return this;
},
2013-05-04 19:13:07 -04:00
// Select a `column` rather than
column: function(value) {
this.columns.push(value);
return this;
},
2013-03-12 11:52:08 -04:00
// Adds a `distinct` clause to the query.
2013-05-04 19:13:07 -04:00
distinct: function(column) {
this.column(column);
2013-03-12 11:52:08 -04:00
this.isDistinct = true;
return this;
},
toSql: function() {
return this.grammar['compile' + capitalize(this.type)](this);
},
2013-03-12 11:52:08 -04:00
// Clones the current query builder, including any
// pieces that have been set thus far.
2013-03-12 11:52:08 -04:00
clone: function() {
var item = new Builder(this.table);
item.client = this.client;
item.grammar = this.grammar;
2013-03-12 11:52:08 -04:00
var items = [
'_idAttribute', 'isDistinct', 'joins', 'wheres', 'orders',
'columns', 'bindings', 'grammar', 'transaction', 'unions'
2013-03-12 11:52:08 -04:00
];
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 = [];
2013-03-12 11:52:08 -04:00
this.bindings = [];
this.isDistinct = false;
},
2013-03-12 11:52:08 -04:00
// 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;
2013-03-19 20:28:03 -04:00
join = new JoinClause(type || 'inner', table);
first.call(join, join);
2013-03-12 11:52:08 -04:00
} else {
2013-03-19 20:28:03 -04:00
join = new JoinClause(type || 'inner', table);
2013-03-12 11:52:08 -04:00
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) {
2013-03-19 20:28:03 -04:00
var key;
2013-03-12 11:52:08 -04:00
bool || (bool = 'and');
if (_.isFunction(column)) {
return this._whereNested(column, bool);
}
if (_.isObject(column)) {
for (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({
2013-03-19 20:28:03 -04:00
type: 'Basic',
2013-03-12 11:52:08 -04:00
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);
},
whereRaw: function(sql, bindings, bool) {
bindings || (bindings = []);
bool || (bool = 'and');
this.wheres.push({type:'raw', sql:sql, bool:bool});
2013-03-19 20:28:03 -04:00
push.apply(this.bindings, bindings);
return this;
2013-03-12 11:52:08 -04:00
},
orWhereRaw: function(sql, bindings) {
return this.whereRaw(sql, bindings, 'or');
},
// Adds an `or where` clause to the query.
orWhere: function(column, operator, value) {
return this.where(column, operator, value, 'or');
},
// Adds a `where exists` clause to the query.
whereExists: function(callback, bool, type) {
var query = new Builder(this);
2013-03-19 20:28:03 -04:00
query.isSubQuery = true;
callback.call(query, query);
2013-03-12 11:52:08 -04:00
this.wheres.push({
type: (type || 'Exists'),
query: query,
bool: (bool || 'and')
});
2013-03-19 20:28:03 -04:00
push.apply(this.bindings, query.bindings);
2013-03-12 11:52:08 -04:00
return this;
},
// Adds an `or where exists` clause to the query.
2013-03-19 20:28:03 -04:00
orWhereExists: function(callback) {
2013-03-12 17:23:44 -04:00
return this.whereExists(callback, 'or');
2013-03-12 11:52:08 -04:00
},
// Adds a `where not exists` clause to the query.
2013-03-19 20:28:03 -04:00
whereNotExists: function(callback) {
2013-03-12 17:23:44 -04:00
return this.whereExists(callback, 'and', 'NotExists');
2013-03-12 11:52:08 -04:00
},
// Adds a `or where not exists` clause to the query.
orWhereNotExists: function(callback) {
2013-03-12 17:23:44 -04:00
return this.whereExists(callback, 'or', 'NotExists');
2013-03-12 11:52:08 -04:00
},
// 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, 'not');
}
this.wheres.push({
2013-03-12 17:23:44 -04:00
type: (condition || 'In'),
2013-03-12 11:52:08 -04:00
column: column,
2013-04-26 11:28:50 -04:00
value: values,
2013-03-12 11:52:08 -04:00
bool: bool
});
2013-03-19 20:28:03 -04:00
push.apply(this.bindings, values);
2013-03-12 11:52:08 -04:00
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.
2013-03-19 20:28:03 -04:00
whereNotIn: function(column, values) {
2013-03-12 17:23:44 -04:00
return this.whereIn(column, values, 'and', 'NotIn');
2013-03-12 11:52:08 -04:00
},
// Adds a `or where not in` clause to the query.
orWhereNotIn: function(column, values) {
2013-03-12 17:23:44 -04:00
return this.whereIn(column, values, 'or', 'NotIn');
2013-03-12 11:52:08 -04:00
},
// 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) {
2013-03-12 17:23:44 -04:00
return this.whereNull(column, 'or', 'Null');
2013-03-12 11:52:08 -04:00
},
// Adds a `where not null` clause to the query.
2013-03-19 20:28:03 -04:00
whereNotNull: function(column) {
2013-03-12 17:23:44 -04:00
return this.whereNull(column, 'and', 'NotNull');
2013-03-12 11:52:08 -04:00
},
// Adds a `or where not null` clause to the query.
orWhereNotNull: function(column) {
2013-03-19 20:28:03 -04:00
return this.whereNull(column, 'or', 'NotNull');
2013-03-12 11:52:08 -04:00
},
// Adds a `where between` clause to the query.
2013-03-19 20:28:03 -04:00
whereBetween: function(column, values) {
this.wheres.push({column: column, type: 'Between', bool: 'and'});
push.apply(this.bindings, values);
2013-03-12 11:52:08 -04:00
return this;
},
// Adds a `or where between` clause to the query.
orWhereBetween: function(column, values) {
2013-03-19 20:28:03 -04:00
this.wheres.push({column: column, type: 'Between', bool: 'or'});
push.apply(this.bindings, values);
return this;
2013-03-12 11:52:08 -04:00
},
// ----------------------------------------------------------------------
// 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;
},
2013-03-12 11:52:08 -04:00
// 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;
},
2013-03-12 11:52:08 -04:00
// ----------------------------------------------------------------------
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;
},
// ----------------------------------------------------------------------
// Determine if any rows exist for the current query.
exists: function() {
this.count();
return this.setType('exists');
2013-03-12 11:52:08 -04:00
},
// 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.
2013-03-12 11:52:08 -04:00
select: function(columns) {
2013-05-04 19:13:07 -04:00
if (columns) {
this.columns = this.columns.concat(_.isArray(columns) ? columns : _.toArray(arguments));
}
return this._setType('select');
2013-03-12 11:52:08 -04:00
},
// Sets the values for an `insert` query.
insert: function(values) {
this.values = this._prepValues(values);
return this._setType('insert');
2013-03-12 11:52:08 -04:00
},
// Sets the values for an `update` query.
2013-03-12 11:52:08 -04:00
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');
2013-03-12 11:52:08 -04:00
},
// Alias to del.
2013-03-12 11:52:08 -04:00
"delete": function() {
return this._setType('delete');
2013-03-12 11:52:08 -04:00
},
2013-04-30 19:08:47 -04:00
// Executes a delete statement on the query;
2013-03-12 11:52:08 -04:00
del: function() {
return this._setType('delete');
2013-03-12 11:52:08 -04:00
},
// Truncate
truncate: function() {
return this._setType('truncate');
2013-03-12 11:52:08 -04:00
},
// 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) {
this.transaction = t;
return this;
},
// ----------------------------------------------------------------------
_prepValues: function(values) {
if (!_.isArray(values)) values = values ? [values] : [];
for (var i = 0, l = values.length; i<l; i++) {
var obj = sortObject(values[i]);
for (var i2 = 0, l2 = obj.length; i2 < l2; i2++) {
this.bindings.push(obj[i2][1]);
}
}
return values;
},
2013-03-12 11:52:08 -04:00
_whereInSub: function(column, callback, bool, condition) {
var type = condition ? 'NotInSub' : 'InSub';
var query = new Builder(this);
2013-03-19 20:28:03 -04:00
query.isSubQuery = true;
callback.call(query, query);
2013-03-12 11:52:08 -04:00
this.wheres.push({type: type, column: column, query: query, bool: bool});
2013-03-19 20:28:03 -04:00
push.apply(this.bindings, query.bindings);
2013-03-12 11:52:08 -04:00
return this;
},
_whereNested: function(callback, bool) {
var query = new Builder(this);
2013-03-19 20:28:03 -04:00
query.isSubQuery = true;
2013-03-12 11:52:08 -04:00
query.table = this.table;
2013-03-19 20:28:03 -04:00
callback.call(query, query);
2013-03-12 11:52:08 -04:00
this.wheres.push({type: 'Nested', query: query, bool: bool});
2013-04-26 11:28:50 -04:00
push.apply(this.bindings, query.bindings);
2013-03-12 11:52:08 -04:00
return this;
},
_whereSub: function(column, operator, callback, bool) {
var query = new Builder(this);
2013-03-19 20:28:03 -04:00
query.isSubQuery = true;
callback.call(query, query);
2013-03-12 11:52:08 -04:00
this.wheres.push({
type: 'Sub',
column: column,
operator: operator,
query: query,
bool: bool
});
2013-03-19 20:28:03 -04:00
push.apply(this.bindings, query.bindings);
2013-03-12 11:52:08 -04:00
return this;
},
_aggregate: function(type, columns) {
if (!_.isArray(columns)) columns = [columns];
2013-03-12 11:52:08 -04:00
this.aggregate = {type: type, columns: columns};
return this._setType('select');
2013-03-12 11:52:08 -04:00
},
_counter: function(column, amount, symbol) {
var sql = {};
sql[column] = new Raw('' + this.grammar.wrap(column) + ' ' + (symbol || '+') + ' ' + amount);
return this.update(sql);
},
_union: function(callback, bool) {
var query = new Builder(this);
query.isSubQuery = true;
callback.call(query, query);
this.unions.push({query: query, all: bool});
push.apply(this.bindings, query.bindings);
2013-03-12 11:52:08 -04:00
}
});
2013-03-12 11:52:08 -04:00
// Knex.JoinClause
// ---------
var JoinClause = Knex.JoinClause = function(type, table) {
this.clauses = [];
this.type = type;
this.table = table;
};
JoinClause.prototype = {
2013-03-19 20:28:03 -04:00
on: function(first, operator, second) {
this.clauses.push({first: first, operator: operator, second: second, bool: 'and'});
2013-03-12 11:52:08 -04:00
return this;
},
2013-05-05 12:38:32 -04:00
andOn: function() {
this.on.apply(this, arguments);
return this;
},
2013-03-12 11:52:08 -04:00
orOn: function(first, operator, second) {
2013-03-19 20:28:03 -04:00
this.clauses.push({first: first, operator: operator, second: second, bool: 'or'});
return this;
2013-03-12 11:52:08 -04:00
}
};
// Knex.Transaction
// ---------
Knex.Transaction = function(container) {
2013-04-30 19:08:47 -04:00
var client = this.client;
return client.initTransaction().then(function(connection) {
2013-03-12 11:52:08 -04:00
2013-04-30 19:08:47 -04:00
// Initiate a deferred object, so we know when the
// transaction completes or fails, we know what to do.
var dfd = Q.defer();
// Call the container with the transaction
// commit & rollback objects
container({
2013-04-30 19:08:47 -04:00
commit: function() {
client.finishTransaction('commit', this, dfd);
},
2013-04-30 19:08:47 -04:00
rollback: function() {
client.finishTransaction('rollback', this, dfd);
},
2013-04-30 19:08:47 -04:00
// "rollback to"?
connection: connection
});
2013-03-12 11:52:08 -04:00
2013-04-30 19:08:47 -04:00
return dfd.promise;
2013-03-12 11:52:08 -04:00
});
};
// Knex.Schema
// ---------
2013-04-30 18:29:53 -04:00
var initSchema = function(Target, client) {
2013-03-12 11:52:08 -04:00
2013-04-30 18:29:53 -04:00
// Top level object for Schema related functions
var Schema = Target.Schema = {};
2013-03-12 11:52:08 -04:00
2013-04-30 18:29:53 -04:00
// Attach main static methods, which passthrough to the
// SchemaBuilder instance methods
_.each(['hasTable', 'createTable', 'table', 'dropTable', 'renameTable', 'dropTableIfExists'], function(method) {
2013-04-30 18:29:53 -04:00
Schema[method] = function() {
var args = _.toArray(arguments);
var builder = new Knex.SchemaBuilder(args[0]);
builder.client = client;
builder.grammar = client.schemaGrammar;
return SchemaInterface[method].apply(builder, args.slice(1));
2013-04-30 18:29:53 -04:00
};
});
};
2013-03-12 11:52:08 -04:00
// All of the Schame methods that should be called with a
// `SchemaBuilder` context, to disallow calling more than one method at once.
var SchemaInterface = {
// Modify a table on the schema.
table: function(callback) {
this.callback(callback);
return this._setType('table');
},
// Create a new table on the schema.
createTable: function(callback) {
this._addCommand('createTable');
this.callback(callback);
return this._setType('createTable');
2013-03-12 11:52:08 -04:00
},
// Drop a table from the schema.
dropTable: function() {
this._addCommand('dropTable');
return this._setType('dropTable');
2013-03-12 11:52:08 -04:00
},
// Drop a table from the schema if it exists.
dropTableIfExists: function() {
this._addCommand('dropTableIfExists');
return this._setType('dropTableIfExists');
2013-03-12 11:52:08 -04:00
},
// Rename a table on the schema.
renameTable: function(to) {
this._addCommand('renameTable', {to: to});
return this._setType('renameTable');
2013-03-12 11:52:08 -04:00
},
// Determine if the given table exists.
hasTable: function() {
this.bindings.push(this.table);
2013-05-04 02:57:12 -04:00
this._addCommand('tableExists');
return this._setType('tableExists');
2013-03-12 11:52:08 -04:00
}
};
// Knex.SchemaBuilder
2013-03-12 11:52:08 -04:00
// --------
var SchemaBuilder = Knex.SchemaBuilder = function(table) {
2013-03-12 11:52:08 -04:00
this.table = table;
this.columns = [];
this.commands = [];
this.bindings = [];
2013-03-12 11:52:08 -04:00
};
_.extend(SchemaBuilder.prototype, Common, {
_source: 'SchemaBuilder',
// A callback from the table building `Knex.schemaBuilder` calls.
callback: function(callback) {
2013-05-04 02:57:12 -04:00
if (callback) callback.call(this, this);
return this;
},
2013-03-12 11:52:08 -04:00
// Get the raw sql statements for the blueprint.
toSql: function() {
2013-03-12 11:52:08 -04:00
// Add the commands that are implied by the blueprint.
if (this.columns.length > 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];
2013-03-19 20:28:03 -04:00
var indexVar = 'is' + capitalize(index);
2013-03-12 11:52:08 -04:00
// 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.
2013-03-19 20:28:03 -04:00
if (column[indexVar] === true) {
2013-03-12 11:52:08 -04:00
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.
2013-03-19 20:28:03 -04:00
} else if (_.has(column, indexVar)) {
this[index](column.name, column[indexVar]);
2013-03-12 11:52:08 -04:00
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);
2013-03-12 11:52:08 -04:00
}
}
return statements;
},
// Determine if the blueprint has a create command.
creating: function() {
var command;
for (var i = 0, l = this.commands.length; i < l; i++) {
command = this.commands[i];
if (command.name == 'createTable') return true;
}
return false;
},
// Indicate that the given columns should be dropped.
dropColumn: function(columns) {
2013-04-01 00:24:01 -04:00
if (!_.isArray(columns)) columns = columns ? [columns] : [];
return this._addCommand('dropColumn', {columns: columns});
2013-03-12 11:52:08 -04:00
},
// 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});
2013-03-12 11:52:08 -04:00
},
// 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);
},
2013-03-12 11:52:08 -04:00
// Create a new text column on the table.
text: function(column, length) {
return this._addColumn('text', column, {length: (length || false)});
2013-03-12 11:52:08 -04:00
},
// 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);
2013-03-12 11:52:08 -04:00
},
// Alias for tinyinteger column.
tinyint: function(column) {
2013-04-30 19:08:47 -04:00
return this.tinyInteger(column);
2013-03-12 11:52:08 -04:00
},
// 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)
});
2013-03-12 11:52:08 -04:00
},
// 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)
});
2013-03-12 11:52:08 -04:00
},
2013-04-30 19:08:47 -04:00
// Alias to "bool"
2013-03-12 11:52:08 -04:00
boolean: function(column) {
2013-04-30 19:08:47 -04:00
return this.bool(column);
2013-03-12 11:52:08 -04:00
},
2013-04-30 19:08:47 -04:00
// Create a new boolean column on the table
2013-03-12 11:52:08 -04:00
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');
},
2013-04-30 19:08:47 -04:00
// Alias to enum.
2013-03-12 11:52:08 -04:00
"enum": function(column, allowed) {
2013-04-30 19:08:47 -04:00
return this.enu(column, allowed);
2013-03-12 11:52:08 -04:00
},
2013-04-30 19:08:47 -04:00
// Create a new enum column on the table.
enu: function(column, allowed) {
2013-03-12 11:52:08 -04:00
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)});
},
2013-03-12 11:52:08 -04:00
// 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.
2013-03-12 11:52:08 -04:00
_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
2013-03-19 20:28:03 -04:00
// index type, such as primary or index, which makes the index unique.
2013-03-12 11:52:08 -04:00
_indexCommand: function(type, columns, index) {
index || (index = null);
2013-04-01 00:24:01 -04:00
if (!_.isArray(columns)) columns = columns ? [columns] : [];
2013-03-12 11:52:08 -04:00
if (index === null) {
2013-03-19 20:28:03 -04:00
var table = this.table.replace(/\.|-/g, '_');
index = (table + '_' + _.map(columns, function(col) { return col.name; }).join('_') + '_' + type).toLowerCase();
2013-03-12 11:52:08 -04:00
}
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');
2013-03-12 11:52:08 -04:00
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;
}
});
2013-03-12 11:52:08 -04:00
// Chainable object used in creating SchemaBuilder commands.
var Chainable = function(obj) {
_.extend(this, obj);
};
Chainable.prototype = {
// Sets the default value for a column.
defaultTo: function(value) {
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;
2013-03-19 20:28:03 -04:00
},
// Adds an index on the specified column.
index: function(name) {
2013-03-19 20:28:03 -04:00
this.isIndex = name || true;
return this;
},
// Sets this column as the primary key.
primary: function(name) {
2013-03-19 20:28:03 -04:00
this.isPrimary = name || true;
return this;
},
// Sets this column as unique.
unique: function(name) {
2013-03-19 20:28:03 -04:00
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;
2013-03-12 11:52:08 -04:00
}
2013-03-19 20:28:03 -04:00
2013-03-12 11:52:08 -04:00
};
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 primary key command if it exists on the blueprint.
getCommandByName: function(blueprint, name) {
var commands = this.getCommandsByName(blueprint, name);
if (commands.length > 0) return commands[0];
},
// Get all of the commands with a given name.
getCommandsByName: function(blueprint, name) {
return _.where(blueprint.commands, function(value) { return value.name == name; });
},
// 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;
}
};
2013-03-12 11:52:08 -04:00
var capitalize = function(word) {
return word.charAt(0).toUpperCase() + word.slice(1);
};
var sortObject = function(obj) {
return _.sortBy(_.pairs(obj), function(a) {
return a[0];
});
};
2013-03-12 11:52:08 -04:00
// 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.
2013-03-12 11:52:08 -04:00
Knex.Raw = function(value) {
return new Raw(value);
};
var Raw = function(value) {
2013-03-12 11:52:08 -04:00
this.value = value;
};
// Knex.runQuery
// -------
// Query runner, the context of this function is set to the caller,
// (either Builder or SchemaBuilder). Checks and fails on an already
// resolved transaction, otherwise calls the query on the specified client
// and returns a deferred promise.
Knex.runQuery = function(builder) {
if (builder.transaction) {
if (!builder.transaction.connection) return Q.reject(new Error('The transaction has already completed.'));
builder._connection = builder.transaction.connection;
2013-03-12 11:52:08 -04:00
}
// Prep the SQL associated with the builder.
builder.sql = builder.toSql();
builder.bindings = builder._cleanBindings();
if (!_.isArray(builder.sql)) builder.sql = [builder.sql];
var chain;
for (var i = 0, l = builder.sql.length; i < l; i++) {
if (chain) {
chain.then(multiQuery(builder, i, chain));
} else {
chain = multiQuery(builder, i);
}
}
// Query on the query builder, which should resolve with a promise.
return chain;
};
// Sets up a multi-query
var multiQuery = function(builder, i, chain) {
if (chain) {
return function() {
return multiQuery(builder, i);
};
}
return builder.client.query(_.extend({}, builder, {sql: builder.sql[i]}));
2013-04-30 18:29:53 -04:00
};
// 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.
2013-04-30 18:29:53 -04:00
if (_.isObject(name)) {
options = name;
name = 'default';
}
// 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,
// require it as the path to the client if it's a string,
// and otherwise, set the object to the client.
if (Clients[client]) {
ClientCtor = require(Clients[client]);
} else if (_.isString(client)) {
ClientCtor = require(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);
2013-04-30 18:29:53 -04:00
// If this is named "default" then we're setting this on the Knex
if (name === 'default') {
Target = Knex;
2013-04-30 18:29:53 -04:00
} else {
Target = function(table) {
var builder = new Target.Builder(table);
builder.client = client;
builder.grammar = client.grammar;
return builder;
2013-04-30 18:29:53 -04:00
};
// Inherit static properties, without any that don't apply except
// on the "root" `Knex`.
_.extend(Target, _.omit(Knex, 'Initialize', 'Instances', 'VERSION'));
2013-04-30 18:29:53 -04:00
}
// Initialize the schema builder methods.
initSchema(Target, client);
// Specifically set the client on the current target.
Target.client = client;
2013-05-02 10:25:55 -04:00
Target.instanceName = name;
// Add this instance to the global `Knex` instances, and return.
Knex.Instances[name] = Target;
return Target;
2013-04-30 18:29:53 -04:00
};
// Default client paths, located in the `./clients` directory.
var Clients = {
'mysql' : './clients/mysql.js',
'postgres' : './clients/postgres.js',
'sqlite' : './clients/sqlite3.js',
2013-04-30 18:29:53 -04:00
'sqlite3' : './clients/sqlite3.js'
};
// Named instances of Knex, presumably with different database
// connections, the main instance being named "default"...
Knex.Instances = {};
2013-03-12 11:52:08 -04:00
// Export the Knex module
module.exports = Knex;
}).call(this);