2014-09-01 17:18:45 +02:00
|
|
|
'use strict';
|
|
|
|
|
2014-05-08 18:31:25 -04:00
|
|
|
var _ = require('lodash');
|
|
|
|
var Promise = require('./promise');
|
2014-04-08 16:25:57 -04:00
|
|
|
|
|
|
|
// The "Runner" constructor takes a "builder" (query, schema, or raw)
|
|
|
|
// and runs through each of the query statements, calling any additional
|
|
|
|
// "output" method provided alongside the query and bindings.
|
|
|
|
function Runner(builder) {
|
2014-04-09 10:11:41 -04:00
|
|
|
this.builder = builder;
|
|
|
|
this.queries = [];
|
2014-04-08 16:25:57 -04:00
|
|
|
|
|
|
|
// The "connection" object is set on the runner when
|
|
|
|
// "run" is called.
|
|
|
|
this.connection = void 0;
|
|
|
|
}
|
|
|
|
|
2014-04-16 01:23:50 -04:00
|
|
|
Runner.prototype._beginTransaction = 'begin;';
|
|
|
|
Runner.prototype._commitTransaction = 'commit;';
|
|
|
|
Runner.prototype._rollbackTransaction = 'rollback;';
|
2014-04-08 16:25:57 -04:00
|
|
|
|
2014-04-09 10:11:41 -04:00
|
|
|
// "Run" the target, calling "toSQL" on the builder, returning
|
2014-04-08 16:25:57 -04:00
|
|
|
// an object or array of queries to run, each of which are run on
|
|
|
|
// a single connection.
|
|
|
|
Runner.prototype.run = Promise.method(function() {
|
|
|
|
if (this.builder._transacting) {
|
|
|
|
return this.transactionQuery();
|
|
|
|
}
|
|
|
|
return Promise.bind(this)
|
|
|
|
.then(this.ensureConnection)
|
2014-04-16 01:23:50 -04:00
|
|
|
.then(function(connection) {
|
|
|
|
this.connection = connection;
|
2014-06-22 15:11:01 -04:00
|
|
|
|
|
|
|
// Emit a "start" event on both the builder and the client,
|
|
|
|
// allowing us to listen in on any events. We fire on the "client"
|
|
|
|
// before building the SQL, and on the builder after building the SQL
|
|
|
|
// in case we want to determine at how long it actually
|
|
|
|
// took to build the query.
|
2014-06-20 18:00:38 -07:00
|
|
|
this.client.emit('start', this.builder);
|
2014-04-09 10:11:41 -04:00
|
|
|
var sql = this.builder.toSQL();
|
2014-06-22 15:11:01 -04:00
|
|
|
this.builder.emit('start', this.builder);
|
|
|
|
|
2014-04-08 16:25:57 -04:00
|
|
|
if (_.isArray(sql)) {
|
|
|
|
return this.queryArray(sql);
|
|
|
|
}
|
|
|
|
return this.query(sql);
|
|
|
|
})
|
2014-06-22 15:11:01 -04:00
|
|
|
|
|
|
|
// If there are any "error" listeners, we fire an error event
|
|
|
|
// and then re-throw the error to be eventually handled by
|
|
|
|
// the promise chain. Useful if you're wrapping in a custom `Promise`.
|
|
|
|
.catch(function(err) {
|
2014-06-23 19:00:21 -04:00
|
|
|
if (this.builder._events && this.builder._events.error) {
|
2014-06-22 15:11:01 -04:00
|
|
|
this.builder.emit('error', err);
|
|
|
|
}
|
|
|
|
throw err;
|
|
|
|
})
|
|
|
|
|
|
|
|
// Fire a single "end" event on the builder when
|
|
|
|
// all queries have successfully completed.
|
2014-06-20 18:00:38 -07:00
|
|
|
.tap(function() {
|
|
|
|
this.builder.emit('end');
|
|
|
|
})
|
2014-04-09 10:11:41 -04:00
|
|
|
.finally(this.cleanupConnection);
|
2014-04-08 16:25:57 -04:00
|
|
|
});
|
|
|
|
|
2014-04-09 10:11:41 -04:00
|
|
|
// Stream the result set, by passing through to the dialect's streaming
|
|
|
|
// capabilities. If the options are
|
|
|
|
var PassThrough;
|
2014-06-25 02:42:22 -04:00
|
|
|
Runner.prototype.stream = function(options, handler) {
|
2014-04-09 10:11:41 -04:00
|
|
|
// If we specify stream(handler).then(...
|
|
|
|
if (arguments.length === 1) {
|
|
|
|
if (_.isFunction(options)) {
|
|
|
|
handler = options;
|
|
|
|
options = {};
|
|
|
|
}
|
|
|
|
}
|
2014-05-06 16:18:39 -04:00
|
|
|
|
2014-06-25 02:42:22 -04:00
|
|
|
// Determines whether we emit an error or throw here.
|
|
|
|
var hasHandler = _.isFunction(handler);
|
|
|
|
|
2014-05-06 16:18:39 -04:00
|
|
|
// Lazy-load the "PassThrough" dependency.
|
|
|
|
PassThrough = PassThrough || require('readable-stream').PassThrough;
|
|
|
|
var stream = new PassThrough({objectMode: true});
|
|
|
|
var promise = Promise.bind(this)
|
2014-05-05 19:48:12 -04:00
|
|
|
.then(this.ensureConnection)
|
|
|
|
.then(function(connection) {
|
|
|
|
this.connection = connection;
|
|
|
|
var sql = this.builder.toSQL();
|
|
|
|
var err = new Error('The stream may only be used with a single query statement.');
|
|
|
|
if (_.isArray(sql)) {
|
2014-06-25 02:42:22 -04:00
|
|
|
if (hasHandler) throw err;
|
2014-05-05 22:00:29 -04:00
|
|
|
stream.emit('error', err);
|
2014-05-05 19:48:12 -04:00
|
|
|
}
|
|
|
|
return sql;
|
|
|
|
}).then(function(sql) {
|
2014-05-06 16:18:39 -04:00
|
|
|
return this._stream(sql, stream, options);
|
2014-05-05 19:48:12 -04:00
|
|
|
}).finally(this.cleanupConnection);
|
2014-05-06 16:18:39 -04:00
|
|
|
|
|
|
|
// If a function is passed to handle the stream, send the stream
|
|
|
|
// there and return the promise, otherwise just return the stream
|
|
|
|
// and the promise will take care of itsself.
|
2014-06-25 02:42:22 -04:00
|
|
|
if (hasHandler) {
|
2014-05-06 16:18:39 -04:00
|
|
|
handler(stream);
|
|
|
|
return promise;
|
|
|
|
}
|
|
|
|
return stream;
|
2014-06-25 02:42:22 -04:00
|
|
|
};
|
2014-04-09 10:11:41 -04:00
|
|
|
|
2014-05-06 16:18:39 -04:00
|
|
|
// Allow you to pipe the stream to a writable stream.
|
|
|
|
Runner.prototype.pipe = function(writable) {
|
|
|
|
return this.stream().pipe(writable);
|
|
|
|
};
|
|
|
|
|
2014-04-15 11:43:47 -04:00
|
|
|
// "Runs" a query, returning a promise. All queries specified by the builder are guaranteed
|
|
|
|
// to run in sequence, and on the same connection, especially helpful when schema building
|
|
|
|
// and dealing with foreign key constraints, etc.
|
|
|
|
Runner.prototype.query = Promise.method(function(obj) {
|
2014-09-01 23:22:07 +02:00
|
|
|
if (!this.connection) {
|
|
|
|
throw new Error('There is an error with the database connection. Please check your config.');
|
|
|
|
}
|
2014-05-08 18:31:25 -04:00
|
|
|
obj.__cid = this.connection.__cid;
|
2014-04-27 19:35:36 -04:00
|
|
|
this.builder.emit('query', obj);
|
2014-06-20 07:27:31 -04:00
|
|
|
this.client.emit('query', obj);
|
2014-04-16 01:23:50 -04:00
|
|
|
return this._query(obj).bind(this).then(this.processResponse);
|
2014-04-15 11:43:47 -04:00
|
|
|
});
|
|
|
|
|
|
|
|
// In the case of the "schema builder" we call `queryArray`, which runs each
|
|
|
|
// of the queries in sequence.
|
|
|
|
Runner.prototype.queryArray = Promise.method(function(queries) {
|
2014-04-16 01:23:50 -04:00
|
|
|
return queries.length === 1 ? this.query(queries[0]) : Promise.bind(this)
|
2014-04-15 11:43:47 -04:00
|
|
|
.thenReturn(queries)
|
|
|
|
.reduce(function(memo, query) {
|
|
|
|
return this.query(query).then(function(resp) {
|
|
|
|
memo.push(resp);
|
|
|
|
return memo;
|
|
|
|
});
|
|
|
|
}, []);
|
|
|
|
});
|
|
|
|
|
2014-04-08 16:25:57 -04:00
|
|
|
// Check whether there's a transaction flag, and that it has a connection.
|
|
|
|
Runner.prototype.ensureConnection = Promise.method(function() {
|
|
|
|
if (this.builder._connection) {
|
|
|
|
return this.builder._connection;
|
|
|
|
}
|
|
|
|
return this.client.acquireConnection();
|
|
|
|
});
|
|
|
|
|
2014-04-09 10:11:41 -04:00
|
|
|
// "Debug" the query being run.
|
2014-04-08 16:25:57 -04:00
|
|
|
Runner.prototype.debug = function(obj) {
|
2014-04-09 10:11:41 -04:00
|
|
|
console.dir(_.extend({__cid: this.connection.__cid}, obj));
|
2014-04-08 16:25:57 -04:00
|
|
|
};
|
|
|
|
|
2014-04-09 10:11:41 -04:00
|
|
|
// Check whether we're "debugging", based on either calling `debug` on the query.
|
2014-04-08 16:25:57 -04:00
|
|
|
Runner.prototype.isDebugging = function() {
|
|
|
|
return (this.client.isDebugging === true || this.builder._debug === true);
|
|
|
|
};
|
|
|
|
|
2014-04-09 10:11:41 -04:00
|
|
|
// Transaction Methods:
|
2014-04-15 11:43:47 -04:00
|
|
|
// -------
|
2014-04-08 16:25:57 -04:00
|
|
|
|
|
|
|
// Run the transaction on the correct "runner" instance.
|
|
|
|
Runner.prototype.transactionQuery = Promise.method(function() {
|
|
|
|
var runner = this.builder._transacting._runner;
|
|
|
|
if (!(runner instanceof Runner)) {
|
|
|
|
throw new Error('Invalid transaction object provided.');
|
|
|
|
}
|
2014-04-16 01:23:50 -04:00
|
|
|
var sql = this.builder.toSQL();
|
2014-04-08 16:25:57 -04:00
|
|
|
if (_.isArray(sql)) {
|
|
|
|
return runner.queryArray(sql);
|
|
|
|
}
|
|
|
|
return runner.query(sql);
|
|
|
|
});
|
|
|
|
|
|
|
|
// Begins a transaction statement on the instance,
|
2014-09-03 16:19:52 -04:00
|
|
|
// resolving with the current runner.
|
2014-05-08 18:31:25 -04:00
|
|
|
Runner.prototype.startTransaction = Promise.method(function() {
|
2014-04-08 16:25:57 -04:00
|
|
|
return Promise.bind(this)
|
|
|
|
.then(this.ensureConnection)
|
|
|
|
.then(function(connection) {
|
|
|
|
this.connection = connection;
|
|
|
|
this.transaction = true;
|
2014-09-03 16:19:52 -04:00
|
|
|
return this.beginTransaction();
|
2014-04-08 16:25:57 -04:00
|
|
|
}).thenReturn(this);
|
2014-05-08 18:31:25 -04:00
|
|
|
});
|
2014-04-08 16:25:57 -04:00
|
|
|
|
|
|
|
// Finishes the transaction statement and handles disposing of the connection,
|
|
|
|
// resolving / rejecting the transaction's promise, and ensuring the transaction object's
|
|
|
|
// `_runner` property is `null`'ed out so it cannot continue to be used.
|
|
|
|
Runner.prototype.finishTransaction = Promise.method(function(action, containerObject, msg) {
|
2014-05-08 17:58:07 -04:00
|
|
|
var query, dfd = containerObject.__dfd__;
|
2014-04-08 16:25:57 -04:00
|
|
|
|
|
|
|
// Run the query to commit / rollback the transaction.
|
|
|
|
switch (action) {
|
|
|
|
case 0:
|
2014-04-16 01:23:50 -04:00
|
|
|
query = this.commitTransaction();
|
2014-04-08 16:25:57 -04:00
|
|
|
break;
|
|
|
|
case 1:
|
2014-04-16 01:23:50 -04:00
|
|
|
query = this.rollbackTransaction();
|
2014-04-08 16:25:57 -04:00
|
|
|
break;
|
|
|
|
}
|
|
|
|
|
2014-04-09 10:11:41 -04:00
|
|
|
return query.then(function(resp) {
|
2014-06-06 17:27:59 -04:00
|
|
|
msg = (msg === void 0) ? resp : msg;
|
2014-04-08 16:25:57 -04:00
|
|
|
switch (action) {
|
|
|
|
case 0:
|
2014-06-06 17:27:59 -04:00
|
|
|
dfd.fulfill(msg);
|
2014-04-08 16:25:57 -04:00
|
|
|
break;
|
|
|
|
case 1:
|
2014-06-06 17:27:59 -04:00
|
|
|
dfd.reject(msg);
|
2014-04-08 16:25:57 -04:00
|
|
|
break;
|
|
|
|
}
|
2014-04-09 10:11:41 -04:00
|
|
|
|
|
|
|
// If there was a problem committing the transaction,
|
|
|
|
// reject the transaction block (to reject the entire transaction block),
|
|
|
|
// then re-throw the error for any promises chained off the commit.
|
|
|
|
}).catch(function(e) {
|
|
|
|
dfd.reject(e);
|
|
|
|
throw e;
|
|
|
|
}).bind(this).finally(function() {
|
2014-04-08 16:25:57 -04:00
|
|
|
|
|
|
|
// Kill the "_runner" object on the containerObject,
|
|
|
|
// so it's not possible to continue using the transaction object.
|
|
|
|
containerObject._runner = void 0;
|
|
|
|
|
2014-04-09 10:11:41 -04:00
|
|
|
return this.cleanupConnection();
|
2014-04-08 16:25:57 -04:00
|
|
|
});
|
|
|
|
});
|
|
|
|
|
2014-09-03 16:19:52 -04:00
|
|
|
Runner.prototype.beginTransaction = function() {
|
|
|
|
return this._beginTransaction && this.query({sql: this._beginTransaction});
|
|
|
|
};
|
2014-04-16 01:23:50 -04:00
|
|
|
Runner.prototype.commitTransaction = function() {
|
2014-08-14 15:32:26 -04:00
|
|
|
return this._commitTransaction && this.query({sql: this._commitTransaction});
|
2014-04-16 01:23:50 -04:00
|
|
|
};
|
|
|
|
Runner.prototype.rollbackTransaction = function() {
|
2014-08-14 15:32:26 -04:00
|
|
|
return this._rollbackTransaction && this.query({sql: this._rollbackTransaction});
|
2014-04-16 01:23:50 -04:00
|
|
|
};
|
|
|
|
|
2014-04-09 10:11:41 -04:00
|
|
|
// Cleanup the connection as necessary, if the `_connection` was
|
|
|
|
// explicitly set on the query we don't need to do anything here,
|
|
|
|
// otherwise we
|
|
|
|
Runner.prototype.cleanupConnection = Promise.method(function() {
|
2014-08-15 15:40:32 -07:00
|
|
|
if (!this.builder._connection && typeof this.connection !== "undefined") {
|
2014-04-09 10:11:41 -04:00
|
|
|
return this.client.releaseConnection(this.connection);
|
|
|
|
}
|
|
|
|
});
|
|
|
|
|
2014-09-01 23:22:07 +02:00
|
|
|
module.exports = Runner;
|