UNPKG

mysql-libmysqlclient-transaction

Version:

nodejs module node-mysql-libmysqlclient transaction

169 lines (168 loc) 6.78 kB
/* Wraps db.query and exposes function db.createQueue() */ module.exports = function(db, debug) { if(debug !== true) debug = false; if(debug) console.log("mysql-queues: debug mode enabled."); var options = { debug: debug, currentlyExecutingQueue: null, mainQueue: [] }; var dbQuery = db.query; //The old db.query function //Wrap db.query db.query = function(sql, cb) { //Run query if no Queue is running; otherwise, queue it in mainQueue if(options.currentlyExecutingQueue == null) return dbQuery.apply(db, arguments); else options.mainQueue.push(arguments); }; //Create a new executable query Queue db.createQueue = function() { return new Queue( function() { return dbQuery.apply(db, arguments); }, function () { //If the current Queue is a transaction that has not yet been committed, commit it var ceq = options.currentlyExecutingQueue; if(ceq != null && ceq.commit != null) { //Also, warn the user that relying on this behavior is a bad idea if(ceq._autoCommit !== true) console.warn("WARNING: mysql-queues: Database transaction was " + "implicitly committed.\nIt is HIGHLY recommended that you " + "explicitly commit all transactions.\n" + "The last query to run was:", ceq.lastExecuted.sql); ceq.commit(ceq._autoCommitCB); return; } options.currentlyExecutingQueue = null; //Called when a Queue has completed its processing and main queue should be executed while(options.mainQueue.length > 0) { var item = options.mainQueue.shift(); //Unsure of shift's performance if(item instanceof Queue) { item.execute(); break; //After the Queue has been executed, the main queue will be resumed } else dbQuery.apply(db, item); } }, options); }; db.startTransaction = function() { return Queue.isNowTransaction( this.createQueue(), function() { return dbQuery.apply(db, arguments); } ); }; } function Queue(dbQuery, resumeMainQueue, options) { this.queue = []; this.paused = false; /* Add a query to the Queue */ this.query = function(sql, cb) { this.queue.push({ 'sql': sql, 'cb': cb }); return this; //Chaining :) }; /* Execute all queries on the Queue in order and prevent other queries from executing until all queries have been completed. */ this.execute = function() { if(this.paused === true || this.executing) return; var that = this; //If another Queue is currently running, we put this on the mainQueue if(options.currentlyExecutingQueue != null && options.currentlyExecutingQueue != this) options.mainQueue.push(this); else if(that.queue.length > 0) { options.currentlyExecutingQueue = this; //console.log("Executing queue:", options.currentlyExecutingQueue); //Run everything in the queue that.executing = true; var done = 0, total = that.queue.length; for(var i = 0; i < total; i ++) { (function(item) { //Execute the query try { if(item.sql == "COMMIT") delete that.rollback; //Keep 'em honest that.lastExecuted = item; //For debugging and convenience dbQuery(item.sql, function() { if(options.debug && arguments[0] != null) console.error("mysql-queues: An error occurred while executing the following " + "query:\n\t", item.sql); //Execute the original callback first (which may add more queries to this Queue) if(item.cb != null) item.cb.apply(this, arguments); //When the entire queue has completed... if(++done == total) { that.executing = false; if(that.paused === true) return; /* The query's callback may have queued more queries on this Queue. If so, execute this Queue again; otherwise, resumeMainQueue() */ if(that.queue.length == 0) resumeMainQueue(); else that.execute(); } }); } catch(e) { if(options.debug) console.log("mysql-queues: An exception occurred for this query:\n\t", item.sql); throw e; } })(that.queue[i]); } that.queue = []; //All queued queries are running, but we don't resume the main queue just yet //console.log("Queue Complete:", options.currentlyExecutingQueue); } else if(options.currentlyExecutingQueue == this) resumeMainQueue(); return this; //Chaining :) }; this.pause = function(maxWaitTime) { this.paused = true; if(maxWaitTime > 0) { var that = this; that.pauseTimer = setTimeout(function() { that.resume(); }, maxWaitTime); } return this; //Chaining } this.resume = function() { if(this.pauseTimer) clearTimeout(this.pauseTimer); this.paused = false; this.execute(); return this; //Chaining } } Queue.isNowTransaction = function(q, dbQuery) { q.query("START TRANSACTION"); q.commit = function(cb) { if(this.queue.length > 0) { this._autoCommit = true; this._autoCommitCB = cb; this.resume(); } else { delete this.commit; delete this._autoCommit; this.query("COMMIT", cb).resume(); } } q.rollback = function(cb) { this.queue = []; delete this.commit; delete this.rollback; dbQuery("ROLLBACK", cb); this.resume(); } return q; }