UNPKG

pooled-pg

Version:

A driver to PostgreSQL that is compatible with pg, with more effective pooling strategies.

231 lines (212 loc) 5.01 kB
'use strict'; /* jshint camelcase: false */ /** * A remote client that sends request to a pooled-pg server. * (C) 2015 Alex Fernández. */ // requires require('prototypes'); var net = require('net'); var Log = require('log'); var testing = require('testing'); var genericPool = require('generic-pool'); var profiler = require('microprofiler'); var server = require('./server.js'); var protocol = require('./protocol.js'); var defaults = require('./defaults.js'); // globals var log = new Log('info'); var clients = {}; exports.remoteConnect = function(address, callback) { var client = getClient(address); callback(null, client, client.done); }; function getClient(address) { if (!clients[address]) { clients[address] = new exports.RemoteClient(address); } return clients[address]; } exports.end = function() { clients.forEach(function(client) { client.end(); }); }; exports.RemoteClient = function(address) { // self-reference var self = this; // attributes var pool = createPool(address); self.query = function(query, params, callback) { var start = profiler.start(); log.debug('query: %s', query); if (typeof params == 'function') { callback = params; params = null; } if (address == 'test') { return callback(null, { rowCount: 1, rows: [{"current_user":"test"}], }); } pool.acquire(function(error, socket) { profiler.measureFrom(start, 'acquired'); if (error) { return callback('Could not connect to ' + address + ': ' + error); } var message = { query: query, params: params, address: getLocalAddress(), }; socket.write(protocol.createData(message), function(error) { profiler.measureFrom(start, 'written'); if (error) { return callback('Could not connect: ' + error); } }); var parser = new protocol.Parser(); parser.on('error', function(error) { log.error('Could not receive data: %s', error); pool.destroy(socket); }); parser.on('data', function(message) { socket.removeAllListeners('data'); socket.removeAllListeners('error'); pool.release(socket); profiler.measureFrom(start, 'message'); try { return callback(null, message); } catch(exception) { return callback('Could not parse response: ' + exception); } }); socket.on('data', function(data) { parser.receive(String(data)); }); socket.on('error', function(error) { log.error('Connection error: %s', error); pool.destroy(socket); }); }); }; function getLocalAddress() { var withoutProtocol = address.substringFrom(':').substringUpTo('@'); var database = address.substringFrom('@').substringFrom('/'); return 'postgresql:' + withoutProtocol + '@localhost:5432/' + database; } self.done = function() { }; self.end = function() { profiler.show('acquired'); profiler.show('written'); profiler.show('data'); }; }; function createPool(address) { var fullHost = address.substringFrom('@').substringUpTo('/'); log.debug('Creating client to %s', fullHost); var options = { host: fullHost.substringUpTo(':'), port: fullHost.substringFrom(':'), }; var pool = genericPool.Pool({ name: 'remote', create: function(callback) { log.debug('Creating socket to %s', fullHost); var socket = net.connect(options, function(error) { if (error) { return callback(error); } socket.setNoDelay(); socket.on('error', function(error) { log.error('Error in remote, removing from pool: %s', error); pool.destroy(socket); }); return callback(null, socket); }); }, destroy: function(client) { log.debug('Destroying socket to %s', fullHost); client.end(); }, max: defaults.poolSize, idleTimeoutMillis: defaults.poolIdleTimeout, }); return pool; } function testRemoteClient(callback) { defaults.poolIdleTimeout = 100; var port = 5444; var address = 'pooled://test:test@localhost: ' + port + '/test'; var options = { port: port, test: true, }; server.start(options, function(error, testServer) { var client = new exports.RemoteClient(address); client.query('select current_user', function(error, result) { testing.check(error, 'Could not run query to %s', address, callback); testing.assert(result, 'No result', callback); testing.assert(result.rowCount, 'No rows', callback); testing.assert(result.rows[0], 'No first row', callback); var user = result.rows[0].current_user; testing.assertEquals(user, 'test', 'Invalid user', callback); client.end(); testServer.close(function(error) { testing.check(error, 'Could not close server', callback); testing.success(callback); }); }); }); } /** * Run package tests. */ exports.test = function(callback) { var tests = [ testRemoteClient, ]; testing.run(tests, callback); }; // run tests if invoked directly if (__filename == process.argv[1]) { log = new Log('debug'); exports.test(testing.show); }