diff --git a/clients/base/client.js b/clients/base/client.js deleted file mode 100644 index e95aabde8..000000000 --- a/clients/base/client.js +++ /dev/null @@ -1,20 +0,0 @@ -(function(define) { - -"use strict"; - -define(function(require, exports) { - - var Helpers = require('../../lib/helpers').Helpers; - - var Client = function(name, options) {}; - - Client.prototype = {}; - Client.extend = Helpers.extend; - - exports.Client = Client; - -}); - -})( - typeof define === 'function' && define.amd ? define : function (factory) { factory(require, exports); } -); \ No newline at end of file diff --git a/clients/base/sqlite3.js b/clients/base/sqlite3.js index a3846da68..f23ec3f44 100644 --- a/clients/base/sqlite3.js +++ b/clients/base/sqlite3.js @@ -10,12 +10,10 @@ define(function(require, exports) { var Grammar = require('./grammar').Grammar; var SchemaGrammar = require('./schemagrammar').SchemaGrammar; - var Client = require('./client').Client; - var Helpers = require('../../lib/helpers').Helpers; + var ClientBase = require('../base').ClientBase; + var Helpers = require('../../lib/helpers').Helpers; - var Sqlite3 = Client.extend({ - - }); + var Sqlite3 = ClientBase.extend({}); // Extends the standard sql grammar. Sqlite3.grammar = _.defaults({ diff --git a/clients/server/base.js b/clients/server/base.js index 957fe73cf..a38231e8e 100644 --- a/clients/server/base.js +++ b/clients/server/base.js @@ -91,8 +91,9 @@ exports.protoProps = { }); }, - finishTransaction: function(type, trans, dfd, msg) { + finishTransaction: function(type, transaction, msg) { var ctx = this; + var dfd = transaction.dfd; nodefn.call(trans.connection.query.bind(trans.connection), type + ';', []).then(function(resp) { if (type === 'commit') dfd.resolve(msg || resp); if (type === 'rollback') dfd.reject(msg || resp); diff --git a/clients/server/mysql.js b/clients/server/mysql.js index 1473f1043..19016d9c7 100644 --- a/clients/server/mysql.js +++ b/clients/server/mysql.js @@ -21,8 +21,8 @@ _.extend(MysqlClient.prototype, base.protoProps, { // acquire a connection, and then dispose of it when we're done. query: function(builder) { var emptyConnection = !builder._connection; - var debug = this.debug || builder._debug; - var instance = this; + var debug = this.debug || builder._debug; + var client = this; return when((builder._connection || this.getConnection())) .tap(this.checkSchema(builder)) .then(function(conn) { @@ -64,7 +64,7 @@ _.extend(MysqlClient.prototype, base.protoProps, { // Empty the connection after we run the query, unless one was specifically // set (in the case of transactions, etc). return dfd.promise.ensure(function() { - if (emptyConnection) instance.pool.release(conn); + if (emptyConnection) client.pool.release(conn); }); }).otherwise(function(err) { throw new Error(err.toString() + ': ' + builder.sql); diff --git a/knex.js b/knex.js index 05e5606e9..381961356 100644 --- a/knex.js +++ b/knex.js @@ -16,9 +16,11 @@ define(function(require, exports, module) { var Raw = require('./lib/raw').Raw; var Transaction = require('./lib/transaction').Transaction; var Builder = require('./lib/builder').Builder; - var Interface = require('./lib/builder/interface').Interface; - var Schema = require('./lib/schema').Schema; - var ClientBase = require('./clients/clientbase').ClientBase; + + // var Interface = require('./lib/builder/interface').Interface; + var Schema = require('./lib/schemabuilder').SchemaBuilder; + var SchemaInterface = require('./lib/schemainterface').SchemaInterface; + var ClientBase = require('./clients/base').ClientBase; // The `Knex` module, taking either a fully initialized // database client, or a configuration to initialize one. This is something @@ -61,12 +63,12 @@ define(function(require, exports, module) { // `Knex.select('*').from('tableName').then(...` // `Knex.insert(values).into('tableName').then(...` // `Knex.update(values).then(...` - _.each(Interface, function(val, key) { - instance[key] = function() { - var builder = new Builder(instance); - return builder[key].apply(builder, arguments); - }; - }); + // _.each(Interface, function(val, key) { + // instance[key] = function() { + // var builder = new Builder(instance); + // return builder[key].apply(builder, arguments); + // }; + // }); // Attach each of the `Schema` "interface" methods directly onto to `Knex.Schema` namespace: // `Knex.Schema.table('tableName', function() {...` @@ -76,22 +78,21 @@ define(function(require, exports, module) { instance.schema[key] = function() { var schemaBuilder = new SchemaBuilder(instance); schemaBuilder.table = _.first(arguments); - return schemaBuilder[key].apply(schemaBuilder, _.rest(arguments)); + return SchemaInterface[key].apply(schemaBuilder, _.rest(arguments)); }; }); // Attach each of the `Migrate` "interface" methods directly on to - _.each(MigrateInterface, function(val, key) { - instance.migrate[key] = function() { - var migrateBuilder = new MigrateBuilder(instance); - return MigrateBuilder[key].apply(migrateBuilder, arguments); - }; - }); + // _.each(MigrateInterface, function(val, key) { + // instance.migrate[key] = function() { + // var migrateBuilder = new MigrateBuilder(instance); + // return MigrateBuilder[key].apply(migrateBuilder, arguments); + // }; + // }); // Method to run a new `Raw` query on the current client. - instance.raw = function() { - var raw = new Raw(instance); - return raw.query.apply(raw, arguments); + instance.raw = function(sql, bindings) { + return new Raw(instance).query(sql, bindings); }; // Keep a reference to the current client. @@ -102,8 +103,7 @@ define(function(require, exports, module) { // Runs a new transaction, taking a container and instance.transaction = function(container) { - var transaction = new Transaction(instance); - return transaction.run(container); + return new Transaction(instance).run(container); }; // Return the new Knex instance. @@ -125,6 +125,10 @@ define(function(require, exports, module) { // finally, export the `Knex` object for node and the browser. module.exports = Knex; + Knex.initialize = function(config) { + return Knex(config); + }; + }); })( diff --git a/lib/builder.js b/lib/builder.js index 7848c8420..b0777f60d 100644 --- a/lib/builder.js +++ b/lib/builder.js @@ -11,19 +11,15 @@ define(function(require, exports) { var Common = require('./common').Common; var Raw = require('./raw').Raw; var Helpers = require('./helpers').Helpers; - var JoinClause = require('./joinclause').JoinClause; + var JoinClause = require('./builder/joinclause').JoinClause; var array = []; var push = array.push; - var Builder = function(client) { - if (client instanceof Builder) { - this.client = client.client; - this.grammar = this.client.grammar; - } else { - this.client = client; - this.grammar = client.grammar; - } + var Builder = function(instance) { + this.knex = instance; + this.client = instance.client; + this.grammar = instance.grammar; this.reset(); }; @@ -64,7 +60,7 @@ define(function(require, exports) { // Clones the current query builder, including any // pieces that have been set thus far. clone: function() { - var item = new Builder(this); + var item = new Builder(this.knex); item.table = this.table; var items = [ 'isDistinct', 'joins', 'wheres', 'orders', @@ -143,8 +139,8 @@ define(function(require, exports) { }, // Alias to `where`, for internal builder consistency. - andWhere: function() { - return this.where.apply(this, arguments); + andWhere: function(column, operator, value) { + return this.where(column, operator, value, 'and'); }, // Adds an `or where` clause to the query. @@ -167,7 +163,7 @@ define(function(require, exports) { // Adds a `where exists` clause to the query. whereExists: function(callback, bool, type) { - var query = new Builder(this); + var query = new Builder(this.knex); callback.call(query, query); this.wheres.push({ type: (type || 'Exists'), @@ -385,7 +381,7 @@ define(function(require, exports) { for (var i = 0, l = obj.length; i < l; i++) { bindings[i] = obj[i][1]; } - this.bindings = bindings.concat(this.bindings || []); + this.bindings = bindings.concat(this.bindings); this.values = obj; return this._setType('update'); }, @@ -425,7 +421,7 @@ define(function(require, exports) { // Helper for compiling any advanced `where in` queries. _whereInSub: function(column, callback, bool, condition) { condition += 'Sub'; - var query = new Builder(this); + var query = new Builder(this.knex); callback.call(query, query); this.wheres.push({type: condition, column: column, query: query, bool: bool}); push.apply(this.bindings, query.bindings); @@ -434,8 +430,7 @@ define(function(require, exports) { // Helper for compiling any advanced `where` queries. _whereNested: function(callback, bool) { - var query = new Builder(this); - query.table = this.table; + var query = new Builder(this.knex); callback.call(query, query); this.wheres.push({type: 'Nested', query: query, bool: bool}); push.apply(this.bindings, query.bindings); @@ -444,7 +439,7 @@ define(function(require, exports) { // Helper for compiling any of the `where` advanced queries. _whereSub: function(column, operator, callback, bool) { - var query = new Builder(this); + var query = new Builder(this.knex); callback.call(query, query); this.wheres.push({ type: 'Sub', @@ -466,14 +461,12 @@ define(function(require, exports) { // Helper for the incrementing/decrementing queries. _counter: function(column, amount, symbol) { - var sql = {}; - sql[column] = new Raw('' + this.grammar.wrap(column) + ' ' + (symbol || '+') + ' ' + amount); - return this.update(sql); + return this.update({column: this.knex.raw('' + this.grammar.wrap(column) + ' ' + (symbol || '+') + ' ' + amount)}); }, // Helper for compiling any `union` queries. _union: function(callback, bool) { - var query = new Builder(this); + var query = new Builder(this.knex); callback.call(query, query); this.unions.push({query: query, all: bool}); push.apply(this.bindings, query.bindings); diff --git a/lib/joinclause.js b/lib/builder/joinclause.js similarity index 93% rename from lib/joinclause.js rename to lib/builder/joinclause.js index 0fcda6440..11b440916 100644 --- a/lib/joinclause.js +++ b/lib/builder/joinclause.js @@ -8,8 +8,8 @@ define(function(require, exports) { var JoinClause = function(type, table) { this.clauses = []; - this.type = type; - this.table = table; + this.type = type; + this.table = table; }; JoinClause.prototype = { diff --git a/lib/chainable/column.js b/lib/chainable/column.js new file mode 100644 index 000000000..e69de29bb diff --git a/lib/chainable/foreign.js b/lib/chainable/foreign.js new file mode 100644 index 000000000..e69de29bb diff --git a/lib/common.js b/lib/common.js index 2f23db3e8..7783b5dbe 100644 --- a/lib/common.js +++ b/lib/common.js @@ -16,12 +16,10 @@ define(function(require, exports) { // used to generate the sql in one form or another. exports.Common = { - _debug: false, - - _promise: null, + isDebugging: false, debug: function() { - this._debug = true; + this.isDebugging = true; return this; }, @@ -30,22 +28,28 @@ define(function(require, exports) { // if called more than once. Any unhandled errors will be thrown // after the last block. exec: function(callback) { - this._promise || (this._promise = this.runQuery()); + this._promise || (this._promise = this.client.query(this)); return this._promise.then(function(resp) { if (callback) callback(null, resp); }, function(err) { if (callback) callback(err, null); - }).then(null, function(err) { + }).otherwise(function(err) { setTimeout(function() { throw err; }, 0); }); }, // The promise interface for the query builder. then: function(onFulfilled, onRejected) { - this._promise || (this._promise = this.runQuery()); + this._promise || (this._promise = this.client.query(this)); return this._promise.then(onFulfilled, onRejected); }, + // Passthrough to the convenient `tap` mechanism of when.js + tap: function(handler) { + this._promise = this._promise || this.client.query(this); + return this._promise.tap(handler); + }, + // Returns an array of query strings filled out with the // correct values based on bindings, etc. Useful for debugging. toString: function() { @@ -63,13 +67,13 @@ define(function(require, exports) { // Explicitly sets the connection. connection: function(connection) { - this._connection = connection; + this.usingConnection = connection; return this; }, // The connection the current query is being run on, optionally // specified by the `connection` method. - _connection: false, + usingConnection: false, // Sets the "type" of the current query, so we can potentially place // `select`, `update`, `del`, etc. anywhere in the query statement @@ -97,29 +101,33 @@ define(function(require, exports) { }, // Runs the query on the current builder instance and returns a promise. - runQuery: function() { - if (this.transaction) { - if (!this.transaction.connection) { - throw new Error('The transaction has already completed.'); - } - this._connection = this.transaction.connection; - } + // runQuery: function() { + // this.client.query(this); - // Prep the SQL associated with the this. - this.sql = this.toSql(); - this.bindings = this._cleanBindings(); - if (!_.isArray(this.sql)) this.sql = [this.sql]; + // if (this.transaction) { + // if (!this.transaction.connection) { + // throw new Error('The transaction has already completed.'); + // } + // this.usingConnection = this.transaction.connection; + // } - 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; - }, + // // 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; + // }, // Sets the current Builder connection to that of the // the currently running transaction diff --git a/lib/helpers.js b/lib/helpers.js index 0f96629cc..8abce87c2 100644 --- a/lib/helpers.js +++ b/lib/helpers.js @@ -21,16 +21,6 @@ define(function(require, exports) { }); }, - // 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({currentIndex: i}, builder, {sql: builder.sql[i]})); - }, - // The standard Backbone.js `extend` method. extend: function(protoProps, staticProps) { var parent = this; diff --git a/lib/migrate.js b/lib/migrate.js index 91c41ae76..2ad6a8a7e 100644 --- a/lib/migrate.js +++ b/lib/migrate.js @@ -11,8 +11,8 @@ var sequence = require('when/sequence'); // The new migration we're performing. // Takes a `config` object, which has the name // of the current migration (`main` if not otherwise specified) -var Migrate = function(Instance) { - this.Knex = Instance; +var Migrate = function(instance) { + this.knex = instance; _.bindAll(this, 'currentVersion', 'createMigrationTable', '_migrationData'); }; diff --git a/lib/raw.js b/lib/raw.js index 64cf72e22..aee8cbb00 100644 --- a/lib/raw.js +++ b/lib/raw.js @@ -7,15 +7,23 @@ define(function(require, exports) { var _ = require('underscore'); var Common = require('./common').Common; - var Raw = function(sql, bindings) { - this.bindings = (!_.isArray(bindings) ? (bindings ? [bindings] : []) : bindings); - this.sql = sql; + var Raw = function(instance) { + this.knex = instance; }; - _.extend(Raw.prototype, { + _.extend(Raw.prototype, Common, { _source: 'Raw', + // Set the sql and the bindings associated with the query, returning + // the current raw object. + query: function(sql, bindings) { + this.bindings = _.isArray(bindings) ? bindings : + bindings ? [bindings] : []; + this.sql = sql; + return this; + }, + // Returns the raw sql for the query. toSql: function() { return this.sql; @@ -28,5 +36,5 @@ define(function(require, exports) { }); })( - typeof define === 'function' && define.amd ? define : function (factory) { factory(require, exports, module); } + typeof define === 'function' && define.amd ? define : function (factory) { factory(require, exports); } ); \ No newline at end of file diff --git a/lib/schemabuilder.js b/lib/schemabuilder.js index 3e579f440..547a2f9e7 100644 --- a/lib/schemabuilder.js +++ b/lib/schemabuilder.js @@ -6,12 +6,13 @@ define(function(require, exports) { 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; + var SchemaBuilder = function(instance) { + this.knex = instance; + this.client = instance.client; + this.grammar = instance.schemaGrammar; this.columns = []; this.commands = []; this.bindings = []; @@ -22,9 +23,8 @@ define(function(require, exports) { _source: 'SchemaBuilder', instance: function() { - var builder = new SchemaBuilder(this.table); - builder.client = this.client; - builder.grammar = this.grammar; + var builder = new SchemaBuilder(this.knex); + builder.table = this.table; return builder; }, diff --git a/lib/transaction.js b/lib/transaction.js index be6f3948b..0b3374863 100644 --- a/lib/transaction.js +++ b/lib/transaction.js @@ -8,27 +8,35 @@ define(function(require, exports) { var _ = require('underscore'); var Transaction = function(instance) { - this.knex = instance; + this.client = instance.client; }; - Transaction.prototype.run = function(container) { + Transaction.prototype = { - var client = this.knex.client; + // Passed a `container` function, this method runs the current + // transaction, returning a promise. + run: function(container) { + return this.client.startTransaction() + .then(this.getContainerObject) + .then(this.initiateDeferred(container)); + }, - return client.startTransaction().then(function(connection) { + getContainerObject: function(connection) { - // Initiate a deferred object, so we know when the - // transaction completes or fails, we know what to do. - var dfd = when.defer(); + // The client we need to call `finishTransaction` on. + var client = this.client; // The object passed around inside the transaction container. var containerObj = { - commit: function(val) { - client.finishTransaction('commit', this, dfd, val); + + commit: function(message) { + client.finishTransaction('commit', this, message); }, - rollback: function(err) { - client.finishTransaction('rollback', this, dfd, err); + + rollback: function(error) { + client.finishTransaction('rollback', this, error); }, + // "rollback to"? connection: connection }; @@ -36,12 +44,27 @@ define(function(require, exports) { // Ensure the transacting object methods are bound with the correct context. _.bindAll(containerObj, 'commit', 'rollback'); - // Call the container with the transaction - // commit & rollback objects. - container(containerObj); + }, + + initiateDeferred: function(container) { + + return function(containerObj) { + + // Initiate a deferred object, so we know when the + // transaction completes or fails, we know what to do. + var dfd = containerObj.dfd = when.defer(); + + // Call the container with the transaction + // commit & rollback objects. + container(containerObj); + + // Return the promise for the entire transaction. + return dfd.promise; + + }; + + } - return dfd.promise; - }); }; exports.Transaction = Transaction; diff --git a/package.json b/package.json index cb4991c38..4ae656299 100644 --- a/package.json +++ b/package.json @@ -7,14 +7,19 @@ "test": "test" }, "devDependencies": { - "mocha": "~1.12.0", "objectdump": "~0.3.0", "mysql": "~2.0.0-alpha7", "pg": "~2.3.1", "sqlite3": "~2.1.7", "node-uuid": "~1.4.0", "grunt": "~0.4.1", - "grunt-release": "~0.5.1" + "grunt-release": "~0.5.1", + "sinon": "~1.7.3", + "chai": "~1.7.2", + "mocha-as-promised": "~1.4.0", + "sinon-chai": "~2.4.0", + "mocha": "~1.12.1", + "chai-as-promised": "~3.3.1" }, "dependencies": { "when": "~2.3.0", diff --git a/test/index.js b/test/index.js index c97e2115b..3c192c986 100644 --- a/test/index.js +++ b/test/index.js @@ -1,71 +1,17 @@ -var equal = require('assert').equal; -var When = require('when'); -var _ = require('underscore'); -var Knex = require('../knex'); -var conn = require(process.env.KNEX_TEST || './shared/config'); +require("mocha-as-promised")(); -// The output goes here. -exports.output = {}; +global.sinon = require("sinon"); -var pool = { - afterCreate: function(conn, done) { - equal(_.has(conn, '__cid'), true); - done(); - }, - beforeDestroy: function(conn, done) { - equal(_.has(conn, '__cid'), true); - done(); - } -}; +var chai = global.chai = require("chai"); +chai.use(require("chai-as-promised")); +chai.use(require("sinon-chai")); +chai.should(); -Knex.Initialize({ - client: 'mysql', - connection: conn.mysql, - pool: _.extend({}, pool, { - afterCreate: function(conn, done) { - conn.query("SET sql_mode='TRADITIONAL';", [], function(err) { - done(err); - }); - } - }) -}); -var Sqlite3 = Knex.Initialize('sqlite3', { - client: 'sqlite3', - connection: conn.sqlite3, - pool: pool -}); -var Postgres = Knex.Initialize('postgres', { - client: 'postgres', - connection: conn.postgres, - pool: pool -}); +global._ = require('underscore'); +global.when = require('when'); +global.expect = chai.expect; +global.AssertionError = chai.AssertionError; +global.Assertion = chai.Assertion; +global.assert = chai.assert; -var regularThen = Knex.Builder.prototype.then; -var then = function(success, error) { - var ctx = this; - var bindings = ctx._cleanBindings(); - var chain = regularThen.call(this, function(resp) { - return { - object: resp, - string: { - sql: ctx.sql, - bindings: bindings - } - }; - }); - return chain.then(success, error); -}; - -describe('Knex', function() { - - _.each(['Builder', 'SchemaBuilder', 'Raw'], function(item) { - Knex[item].prototype.then = then; - Postgres[item].prototype.then = then; - Sqlite3[item].prototype.then = then; - }); - - require('./regular')(Knex, 'mysql'); - require('./regular')(Postgres, 'postgres'); - require('./regular')(Sqlite3, 'sqlite3'); - -}); \ No newline at end of file +require('./unit/builder')(); \ No newline at end of file diff --git a/test/integration/index-old.js b/test/integration/index-old.js new file mode 100644 index 000000000..e0928e6ca --- /dev/null +++ b/test/integration/index-old.js @@ -0,0 +1,64 @@ +var equal = require('assert').equal; +var When = require('when'); +var _ = require('underscore'); +var Knex = require('../knex'); +var conn = require(process.env.KNEX_TEST || './shared/config'); + +// The output goes here. +exports.output = {}; + +var pool = { + afterCreate: function(conn, done) { + equal(_.has(conn, '__cid'), true); + done(); + }, + beforeDestroy: function(conn, done) { + equal(_.has(conn, '__cid'), true); + done(); + } +}; + +var MySQL = Knex.initialize({ + client: 'mysql', + connection: conn.mysql, + pool: _.extend({}, pool, { + afterCreate: function(conn, done) { + conn.query("SET sql_mode='TRADITIONAL';", [], function(err) { + done(err); + }); + } + }) +}); +var SQLite3 = Knex.initialize('sqlite3', { + client: 'sqlite3', + connection: conn.sqlite3, + pool: pool +}); +var PostgreSQL = Knex.initialize('postgres', { + client: 'postgres', + connection: conn.postgres, + pool: pool +}); + +var regularThen = MySQL.builder.prototype.then; +var then = function(success, error) { + var ctx = this; + var bindings = ctx._cleanBindings(); + var chain = regularThen.call(this, function(resp) { + return { + object: resp, + string: { + sql: ctx.sql, + bindings: bindings + } + }; + }); + return chain.then(success, error); +}; + +describe('Knex', function() { + require('./regular')(MySQL, 'mysql'); + require('./regular')(PostgreSQL, 'postgres'); + require('./regular')(SQLite3, 'sqlite3'); +}); + diff --git a/test/lib/additional.js b/test/integration/lib/additional.js similarity index 100% rename from test/lib/additional.js rename to test/integration/lib/additional.js diff --git a/test/lib/aggregate.js b/test/integration/lib/aggregate.js similarity index 100% rename from test/lib/aggregate.js rename to test/integration/lib/aggregate.js diff --git a/test/lib/deletes.js b/test/integration/lib/deletes.js similarity index 100% rename from test/lib/deletes.js rename to test/integration/lib/deletes.js diff --git a/test/lib/inserts.js b/test/integration/lib/inserts.js similarity index 100% rename from test/lib/inserts.js rename to test/integration/lib/inserts.js diff --git a/test/lib/joins.js b/test/integration/lib/joins.js similarity index 100% rename from test/lib/joins.js rename to test/integration/lib/joins.js diff --git a/test/lib/schema-tests.js b/test/integration/lib/schema-tests.js similarity index 100% rename from test/lib/schema-tests.js rename to test/integration/lib/schema-tests.js diff --git a/test/lib/schema.js b/test/integration/lib/schema.js similarity index 100% rename from test/lib/schema.js rename to test/integration/lib/schema.js diff --git a/test/lib/selects.js b/test/integration/lib/selects.js similarity index 100% rename from test/lib/selects.js rename to test/integration/lib/selects.js diff --git a/test/lib/transaction.js b/test/integration/lib/transaction.js similarity index 100% rename from test/lib/transaction.js rename to test/integration/lib/transaction.js diff --git a/test/lib/unions.js b/test/integration/lib/unions.js similarity index 100% rename from test/lib/unions.js rename to test/integration/lib/unions.js diff --git a/test/lib/updates.js b/test/integration/lib/updates.js similarity index 100% rename from test/lib/updates.js rename to test/integration/lib/updates.js diff --git a/test/regular.js b/test/integration/regular.js similarity index 100% rename from test/regular.js rename to test/integration/regular.js diff --git a/test/shared/config.js b/test/integration/shared/config.js similarity index 100% rename from test/shared/config.js rename to test/integration/shared/config.js diff --git a/test/shared/output.js b/test/integration/shared/output.js similarity index 100% rename from test/shared/output.js rename to test/integration/shared/output.js diff --git a/test/unit/builder.js b/test/unit/builder.js index 1e64f1206..92fd45220 100644 --- a/test/unit/builder.js +++ b/test/unit/builder.js @@ -1,12 +1,269 @@ -module.exports = function(client) { +module.exports = function() { - var Builder = require('../../lib/builder'); + var when = require('when'); + var Builder = require('../../lib/builder').Builder; + var Common = require('../../lib/common').Common; + var Raw = require('../../lib/raw').Raw; describe('Builder', function () { - it('should be a constructor, accepting the current Knex client'); + var builder; + beforeEach(function() { + builder = new Builder({ + query: function(obj) { + return when.resolve(obj); + }, + grammar: require('../../clients/server/mysql').grammar + }); + }); + describe('constructor', function () { + it('accepts the current Knex instance, attaching the client, and grammar', function() { + var grammar = {some: 'grammar'}; + var client = {some: 'db'}; + var knex = {client: client, grammar: grammar}; + var reset = sinon.stub(Builder.prototype, 'reset'); + var builder = new Builder(knex); + + expect(builder.knex).to.eql(knex); + expect(builder.client).to.eql(client); + expect(builder.grammar).to.eql(grammar); + reset.should.have.been.calledOnce; + reset.restore(); + }); + + it('has the Common methods mixed-in to the Builder.prototype', function() { + _.each(Common, function(val, key) { + expect(Builder.prototype[key]).to.equal(val); + }); + }); + + }); + + describe('from', function() { + + it('sets the tableName', function() { + expect(builder.table).not.to.exist; + var result = builder.from('myTable'); + expect(builder.table).to.equal('myTable'); + expect(result).to.equal(builder); + }); + + it('returns the tableName when no arguments are passed', function() { + expect(builder.table).not.to.exist; + var result = builder.from('myTable'); + expect(builder.from()).to.equal('myTable'); + }); + + }); + + describe('column', function() { + + it('adds a value to the columns array', function() { + expect(builder.columns).to.have.length(0); + builder.column('myColumn'); + expect(builder.columns).to.have.length(1); + }); + + }); + + describe('distinct', function() { + + it('sets the isDistinct flag to true', function() { + builder.distinct('distinctCol'); + expect(builder.columns[0]).to.equal('distinctCol'); + }); + + it('adds the column to the columns array', function() { + builder.distinct('distinctCol'); + expect(builder.isDistinct).to.be.true; + }); + + }); + + describe('toSql', function() { + + it('sets the type to "select" if not otherwise set', function() { + expect(builder.type).not.to.exist; + builder.toSql(); + expect(builder.type).to.equal('select'); + }); + + it('compiles based on the type, passing the current context', function(done) { + var compileSelect = builder.grammar.compileSelect; + builder.grammar.compileSelect = function(ctx) { + expect(ctx).to.eql(builder); + builder.grammar.compileSelect = compileSelect; + done(); + }; + builder.toSql(); + }); + + }); + + describe('clone', function() { + + }); + + describe('reset', function() { + + }); + + describe('join', function() { + + var JoinClause = require('../../lib/builder/joinclause').JoinClause; + + it('accepts the joining table, the first column, operator, second column, and (optional) type', function() { + expect(builder.joins).to.have.length(0); + builder.from('users'); + builder.join('accounts', 'users.id', '=', 'accounts.id'); + expect(builder.joins).to.have.length(1); + expect(builder.joins[0]).to.be.an.instanceOf(JoinClause); + expect(builder.toString()).to.equal("select * from `users` inner join `accounts` on `users`.`id` = `accounts`.`id`"); + }); + + it('accepts a different join type as the fifth parameter', function() { + builder.from('users'); + builder.join('accounts', 'users.id', '=', 'accounts.id', 'left outer'); + expect(builder.toString()).to.equal("select * from `users` left outer join `accounts` on `users`.`id` = `accounts`.`id`"); + }); + + it('may take a function as the second argument, for a grouped join', function() { + + builder.join('accounts', function(join) { + expect(builder.joins).to.have.length(0); + expect(this).to.be.an.instanceOf(JoinClause); + expect(join).to.be.an.instanceOf(JoinClause); + }); + + }); + + }); + + describe('where', function() { + + describe('where', function() { + + it('should allow a function as the first argument, for a grouped where clause', function() { + builder.where('id', '=', 1); + expect(builder.wheres).to.have.length(1); + expect(builder.bindings).to.have.length(1); + var subWhere = function(qb) { + expect(this).to.equal(qb); + expect(this).to.be.an.instanceOf(Builder); + this.where({id: 3}).orWhere('id', 4); + }; + builder.where(subWhere); + expect(builder.bindings).to.have.length(3); + expect(builder.wheres).to.have.length(2); + expect(builder.from('test').toString()).to.equal("select * from `test` where `id` = 1 and (`id` = 3 or `id` = 4)"); + }); + + it('should allow a raw instance as the first argument, which will add a whereRaw clause', function() { + builder.where(new Raw({}).query('id > ?', 2)); + expect(builder.wheres).to.have.length(1); + expect(builder.bindings).to.have.length(1); + expect(builder.bindings[0]).to.equal(2); + }); + + it('should allow an object as the first argument, which will assume to be a k/v pair of where "="', function() { + builder.where({id: 2, name: 'Test'}); + expect(builder.wheres).to.have.length(2); + expect(builder.bindings).to.have.length(2); + }); + + it('should assume that if the second argument is not an operator, it should be an "="', function() { + builder.where('id', 2); + expect(builder.wheres).to.have.length(1); + expect(builder.bindings[0]).to.equal(2); + }); + + it('should accept a function as the "value", for a sub select', function() { + builder.where('id', '=', function(qb) { + expect(this).to.equal(qb); + expect(this).to.be.an.instanceOf(Builder); + this.select('account_id').from('names').where('names.id', '>', 1).orWhere(function() { + this.where('names.first_name', 'like', 'Tim%').andWhere('names.id', '>', 10); + }); + }); + expect(builder.bindings).to.have.length(3); + expect(builder.toString()).to.equal("select * where `id` = (select `account_id` from `names` where `names`.`id` > 1 or (`names`.`first_name` like Tim% and `names`.`id` > 10))"); + }); + + var arg1 = {}, arg2 = {}, arg3 = {}, arg4 = {}; + + it('is called by andWhere, passing the first three arguments and an "and"', function() { + sinon.mock(builder).expects('where').withExactArgs(arg1, arg2, arg3, 'and'); + builder.andWhere(arg1, arg2, arg3, 'error'); + }); + + it('is called by orWhere, passing the first three arguments and an "or"', function() { + sinon.mock(builder).expects('where').withExactArgs(arg1, arg2, arg3, 'or'); + builder.orWhere(arg1, arg2, arg3, 'error'); + }); + + }); + + describe('whereRaw', function() { + + it('is called by orWhereRaw, passing the sql, bindings, and "or"', function() { + + }); + + }); + + describe('whereExists', function() { + + }); + + }); + + describe('groupBy', function() { + + }); + + describe('orderBy', function() { + + }); + + describe('union', function() { + + }); + + describe('having', function() { + + }); + + describe('offset / limit', function() { + + }); + + describe('aggregate', function() { + + }); + + describe('select', function() { + + it('should throw if a table has not been specified when compiling'); + + }); + + describe('insert', function() { + + }); + + describe('update', function() { + + }); + + describe('delete', function() { + + }); + + describe('truncate', function() { + + }); }); diff --git a/test/unit/common.js b/test/unit/common.js new file mode 100644 index 000000000..b168f70dd --- /dev/null +++ b/test/unit/common.js @@ -0,0 +1,66 @@ +module.exports = function(client) { + + var Common = require('../../lib/common').Common; + + var CommonStub = function(knex) { + this.knex = knex; + this.client = client; + this.grammar = client.grammar; + }; + CommonStub.prototype = Common; + + describe('Common', function () { + + var common; + + beforeEach(function() { + common = new CommonStub(); + }); + + describe('debug', function () { + + it('should set the flag for the isDebugging property of the object'); + + }); + + describe('exec', function() { + + it('should accept a callback, which provides an err / success'); + + it('should rethrow any error in the handler'); + + }); + + describe('then', function() { + + it('should accept two functions, which are passed along to the promise handler'); + + }); + + describe('tap', function () { + + it('is essentially a passthrough to the `tap` method of when.js'); + + }); + + describe('toString', function() { + + it('turns the current query into a string value, based on the current client'); + + }); + + describe('connection', function() { + + it('should set the usingConnection property of the current object'); + + }); + + describe('transacting', function () { + + it('should set the transaction object'); + + }); + + }); + +}; \ No newline at end of file diff --git a/test/unit/knex.js b/test/unit/knex.js new file mode 100644 index 000000000..e69de29bb diff --git a/test/unit/raw.js b/test/unit/raw.js new file mode 100644 index 000000000..7c0edabc0 --- /dev/null +++ b/test/unit/raw.js @@ -0,0 +1,15 @@ +module.exports = function(client) { + + var Raw = require('../../lib/raw').Raw; + + describe('Raw', function () { + + it('has the Common methods mixed-in to the Builder.prototype', function() { + _.each(Common, function(val, key) { + expect(Builder.prototype[key]).to.equal(val); + }); + }); + + }); + +}; \ No newline at end of file diff --git a/test/unit/sqlite3.js b/test/unit/sqlite3.js deleted file mode 100644 index c9dd53d7d..000000000 --- a/test/unit/sqlite3.js +++ /dev/null @@ -1,5 +0,0 @@ -var Sqlite3 = require('../../clients/base/sqlite3').Sqlite3; - -describe('Sqlite3', function () { - -}); \ No newline at end of file diff --git a/test/unit/transaction.js b/test/unit/transaction.js new file mode 100644 index 000000000..45122c97b --- /dev/null +++ b/test/unit/transaction.js @@ -0,0 +1,23 @@ +module.exports = function(client) { + + var Transaction = require('../../lib/transaction').Transaction; + + describe('Transaction', function () { + + describe('Constructor', function() { + + it('saves the current client off the knex instance', function() { + + var knex = {client: {name: 'mysql'}}; + + var transaction = new Transaction(knex); + + assert(transaction.client).should.eql(knex.client); + + }); + + }); + + }); + +}; \ No newline at end of file