UNPKG

mariadb-transact

Version:

A transaction manager for the mariasql module that uses promises.

364 lines (324 loc) 10.6 kB
(function() { var MariaSQL, Promise, TransactionManager, datetime; MariaSQL = require("mariasql"); Promise = require("bluebird"); datetime = require("node-datetime"); TransactionManager = (function() { /* Handles a queue of transactions and a pool of MariaSQL connections. */ function TransactionManager(opts) { if (!opts) { opts = {}; } this.conn = { connected: false }; this.autoconvert = typeof opts.metadata === "undefined" ? true : !!opts.metadata; opts.charset = typeof opts.charset === "string" ? opts.charset : "utf8"; this._pool = []; this._queue = []; this._poolsize = typeof opts.poolsize === "number" ? opts.poolsize : 3; this.conncfg = opts; this._closed = false; this._initialized = false; } TransactionManager.prototype._createConnection = function() { /* Create a new connection object. */ return new Promise((function(_this) { return function(resolve, reject) { var conn, handled; conn = new MariaSQL(); conn.connect(_this.conncfg); conn.command = conn.cmd = _this.command.bind(_this, conn); conn.commit = _this.commit.bind(_this, conn); conn.fetchArray = _this.fetchArray.bind(_this, conn); conn.fetchOne = _this.fetchOne.bind(_this, conn); conn.rollback = _this.rollback.bind(_this, conn); conn.insert = _this.insert.bind(_this, conn); conn._queryAsync = Promise.promisify(conn.query, { context: conn }); handled = false; conn.on("error", function(err) { if (!handled) { handled = true; return reject(err); } }); conn.once("ready", function() { if (!handled) { handled = true; return resolve(conn); } }); return conn.on("close", _this._handleConnectionClose.bind(_this, conn)); }; })(this)); }; TransactionManager.prototype._handleConnectionClose = function(conn) { /* Attempt to reconnect if connection is closed. */ if (this._closed) { return; } return conn.connect(this.conncfg); }; TransactionManager.prototype.init = function() { /* Initialize all connections. */ if (this._initialized) { return Promise["try"]((function(_this) { return function() {}; })(this)); } else { return Promise["try"]((function(_this) { return function() { return _this._createConnection(); }; })(this)).then((function(_this) { return function(conn) { var funcs, i, j, ref; _this.conn = conn; funcs = []; if (_this._poolsize > 0) { for (i = j = 1, ref = _this._poolsize; 1 <= ref ? j <= ref : j >= ref; i = 1 <= ref ? ++j : --j) { funcs.push(_this._createConnection()); } } return Promise.all(funcs); }; })(this)).then((function(_this) { return function(tconns) { var funcs, j, len, tc; funcs = []; for (j = 0, len = tconns.length; j < len; j++) { tc = tconns[j]; _this._pool.push(tc); funcs.push(tc._queryAsync("SET autocommit = 0")); } return Promise.all(funcs); }; })(this)).then((function(_this) { return function() { return _this._initialized = true; }; })(this)); } }; TransactionManager.prototype.basic = function() { /* Get a basic, non-transactional connection. (Only for simple queries.) */ return new Promise((function(_this) { return function(resolve, reject) { if (_this.conn.connected) { return resolve(_this.conn); } else { return reject(new Error("The transaction manager is not connected to a database.")); } }; })(this)); }; TransactionManager.prototype.close = function() { /* Close all connections. */ return new Promise((function(_this) { return function(resolve) { var c, j, len, ref; _this._closed = true; _this.conn.end(); _this.conn.removeAllListeners("ready"); _this.conn.removeAllListeners("close"); ref = _this._pool; for (j = 0, len = ref.length; j < len; j++) { c = ref[j]; c.end(); c.removeAllListeners("ready"); c.removeAllListeners("close"); } return resolve(); }; })(this)); }; TransactionManager.prototype._checkQueue = function() { /* Check the queue for waiting transaction initializations. */ var resolve; if (this._queue.length > 0 && this._pool.length > 0) { resolve = this._queue.shift(); return resolve(this._pool.pop()); } }; TransactionManager.prototype._finalCmd = function(cmd, conn) { /* Execute rollback or commit. */ return Promise["try"]((function(_this) { return function() { return conn._queryAsync(cmd); }; })(this))["finally"]((function(_this) { return function() { _this._pool.push(conn); return _this._checkQueue(); }; })(this)); }; TransactionManager.prototype.commit = function(conn) { /* Commit a transaction. */ return this._finalCmd("COMMIT", conn); }; TransactionManager.prototype.rollback = function(conn) { /* Roll back a transaction. */ return this._finalCmd("ROLLBACK", conn); }; TransactionManager.prototype.command = function(conn, sql, params) { /* Perform an SQL command (no result returned, use for INSERT/UPDATE queries). */ return Promise["try"]((function(_this) { return function() { return conn._queryAsync(sql, params || {}); }; })(this)).then((function(_this) { return function(res) { if (res.info) { res.info.numRows = parseInt(res.info.numRows); res.info.affectedRows = parseInt(res.info.affectedRows); res.info.insertId = parseInt(res.info.insertId); } return res.info; }; })(this)); }; TransactionManager.prototype.insert = function(conn, tableName, obj, throwIfEmpty) { if (throwIfEmpty == null) { throwIfEmpty = true; } /* Convenience function to insert an object into a given table. */ return Promise["try"]((function(_this) { return function() { var k, props, q, v; props = {}; if (obj) { for (k in obj) { v = obj[k]; if (typeof v === "string" || typeof v === "number" || typeof v === "boolean" || v === null) { props[k] = v; } else if (v instanceof Date) { props[k] = datetime.create(v).format("Y-m-d H:M:S"); } } } if (Object.keys(props).length) { q = "INSERT INTO " + tableName + " (" + Object.keys(props).join(", ") + ") VALUES (:" + Object.keys(props).join(", :") + ")"; return conn.command(q, props); } else if (throwIfEmpty) { throw new Error("No valid fields passed to insert."); } }; })(this)); }; TransactionManager.prototype.convert = function(row, metadata) { /* Convert row elements based on type info. */ var key, newVal, results, t; results = []; for (key in row) { t = metadata[key].type; if (t === "DATE" || t === "DATETIME" || t === "TIMESTAMP") { row[key] = new Date(row[key]); } if (t === "DECIMAL" || t === "DOUBLE" || t === "FLOAT") { newVal = parseFloat(row[key]); row[key] = isNaN(newVal) ? null : newVal; } if (t === "INTEGER" || t === "TINYINT" || t === "SMALLINT" || t === "MEDIUMINT" || t === "BIGINT") { newVal = parseInt(row[key]); results.push(row[key] = isNaN(newVal) ? null : newVal); } else { results.push(void 0); } } return results; }; TransactionManager.prototype.fetchArray = function(conn, sql, params) { /* Fetch an array of SQL result rows. */ return Promise["try"]((function(_this) { return function() { return conn._queryAsync(sql, params || {}, { metadata: _this.autoconvert }); }; })(this)).then((function(_this) { return function(res) { var j, len, row; for (j = 0, len = res.length; j < len; j++) { row = res[j]; if (res.info && res.info.metadata && _this.autoconvert) { _this.convert(row, res.info.metadata); } } return res; }; })(this)); }; TransactionManager.prototype.fetchOne = function(conn, sql, params) { /* Fetch a single SQL result row. */ return Promise["try"]((function(_this) { return function() { return conn._queryAsync(sql, params || {}, { metadata: _this.autoconvert }); }; })(this)).then((function(_this) { return function(res) { var row; if (res && res.length) { row = res[0]; if (res.info && res.info.metadata && _this.autoconvert) { _this.convert(row, res.info.metadata); } return row; } else { return null; } }; })(this)); }; TransactionManager.prototype.begin = function() { /* Attempt to begin a transaction. Add to promise to queue if connection pool is empty. */ return new Promise((function(_this) { return function(resolve) { if (_this._pool.length > 0) { return resolve(_this._pool.pop()); } else { return _this._queue.push(resolve); } }; })(this)); }; return TransactionManager; })(); module.exports = TransactionManager; }).call(this);