beginning to properly modularize things

This commit is contained in:
Tim Griesser 2013-09-03 22:01:31 -04:00
parent 069b4e56d0
commit 10940cd0c3
12 changed files with 1114 additions and 1038 deletions

13
.jshintrc Normal file
View File

@ -0,0 +1,13 @@
{
"boss": true,
"browser": true,
"expr": true,
"eqnull": true,
"latedef": true,
"newcap": false,
"node": true,
"strict": false,
"supernew": true,
"sub": true,
"trailing": true
}

View File

@ -105,97 +105,4 @@ exports.protoProps = {
});
}
};
exports.grammar = {};
exports.schemaGrammar = {
// 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 _.filter(blueprint.commands, function(value) { return value.name == name; }) || [];
},
// Used to compile any database specific items.
compileAdditional: function() {},
// Compile a create table command.
compileCreateTable: function(blueprint) {
var columns = this.getColumns(blueprint).join(', ');
return 'create table ' + this.wrapTable(blueprint) + ' (' + columns + ')';
},
// Compile a drop table command.
compileDropTable: function(blueprint) {
return 'drop table ' + this.wrapTable(blueprint);
},
// Compile a drop table (if exists) command.
compileDropTableIfExists: function(blueprint) {
return 'drop table if exists ' + this.wrapTable(blueprint);
},
// Compile a drop index command.
compileDropIndex: function(blueprint, command) {
return 'drop index ' + command.index;
},
// Create the column definition for a string type.
typeString: function(column) {
return "varchar(" + column.length + ")";
},
// Create the column definition for a text type.
typeText: function() {
return 'text';
},
// Create the column definition for a tiny integer type.
typeTinyInteger: function() {
return 'tinyint';
},
// Create the column definition for a time type.
typeTime: function() {
return 'time';
},
// Create the column definition for a date type.
typeDate: function() {
return 'date';
},
// Create the column definition for a binary type.
typeBinary: function() {
return 'blob';
},
// Create the column definition for a json type.
typeJson: function() {
return 'text';
},
// Create the column definition for a uuid type.
typeUuid: function() {
return 'char(36)';
},
// Get the SQL for a nullable column modifier.
modifyNullable: function(blueprint, column) {
return column.isNullable ? ' null' : ' not null';
},
// Get the SQL for a default column modifier.
modifyDefault: function(blueprint, column) {
if (column.defaultValue != void 0) {
return " default '" + this.getDefaultValue(column.defaultValue) + "'";
}
}
};
};

307
clients/base/grammar.js Normal file
View File

