├── tests ├── test.sh ├── fake-test.out ├── multi-trans-test.js ├── fake-test.js └── trans-test.js ├── package.json ├── license.txt ├── index.js └── README.md /tests/test.sh: -------------------------------------------------------------------------------- 1 | node fake-test.js | diff - fake-test.out 2 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "author": "Blake Miner (http://www.blakeminer.com)", 3 | "name": "mysql-queues", 4 | "description": "Wraps 'mysql' to provide mulitple query queues, allowing support for multiple statements and transactions.", 5 | "version": "1.0.0", 6 | "homepage": "https://github.com/bminer/node-mysql-queues", 7 | "repository": { 8 | "type": "git", 9 | "url": "git://github.com/bminer/node-mysql-queues.git" 10 | }, 11 | "contributors": [ 12 | "Tom Atkinson ", 13 | "Kris Reeves " 14 | ], 15 | "main": "index.js", 16 | "engines": { 17 | "node": ">=0.4.0" 18 | }, 19 | "optionalDependencies": { 20 | "mysql": ">=0.9.5" 21 | }, 22 | "bugs": { 23 | "url": "https://github.com/bminer/node-mysql-queues/issues" 24 | }, 25 | "devDependencies": {}, 26 | "keywords": [ 27 | "mysql", 28 | "transaction", 29 | "multiple statements", 30 | "queue", 31 | "query", 32 | "database" 33 | ] 34 | } 35 | -------------------------------------------------------------------------------- /license.txt: -------------------------------------------------------------------------------- 1 | Copyright (c) 2011-2012 Blake Miner (http://www.blakeminer.com) 2 | 3 | Permission is hereby granted, free of charge, to any person obtaining a copy 4 | of this software and associated documentation files (the "Software"), to deal 5 | in the Software without restriction, including without limitation the rights 6 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 7 | copies of the Software, and to permit persons to whom the Software is 8 | furnished to do so, subject to the following conditions: 9 | 10 | The above copyright notice and this permission notice shall be included in 11 | all copies or substantial portions of the Software. 12 | 13 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 14 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 15 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 16 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 17 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 18 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 19 | THE SOFTWARE. 20 | -------------------------------------------------------------------------------- /tests/fake-test.out: -------------------------------------------------------------------------------- 1 | Executing 1 2 | Executing 2 3 | Executing 3A 4 | Executing 3B 5 | Executing Q1 1 6 | Executing Q1 2 7 | Executing Q1 3 8 | -------------1 CB 9 | -------------2 CB 10 | -------------3A CB 11 | -------------3B CB 12 | -------------Q1 1 CB 13 | -------------Q1 2 CB 14 | -------------Q1 3 CB 15 | Executing Q1 2cb 1 16 | Executing Q1 2cb 2 17 | -------------Q1 2cb 1 CB 18 | -------------Q1 2cb 2 CB 19 | Executing Q1 2cb 2cb 20 | -------------Q1 2cb 2cb CB 21 | Executing 4 22 | Executing Q2 1 23 | Executing Q2 2 24 | Executing Q2 3 25 | -------------4 CB 26 | -------------Q2 1 CB 27 | -------------Q2 2 CB 28 | -------------Q2 3 CB 29 | Executing Q2 2cb 1 30 | Executing Q2 2cb 2 31 | -------------Q2 2cb 1 CB 32 | -------------Q2 2cb 2 CB 33 | Executing Q2 2cb 2cb 34 | -------------Q2 2cb 2cb CB 35 | Executing 5 36 | Executing 2cb 1 37 | Executing 2cb 2 38 | Executing 6 39 | Executing 7 40 | -------------5 CB 41 | -------------2cb 1 CB 42 | -------------2cb 2 CB 43 | Executing 2cb 2cb 44 | -------------6 CB 45 | Executing 8 46 | Executing 9 47 | -------------7 CB 48 | -------------2cb 2cb CB 49 | -------------8 CB 50 | -------------9 CB 51 | Executing 10 52 | Executing 11 53 | -------------10 CB 54 | -------------11 CB 55 | Executing 12 56 | -------------12 CB 57 | -------------------------------------------------------------------------------- /tests/multi-trans-test.js: -------------------------------------------------------------------------------- 1 | var queue = require('../index'); 2 | 3 | //Create a fake MySQL client 4 | var db = { 5 | 'query': function(sql, input, cb) { 6 | if(typeof input == "function") 7 | { 8 | cb = input; 9 | input = undefined; 10 | } 11 | console.log("Executing " + sql); 12 | //Simulate a database delay of 1 second 13 | setTimeout(function() { 14 | console.log("-------------" + sql + " CB"); 15 | if(cb != null) cb(); 16 | }, 1000); 17 | } 18 | }; 19 | 20 | queue(db, true); 21 | 22 | var trans = db.startTransaction(); 23 | trans.query("QUERY 1", function(err, info) { 24 | if(err) return trans.rollback(); 25 | trans.commit(function() {console.log("COMMIT A");}); 26 | 27 | console.log("TRANSACTION 1 WAS COMMITTED"); 28 | var trans2 = db.startTransaction(); 29 | trans2.query("QUERY 2", function(err, info) { 30 | console.log("QUERY 2 CALLBACK JUST GOT CALLED"); 31 | if(err) return trans2.rollback(); 32 | trans2.commit(function() {console.log("COMMIT B");}); 33 | 34 | var trans3 = db.startTransaction(); 35 | trans3.query("QUERY 3", function(err, info) { 36 | console.log("QUERY 3 CALLBACK JUST GOT CALLED"); 37 | if(err) return trans2.rollback(); 38 | trans3.commit(function() {console.log("COMMIT C");}); 39 | }).execute(); 40 | }).execute(); 41 | }).execute(); 42 | -------------------------------------------------------------------------------- /tests/fake-test.js: -------------------------------------------------------------------------------- 1 | var queue = require('../index'); 2 | 3 | //Create a fake MySQL client 4 | var db = { 5 | 'query': function(sql, input, cb) { 6 | if(typeof input == "function") 7 | { 8 | cb = input; 9 | input = undefined; 10 | } 11 | console.log("Executing " + sql); 12 | //Simulate a database delay of 1 second 13 | setTimeout(function() { 14 | console.log("-------------" + sql + " CB"); 15 | if(cb != null) cb(); 16 | }, 1000); 17 | } 18 | }; 19 | 20 | queue(db); //Enable queuing 21 | 22 | db.query("1"); 23 | db.query("2", function() { 24 | db.query("2cb 1"); 25 | db.query("2cb 2", function() { 26 | db.query("2cb 2cb"); 27 | }); 28 | }); 29 | db.query("3A"); 30 | 31 | var q1 = db.createQueue(); 32 | q1.query("Q1 1"); 33 | q1.query("Q1 2", function() { 34 | db.query("6", function() { 35 | q1.query("9", function() { 36 | q1.query("11"); 37 | }); 38 | db.query("8"); 39 | q1.execute(); 40 | q1.query("10"); 41 | db.query("12"); 42 | }); 43 | q1.query("Q1 2cb 1"); 44 | q1.query("Q1 2cb 2", function() { 45 | q1.query("Q1 2cb 2cb"); 46 | }); 47 | }); 48 | q1.query("Q1 3"); 49 | db.query("3B"); 50 | q1.execute(); 51 | db.query("4"); 52 | 53 | //Should do nothing since queue is empty 54 | q1.execute(); 55 | q1.execute(); 56 | q1.execute(); 57 | 58 | var q2 = db.createQueue(); 59 | q2.query("Q2 1"); 60 | q2.query("Q2 2", function() { 61 | db.query("7"); 62 | q2.query("Q2 2cb 1"); 63 | q2.query("Q2 2cb 2", function() { 64 | q2.query("Q2 2cb 2cb"); 65 | }); 66 | }); 67 | q2.query("Q2 3"); 68 | q2.execute(); 69 | db.query("5"); 70 | -------------------------------------------------------------------------------- /tests/trans-test.js: -------------------------------------------------------------------------------- 1 | var queue = require('../index'); 2 | 3 | //Create a fake MySQL client 4 | var db = { 5 | 'query': function(sql, input, cb) { 6 | if(typeof input == "function") 7 | { 8 | cb = input; 9 | input = undefined; 10 | } 11 | console.log("Executing " + sql); 12 | //Simulate a database delay of 1 second 13 | setTimeout(function() { 14 | console.log("-------------" + sql + " CB"); 15 | if(cb != null) cb(); 16 | }, 1000); 17 | } 18 | }; 19 | 20 | queue(db); //Enable queuing 21 | //test1(); 22 | //test2(); 23 | test3(); 24 | 25 | function test1() { 26 | //Let's use an async call in the query callback... 27 | var trans = db.startTransaction(); 28 | db.query("START"); 29 | trans.query("INSERT", function() { 30 | //Emulate an async operation 31 | trans.pause(); //try with and without pause() to see the effect 32 | setTimeout(function() { 33 | try { 34 | //You can't use trans here! 35 | if(Math.random() > 0.5) 36 | { 37 | console.log("About to Commit"); 38 | trans.commit(); //implicit resume 39 | } 40 | else 41 | { 42 | console.log("About to Rollback"); 43 | trans.rollback(); 44 | } 45 | } catch(e) {console.log(e)} 46 | }, 20); 47 | }).execute(); 48 | db.query("FINALLY"); 49 | } 50 | 51 | function test2() { 52 | //Let's try another example, without nesting... 53 | var trans2 = db.startTransaction(); 54 | function error() { 55 | if(trans2.rollback) 56 | { 57 | console.log("Print once"); 58 | trans2.rollback(); 59 | } 60 | } 61 | trans2.query("1", error); 62 | trans2.query("2", error); 63 | //Note that trans2.execute().commit() is different from trans2.commit() 64 | trans2.commit(); //In this case, COMMIT is queued, not executed immediately 65 | } 66 | 67 | function test3() { 68 | var trans = db.startTransaction(); 69 | function error() { 70 | if(trans.rollback && Math.random() > 0.5) 71 | trans.rollback(); 72 | } 73 | trans.query("INSERT", function(err, info) { 74 | if(err) error(); 75 | else 76 | { 77 | trans.query("UPDATE 1", error); 78 | trans.query("UPDATE 2", error); 79 | trans.commit(); 80 | } 81 | }).execute(); 82 | db.query("FINALLY"); 83 | } 84 | -------------------------------------------------------------------------------- /index.js: -------------------------------------------------------------------------------- 1 | /* Wraps db.query and exposes function db.createQueue() */ 2 | module.exports = function(db, debug) { 3 | if(debug !== true) debug = false; 4 | if(debug) console.log("mysql-queues: debug mode enabled."); 5 | var options = { debug: debug, currentlyExecutingQueue: null, mainQueue: [] }; 6 | var dbQuery = db.query; //The old db.query function 7 | //Wrap db.query 8 | db.query = function(sql, params, cb) { 9 | //Run query if no Queue is running; otherwise, queue it in mainQueue 10 | if(options.currentlyExecutingQueue == null) 11 | return dbQuery.apply(db, arguments); 12 | else 13 | options.mainQueue.push(arguments); 14 | } 15 | //Create a new executable query Queue 16 | db.createQueue = function() { 17 | return new Queue(function() {return dbQuery.apply(db, arguments);}, function () { 18 | //If the current Queue is a transaction that has not yet been committed, commit it 19 | var ceq = options.currentlyExecutingQueue; 20 | if(ceq != null && ceq.commit != null) 21 | { 22 | //Also, warn the user that relying on this behavior is a bad idea 23 | if(ceq._autoCommit !== true) 24 | console.warn("WARNING: mysql-queues: Database transaction was " + 25 | "implicitly committed.\nIt is HIGHLY recommended that you " + 26 | "explicitly commit all transactions.\n" + 27 | "The last query to run was:", ceq.lastExecuted.sql); 28 | ceq.commit(ceq._autoCommitCB); 29 | return; 30 | } 31 | options.currentlyExecutingQueue = null; 32 | //Called when a Queue has completed its processing and main queue should be executed 33 | while(options.mainQueue.length > 0) 34 | { 35 | var item = options.mainQueue.shift(); //Unsure of shift's performance 36 | if(item instanceof Queue) 37 | { 38 | item.execute(); 39 | break; //After the Queue has been executed, the main queue will be resumed 40 | } 41 | else 42 | dbQuery.apply(db, item); 43 | } 44 | }, options); 45 | } 46 | db.startTransaction = function() { 47 | return Queue.isNowTransaction(this.createQueue(), function() {return dbQuery.apply(db, arguments);}); 48 | } 49 | } 50 | function Queue(dbQuery, resumeMainQueue, options) { 51 | this.queue = []; 52 | this.paused = false; 53 | /* Add a query to the Queue */ 54 | this.query = function(sql, params, cb) { 55 | if(typeof params == "function") 56 | { 57 | cb = params; 58 | params = undefined; 59 | } 60 | this.queue.push({ 61 | 'sql': sql, 62 | 'params': params, 63 | 'cb': cb 64 | }); 65 | return this; //Chaining :) 66 | }; 67 | /* Execute all queries on the Queue in order and prevent other queries from executing until 68 | all queries have been completed. 69 | */ 70 | this.execute = function() { 71 | if(this.paused === true || this.executing) return; 72 | var that = this; 73 | //If another Queue is currently running, we put this on the mainQueue 74 | if(options.currentlyExecutingQueue != null && options.currentlyExecutingQueue != this) 75 | options.mainQueue.push(this); 76 | else if(that.queue.length > 0) 77 | { 78 | options.currentlyExecutingQueue = this; 79 | //console.log("Executing queue:", options.currentlyExecutingQueue); 80 | //Run everything in the queue 81 | that.executing = true; 82 | var done = 0, total = that.queue.length; 83 | for(var i = 0; i < total; i ++) 84 | { 85 | (function(item) { 86 | //Execute the query 87 | try { 88 | if(item.sql == "COMMIT") delete that.rollback; //Keep 'em honest 89 | that.lastExecuted = item; //For debugging and convenience 90 | dbQuery(item.sql, item.params || [], function() { 91 | if(options.debug && arguments[0] != null) 92 | console.error("mysql-queues: An error occurred while executing the following " + 93 | "query:\n\t", item.sql); 94 | //Execute the original callback first (which may add more queries to this Queue) 95 | if(item.cb != null) 96 | item.cb.apply(this, arguments); 97 | //When the entire queue has completed... 98 | if(++done == total) 99 | { 100 | that.executing = false; 101 | if(that.paused === true) return; 102 | /* The query's callback may have queued more queries on this Queue. 103 | If so, execute this Queue again; otherwise, resumeMainQueue() */ 104 | if(that.queue.length == 0) 105 | resumeMainQueue(); 106 | else 107 | that.execute(); 108 | } 109 | }); 110 | } catch(e) { 111 | if(options.debug) 112 | console.log("mysql-queues: An exception occurred for this query:\n\t", 113 | item.sql, "\twith parameters:\n\t", item.params); 114 | throw e; 115 | } 116 | })(that.queue[i]); 117 | } 118 | that.queue = []; 119 | //All queued queries are running, but we don't resume the main queue just yet 120 | //console.log("Queue Complete:", options.currentlyExecutingQueue); 121 | } 122 | else if(options.currentlyExecutingQueue == this) 123 | resumeMainQueue(); 124 | return this; //Chaining :) 125 | }; 126 | this.pause = function(maxWaitTime) { 127 | this.paused = true; 128 | if(maxWaitTime > 0) 129 | { 130 | var that = this; 131 | that.pauseTimer = setTimeout(function() { 132 | that.resume(); 133 | }, maxWaitTime); 134 | } 135 | return this; //Chaining 136 | } 137 | this.resume = function() { 138 | if(this.pauseTimer) 139 | clearTimeout(this.pauseTimer); 140 | this.paused = false; 141 | this.execute(); 142 | return this; //Chaining 143 | } 144 | } 145 | Queue.isNowTransaction = function(q, dbQuery) { 146 | q.query("START TRANSACTION"); 147 | q.commit = function(cb) { 148 | if(this.queue.length > 0) 149 | { 150 | this._autoCommit = true; 151 | this._autoCommitCB = cb; 152 | this.resume(); 153 | } 154 | else 155 | { 156 | delete this.commit; 157 | delete this._autoCommit; 158 | this.query("COMMIT", cb).resume(); 159 | } 160 | } 161 | q.rollback = function(cb) { 162 | this.queue = []; 163 | delete this.commit; 164 | delete this.rollback; 165 | dbQuery("ROLLBACK", cb); 166 | this.resume(); 167 | } 168 | return q; 169 | } 170 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # node-mysql-queues 2 | 3 | Add your own node-mysql query queues to support transactions and multiple statements. 4 | 5 | For use with Node.js and node-mysql: https://github.com/felixge/node-mysql 6 | 7 | ## DEPRECATION NOTICE 8 | 9 | This library is now pointless because node-mysql supports transactions and connection pooling. Thank you and goodbye. 10 | 11 | ## Background 12 | 13 | ~~node-mysql does not provide an API for MySQL transactions (yet).~~ 14 | 15 | There are a few problems with this: 16 | 17 | - If you use the same database connection for 2 or more requests, then you can run 18 | into an issue where queries that should not intermix, end up intermixing. 19 | This can mess up MySQL transactions. 20 | - There is no nice API to start, commit, or rollback transactions. 21 | 22 | Fortunately, there are a few solutions: 23 | 24 | - The easy solution: create a new connection to the database for each request, or 25 | to be extra safe, create a new connection for each transaction. This is 26 | probably what PHP does. Unfortunately, a new connection for each request 27 | can get expensive and slightly harm performance. 28 | - The other solution: node-mysql-queues. The idea behind node-mysql-queues is 29 | that we create separate query queues to ensure that queries in a particular 30 | queue do not overlap with queries in another queue; that is, they get executed 31 | in order, as expected. Plus, you have a nice, simple API for MySQL 32 | transactions. The disadvantage is that other requests with DB queries need 33 | to block while a transaction is executed, but I'm not sure about the effect 34 | on performance here. 35 | 36 | ~~All that being said, this project is still being actively maintained.~~ It has 37 | also been tested with node-mysql 2.0. 38 | 39 | ## Install 40 | 41 | `npm install mysql-queues` 42 | 43 | ## Usage 44 | 45 | ```javascript 46 | var mysql = require('mysql'); 47 | var client = mysql.createConnection({ //Use `mysql.createClient` in older versions of node-mysql 48 | user: 'root', 49 | password: 'root' 50 | }); 51 | //Enable mysql-queues 52 | var queues = require('mysql-queues'); 53 | const DEBUG = true; 54 | queues(client, DEBUG); 55 | //Start running queries as normal... 56 | client.query(...); 57 | 58 | //Now you want a separate queue? 59 | var q = client.createQueue(); 60 | q.query(...); 61 | q.query(...); 62 | q.execute(); 63 | 64 | client.query(...); //Will not execute until all queued queries (and their callbacks) completed. 65 | 66 | //Now you want a transaction? 67 | var trans = client.startTransaction(); 68 | trans.query("INSERT...", [x, y, z], function(err, info) { 69 | if(err) 70 | trans.rollback(); 71 | else 72 | trans.query("UPDATE...", [a, b, c, info.insertId], function(err) { 73 | if(err) 74 | trans.rollback(); 75 | else 76 | trans.commit(); 77 | }); 78 | }); 79 | trans.execute(); 80 | //No other queries will get executed until the transaction completes 81 | client.query("SELECT ...") //This won't execute until the transaction is COMPLETELY done (including callbacks) 82 | 83 | //Or... as of version 0.3.0, you can do this... 84 | var trans = client.startTransaction(); 85 | function error(err) { 86 | if(err && trans.rollback) {trans.rollback(); throw err;} 87 | } 88 | trans.query("DELETE...", [x], error); 89 | for(var i = 0; i < n; i++) 90 | trans.query("INSERT...", [ y[i] ], error); 91 | trans.commit(); //Implictly calls resume(), which calls execute() 92 | /* In the case written above, COMMIT is placed at the end of the Queue, yet the 93 | entire transaction can be rolled back if an error occurs. Nesting these queries 94 | was not required. */ 95 | 96 | ``` 97 | Even multiple Queues work! They get executed in the order that `execute()` is called. 98 | 99 | ## How it works 100 | 101 | * If I'm a client.query() call or a Queue.execute() call... 102 | * If a Queue is currently executing 103 | * Place me on the main queue to be executed 104 | * Otherwise, Execute me now 105 | * Run all queries in the Queue in order 106 | * Wait for all query callbacks to complete. When they all complete, continue. 107 | * If the callback added more queries to this Queue, then jump to "Execute me now" 108 | * Otherwise 109 | * If this Queue is a transaction that has not been committed, then 110 | commit it now and issue a warning message. 111 | * Finally, Return control to the main queue by executing all queued queries 112 | 113 | ## API 114 | 115 | ### client.query(sql, [params, cb]) 116 | 117 | Use normally. Same as node-mysql, except that if a Queue is still pending 118 | completion, this query may be queued for later execution. 119 | 120 | ### client.createQueue() 121 | 122 | Creates a new query Queue. 123 | 124 | ### client.startTransaction() 125 | 126 | Creates a new query Queue with "START TRANSACTION" as the first queued query. 127 | The Queue object will also have `commit()` and `rollback()` methods. 128 | 129 | ### Queue.query(sql, [params, cb]) 130 | 131 | Same as node-mysql. This query will be queued for execution until `execute()` 132 | is called on the `Queue`. 133 | 134 | ### Queue.execute() 135 | 136 | Executes all queries that were queued using `Queue.query`. Until all query 137 | *callbacks* complete, it is guaranteed that all queries in this Queue 138 | will be executed in order, with no other queries intermixed. That is, during 139 | execution of this query Queue, all queries executed using `client.query` will 140 | be queued until this Queue is empty and all callbacks of this Queue have 141 | finished executing. That means that a query added to a Queue can also queue 142 | a query using `Queue.query`, and it will be executed before any `client.query` 143 | call. Thus, nested query queueing is supported in query callbacks, allowing 144 | support for transactions and more. 145 | See the source code for further documentation. 146 | 147 | Calling `execute()` on an already executing Queue has no effect. 148 | Calling `execute()` on a paused Queue has no effect. (see `pause()` below) 149 | 150 | Note: Once `execute()` is called and all queries have completed, the Queue 151 | will be empty again, returning control to either: (a) another Queue that has been 152 | queued for execution; or (b) the main node-mysql queue (a.k.a. queries executed 153 | with `client.query`). Once a Queue is empty and has finished executing, you may 154 | continue to use `Queue.query` and `Queue.execute` to queue and execute more 155 | queries; however, as noted below, you should *never* reuse a Queue created by 156 | `client.startTransaction` 157 | 158 | ### Queue.commit([cb]) 159 | 160 | Available only if this Queue was created with `client.startTransaction`. 161 | Calls `cb(err, info)` when the COMMIT has completed. 162 | 163 | As of version 0.3.0, the behavior of `commit()` is: 164 | 165 | * If the queue is empty when `commit()` is called, then 'COMMIT' will be 166 | queued to be executed immediately. If this behavior is desired, and you 167 | are not sure if the queue will be empty, simply call `resume()` 168 | before calling `commit()`. 169 | * If the queue is not empty when `commit()` is called, then 'COMMIT' will 170 | be queued for execution when the queue is empty and all query callbacks 171 | have completed. 172 | 173 | Calling `commit()` also implicitly calls `resume()` on the Queue. 174 | 175 | You may only call `commit()` once. Once you call `commit()` on this Queue, 176 | you should discard it. To avoid calling `commit()` twice, you can check 177 | to see if it exists; once you call `commit()`, in most circumstances, the 178 | function is deleted from the Queue object after it is called. 179 | 180 | As of version 0.3.0, it is sometimes 181 | possible to call `rollback()` even after `commit()` has been called. 182 | If 'COMMIT' is queued for execution (i.e. if the queue is *not* empty when 183 | `commit()` is called), then you may call `rollback()` on this Queue, 184 | as long as `rollback()` occurs before the 'COMMIT' is executed (i.e. when the 185 | Queue is empty and all query callbacks have completed). 186 | You might use the functionality in a scenario where you only want your query 187 | callbacks to call `rollback()` if an error occurred (i.e. a foreign key 188 | constraint was violated). If no error occurs, you want to call `commit()`. 189 | Rather than nesting all of these queries to determine whether or not to 190 | call `commit()` or `rollback()`, you can simply queue up all of your queries, 191 | call `commit()` to queue up a 'COMMIT', and call `rollback()` in your 192 | query callbacks if an error occurs. 193 | 194 | ### Important Note! 195 | 196 | If you do not call `commit()` or `rollback()` and the Queue has completed 197 | execution, `commit()` will be called automatically to end the transaction; 198 | however, one should **NOT** rely on this behavior. In fact, mysql-queues 199 | will print nasty warning messages if you do not explicitly `commit()` or 200 | `rollback()` a transaction. 201 | 202 | ### Queue.rollback([cb]) 203 | 204 | Available only if this Queue was created with `client.startTransaction`. 205 | This executes 'ROLLBACK' immediately, purges the remaining queries in the 206 | queue, and immediately returns control to the main queue. Finally, the 207 | callback `cb(err, info)` is called when the ROLLBACK has completed. 208 | 209 | You may only call `rollback()` once. To avoid calling it twice, you can 210 | check to see if it exists; once you call `rollback()`, the function is 211 | deleted from the Queue object. Also, once you call `rollback()`, you cannot 212 | call `commit()`. 213 | 214 | Note: Before 0.2.3, `rollback()` would add the 'ROLLBACK' query to the Queue 215 | and the Queue would continue executing. This was changed in 0.2.3 because it 216 | is more natural for a ROLLBACK operation to abort the remaining Queue, since 217 | it will be rolled back anyway. As mentioned above, this also allows you to 218 | queue the COMMIT query at the bottom of the queue, and if an error occurs 219 | before the COMMIT, you can safely `rollback()` the entire transaction. 220 | 221 | ### Queue.pause([maxWaitDuration]) 222 | 223 | Pauses the Queue, preventing it from returning control to the next Queue or 224 | to the main node-mysql Queue. You can call `resume()` to resume the Queue, 225 | or if the Queue is a transaction, `commit()` or `rollback()` will 226 | automatically resume the Queue. 227 | 228 | By default, the Queue will remain paused until you call `resume()` or end 229 | the transaction; however, you may set an optional maximum wait duration, 230 | which will prevent the Queue from pausing for too long. 231 | 232 | *CAUTION:* A paused Queue will block all queries for this connection. 233 | *Use with care.* 234 | 235 | Pausing a Queue is useful to make additional asynchronous calls within a 236 | query callback. An example of this is shown below. 237 | 238 | ### Queue.resume() 239 | 240 | Resumes Queue execution. This function basically unpauses the Queue and 241 | calls `execute()`. 242 | 243 | ### require('mysql-queues')(client, debug) 244 | 245 | Attaches mysql-queues to the mysql client. When `debug` mode is enabled, 246 | debugging messages are printed to standard error when certain exceptions occur. 247 | When you queue a query, the call stack becomes somewhat useless, and it can 248 | become difficult to determine which query is causing a problem. The debug 249 | feature allows you to more easily determine which query that caused a problem. 250 | 251 | ## Don't do this... 252 | 253 | ```javascript 254 | //You may be tempted to do this... 255 | var fs = require('fs'); 256 | var trans = db.startTransaction(); 257 | trans.query("INSERT ...", [...], function(err, info) { 258 | fs.readFile("foobar.txt", function(err, data) { 259 | //By now, it's too late to use `trans` 260 | if(data == "something") 261 | trans.commit(); 262 | else 263 | trans.rollback(); 264 | }); 265 | //The query callback is now done!! This is your last chance 266 | //to call `commit` or `rollback` 267 | }).execute(); 268 | ``` 269 | 270 | In the case above, an asynchronous call was placed in the query callback. 271 | This won't work as expected. The query callback completes and automatically 272 | executes `commit()` before the asychronous filesystem call completes. In this 273 | example, you will get a warning message, your transaction will be committed 274 | no matter what, and your program may throw an exception after the I/O 275 | operation completes (because neither `commit()` nor `rollback()` can be 276 | called more than once). 277 | 278 | To be clear, the scope of this problem is *not* limited by asynchronous 279 | file I/O operations; any asychronous call can cause this problem - even a 280 | query to another database will cause this problem (i.e. if you execute a 281 | series of MySQL queries and then update Redis, for example) 282 | 283 | ### Fortunately, there are a few solutions... 284 | 285 | Possible solutions include: (in order of personal preference) 286 | 287 | * Performing your asynchronous operation BEFORE you execute any queued 288 | queries (i.e. we could have read "foobar.txt" first, then executed the query). 289 | I understand... most of the time, this is not possible. 290 | * Call `Queue.pause()` right before the asynchrous operation. This is the 291 | easy way out, but it comes at a small cost. If you pause a Queue, no query 292 | can be executed during the asynchronous operation. So, for scalability 293 | reasons, be sure that your asynchronous operation runs quickly (i.e. a Redis 294 | command or something). Don't do any video encoding on a 1 GB file. 295 | * Use synchronous I/O operations (i.e. readFileSync in this case). This 296 | is "just as bad" as calling `Queue.pause()` because the query execution is 297 | paused during the synchronous operation, which will take just as long. 298 | But, this works, too. 299 | 300 | And finally, to be clear, you are allowed to do asynchronous calls within the 301 | query callback of a transaction. You just need to `commit()` or `rollback()` 302 | or `pause()` beforehand because the Queue will be empty by the time the 303 | asynchronous operation completes. 304 | 305 | ## Questions / Comments / Bugs 306 | 307 | Please feel free to contact me via GitHub, send pull requests, open issues, etc. 308 | 309 | I am open to suggestions and criticisms. 310 | --------------------------------------------------------------------------------