UNPKG

jaune-engine

Version:
396 lines (349 loc) 9.9 kB
/** * @file Source code for POSTGRESQL client. * @author Alvaro Juste */ "use strict"; // 3rd const _bind = require("lodash").bind; const _pg = require("pg"); const _pgConnect = _bind(_pg.connect, _pg); const _pgPools = _pg.pools; const _pgPoolGet = _bind(_pgPools.getOrCreate, _pgPools); const _map = require("lodash").map; const _nbind = require("q").nbind; const _defer = require("q").defer; // jaune const _debug = require("jaune-util").Debug; // constants const DEBUG_CONFIG_SECTION = "jaune.debug"; // variables let _lastId = 0; /** * @function Standarize sql error * @param {Object} err The sql error * @returns {Object} The error */ const standarizeError = function(err) { var result = err; var fieldText = null; if (err && "object" === typeof err) { if ("undefined" !== typeof err) { try { fieldText = JSON.stringify({ arguments : err.arguments, code : err.code, detail : err.detail, file : err.file, hint : err.hint, internalPosition : err.internalPosition, internalQuery : err.internalQuery, length : err.length, line : err.line, message : err.message, name : err.name, position : err.position, routine : err.routine, severity : err.severity, stack : err.stack, type : err.type, where : err.where }); } catch(ex) { fieldText = "Error while standarizing exception: " + ex; } finally { result.causeToString = fieldText; } } } return result; }; /** * @class PostgreSQL client * @param {Object} opts The options * @param {Object} env The environment */ const PostgresClient = function(opts, env) { this.debugSettings = env.getEnvProperty(DEBUG_CONFIG_SECTION, "sql"); this.opts = opts; this.env = env; }; /** * @function Initializes a connection. * @param {Object} client Driver provided database client. * @param {Function} release Driver provided release function. * @returns {Object} A new connection. */ PostgresClient.prototype.initConnection = function(client, release) { client.jaune = "undefined" === typeof client.jaune ? new PostgresConnection(client, release, this.env, this.debugSettings) : client.jaune; return client.jaune; }; /** * @function Creates a new Link object. * @param {Boolean} [handleConnection] Handle connection flag. * @returns {Object} A new link. */ PostgresClient.prototype.getLink = function(handleConnection, tx) { return { handleConnection : typeof handleConnection === "boolean" ? handleConnection : true }; }; /** * @callback Function provided to close. */ PostgresClient.prototype.__chainClose = function* (opts) { if (this.debugSettings.links) { _debug.printText("Closing connection (" + this.id + ") from chained."); } yield this.close(); }; /** * @function Function that will reuse same connection or create a new one if no connection is provided. * @param {Object} [chainedClient] A client provided by this manager. */ PostgresClient.prototype.chain = function* (chainedClient) { var handleConnection = !chainedClient; var client = null; try { client = handleConnection ? yield this.connect() : chainedClient; if (!handleConnection) { client.push(this.getLink(false), client); if (this.debugSettings.links) { _debug.printText("Link pushed while connecting chained for connection (" + client.id + "): " + client.links.length); } } return client; } catch(err) { throw standarizeError(err); } }; /** * @function Connect by providing pool name. */ PostgresClient.prototype.connect = function* () { var deferred = _defer(); _pgConnect(this.opts.server, _bind(function(err, cli, done) { var connection = this.initConnection(cli, done); if (connection.links.length !== 0) { deferred.reject(new Error("this connection is dirty - links already present: " + connection.links.length)); } else { if (!err) { connection.links.push(this.getLink()); if (this.debugSettings.links) { _debug.printText("Link pushed while connecting for connection (" + connection.id + "): " + connection.links.length); } return deferred.resolve(connection); } deferred.reject(standarizeError(err)); } }, this)); return yield deferred.promise; }; /** * @function Get count in connection pool. * @returns {Number} The count */ PostgresClient.prototype.getConnectionsCountInPool = function() { return _pgPoolGet(this.opts.server).availableObjectsCount(); }; /** * @function Get connection pool size * @returns {Number} The pool size */ PostgresClient.prototype.getConnectionPoolSize = function() { return _pgPoolGet(this.opts.server).getPoolSize(); }; /** * @class POSTGRESQL connection handler. * @param {Object} opts Options * @param {Object} client Native client object. * @param {Function} release Function used to release connection which is provided by driver. */ var PostgresConnection = function(client, release, env, debugSettings) { this.release = release; this.client = client; this.opts = env; this.links = []; this.tx = false; this.id = "PG-" + _lastId++; this.debugSettings = debugSettings; }; /** * @function Initialize * @param {Object} client The postgresql client */ PostgresConnection.prototype.init = function(client) { if ("undefined" === typeof client.jaune) { client.jaune = this; } }; /** * @function Pushs a link * @param {Object} link A link */ PostgresConnection.prototype.push = function(link) { this.links.push(link); }; /** * @function Pops a link * @returns {Object} Current link */ PostgresConnection.prototype.pop = function() { return this.links.pop(); }; /** * @function Gets current link * @returns {Object} Current link */ PostgresConnection.prototype.current = function() { return this.links[this.links.length - 1]; }; /** * @function Evaluate if transaction is open * @returns {Boolean} True when transaction open */ PostgresConnection.prototype.isTransactionOpen = function() { return this.tx === true; }; /** * @function Sets open transaction open * @param {Boolean} value The flag */ PostgresConnection.prototype.setTransaction = function(value) { this.tx = value; }; /** * @function Evaluate if any pending operation * @returns {Boolean} True when operation pending */ PostgresConnection.prototype.anyPendingLink = function() { return this.links > 1; }; /** * @function Begins a transaction. */ PostgresConnection.prototype.begin = function* () { try { if (!this.isTransactionOpen()) { yield this.__onBeginQueryExecuted(); this.setTransaction(true); yield this.query({ sql: "BEGIN;" }); } else { //TODO: Remove throw "Should not be here"; } } catch(err) { throw standarizeError(err); } }; /** * @function Rolls back a transaction. */ PostgresConnection.prototype.rollback = function* () { try { if (this.current().handleConnection === true && this.isTransactionOpen()) { this.setTransaction(false); yield this.query({ sql: "ROLLBACK;" }); } } catch(err) { throw standarizeError(err); } }; /** * @function Commits a transaction. */ PostgresConnection.prototype.commit = function* () { try { if (this.current().handleConnection === true && this.isTransactionOpen()) { yield this.query({ sql: "COMMIT;" }); } } catch(err) { throw standarizeError(err); } }; /** * @function Rolls back a transaction and close the connection. */ PostgresConnection.prototype.rollbackAndClose = function* () { try{ yield this.rollback(); this.close(); } catch(err) { throw standarizeError(err); } }; /** * @function Commits a transaction and close the connection. */ PostgresConnection.prototype.commitAndClose = function* () { try{ yield this.commit(); this.close(); } catch(err) { throw standarizeError(err); } }; /** * @function Execute a query * @param {Object} query.sql Query to be executed. * @param {Array} [query.args] Arguments */ PostgresConnection.prototype.query = function* (query) { if (this.debugSettings.queries) { _debug.printObject(query); _debug.printText((query.args || []).join(",")); } try{ return (yield _nbind(this.client.query, this.client, query.sql, query.args || [])).rows; } catch(err) { throw standarizeError(err); } }; /** * @function Call a function / stored procedure * @param {Object} query.sql The name of the member to call * @param {Array} [query.args] Arguments */ PostgresConnection.prototype.call = function* (query, cb) { var parameterIndex = 1; var args = query.args || []; return yield this.query({ sql : "SELECT * FROM " + query.sql + "(" + _map(args, function(e) { return "$" + parameterIndex++; }).join(", ") + ");", args : query.args }); }; /** * @function Close the connection if no pending operations */ PostgresConnection.prototype.close = function* () { yield this.commit(); if (typeof this.release === "function") { var link = this.current(); this.pop(); if (this.debugSettings.links) { _debug.printText("Link pop while closing for connection (" + this.id + "): " + this.links.length); } if (link.handleConnection === true) { if (this.debugSettings.links) { _debug.printText("Connection released - " + this.id + "."); } this.release(); } } }; /** * @function Handle exception */ PostgresConnection.prototype.exception = function() { this.close(); }; module.exports = { PostgresClient : PostgresClient };