@ -0,0 +1,307 @@
(function(define) {
"use strict";
define(function(require, exports) {
var _ = require('underscore');
var Raw = require('../../lib/raw').Raw;
var Helpers = require('../../lib/helpers').Helpers;
// Grammar
// -------
// The list of different components
var components = [
'aggregate', 'columns', 'from',
'joins', 'wheres', 'groups', 'havings',
'orders', 'limit', 'offset', 'unions'
];
exports.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' + Helpers.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 having.bool + ' ' + this.wrap(having.column) + ' ' + having.operator + ' ' + this.parameter(having['value']);
}, this).replace(/and |or /, '');
},
// Compiles the `order by` statements.
compileOrders: function(qb, orders) {
if (orders.length > 0) {
return 'order by ' + orders.map(function(order) {
return '' + this.wrap(order.column) + ' ' + order.direction;
}, this).join(', ');
}
},
// Compiles the `limit` statements.
compileLimit: function(qb, limit) {
return 'limit ' + limit;
},
// Compiles an `offset` statement on the query.
compileOffset: function(qb, offset) {
return 'offset ' + offset;
},
// Compiles an `insert` query, allowing for multiple
// inserts using a single query statement.
compileInsert: function(qb) {
var values = qb.values;
var table = this.wrapTable(qb.table);
var columns = _.pluck(values[0], 0);
var paramBlocks = [];
// If there are any "where" clauses, we need to omit
// any bindings that may have been associated with them.
if (qb.wheres.length > 0) this._clearWhereBindings(qb);
for (var i = 0, l = values.length; i < l; ++i) {
paramBlocks.push("(" + this.parameterize(_.pluck(values[i], 1)) + ")");
}
return "insert into " + table + " (" + this.columnize(columns) + ") values " + paramBlocks.join(', ');
},
// Depending on the type of `where` clause, this will appropriately
// remove any binding caused by "where" constraints, allowing the same
// query to be used for `insert` and `update` without issue.
_clearWhereBindings: function(qb) {
var wheres = qb.wheres;
var bindingCount = 0;
for (var i = 0, l = wheres.length; i<l; i++) {
var where = wheres[i];
if (_.isArray(where.value)) {
bindingCount += where.value.length;
} else if (where.query) {
bindingCount += where.query.bindings.length;
} else {
bindingCount += 1;
}
}
qb.bindings = qb.bindings.slice(bindingCount);
},
// Compiles an `update` query.
compileUpdate: function(qb) {
var values = qb.values;
var table = this.wrapTable(qb.table), columns = [];
for (var i=0, l = values.length; i < l; i++) {
var value = values[i];
columns.push(this.wrap(value[0]) + ' = ' + this.parameter(value[1]));
}
return 'update ' + table + ' set ' + columns.join(', ') + ' ' + this.compileWheres(qb);
},
// Compiles a `delete` query.
compileDelete: function(qb) {
var table = this.wrapTable(qb.table);
var where = !_.isEmpty(qb.wheres) ? this.compileWheres(qb) : '';
return 'delete from ' + table + ' ' + where;
},
// Compiles a `truncate` query.
compileTruncate: function(qb) {
return 'truncate ' + this.wrapTable(qb.table);
},
wrap: function(value) {
var segments;
if (value instanceof Raw) return value.sql;
if (_.isNumber(value)) return value;
if (value.toLowerCase().indexOf(' as ') !== -1) {
segments = value.split(' ');
return this.wrap(segments[0]) + ' as ' + this.wrap(segments[2]);
}
var wrapped = [];
segments = value.split('.');
for (var i = 0, l = segments.length; i < l; i = ++i) {
value = segments[i];
if (i === 0 && segments.length > 1) {
wrapped.push(this.wrapTable(value));
} else {
wrapped.push(this.wrapValue(value));
}
}
return wrapped.join('.');
},
wrapArray: function(values) {
return _.map(values, this.wrap, this);
},
wrapTable: function(table) {
if (table instanceof Raw) return table.sql;
return this.wrap(table);
},
columnize: function(columns) {
if (!_.isArray(columns)) columns = [columns];
return _.map(columns, this.wrap, this).join(', ');
},
parameterize: function(values) {
if (!_.isArray(values)) values = [values];
return _.map(values, this.parameter, this).join(', ');
},
parameter: function(value) {
return (value instanceof Raw ? value.sql : '?');
}
};
});
})(
typeof define === 'function' && define.amd ? define : function (factory) { factory(require, exports, module); }
);

View File

@ -0,0 +1,186 @@
(function(define) {
"use strict";
define(function(require, exports) {
var _ = require('underscore');
var BaseGrammar = require('./grammar').Grammar;
var SchemaBuilder = require('../../lib/schemabuilder').SchemaBuilder;
var Helpers = require('../../lib/helpers').Helpers;
var Raw = require('../../lib/raw').Raw;
exports.SchemaGrammar = {
// Compile a foreign key command.
compileForeign: function(blueprint, command) {
var sql;
if (command.foreignTable && command.foreignColumn) {
var table = this.wrapTable(blueprint);
var column = this.columnize(command.columns);
var foreignTable = this.wrapTable(command.foreignTable);
var foreignColumn = this.columnize(command.foreignColumn);
sql = "alter table " + table + " add constraint " + command.index + " ";
sql += "foreign key (" + column + ") references " + foreignTable + " (" + foreignColumn + ")";
// 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.commandOnDelete) sql += " on delete " + command.commandOnDelete;
if (command.commandOnUpdate) sql += " on update " + command.commandOnUpdate;
}
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, blueprint);
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, blueprint) {
return this['type' + Helpers.capitalize(column.type)](column, blueprint);
},
// 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 BaseGrammar.wrapTable.call(this, table);
},
// Wrap a value in keyword identifiers.
wrap: function(value) {
if (value && value.name) value = value.name;
return BaseGrammar.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.sql;
if (value === true || value === false) {
return parseInt(value, 10);
}
return '' + value;
},
// 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 _.filter(blueprint.commands, function(value) { return value.name == name; }) || [];
},
// Used to compile any database specific items.
compileAdditional: function() {},
// Compile a create table command.
compileCreateTable: function(blueprint) {
var columns = this.getColumns(blueprint).join(', ');
return 'create table ' + this.wrapTable(blueprint) + ' (' + columns + ')';
},
// Compile a drop table command.
compileDropTable: function(blueprint) {
return 'drop table ' + this.wrapTable(blueprint);
},
// Compile a drop table (if exists) command.
compileDropTableIfExists: function(blueprint) {
return 'drop table if exists ' + this.wrapTable(blueprint);
},
// Compile a drop index command.
compileDropIndex: function(blueprint, command) {
return 'drop index ' + command.index;
},
// Create the column definition for a string type.
typeString: function(column) {
return "varchar(" + column.length + ")";
},
// Create the column definition for a text type.
typeText: function() {
return 'text';
},
// Create the column definition for a tiny integer type.
typeTinyInteger: function() {
return 'tinyint';
},
// Create the column definition for a time type.
typeTime: function() {
return 'time';
},
// Create the column definition for a date type.
typeDate: function() {
return 'date';
},
// Create the column definition for a binary type.
typeBinary: function() {
return 'blob';
},
// Create the column definition for a json type.
typeJson: function() {
return 'text';
},
// Create the column definition for a uuid type.
typeUuid: function() {
return 'char(36)';
},
// Get the SQL for a nullable column modifier.
modifyNullable: function(blueprint, column) {
return column.isNullable ? ' null' : ' not null';
},
// Get the SQL for a default column modifier.
modifyDefault: function(blueprint, column) {
if (column.defaultValue != void 0) {
return " default '" + this.getDefaultValue(column.defaultValue) + "'";
}
}
};
});
})(
typeof define === 'function' && define.amd ? define : function (factory) { factory(require, exports, module); }
);

View File

@ -1,8 +1,11 @@
var When = require('when');
var _ = require('underscore');
var util = require('util');
var base = require('./base');
var mysql = require('mysql');
var When = require('when');
var _ = require('underscore');
var util = require('util');
var base = require('./base');
var mysql = require('mysql');
var Grammar = require('./base/grammar').Grammar;
var SchemaGrammar = require('./base/schemagrammar').SchemaGrammar;
// Constructor for the MysqlClient
var MysqlClient = module.exports = function(name, options) {
@ -72,24 +75,24 @@ _.extend(MysqlClient.prototype, base.protoProps, {
});
// Extends the standard sql grammar.
MysqlClient.grammar = {
MysqlClient.grammar = _.defaults({
// The keyword identifier wrapper format.
wrapValue: function(value) {
return (value !== '*' ? util.format('`%s`', value) : "*");
}
};
}, Grammar);
// Grammar for the schema builder.
MysqlClient.schemaGrammar = _.extend({}, base.schemaGrammar, MysqlClient.grammar, {
MysqlClient.schemaGrammar = _.defaults({
// The possible column modifiers.
modifiers: ['Unsigned', 'Nullable', 'Default', 'Increment', 'After', 'Comment'],
// Compile a create table command.
compileCreateTable: function(blueprint, command) {
var sql = base.schemaGrammar.compileCreateTable.call(this, blueprint, command);
var sql = SchemaGrammar.compileCreateTable.call(this, blueprint, command);
var conn = blueprint.client.connectionSettings;
if (conn.charset) sql += ' default character set ' + conn.charset;
@ -271,4 +274,4 @@ MysqlClient.schemaGrammar = _.extend({}, base.schemaGrammar, MysqlClient.grammar
}
}
});
}, SchemaGrammar, MysqlClient.grammar);

View File

@ -4,6 +4,9 @@ var util = require('util');
var base = require('./base');
var pg = require('pg');
var Grammar = require('./base/grammar').Grammar;
var SchemaGrammar = require('./base/schemagrammar').SchemaGrammar;
// Constructor for the PostgresClient
var PostgresClient = module.exports = function(name, options) {
base.setup.call(this, PostgresClient, name, options);
@ -94,7 +97,7 @@ _.extend(PostgresClient.prototype, base.protoProps, {
});
// Extends the standard sql grammar.
PostgresClient.grammar = {
PostgresClient.grammar = _.defaults({
// The keyword identifier wrapper format.
wrapValue: function(value) {
@ -133,10 +136,10 @@ PostgresClient.grammar = {
return sql;
}
};
}, Grammar);
// Grammar for the schema builder.
PostgresClient.schemaGrammar = _.extend({}, base.schemaGrammar, PostgresClient.grammar, {
PostgresClient.schemaGrammar = _.defaults({
// The possible column modifiers.
modifiers: ['Increment', 'Nullable', 'Default'],
@ -293,4 +296,4 @@ PostgresClient.schemaGrammar = _.extend({}, base.schemaGrammar, PostgresClient.g
}
}
});
}, SchemaGrammar, PostgresClient.grammar);

View File

@ -5,6 +5,9 @@ var util = require('util');
var base = require('./base');
var sqlite3 = require('sqlite3');
var Grammar = require('./base/grammar').Grammar;
var SchemaGrammar = require('./base/schemagrammar').SchemaGrammar;
// Constructor for the Sqlite3Client
var Sqlite3Client = module.exports = function(name, options) {
base.setup.call(this, Sqlite3Client, name, options);
@ -102,7 +105,7 @@ _.extend(Sqlite3Client.prototype, base.protoProps, {
});
// Extends the standard sql grammar.
Sqlite3Client.grammar = {
Sqlite3Client.grammar = _.defaults({
// The keyword identifier wrapper format.
wrapValue: function(value) {
@ -166,10 +169,11 @@ Sqlite3Client.grammar = {
sql.push('delete from ' + table);
return sql;
}
};
}, Grammar);
// Grammar for the schema builder.
Sqlite3Client.schemaGrammar = _.extend({}, base.schemaGrammar, Sqlite3Client.grammar, {
Sqlite3Client.schemaGrammar = _.defaults({
// The possible column modifiers.
modifiers: ['Nullable', 'Default', 'Increment'],
@ -319,4 +323,4 @@ Sqlite3Client.schemaGrammar = _.extend({}, base.schemaGrammar, Sqlite3Client.gra
return ' primary key autoincrement';
}
}
});
}, SchemaGrammar, Sqlite3Client.grammar);

936
knex.js
View File

@ -11,8 +11,10 @@
define(function(require, exports, module) {
// Required dependencies.
var _ = require('underscore');
var when = require('when');
var _ = require('underscore');
var when = require('when');
var Common = require('./lib/common').Common;
var Helpers = require('./lib/helpers').Helpers;
// `Knex` is the root namespace and a chainable function: `Knex('tableName')`
var Knex = function(table) {
@ -25,406 +27,6 @@ define(function(require, exports, module) {
// Keep in sync with package.json
Knex.VERSION = '0.2.6';
// 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. Any unhandled errors will be thrown
// after the last block.
exec: function(callback) {
this._promise || (this._promise = this.runQuery());
return this._promise.then(function(resp) {
if (callback) callback(null, resp);
}, function(err) {
if (callback) callback(err, null);
}).then(null, function(err) {
setTimeout(function() { throw err; }, 0);
});
},
// 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]);
} else {
push.apply(cleaned, bindings[i].bindings);
}
}
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 having.bool + ' ' + this.wrap(having.column) + ' ' + having.operator + ' ' + this.parameter(having['value']);
}, this).replace(/and |or /, '');
},
// Compiles the `order by` statements.
compileOrders: function(qb, orders) {
if (orders.length > 0) {
return 'order by ' + orders.map(function(order) {
return '' + this.wrap(order.column) + ' ' + order.direction;
}, this).join(', ');
}
},
// Compiles the `limit` statements.
compileLimit: function(qb, limit) {
return 'limit ' + limit;
},
// Compiles an `offset` statement on the query.
compileOffset: function(qb, offset) {
return 'offset ' + offset;
},
// Compiles an `insert` query, allowing for multiple
// inserts using a single query statement.
compileInsert: function(qb) {
var values = qb.values;
var table = this.wrapTable(qb.table);
var columns = _.pluck(values[0], 0);
var paramBlocks = [];
// If there are any "where" clauses, we need to omit
// any bindings that may have been associated with them.
if (qb.wheres.length > 0) this._clearWhereBindings(qb);
for (var i = 0, l = values.length; i < l; ++i) {
paramBlocks.push("(" + this.parameterize(_.pluck(values[i], 1)) + ")");
}
return "insert into " + table + " (" + this.columnize(columns) + ") values " + paramBlocks.join(', ');
},
// Depending on the type of `where` clause, this will appropriately
// remove any binding caused by "where" constraints, allowing the same
// query to be used for `insert` and `update` without issue.
_clearWhereBindings: function(qb) {
var wheres = qb.wheres;
var bindingCount = 0;
for (var i = 0, l = wheres.length; i<l; i++) {
var where = wheres[i];
if (_.isArray(where.value)) {
bindingCount += where.value.length;
} else if (where.query) {
bindingCount += where.query.bindings.length;
} else {
bindingCount += 1;
}
}
qb.bindings = qb.bindings.slice(bindingCount);
},
// Compiles an `update` query.
compileUpdate: function(qb) {
var values = qb.values;
var table = this.wrapTable(qb.table), columns = [];
for (var i=0, l = values.length; i < l; i++) {
var value = values[i];
columns.push(this.wrap(value[0]) + ' = ' + this.parameter(value[1]));
}
return 'update ' + table + ' set ' + columns.join(', ') + ' ' + this.compileWheres(qb);
},
// Compiles a `delete` query.
compileDelete: function(qb) {
var table = this.wrapTable(qb.table);
var where = !_.isEmpty(qb.wheres) ? this.compileWheres(qb) : '';
return 'delete from ' + table + ' ' + where;
},
// Compiles a `truncate` query.
compileTruncate: function(qb) {
return 'truncate ' + this.wrapTable(qb.table);
},
wrap: function(value) {
var segments;
if (value instanceof Raw) return value.sql;
if (_.isNumber(value)) return value;
if (value.toLowerCase().indexOf(' as ') !== -1) {
segments = value.split(' ');
return this.wrap(segments[0]) + ' as ' + this.wrap(segments[2]);
}
var wrapped = [];
segments = value.split('.');
for (var i = 0, l = segments.length; i < l; i = ++i) {
value = segments[i];
if (i === 0 && segments.length > 1) {
wrapped.push(this.wrapTable(value));
} else {
wrapped.push(this.wrapValue(value));
}
}
return wrapped.join('.');
},
wrapArray: function(values) {
return _.map(values, this.wrap, this);
},
wrapTable: function(table) {
if (table instanceof Raw) return table.sql;
return this.wrap(table);
},
columnize: function(columns) {
if (!_.isArray(columns)) columns = [columns];
return _.map(columns, this.wrap, this).join(', ');
},
parameterize: function(values) {
if (!_.isArray(values)) values = [values];
return _.map(values, this.parameter, this).join(', ');
},
parameter: function(value) {
return (value instanceof Raw ? value.sql : '?');
}
};
// Knex.Builder
// -------
var Builder = Knex.Builder = function(table) {
@ -474,7 +76,7 @@ define(function(require, exports, module) {
// Compiles the current query builder.
toSql: function() {
this.type || (this.type = 'select');
return this.grammar['compile' + capitalize(this.type)](this);
return this.grammar['compile' + Helpers.capitalize(this.type)](this);
},
// Clones the current query builder, including any
@ -797,7 +399,7 @@ define(function(require, exports, module) {
// Sets the values for an `update` query.
update: function(values) {
var obj = sortObject(values);
var obj = Helpers.sortObject(values);
var bindings = [];
for (var i = 0, l = obj.length; i < l; i++) {
bindings[i] = obj[i][1];
@ -841,7 +443,7 @@ define(function(require, exports, module) {
_prepValues: function(values) {
if (!_.isArray(values)) values = values ? [values] : [];
for (var i = 0, l = values.length; i<l; i++) {
var obj = values[i] = sortObject(values[i]);
var obj = values[i] = Helpers.sortObject(values[i]);
for (var i2 = 0, l2 = obj.length; i2 < l2; i2++) {
this.bindings.push(obj[i2][1]);
}
@ -1045,487 +647,7 @@ define(function(require, exports, module) {
// Knex.SchemaBuilder
// --------
var SchemaBuilder = Knex.SchemaBuilder = function(table) {
this.table = table;
this.columns = [];
this.commands = [];
this.bindings = [];
};
_.extend(SchemaBuilder.prototype, Common, {
_source: 'SchemaBuilder',
// A callback from the table building `Knex.schemaBuilder` calls.
callback: function(callback) {
if (callback) callback.call(this, this);
return this;
},
// Get the raw sql statements for the blueprint.
toSql: function() {
// Add the commands that are implied by the blueprint.
if (this.columns.length > 0 && !this.creating()) {
this.commands.unshift({name: 'add'});
}
// Add an "additional" command, for any extra dialect-specific logic.
this.commands.push({name: 'additional'});
// Add indicies
for (var i = 0, l = this.columns.length; i < l; i++) {
var column = this.columns[i];
var indices = ['primary', 'unique', 'index', 'foreign'];
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, null);
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], column);
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);
if (sql) 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;
},
// Adds a comment to the current table being created.
comment: function(comment) {
return this._addCommand('comment', {comment: comment});
},
// 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, also getting any
// relevant info from the chain during column.
foreign: function(column, name) {
var chained, chainable = this._indexCommand('foreign', column, name);
if (_.isObject(column)) {
chained = _.pick(column, 'foreignColumn', 'foreignTable', 'commandOnDelete', 'commandOnUpdate');
}
return _.extend(chainable, ForeignChainable, chained);
},
// 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 dateTime's to the table.
timestamps: function() {
this.dateTime('created_at');
this.dateTime('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) {
if (!_.isArray(allowed)) allowed = [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 json column on the table.
json: function(column) {
return this._addColumn('json', column);
},
// Create a new uuid column on the table.
uuid: function(column) {
return this._addColumn('uuid', 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 || col; }).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 column = _.extend({type: type, name: name}, ChainableColumn, parameters);
this.columns.push(column);
return column;
},
// Add a new command to the blueprint.
_addCommand: function(name, parameters) {
var command = _.extend({name: name}, parameters);
this.commands.push(command);
return command;
}
});
var ForeignChainable = {
// Sets the "column" that the current column references
// as the a foreign key
references: function(column) {
this.isForeign = true;
this.foreignColumn = column || null;
return this;
},
// Sets the "table" where the foreign key column is located.
inTable: function(table) {
this.foreignTable = table || null;
return this;
},
// SQL command to run "onDelete"
onDelete: function(command) {
this.commandOnDelete = command || null;
return this;
},
// SQL command to run "onUpdate"
onUpdate: function(command) {
this.commandOnUpdate = command || null;
return this;
}
};
var ChainableColumn = _.extend({
// 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;
},
// Adds a comment to this column.
comment: function(comment) {
this.isCommented = comment || null;
return this;
}
}, ForeignChainable);
Knex.SchemaGrammar = {
// Compile a foreign key command.
compileForeign: function(blueprint, command) {
var sql;
if (command.foreignTable && command.foreignColumn) {
var table = this.wrapTable(blueprint);
var column = this.columnize(command.columns);
var foreignTable = this.wrapTable(command.foreignTable);
var foreignColumn = this.columnize(command.foreignColumn);
sql = "alter table " + table + " add constraint " + command.index + " ";
sql += "foreign key (" + column + ") references " + foreignTable + " (" + foreignColumn + ")";
// 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.commandOnDelete) sql += " on delete " + command.commandOnDelete;
if (command.commandOnUpdate) sql += " on update " + command.commandOnUpdate;
}
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, blueprint);
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, blueprint) {
return this['type' + capitalize(column.type)](column, blueprint);
},
// 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 && value.name) 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.sql;
if (value === true || value === false) {
return parseInt(value, 10);
}
return '' + value;
}
};
Knex.SchemaBuilder = require('./lib/schemabuilder').SchemaBuilder;
// Knex.Raw
// -------
@ -1540,43 +662,9 @@ define(function(require, exports, module) {
return Knex.Instances['main'].Raw(sql, bindings);
};
var Raw = function(sql, bindings) {
this.bindings = (!_.isArray(bindings) ? (bindings ? [bindings] : []) : bindings);
this.sql = sql;
};
var Raw = require('./lib/raw').Raw;
_.extend(Raw.prototype, Common, {
_source: 'Raw',
// Returns the raw sql for the query.
toSql: function() {
return this.sql;
}
});
// 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]}));
};
_.extend(Raw.prototype, Common)
// Knex.Initialize
// -------
@ -1622,10 +710,6 @@ define(function(require, exports, module) {
// 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);

115
lib/common.js Normal file
View File

@ -0,0 +1,115 @@
var _ = require('underscore');
var when = require('when');
var Raw = require('./raw').Raw;
var Helpers = require('./helpers').Helpers;
// Methods common to both the `Grammar` and `SchemaGrammar` interfaces,
// used to generate the sql in one form or another.
exports.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. Any unhandled errors will be thrown
// after the last block.
exec: function(callback) {
this._promise || (this._promise = this.runQuery());
return this._promise.then(function(resp) {
if (callback) callback(null, resp);
}, function(err) {
if (callback) callback(err, null);
}).then(null, function(err) {
setTimeout(function() { throw err; }, 0);
});
},
// 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]);
} else {
push.apply(cleaned, bindings[i].bindings);
}
}
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(Helpers.multiQuery(this, i, chain));
} else {
chain = Helpers.multiQuery(this, i);
}
}
return chain;
}
};

26
lib/helpers.js Normal file
View File

@ -0,0 +1,26 @@
var _ = require('underscore');
var Helpers = exports.Helpers = {
capitalize: function(word) {
return word.charAt(0).toUpperCase() + word.slice(1);
},
// Sorts an object based on the names.
sortObject: function(obj) {
return _.sortBy(_.pairs(obj), function(a) {
return a[0];
});
},
// Sets up a multi-query to be executed with serial promises.
multiQuery: function(builder, i, chain) {
if (chain) {
return function() {
return Helpers.multiQuery(builder, i);
};
}
return builder.client.query(_.extend({}, builder, {sql: builder.sql[i]}));
}
};

21
lib/raw.js Normal file
View File

@ -0,0 +1,21 @@
var _ = require('underscore');
var Common = require('./common').Common;
var Raw = function(sql, bindings) {
this.bindings = (!_.isArray(bindings) ? (bindings ? [bindings] : []) : bindings);
this.sql = sql;
};
_.extend(Raw.prototype, {
_source: 'Raw',
// Returns the raw sql for the query.
toSql: function() {
return this.sql;
}
});
exports.Raw = Raw;

407
lib/schemabuilder.js Normal file
View File

@ -0,0 +1,407 @@
var _ = require('underscore');
var Raw = require('./raw').Raw;
var Common = require('./common').Common;
var Helpers = require('./helpers').Helpers;
var SchemaBuilder = function(table) {
this.table = table;
this.columns = [];
this.commands = [];
this.bindings = [];
};
_.extend(SchemaBuilder.prototype, Common, {
_source: 'SchemaBuilder',
// A callback from the table building `Knex.schemaBuilder` calls.
callback: function(callback) {
if (callback) callback.call(this, this);
return this;
},
// Get the raw sql statements for the blueprint.
toSql: function() {
// Add the commands that are implied by the blueprint.
if (this.columns.length > 0 && !this.creating()) {
this.commands.unshift({name: 'add'});
}
// Add an "additional" command, for any extra dialect-specific logic.
this.commands.push({name: 'additional'});
// Add indicies
for (var i = 0, l = this.columns.length; i < l; i++) {
var column = this.columns[i];
var indices = ['primary', 'unique', 'index', 'foreign'];
continueIndex:
for (var i2 = 0, l2 = indices.length; i2 < l2; i2++) {
var index = indices[i2];
var indexVar = 'is' + Helpers.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, null);
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], column);
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' + Helpers.capitalize(command.name);
if (_.has(this.grammar, method)) {
var sql = this.grammar[method](this, command);
if (sql) 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;
},
// Adds a comment to the current table being created.
comment: function(comment) {
return this._addCommand('comment', {comment: comment});
},
// 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, also getting any
// relevant info from the chain during column.
foreign: function(column, name) {
var chained, chainable = this._indexCommand('foreign', column, name);
if (_.isObject(column)) {
chained = _.pick(column, 'foreignColumn', 'foreignTable', 'commandOnDelete', 'commandOnUpdate');
}
return _.extend(chainable, ForeignChainable, chained);
},
// 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 dateTime's to the table.
timestamps: function() {
this.dateTime('created_at');
this.dateTime('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) {
if (!_.isArray(allowed)) allowed = [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 json column on the table.
json: function(column) {
return this._addColumn('json', column);
},
// Create a new uuid column on the table.
uuid: function(column) {
return this._addColumn('uuid', 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 || col; }).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 column = _.extend({type: type, name: name}, ChainableColumn, parameters);
this.columns.push(column);
return column;
},
// Add a new command to the blueprint.
_addCommand: function(name, parameters) {
var command = _.extend({name: name}, parameters);
this.commands.push(command);
return command;
}
});
var ForeignChainable = {
// Sets the "column" that the current column references
// as the a foreign key
references: function(column) {
this.isForeign = true;
this.foreignColumn = column || null;
return this;
},
// Sets the "table" where the foreign key column is located.
inTable: function(table) {
this.foreignTable = table || null;
return this;
},
// SQL command to run "onDelete"
onDelete: function(command) {
this.commandOnDelete = command || null;
return this;
},
// SQL command to run "onUpdate"
onUpdate: function(command) {
this.commandOnUpdate = command || null;
return this;
}
};
var ChainableColumn = _.extend({
// 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;
},
// Adds a comment to this column.
comment: function(comment) {
this.isCommented = comment || null;
return this;
}
}, ForeignChainable);
exports.SchemaBuilder = SchemaBuilder;