pax_global_header00006660000000000000000000000064117677723740014537gustar00rootroot0000000000000052 comment=deb509c5f610565d023876b9d4e8afa87787e9d7 brianc-node-postgres-e5c48f3/000077500000000000000000000000001176777237400162075ustar00rootroot00000000000000brianc-node-postgres-e5c48f3/.gitignore000066400000000000000000000000571176777237400202010ustar00rootroot00000000000000node_modules/ *.swp *.log .lock-wscript build/ brianc-node-postgres-e5c48f3/.npmignore000066400000000000000000000000251176777237400202030ustar00rootroot00000000000000.lock-wscript build/ brianc-node-postgres-e5c48f3/Makefile000066400000000000000000000021621176777237400176500ustar00rootroot00000000000000SHELL := /bin/bash connectionString=pg://postgres:5432@localhost/postgres params := $(connectionString) node-command := xargs -n 1 -I file node file $(params) .PHONY : test test-connection test-integration bench test-native build/default/binding.node test: test-unit test-all: test-unit test-integration test-native test-binary bench: @find benchmark -name "*-bench.js" | $(node-command) build/default/binding.node: @node-gyp rebuild test-unit: @find test/unit -name "*-tests.js" | $(node-command) test-connection: @node script/test-connection.js $(params) test-connection-binary: @node script/test-connection.js $(params) binary test-native: build/default/binding.node @echo "***Testing native bindings***" @find test/native -name "*-tests.js" | $(node-command) @find test/integration -name "*-tests.js" | $(node-command) native test-integration: test-connection @echo "***Testing Pure Javascript***" @find test/integration -name "*-tests.js" | $(node-command) test-binary: test-connection-binary @echo "***Testing Pure Javascript (binary)***" @find test/integration -name "*-tests.js" | $(node-command) binary brianc-node-postgres-e5c48f3/README.md000066400000000000000000000152731176777237400174760ustar00rootroot00000000000000#node-postgres Non-blocking PostgreSQL client for node.js. Pure JavaScript and native libpq bindings. Active development, well tested, and production use. ## Installation npm install pg ## Examples ### Simple, using built-in client pool var pg = require('pg'); //or native libpq bindings //var pg = require('pg').native var conString = "tcp://postgres:1234@localhost/postgres"; //error handling omitted pg.connect(conString, function(err, client) { client.query("SELECT NOW() as when", function(err, result) { console.log("Row count: %d",result.rows.length); // 1 console.log("Current year: %d", result.rows[0].when.getYear()); }); }); ### Evented api var pg = require('pg'); //native libpq bindings = `var pg = require('pg').native` var conString = "tcp://postgres:1234@localhost/postgres"; var client = new pg.Client(conString); client.connect(); //queries are queued and executed one after another once the connection becomes available client.query("CREATE TEMP TABLE beatles(name varchar(10), height integer, birthday timestamptz)"); client.query("INSERT INTO beatles(name, height, birthday) values($1, $2, $3)", ['Ringo', 67, new Date(1945, 11, 2)]); client.query("INSERT INTO beatles(name, height, birthday) values($1, $2, $3)", ['John', 68, new Date(1944, 10, 13)]); //queries can be executed either via text/parameter values passed as individual arguments //or by passing an options object containing text, (optional) parameter values, and (optional) query name client.query({ name: 'insert beatle', text: "INSERT INTO beatles(name, height, birthday) values($1, $2, $3)", values: ['George', 70, new Date(1946, 02, 14)] }); //subsequent queries with the same name will be executed without re-parsing the query plan by postgres client.query({ name: 'insert beatle', values: ['Paul', 63, new Date(1945, 04, 03)] }); var query = client.query("SELECT * FROM beatles WHERE name = $1", ['John']); //can stream row results back 1 at a time query.on('row', function(row) { console.log(row); console.log("Beatle name: %s", row.name); //Beatle name: John console.log("Beatle birth year: %d", row.birthday.getYear()); //dates are returned as javascript dates console.log("Beatle height: %d' %d\"", Math.floor(row.height/12), row.height%12); //integers are returned as javascript ints }); //fired after last row is emitted query.on('end', function() { client.end(); }); ### Example notes node-postgres supports both an 'event emitter' style API and a 'callback' style. The callback style is more concise and generally preferred, but the evented API can come in handy. They can be mixed and matched. The only events which do __not__ fire when callbacks are supplied are the `error` events, as they are to be handled by the callback function. All examples will work with the pure javascript bindings (currently default) or the libpq native (c/c++) bindings (currently in beta) To use native libpq bindings replace `require('pg')` with `require('pg').native`. The two share the same interface so __no other code changes should be required__. If you find yourself having to change code other than the require statement when switching from `pg` to `pg.native`, please report an issue. ### Info * pure javascript client and native libpq bindings share _the same api_ * _heavily_ tested * the same suite of 200+ integration tests passed by both javascript & libpq bindings * benchmark & long-running memory leak tests performed before releases * tested with with * postgres 8.x, 9.x * Linux, OS X * node 2.x & 4.x * row-by-row result streaming * built-in (optional) connection pooling * responsive project maintainer * supported PostgreSQL features * parameterized queries * named statements with query plan caching * async notifications * extensible js<->postgresql data-type coercion * query queue * active development * fast * close mirror of the node-mysql api for future multi-database-supported ORM implementation ease ### Contributors Many thanks to the following: * [creationix](https://github.com/creationix) * [felixge](https://github.com/felixge) * [pshc](https://github.com/pshc) * [pjornblomqvist](https://github.com/bjornblomqvist) * [JulianBirch](https://github.com/JulianBirch) * [ef4](https://github.com/ef4) * [napa3um](https://github.com/napa3um) * [drdaeman](https://github.com/drdaeman) * [booo](https://github.com/booo) * [neonstalwart](https://github.com/neonstalwart) * [homme](https://github.com/homme) * [bdunavant](https://github.com/bdunavant) * [tokumine](https://github.com/tokumine) * [shtylman](https://github.com/shtylman) * [cricri](https://github.com/cricri) * [AlexanderS](https://github.com/AlexanderS) * [ahtih](https://github.com/ahtih) * [chowey](https://github.com/chowey) * [kennym](https://github.com/kennym) ## Documentation Documentation is a work in progress primarily taking place on the github WIKI ### [Documentation](https://github.com/brianc/node-postgres/wiki) ### __PLEASE__ check out the WIKI If you have a question, post it to the FAQ section of the WIKI so everyone can read the answer ## Production Use * [yammer.com](http://www.yammer.com) * [bayt.com](http://bayt.com) _if you use node-postgres in production and would like your site listed here, fork & add it_ ## Help If you need help or run into _any_ issues getting node-postgres to work on your system please report a bug or contact me directly. I am usually available via google-talk at my github account public email address. ## License Copyright (c) 2010 Brian Carlson (brian.m.carlson@gmail.com) Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. brianc-node-postgres-e5c48f3/benchmark/000077500000000000000000000000001176777237400201415ustar00rootroot00000000000000brianc-node-postgres-e5c48f3/benchmark/js-versus-native-bench.js000066400000000000000000000037121176777237400250040ustar00rootroot00000000000000var pg = require(__dirname + '/../lib') var pgNative = require(__dirname + '/../lib/native'); var bencher = require('bencher'); var helper = require(__dirname + '/../test/test-helper') var conString = helper.connectionString() var round = function(num) { return Math.round((num*1000))/1000 } var doBenchmark = function() { var bench = bencher({ name: 'js/native compare', repeat: 1000, actions: [{ name: 'javascript client - simple query', run: function(next) { var query = client.query('SELECT name, age FROM person WHERE age > 10'); query.on('end', function() { next(); }); } },{ name: 'native client - simple query', run: function(next) { var query = nativeClient.query('SELECT name FROM person WHERE age > $1', [10]); query.on('end', function() { next(); }); } }, { name: 'javascript client - parameterized query', run: function(next) { var query = client.query('SELECT name, age FROM person WHERE age > $1', [10]); query.on('end', function() { next(); }); } },{ name: 'native client - parameterized query', run: function(next) { var query = nativeClient.query('SELECT name, age FROM person WHERE age > $1', [10]); query.on('end', function() { next(); }); } }] }); bench(function(result) { console.log(); console.log("%s (%d repeats):", result.name, result.repeat) result.actions.forEach(function(action) { console.log(" %s: \n average: %d ms\n total: %d ms", action.name, round(action.meanTime), round(action.totalTime)); }) client.end(); nativeClient.end(); }) } var client = new pg.Client(conString); var nativeClient = new pgNative.Client(conString); client.connect(); client.on('connect', function() { nativeClient.connect(); nativeClient.on('connect', function() { doBenchmark(); }); }); brianc-node-postgres-e5c48f3/benchmark/large-datatset-bench.js000066400000000000000000000065431176777237400244650ustar00rootroot00000000000000var pg = require(__dirname + '/../lib') var bencher = require('bencher'); var helper = require(__dirname + '/../test/test-helper') var conString = helper.connectionString() var round = function(num) { return Math.round((num*1000))/1000 } var doBenchmark = function(cb) { var bench = bencher({ name: 'select large sets', repeat: 10, actions: [{ name: 'selecting string', run: function(next) { var query = client.query('SELECT name FROM items'); query.on('error', function(er) { console.log(er);throw er; }); query.on('end', function() { next(); }); } }, { name: 'selecting integer', run: function(next) { var query = client.query('SELECT count FROM items'); query.on('error', function(er) { console.log(er);throw er; }); query.on('end', function() { next(); }) } }, { name: 'selecting date', run: function(next) { var query = client.query('SELECT created FROM items'); query.on('error', function(er) { console.log(er);throw er; }); query.on('end', function() { next(); }) } }, { name: 'selecting row', run: function(next) { var query = client.query('SELECT * FROM items'); query.on('end', function() { next(); }) } }, { name: 'loading all rows into memory', run: function(next) { var query = client.query('SELECT * FROM items', next); } }] }); bench(function(result) { console.log(); console.log("%s (%d repeats):", result.name, result.repeat) result.actions.forEach(function(action) { console.log(" %s: \n average: %d ms\n total: %d ms", action.name, round(action.meanTime), round(action.totalTime)); }) client.end(); cb(); }) } var client = new pg.Client(conString); client.connect(); console.log(); console.log("creating temp table"); client.query("CREATE TEMP TABLE items(name VARCHAR(10), created TIMESTAMPTZ, count INTEGER)"); var count = 10000; console.log("inserting %d rows", count); for(var i = 0; i < count; i++) { var query = { name: 'insert', text: "INSERT INTO items(name, created, count) VALUES($1, $2, $3)", values: ["item"+i, new Date(2010, 01, 01, i, 0, 0), i] }; client.query(query); } client.once('drain', function() { console.log('done with insert. executing pure-javascript benchmark.'); doBenchmark(function() { var oldclient = client; client = new pg.native.Client(conString); client.on('error', function(err) { console.log(err); throw err; }); client.connect(); client.connect(); console.log(); console.log("creating temp table"); client.query("CREATE TEMP TABLE items(name VARCHAR(10), created TIMESTAMPTZ, count INTEGER)"); var count = 10000; console.log("inserting %d rows", count); for(var i = 0; i < count; i++) { var query = { name: 'insert', text: "INSERT INTO items(name, created, count) VALUES($1, $2, $3)", values: ["item"+i, new Date(2010, 01, 01, i, 0, 0), i] }; client.query(query); } client.once('drain', function() { console.log("executing native benchmark"); doBenchmark(function() { console.log("all done"); }) }) }); }); brianc-node-postgres-e5c48f3/benchmark/simple-query-bench.js000066400000000000000000000030201176777237400242030ustar00rootroot00000000000000var pg = require(__dirname + '/../lib') var bencher = require('bencher'); var helper = require(__dirname + '/../test/test-helper') var conString = helper.connectionString() var round = function(num) { return Math.round((num*1000))/1000 } var doBenchmark = function() { var bench = bencher({ name: 'query compare', repeat: 1000, actions: [{ name: 'simple query', run: function(next) { var query = client.query('SELECT name FROM person WHERE age > 10'); query.on('end', function() { next(); }); } },{ name: 'unnamed prepared statement', run: function(next) { var query = client.query('SELECT name FROM person WHERE age > $1', [10]); query.on('end', function() { next(); }); } },{ name: 'named prepared statement', run: function(next) { var config = { name: 'get peeps', text: 'SELECT name FROM person WHERE age > $1', values: [10] } client.query(config).on('end', function() { next(); }); } }] }); bench(function(result) { console.log(); console.log("%s (%d repeats):", result.name, result.repeat) result.actions.forEach(function(action) { console.log(" %s: \n average: %d ms\n total: %d ms", action.name, round(action.meanTime), round(action.totalTime)); }) client.end(); }) } var client = new pg.Client(conString); client.connect(); client.connection.once('readyForQuery', doBenchmark) brianc-node-postgres-e5c48f3/binding.gyp000066400000000000000000000003101176777237400203340ustar00rootroot00000000000000{ 'targets': [ { 'target_name': 'binding', 'sources': [ 'src/binding.cc' ], 'include_dirs': ['/usr/include/postgresql'], 'libraries' : ['-lpq'] } ] } brianc-node-postgres-e5c48f3/lib/000077500000000000000000000000001176777237400167555ustar00rootroot00000000000000brianc-node-postgres-e5c48f3/lib/arrayParser.js000066400000000000000000000041371176777237400216130ustar00rootroot00000000000000function ArrayParser(source, converter) { this.source = source; this.converter = converter; this.pos = 0; this.entries = []; this.recorded = []; this.dimension = 0; if (!this.converter) { this.converter = function(entry) { return entry; }; } } ArrayParser.prototype.eof = function() { return this.pos >= this.source.length; }; ArrayParser.prototype.nextChar = function() { var c; if ((c = this.source[this.pos++]) === "\\") { return { char: this.source[this.pos++], escaped: true }; } else { return { char: c, escaped: false }; } }; ArrayParser.prototype.record = function(char) { return this.recorded.push(char); }; ArrayParser.prototype.newEntry = function(includeEmpty) { var entry; if (this.recorded.length > 0 || includeEmpty) { entry = this.recorded.join(""); if (entry === "NULL" && !includeEmpty) { entry = null; } if (entry !== null) { entry = this.converter(entry); } this.entries.push(entry); this.recorded = []; } }; ArrayParser.prototype.parse = function(nested) { var c, p, quote; if (nested == null) { nested = false; } quote = false; while (!this.eof()) { c = this.nextChar(); if (c.char === "{" && !quote) { this.dimension++; if (this.dimension > 1) { p = new ArrayParser(this.source.substr(this.pos - 1), this.converter); this.entries.push(p.parse(true)); this.pos += p.pos - 2; } } else if (c.char === "}" && !quote) { this.dimension--; if (this.dimension === 0) { this.newEntry(); if (nested) { return this.entries; } } } else if (c.char === '"' && !c.escaped) { if (quote) { this.newEntry(true); } quote = !quote; } else if (c.char === ',' && !quote) { this.newEntry(); } else { this.record(c.char); } } if (this.dimension !== 0) { throw "array dimension not balanced"; } return this.entries; }; module.exports = { create: function(source, converter){ return new ArrayParser(source, converter); } } brianc-node-postgres-e5c48f3/lib/binaryParsers.js000066400000000000000000000147161176777237400221500ustar00rootroot00000000000000var parseBits = function(data, bits, offset, invert, callback) { offset = offset || 0; invert = invert || false; callback = callback || function(lastValue, newValue, bits) { return (lastValue * Math.pow(2, bits)) + newValue; }; var offsetBytes = offset >> 3; var inv = function(value) { if (invert) { return ~value & 0xff; } return value; }; // read first (maybe partial) byte var mask = 0xff; var firstBits = 8 - (offset % 8); if (bits < firstBits) { mask = (0xff << (8 - bits)) & 0xff; firstBits = bits; } if (offset) { mask = mask >> (offset % 8); } var result = 0; if ((offset % 8) + bits >= 8) { result = callback(0, inv(data[offsetBytes]) & mask, firstBits); } // read bytes var bytes = (bits + offset) >> 3; for (var i = offsetBytes + 1; i < bytes; i++) { result = callback(result, inv(data[i]), 8); } // bits to read, that are not a complete byte var lastBits = (bits + offset) % 8; if (lastBits > 0) { result = callback(result, inv(data[bytes]) >> (8 - lastBits), lastBits); } return result; }; var parseFloatFromBits = function(data, precisionBits, exponentBits) { var bias = Math.pow(2, exponentBits - 1) - 1; var sign = parseBits(data, 1); var exponent = parseBits(data, exponentBits, 1); if (exponent === 0) return 0; // parse mantissa var precisionBitsCounter = 1; var parsePrecisionBits = function(lastValue, newValue, bits) { if (lastValue === 0) { lastValue = 1; } for (var i = 1; i <= bits; i++) { precisionBitsCounter /= 2; if ((newValue & (0x1 << (bits - i))) > 0) { lastValue += precisionBitsCounter; } } return lastValue; }; var mantissa = parseBits(data, precisionBits, exponentBits + 1, false, parsePrecisionBits); // special cases if (exponent == (Math.pow(2, exponentBits + 1) - 1)) { if (mantissa === 0) { return (sign === 0) ? Infinity : -Infinity; } return NaN; } // normale number return ((sign === 0) ? 1 : -1) * Math.pow(2, exponent - bias) * mantissa; }; var parseBool = function(value) { return (parseBits(value, 8) == 1); }; var parseInt16 = function(value) { if (parseBits(value, 1) == 1) { return -1 * (parseBits(value, 15, 1, true) + 1); } return parseBits(value, 15, 1); }; var parseInt32 = function(value) { if (parseBits(value, 1) == 1) { return -1 * (parseBits(value, 31, 1, true) + 1); } return parseBits(value, 31, 1); }; var parseInt64 = function(value) { if (parseBits(value, 1) == 1) { return -1 * (parseBits(value, 63, 1, true) + 1); } return parseBits(value, 63, 1); }; var parseFloat32 = function(value) { return parseFloatFromBits(value, 23, 8); }; var parseFloat64 = function(value) { return parseFloatFromBits(value, 52, 11); }; var parseNumeric = function(value) { var sign = parseBits(value, 16, 32); if (sign == 0xc000) { return NaN; } var weight = Math.pow(10000, parseBits(value, 16, 16)); var result = 0; var digits = []; var ndigits = parseBits(value, 16); for (var i = 0; i < ndigits; i++) { result += parseBits(value, 16, 64 + (16 * i)) * weight; weight /= 10000; } var scale = Math.pow(10, parseBits(value, 16, 48)); return ((sign === 0) ? 1 : -1) * Math.round(result * scale) / scale; }; var parseDate = function(value) { var sign = parseBits(value, 1); var rawValue = parseBits(value, 63, 1); // discard usecs and shift from 2000 to 1970 var result = new Date((((sign === 0) ? 1 : -1) * rawValue / 1000) + 946684800000); // add microseconds to the date result.usec = rawValue % 1000; result.getMicroSeconds = function() { return this.usec; }; result.setMicroSeconds = function(value) { this.usec = value; }; result.getUTCMicroSeconds = function() { return this.usec; }; return result; }; var parseArray = function(value) { var dim = parseBits(value, 32); var flags = parseBits(value, 32, 32); var elementType = parseBits(value, 32, 64); var offset = 96; var dims = []; for (var i = 0; i < dim; i++) { // parse dimension dims[i] = parseBits(value, 32, offset); offset += 32; // ignore lower bounds offset += 32; } var parseElement = function(elementType) { // parse content length var length = parseBits(value, 32, offset); offset += 32; // parse null values if (length == 0xffffffff) { return null; } if ((elementType == 0x17) || (elementType == 0x14)) { // int/bigint var result = parseBits(value, length * 8, offset); offset += length * 8; return result; } else if (elementType == 0x19) { // string var result = value.toString(this.encoding, offset >> 3, (offset += (length << 3)) >> 3); return result; } else { console.log("ERROR: ElementType not implemented: " + elementType); } }; var parse = function(dimension, elementType) { var array = []; if (dimension.length > 1) { var count = dimension.shift(); for (var i = 0; i < count; i++) { array[i] = parse(dimension, elementType); } dimension.unshift(count); } else { for (var i = 0; i < dimension[0]; i++) { array[i] = parseElement(elementType); } } return array; }; return parse(dims, elementType); }; var parseText = function(value) { return value.toString('utf8'); }; var parseBool = function(value) { return (parseBits(value, 8) > 0); }; var init = function(register) { register(20, parseInt64); register(21, parseInt16); register(23, parseInt32); register(26, parseInt32); register(1700, parseNumeric); register(700, parseFloat32); register(701, parseFloat64); register(16, parseBool); register(1114, parseDate); register(1184, parseDate); register(1007, parseArray); register(1016, parseArray); register(1008, parseArray); register(1009, parseArray); register(25, parseText); }; module.exports = { init: init }; brianc-node-postgres-e5c48f3/lib/client.js000066400000000000000000000132211176777237400205700ustar00rootroot00000000000000var crypto = require('crypto'); var EventEmitter = require('events').EventEmitter; var util = require('util'); var Query = require(__dirname + '/query'); var utils = require(__dirname + '/utils'); var defaults = require(__dirname + '/defaults'); var Connection = require(__dirname + '/connection'); var Client = function(config) { EventEmitter.call(this); if(typeof config === 'string') { config = utils.normalizeConnectionInfo(config) } config = config || {}; this.user = config.user || defaults.user; this.database = config.database || defaults.database; this.port = config.port || defaults.port; this.host = config.host || defaults.host; this.connection = config.connection || new Connection({stream: config.stream}); this.queryQueue = []; this.password = config.password || defaults.password; this.binary = config.binary || defaults.binary; this.encoding = 'utf8'; this.processID = null; this.secretKey = null; var self = this; }; util.inherits(Client, EventEmitter); var p = Client.prototype; p.connect = function(callback) { var self = this; var con = this.connection; if(this.host && this.host.indexOf('/') === 0) { con.connect(this.host + '/.s.PGSQL.' + this.port); } else { con.connect(this.port, this.host); } //once connection is established send startup message con.on('connect', function() { con.startup({ user: self.user, database: self.database }); }); //password request handling con.on('authenticationCleartextPassword', function() { con.password(self.password); }); //password request handling con.on('authenticationMD5Password', function(msg) { var inner = Client.md5(self.password + self.user); var outer = Client.md5(inner + msg.salt.toString('binary')); var md5password = "md5" + outer; con.password(md5password); }); con.once('backendKeyData', function(msg) { self.processID = msg.processID; self.secretKey = msg.secretKey; }); //hook up query handling events to connection //after the connection initially becomes ready for queries con.once('readyForQuery', function() { //delegate row descript to active query con.on('rowDescription', function(msg) { self.activeQuery.handleRowDescription(msg); }); //delegate datarow to active query con.on('dataRow', function(msg) { self.activeQuery.handleDataRow(msg); }); //TODO should query gain access to connection? con.on('portalSuspended', function(msg) { self.activeQuery.getRows(con); }); con.on('commandComplete', function(msg) { //delegate command complete to query self.activeQuery.handleCommandComplete(msg); //need to sync after each command complete of a prepared statement if(self.activeQuery.isPreparedStatement) { con.sync(); } }); if (!callback) { self.emit('connect'); } else { callback(null,self); //remove callback for proper error handling after the connect event callback = null; } con.on('notification', function(msg) { self.emit('notification', msg); }); }); con.on('readyForQuery', function() { if(self.activeQuery) { self.activeQuery.handleReadyForQuery(); } self.activeQuery = null; self.readyForQuery = true; self._pulseQueryQueue(); }); con.on('error', function(error) { if(!self.activeQuery) { if(!callback) { self.emit('error', error); } else { callback(error); } } else { //need to sync after error during a prepared statement if(self.activeQuery.isPreparedStatement) { con.sync(); } self.activeQuery.handleError(error); self.activeQuery = null; } }); con.on('notice', function(msg) { self.emit('notice', msg); }); }; p.cancel = function(client, query) { if (client.activeQuery == query) { var con = this.connection; if(this.host && this.host.indexOf('/') === 0) { con.connect(this.host + '/.s.PGSQL.' + this.port); } else { con.connect(this.port, this.host); } //once connection is established send cancel message con.on('connect', function() { con.cancel(client.processID, client.secretKey); }); } else if (client.queryQueue.indexOf(query) != -1) client.queryQueue.splice(client.queryQueue.indexOf(query), 1); }; p._pulseQueryQueue = function() { if(this.readyForQuery===true) { this.activeQuery = this.queryQueue.shift(); if(this.activeQuery) { this.readyForQuery = false; this.hasExecuted = true; this.activeQuery.submit(this.connection); } else if(this.hasExecuted) { this.activeQuery = null; this._drainPaused > 0 ? this._drainPaused++ : this.emit('drain') } } }; p.query = function(config, values, callback) { //can take in strings or config objects config = (typeof(config) == 'string') ? { text: config } : config; if (this.binary && !('binary' in config)) { config.binary = true; } if(values) { if(typeof values === 'function') { callback = values; } else { config.values = values; } } config.callback = callback; var query = new Query(config); this.queryQueue.push(query); this._pulseQueryQueue(); return query; }; //prevents client from otherwise emitting 'drain' event until 'resumeDrain' is called p.pauseDrain = function() { this._drainPaused = 1; }; //resume raising 'drain' event p.resumeDrain = function() { if(this._drainPaused > 1) { this.emit('drain'); } this._drainPaused = 0; }; p.end = function() { this.connection.end(); }; Client.md5 = function(string) { return crypto.createHash('md5').update(string).digest('hex'); }; module.exports = Client; brianc-node-postgres-e5c48f3/lib/connection.js000066400000000000000000000256011176777237400214560ustar00rootroot00000000000000var net = require('net'); var crypto = require('crypto'); var EventEmitter = require('events').EventEmitter; var util = require('util'); var utils = require(__dirname + '/utils'); var Writer = require(__dirname + '/writer'); var Connection = function(config) { EventEmitter.call(this); config = config || {}; this.stream = config.stream || new net.Stream(); this.lastBuffer = false; this.lastOffset = 0; this.buffer = null; this.offset = null; this.encoding = 'utf8'; this.parsedStatements = {}; this.writer = new Writer(); }; util.inherits(Connection, EventEmitter); var p = Connection.prototype; p.connect = function(port, host) { if(this.stream.readyState === 'closed'){ this.stream.connect(port, host); } else if(this.stream.readyState == 'open') { this.emit('connect'); } var self = this; this.stream.on('connect', function() { self.emit('connect'); }); this.stream.on('data', function(buffer) { self.setBuffer(buffer); var msg; while(msg = self.parseMessage()) { self.emit('message', msg); self.emit(msg.name, msg); } }); this.stream.on('error', function(error) { self.emit('error', error); }); }; p.startup = function(config) { var bodyBuffer = this.writer .addInt16(3) .addInt16(0) .addCString('user') .addCString(config.user) .addCString('database') .addCString(config.database) .addCString('').flush(); //this message is sent without a code var length = bodyBuffer.length + 4; var buffer = new Writer() .addInt32(length) .add(bodyBuffer) .join(); this.stream.write(buffer); }; p.cancel = function(processID, secretKey) { var bodyBuffer = this.writer .addInt16(1234) .addInt16(5678) .addInt32(processID) .addInt32(secretKey) .addCString('').flush(); var length = bodyBuffer.length + 4; var buffer = new Writer() .addInt32(length) .add(bodyBuffer) .join(); this.stream.write(buffer); }; p.password = function(password) { //0x70 = 'p' this._send(0x70, this.writer.addCString(password)); }; p._send = function(code, more) { if(!this.stream.writable) return false; if(more === true) { this.writer.addHeader(code); } else { return this.stream.write(this.writer.flush(code)); } } p.query = function(text) { //0x51 = Q this.stream.write(this.writer.addCString(text).flush(0x51)); }; //send parse message //"more" === true to buffer the message until flush() is called p.parse = function(query, more) { //expect something like this: // { name: 'queryName', // text: 'select * from blah', // types: ['int8', 'bool'] } //normalize missing query names to allow for null query.name = query.name || ''; //normalize null type array query.types = query.types || []; var len = query.types.length; var buffer = this.writer .addCString(query.name) //name of query .addCString(query.text) //actual query text .addInt16(len); for(var i = 0; i < len; i++) { buffer.addInt32(query.types[i]); } var code = 0x50; this._send(code, more); }; //send bind message //"more" === true to buffer the message until flush() is called p.bind = function(config, more) { //normalize config config = config || {}; config.portal = config.portal || ''; config.statement = config.statement || ''; config.binary = config.binary || false; var values = config.values || []; var len = values.length; var buffer = this.writer .addCString(config.portal) .addCString(config.statement) .addInt16(0) //always use default text format .addInt16(len); //number of parameters for(var i = 0; i < len; i++) { var val = values[i]; if(val === null || typeof val === "undefined") { buffer.addInt32(-1); } else { val = val.toString(); buffer.addInt32(Buffer.byteLength(val)); buffer.addString(val); } } if (config.binary) { buffer.addInt16(1); // format codes to use binary buffer.addInt16(1); } else { buffer.addInt16(0); // format codes to use text } //0x42 = 'B' this._send(0x42, more); }; //send execute message //"more" === true to buffer the message until flush() is called p.execute = function(config, more) { config = config || {}; config.portal = config.portal || ''; config.rows = config.rows || ''; var buffer = this.writer .addCString(config.portal) .addInt32(config.rows); //0x45 = 'E' this._send(0x45, more); }; var emptyBuffer = Buffer(0); p.flush = function() { //0x48 = 'H' this.writer.add(emptyBuffer) this._send(0x48); } p.sync = function() { //clear out any pending data in the writer this.writer.flush(0) this.writer.add(emptyBuffer); this._send(0x53); }; p.end = function() { //0x58 = 'X' this.writer.add(emptyBuffer); this._send(0x58); }; p.describe = function(msg, more) { this.writer.addCString(msg.type + (msg.name || '')); this._send(0x44, more); }; //parsing methods p.setBuffer = function(buffer) { if(this.lastBuffer) { //we have unfinished biznaz //need to combine last two buffers var remaining = this.lastBuffer.length - this.lastOffset; var combinedBuffer = new Buffer(buffer.length + remaining); this.lastBuffer.copy(combinedBuffer, 0, this.lastOffset); buffer.copy(combinedBuffer, remaining, 0); buffer = combinedBuffer; } this.buffer = buffer; this.offset = 0; }; p.parseMessage = function() { var remaining = this.buffer.length - (this.offset); if(remaining < 5) { //cannot read id + length without at least 5 bytes //just abort the read now this.lastBuffer = this.buffer; this.lastOffset = this.offset; return false; } //read message id code var id = this.buffer[this.offset++]; //read message length var length = this.parseInt32(); if(remaining <= length) { this.lastBuffer = this.buffer; //rewind the last 5 bytes we read this.lastOffset = this.offset-5; return false; } var msg = { length: length }; switch(id) { case 0x52: //R msg.name = 'authenticationOk'; return this.parseR(msg); case 0x53: //S msg.name = 'parameterStatus'; return this.parseS(msg); case 0x4b: //K msg.name = 'backendKeyData'; return this.parseK(msg); case 0x43: //C msg.name = 'commandComplete'; return this.parseC(msg); case 0x5a: //Z msg.name = 'readyForQuery'; return this.parseZ(msg); case 0x54: //T msg.name = 'rowDescription'; return this.parseT(msg); case 0x44: //D msg.name = 'dataRow'; return this.parseD(msg); case 0x45: //E msg.name = 'error'; return this.parseE(msg); case 0x4e: //N msg.name = 'notice'; return this.parseN(msg); case 0x31: //1 msg.name = 'parseComplete'; return msg; case 0x32: //2 msg.name = 'bindComplete'; return msg; case 0x41: //A msg.name = 'notification'; return this.parseA(msg); case 0x6e: //n msg.name = 'noData'; return msg; case 0x49: //I msg.name = 'emptyQuery'; return msg; case 0x73: //s msg.name = 'portalSuspended'; return msg; default: throw new Error("Unrecognized message code " + id); } }; p.parseR = function(msg) { var code = 0; if(msg.length === 8) { code = this.parseInt32(); if(code === 3) { msg.name = 'authenticationCleartextPassword'; } return msg; } if(msg.length === 12) { code = this.parseInt32(); if(code === 5) { //md5 required msg.name = 'authenticationMD5Password'; msg.salt = new Buffer(4); this.buffer.copy(msg.salt, 0, this.offset, this.offset + 4); this.offset += 4; return msg; } } throw new Error("Unknown authenticatinOk message type" + util.inspect(msg)); }; p.parseS = function(msg) { msg.parameterName = this.parseCString(); msg.parameterValue = this.parseCString(); return msg; }; p.parseK = function(msg) { msg.processID = this.parseInt32(); msg.secretKey = this.parseInt32(); return msg; }; p.parseC = function(msg) { msg.text = this.parseCString(); return msg; }; p.parseZ = function(msg) { msg.status = this.readChar(); return msg; }; p.parseT = function(msg) { msg.fieldCount = this.parseInt16(); var fields = []; for(var i = 0; i < msg.fieldCount; i++){ fields[i] = this.parseField(); } msg.fields = fields; return msg; }; p.parseField = function() { var field = { name: this.parseCString(), tableID: this.parseInt32(), columnID: this.parseInt16(), dataTypeID: this.parseInt32(), dataTypeSize: this.parseInt16(), dataTypeModifier: this.parseInt32(), format: this.parseInt16() === 0 ? 'text' : 'binary' }; return field; }; p.parseD = function(msg) { var fieldCount = this.parseInt16(); var fields = []; for(var i = 0; i < fieldCount; i++) { var length = this.parseInt32(); fields[i] = (length === -1 ? null : this.readBytes(length)) }; msg.fieldCount = fieldCount; msg.fields = fields; return msg; }; //parses error p.parseE = function(input) { var fields = {}; var msg, item; var fieldType = this.readString(1); while(fieldType != '\0') { fields[fieldType] = this.parseCString(); fieldType = this.readString(1); } if (input.name === 'error') { // the msg is an Error instance msg = new Error(fields.M); for (item in input) { // copy input properties to the error if (input.hasOwnProperty(item)) { msg[item] = input[item]; } } } else { // the msg is an object literal msg = input; msg.message = fields.M; } msg.severity = fields.S; msg.code = fields.C; msg.detail = fields.D; msg.hint = fields.H; msg.position = fields.P; msg.internalPosition = fields.p; msg.internalQuery = fields.q; msg.where = fields.W; msg.file = fields.F; msg.line = fields.L; msg.routine = fields.R; return msg; }; //same thing, different name p.parseN = p.parseE; p.parseA = function(msg) { msg.processId = this.parseInt32(); msg.channel = this.parseCString(); msg.payload = this.parseCString(); return msg; }; p.readChar = function() { return Buffer([this.buffer[this.offset++]]).toString(this.encoding); }; p.parseInt32 = function() { var value = this.peekInt32(); this.offset += 4; return value; }; p.peekInt32 = function(offset) { offset = offset || this.offset; var buffer = this.buffer; return ((buffer[offset++] << 24) + (buffer[offset++] << 16) + (buffer[offset++] << 8) + buffer[offset++]); }; p.parseInt16 = function() { return ((this.buffer[this.offset++] << 8) + (this.buffer[this.offset++] << 0)); }; p.readString = function(length) { return this.buffer.toString(this.encoding, this.offset, (this.offset += length)); }; p.readBytes = function(length) { return this.buffer.slice(this.offset, this.offset += length); }; p.parseCString = function() { var start = this.offset; while(this.buffer[this.offset++]) { }; return this.buffer.toString(this.encoding, start, this.offset - 1); }; //end parsing methods module.exports = Connection; brianc-node-postgres-e5c48f3/lib/defaults.js000066400000000000000000000013011176777237400211150ustar00rootroot00000000000000module.exports = { //database user's name user: process.env.USER, //name of database to connect database: process.env.USER, //database user's password password: null, //database port port: 5432, //number of rows to return at a time from a prepared statement's //portal. 0 will return all rows at once rows: 0, //number of connections to use in connection pool //0 will disable connection pooling poolSize: 10, //max milliseconds a client can go unused before it is removed //from the pool and destroyed poolIdleTimeout: 30000, //frequeny to check for idle clients within the client pool reapIntervalMillis: 1000, // binary result mode binary: false } brianc-node-postgres-e5c48f3/lib/index.js000066400000000000000000000053541176777237400204310ustar00rootroot00000000000000var EventEmitter = require('events').EventEmitter; var util = require('util'); var Client = require(__dirname+'/client'); var defaults = require(__dirname + '/defaults'); //external genericPool module var genericPool = require('generic-pool'); //cache of existing client pools var pools = {}; var PG = function(clientConstructor) { EventEmitter.call(this); this.Client = clientConstructor; this.Connection = require(__dirname + '/connection'); this.defaults = defaults; }; util.inherits(PG, EventEmitter); PG.prototype.end = function() { Object.keys(pools).forEach(function(name) { var pool = pools[name]; pool.drain(function() { pool.destroyAllNow(); }); }) } PG.prototype.connect = function(config, callback) { var self = this; var c = config; var cb = callback; //allow for no config to be passed if(typeof c === 'function') { cb = c; c = defaults; } //get unique pool name even if object was used as config var poolName = typeof(c) === 'string' ? c : c.user+c.host+c.port+c.database; var pool = pools[poolName]; if(pool) return pool.acquire(cb); var pool = pools[poolName] = genericPool.Pool({ name: poolName, create: function(callback) { var client = new self.Client(c); client.connect(); var connectError = function(err) { client.removeListener('connect', connectSuccess); callback(err, null); }; var connectSuccess = function() { client.removeListener('error', connectError); //handle connected client background errors by emitting event //via the pg object and then removing errored client from the pool client.on('error', function(e) { self.emit('error', e, client); pool.destroy(client); }); callback(null, client); }; client.once('connect', connectSuccess); client.once('error', connectError); client.on('drain', function() { pool.release(client); }); }, destroy: function(client) { client.end(); }, max: defaults.poolSize, idleTimeoutMillis: defaults.poolIdleTimeout, reapIntervalMillis: defaults.reapIntervalMillis }); return pool.acquire(cb); } // cancel the query runned by the given client PG.prototype.cancel = function(config, client, query) { var c = config; //allow for no config to be passed if(typeof c === 'function') c = defaults; var cancellingClient = new this.Client(c); cancellingClient.cancel(client, query); } module.exports = new PG(Client); //lazy require native module...the native module may not have installed module.exports.__defineGetter__("native", function() { delete module.exports.native; return (module.exports.native = new PG(require(__dirname + '/native'))); }) brianc-node-postgres-e5c48f3/lib/native/000077500000000000000000000000001176777237400202435ustar00rootroot00000000000000brianc-node-postgres-e5c48f3/lib/native/index.js000066400000000000000000000113301176777237400217060ustar00rootroot00000000000000//require the c++ bindings & export to javascript var EventEmitter = require('events').EventEmitter; var utils = require(__dirname + "/../utils"); var binding; try{ //v0.5.x binding = require(__dirname + '/../../build/Release/binding.node'); } catch(e) { //v0.4.x binding = require(__dirname + '/../../build/default/binding'); } var Connection = binding.Connection; var types = require(__dirname + "/../types"); var NativeQuery = require(__dirname + '/query'); var EventEmitter = require('events').EventEmitter; var p = Connection.prototype; for(var k in EventEmitter.prototype) { p[k] = EventEmitter.prototype[k]; } var nativeConnect = p.connect; p.connect = function(cb) { var self = this; utils.buildLibpqConnectionString(this._config, function(err, conString) { if(err) { return cb ? cb(err) : self.emit('error', err); } nativeConnect.call(self, conString); if(cb) { var errCallback; var connectCallback = function() { //remove single-fire connection error callback self.removeListener('error', errCallback); cb(null); } errCallback = function(err) { //remove singel-fire connection success callback self.removeListener('connect', connectCallback); cb(err); } self.once('connect', connectCallback); self.once('error', errCallback); } }) } p.query = function(config, values, callback) { var q = new NativeQuery(config, values, callback); this._queryQueue.push(q); this._pulseQueryQueue(); return q; } var nativeCancel = p.cancel; p.cancel = function(client, query) { if (client._activeQuery == query) this.connect(nativeCancel.bind(client)); else if (client._queryQueue.indexOf(query) != -1) client._queryQueue.splice(client._queryQueue.indexOf(query), 1); }; p._pulseQueryQueue = function(initialConnection) { if(!this._connected) { return; } if(this._activeQuery) { return; } var query = this._queryQueue.shift(); if(!query) { if(!initialConnection) { this._drainPaused ? this._drainPaused++ : this.emit('drain'); } return; } this._activeQuery = query; if(query.name) { if(this._namedQueries[query.name]) { this._sendQueryPrepared(query.name, query.values||[]); } else { this._namedQuery = true; this._namedQueries[query.name] = true; this._sendPrepare(query.name, query.text, (query.values||[]).length); } } else if(query.values) { //call native function this._sendQueryWithParams(query.text, query.values) } else { //call native function this._sendQuery(query.text); } } p.pauseDrain = function() { this._drainPaused = 1; }; p.resumeDrain = function() { if(this._drainPaused > 1) { this.emit('drain') }; this._drainPaused = 0; }; var clientBuilder = function(config) { config = config || {}; var connection = new Connection(); connection._queryQueue = []; connection._namedQueries = {}; connection._activeQuery = null; connection._config = utils.normalizeConnectionInfo(config); //attach properties to normalize interface with pure js client connection.user = connection._config.user; connection.password = connection._config.password; connection.database = connection._config.database; connection.host = connection._config.host; connection.port = connection._config.port; connection.on('connect', function() { connection._connected = true; connection._pulseQueryQueue(true); }); //proxy some events to active query connection.on('_row', function(row) { connection._activeQuery.handleRow(row); }); connection.on('_cmdStatus', function(status) { var meta = { }; meta.command = status.command.split(' ')[0]; meta.rowCount = parseInt(status.value); connection._lastMeta = meta; }); //TODO: emit more native error properties (make it match js error) connection.on('_error', function(err) { //create Error object from object literal var error = new Error(err.message || "Unknown native driver error"); for(var key in err) { error[key] = err[key]; } //give up on trying to wait for named query prepare this._namedQuery = false; if(connection._activeQuery) { connection._activeQuery.handleError(error); } else { connection.emit('error', error); } }); connection.on('_readyForQuery', function() { var q = this._activeQuery; //a named query finished being prepared if(this._namedQuery) { this._namedQuery = false; this._sendQueryPrepared(q.name, q.values||[]); } else { connection._activeQuery.handleReadyForQuery(connection._lastMeta); connection._activeQuery = null; connection._pulseQueryQueue(); } }); return connection; }; module.exports = clientBuilder; brianc-node-postgres-e5c48f3/lib/native/query.js000066400000000000000000000042041176777237400217460ustar00rootroot00000000000000var EventEmitter = require('events').EventEmitter; var util = require('util'); var types = require(__dirname + "/../types"); //event emitter proxy var NativeQuery = function(text, values, callback) { //TODO there are better ways to detect overloads if(typeof text == 'object') { this.text = text.text; this.values = text.values; this.name = text.name; if(typeof values === 'function') { this.callback = values; } else if(values) { this.values = values; this.callback = callback; } } else { this.text = text; this.values = values; this.callback = callback; if(typeof values == 'function') { this.values = null; this.callback = values; } } if(this.callback) { this.rows = []; } //normalize values if(this.values) { for(var i = 0, len = this.values.length; i < len; i++) { var item = this.values[i]; switch(typeof item) { case 'undefined': this.values[i] = null; break; case 'object': this.values[i] = item === null ? null : JSON.stringify(item); break; case 'string': //value already string break; default: //numbers this.values[i] = item.toString(); } } } EventEmitter.call(this); }; util.inherits(NativeQuery, EventEmitter); var p = NativeQuery.prototype; //maps from native rowdata into api compatible row object var mapRowData = function(row) { var result = {}; for(var i = 0, len = row.length; i < len; i++) { var item = row[i]; result[item.name] = item.value == null ? null : types.getTypeParser(item.type, 'text')(item.value); } return result; } p.handleRow = function(rowData) { var row = mapRowData(rowData); if(this.callback) { this.rows.push(row); } this.emit('row', row); }; p.handleError = function(error) { if(this.callback) { this.callback(error); this.callback = null; } else { this.emit('error', error); } } p.handleReadyForQuery = function(meta) { if(this.callback) { (meta || {}).rows = this.rows; this.callback(null, meta); } this.emit('end'); }; module.exports = NativeQuery; brianc-node-postgres-e5c48f3/lib/query.js000066400000000000000000000073101176777237400204610ustar00rootroot00000000000000var EventEmitter = require('events').EventEmitter; var util = require('util'); var Result = require(__dirname + "/result"); var Types = require(__dirname + "/types"); var Query = function(config) { this.text = config.text; this.values = config.values; this.rows = config.rows; this.types = config.types; this.name = config.name; this.binary = config.binary; //use unique portal name each time this.portal = config.portal || "" this.callback = config.callback; this._fieldNames = []; this._fieldConverters = []; this._result = new Result(); this.isPreparedStatement = false; EventEmitter.call(this); }; util.inherits(Query, EventEmitter); var p = Query.prototype; p.requiresPreparation = function() { return (this.values || 0).length > 0 || this.name || this.rows || this.binary; }; var noParse = function(val) { return val; }; //associates row metadata from the supplied //message with this query object //metadata used when parsing row results p.handleRowDescription = function(msg) { this._fieldNames = []; this._fieldConverters = []; var len = msg.fields.length; for(var i = 0; i < len; i++) { var field = msg.fields[i]; var format = field.format; this._fieldNames[i] = field.name; this._fieldConverters[i] = Types.getTypeParser(field.dataTypeID, format); }; }; p.handleDataRow = function(msg) { var self = this; var row = {}; for(var i = 0; i < msg.fields.length; i++) { var rawValue = msg.fields[i]; if(rawValue === null) { //leave null values alone row[self._fieldNames[i]] = null; } else { //convert value to javascript row[self._fieldNames[i]] = self._fieldConverters[i](rawValue); } } self.emit('row', row, self._result); //if there is a callback collect rows if(self.callback) { self._result.addRow(row); } }; p.handleCommandComplete = function(msg) { this._result.addCommandComplete(msg); }; p.handleReadyForQuery = function() { if(this.callback) { this.callback(null, this._result); } this.emit('end', this._result); }; p.handleError = function(err) { //if callback supplied do not emit error event as uncaught error //events will bubble up to node process if(this.callback) { this.callback(err) } else { this.emit('error', err); } this.emit('end'); }; p.submit = function(connection) { var self = this; if(this.requiresPreparation()) { this.prepare(connection); } else { connection.query(this.text); } }; p.hasBeenParsed = function(connection) { return this.name && connection.parsedStatements[this.name]; }; p.getRows = function(connection) { connection.execute({ portal: this.portalName, rows: this.rows }, true); connection.flush(); }; p.prepare = function(connection) { var self = this; //prepared statements need sync to be called after each command //complete or when an error is encountered this.isPreparedStatement = true; //TODO refactor this poor encapsulation if(!this.hasBeenParsed(connection)) { connection.parse({ text: self.text, name: self.name, types: self.types }, true); connection.parsedStatements[this.name] = true; } //TODO is there some better way to prepare values for the database? if(self.values) { self.values = self.values.map(function(val) { return (val instanceof Date) ? JSON.stringify(val) : val; }); } //http://developer.postgresql.org/pgdocs/postgres/protocol-flow.html#PROTOCOL-FLOW-EXT-QUERY connection.bind({ portal: self.portalName, statement: self.name, values: self.values, binary: self.binary }, true); connection.describe({ type: 'P', name: self.portalName || "" }, true); this.getRows(connection); }; module.exports = Query; brianc-node-postgres-e5c48f3/lib/result.js000066400000000000000000000012711176777237400206320ustar00rootroot00000000000000//result object returned from query //in the 'end' event and also //passed as second argument to provided callback var Result = function() { this.rows = []; }; var p = Result.prototype; var matchRegexp = /([A-Za-z]+) (\d+ )?(\d+)?/ //adds a command complete message p.addCommandComplete = function(msg) { var match = matchRegexp.exec(msg.text); if(match) { this.command = match[1]; //match 3 will only be existing on insert commands if(match[3]) { this.rowCount = parseInt(match[3]); this.oid = parseInt(match[2]); } else { this.rowCount = parseInt(match[2]); } } }; p.addRow = function(row) { this.rows.push(row); }; module.exports = Result; brianc-node-postgres-e5c48f3/lib/textParsers.js000066400000000000000000000101041176777237400216330ustar00rootroot00000000000000var arrayParser = require(__dirname + "/arrayParser.js"); //parses PostgreSQL server formatted date strings into javascript date objects var parseDate = function(isoDate) { //TODO this could do w/ a refactor var dateMatcher = /(\d{4})-(\d{2})-(\d{2}) (\d{2}):(\d{2}):(\d{2})(\.\d{1,})?/; var match = dateMatcher.exec(isoDate); //could not parse date if(!match) { return null; } var year = match[1]; var month = parseInt(match[2],10)-1; var day = match[3]; var hour = parseInt(match[4],10); var min = parseInt(match[5],10); var seconds = parseInt(match[6], 10); var miliString = match[7]; var mili = 0; if(miliString) { mili = 1000 * parseFloat(miliString); } var tZone = /([Z|+\-])(\d{2})?(\d{2})?/.exec(isoDate.split(' ')[1]); //minutes to adjust for timezone var tzAdjust = 0; if(tZone) { var type = tZone[1]; switch(type) { case 'Z': break; case '-': tzAdjust = -(((parseInt(tZone[2],10)*60)+(parseInt(tZone[3]||0,10)))); break; case '+': tzAdjust = (((parseInt(tZone[2],10)*60)+(parseInt(tZone[3]||0,10)))); break; default: throw new Error("Unidentifed tZone part " + type); } } var utcOffset = Date.UTC(year, month, day, hour, min, seconds, mili); var date = new Date(utcOffset - (tzAdjust * 60* 1000)); return date; }; var parseBool = function(val) { return val === 't'; } var parseIntegerArray = function(val) { if(!val) return null; var p = arrayParser.create(val, function(entry){ if(entry != null) entry = parseInt(entry, 10); return entry; }); return p.parse(); }; var parseFloatArray = function(val) { if(!val) return null; var p = arrayParser.create(val, function(entry){ if(entry != null) entry = parseFloat(entry, 10); return entry; }); return p.parse(); }; var parseStringArray = function(val) { if(!val) return null; var p = arrayParser.create(val); return p.parse(); }; var NUM = '([+-]?\\d+)'; var YEAR = NUM + '\\s+years?'; var MON = NUM + '\\s+mons?'; var DAY = NUM + '\\s+days?'; var TIME = '([+-])?(\\d\\d):(\\d\\d):(\\d\\d)'; var INTERVAL = [YEAR,MON,DAY,TIME].map(function(p){ return "("+p+")?" }).join('\\s*'); var parseInterval = function(val) { if (!val) return {}; var m = new RegExp(INTERVAL).exec(val); var i = {}; if (m[2]) i.years = parseInt(m[2], 10); if (m[4]) i.months = parseInt(m[4], 10); if (m[6]) i.days = parseInt(m[6], 10); if (m[9]) i.hours = parseInt(m[9], 10); if (m[10]) i.minutes = parseInt(m[10], 10); if (m[11]) i.seconds = parseInt(m[11], 10); if (m[8] == '-'){ if (i.hours) i.hours *= -1; if (i.minutes) i.minutes *= -1; if (i.seconds) i.seconds *= -1; } for (field in i){ if (i[field] == 0) delete i[field]; } return i; }; var parseByteA = function(val) { return new Buffer(val.replace(/\\([0-7]{3})/g, function (full_match, code) { return String.fromCharCode(parseInt(code, 8)); }).replace(/\\\\/g, "\\"), "binary"); } var maxLen = Number.MAX_VALUE.toString().length var parseInteger = function(val) { return parseInt(val, 10); } var init = function(register) { register(20, parseInteger); register(21, parseInteger); register(23, parseInteger); register(26, parseInteger); register(1700, function(val){ if(val.length > maxLen) { console.warn('WARNING: value %s is longer than max supported numeric value in javascript. Possible data loss', val) } return parseFloat(val); }); register(700, parseFloat); register(701, parseFloat); register(16, parseBool); register(1114, parseDate); register(1184, parseDate); register(1005, parseIntegerArray); // _int2 register(1007, parseIntegerArray); // _int4 register(1016, parseIntegerArray); // _int8 register(1021, parseFloatArray); // _float4 register(1022, parseFloatArray); // _float8 register(1231, parseIntegerArray); // _numeric register(1008, parseStringArray); register(1009, parseStringArray); register(1186, parseInterval); register(17, parseByteA); }; module.exports = { init: init, }; brianc-node-postgres-e5c48f3/lib/types.js000066400000000000000000000017171176777237400204650ustar00rootroot00000000000000var textParsers = require(__dirname + "/textParsers"), binaryParsers = require(__dirname + "/binaryParsers"); var typeParsers = { text: {}, binary: {} }; //the empty parse function var noParse = function(val) { return String(val); } //returns a function used to convert a specific type (specified by //oid) into a result javascript type var getTypeParser = function(oid, format) { if (!typeParsers[format]) return noParse; return typeParsers[format][oid] || noParse; }; var setTypeParser = function(oid, format, parseFn) { if(typeof format == 'function') { parseFn = format; format = 'text'; } typeParsers[format][oid] = parseFn; } textParsers.init(function(oid, converter) { typeParsers.text[oid] = function(value) { return converter(String(value)); }; }); binaryParsers.init(function(oid, converter) { typeParsers.binary[oid] = converter; }); module.exports = { getTypeParser: getTypeParser, setTypeParser: setTypeParser } brianc-node-postgres-e5c48f3/lib/utils.js000066400000000000000000000057701176777237400204640ustar00rootroot00000000000000var url = require('url'); var defaults = require(__dirname + "/defaults"); var events = require('events'); //compatibility for old nodes if(typeof events.EventEmitter.prototype.once !== 'function') { events.EventEmitter.prototype.once = function (type, listener) { var self = this; self.on(type, function g () { self.removeListener(type, g); listener.apply(this, arguments); }); }; } var parseConnectionString = function(str) { //unix socket if(str.charAt(0) === '/') { return { host: str }; } var result = url.parse(str); var config = {}; config.host = result.hostname; config.database = result.pathname ? result.pathname.slice(1) : null var auth = (result.auth || ':').split(':'); config.user = auth[0]; config.password = auth[1]; config.port = result.port; return config; }; //allows passing false as property to remove it from config var norm = function(config, propName) { config[propName] = (config[propName] || (config[propName] === false ? undefined : defaults[propName])) }; //normalizes connection info //which can be in the form of an object //or a connection string var normalizeConnectionInfo = function(config) { switch(typeof config) { case 'object': norm(config, 'user'); norm(config, 'password'); norm(config, 'host'); norm(config, 'port'); norm(config, 'database'); return config; case 'string': return normalizeConnectionInfo(parseConnectionString(config)); default: throw new Error("Unrecognized connection config parameter: " + config); } }; var add = function(params, config, paramName) { var value = config[paramName]; if(value) { params.push(paramName+"='"+value+"'"); } } //builds libpq specific connection string //from a supplied config object //the config object conforms to the interface of the config object //accepted by the pure javascript client var getLibpgConString = function(config, callback) { if(typeof config == 'object') { var params = [] add(params, config, 'user'); add(params, config, 'password'); add(params, config, 'port'); if(config.database) { params.push("dbname='" + config.database + "'"); } if(config.host) { if(config.host != 'localhost' && config.host != '127.0.0.1') { //do dns lookup return require('dns').lookup(config.host, 4, function(err, address) { if(err) return callback(err, null); params.push("hostaddr="+address) callback(null, params.join(" ")) }) } params.push("hostaddr=127.0.0.1 "); } callback(null, params.join(" ")); } else { throw new Error("Unrecognized config type for connection"); } } module.exports = { normalizeConnectionInfo: normalizeConnectionInfo, //only exported here to make testing of this method possible //since it contains quite a bit of logic and testing for //each connection scenario in an integration test is impractical buildLibpqConnectionString: getLibpgConString, parseConnectionString: parseConnectionString } brianc-node-postgres-e5c48f3/lib/writer.js000066400000000000000000000063241176777237400206340ustar00rootroot00000000000000//binary data writer tuned for creating //postgres message packets as effeciently as possible by reusing the //same buffer to avoid memcpy and limit memory allocations var Writer = function(size) { this.size = size || 1024; this.buffer = Buffer(this.size + 5); this.offset = 5; this.headerPosition = 0; }; var p = Writer.prototype; //resizes internal buffer if not enough size left p._ensure = function(size) { var remaining = this.buffer.length - this.offset; if(remaining < size) { var oldBuffer = this.buffer; this.buffer = new Buffer(oldBuffer.length + size); oldBuffer.copy(this.buffer); } } p.addInt32 = function(num) { this._ensure(4) this.buffer[this.offset++] = (num >>> 24 & 0xFF) this.buffer[this.offset++] = (num >>> 16 & 0xFF) this.buffer[this.offset++] = (num >>> 8 & 0xFF) this.buffer[this.offset++] = (num >>> 0 & 0xFF) return this; } p.addInt16 = function(num) { this._ensure(2) this.buffer[this.offset++] = (num >>> 8 & 0xFF) this.buffer[this.offset++] = (num >>> 0 & 0xFF) return this; } //for versions of node requiring 'length' as 3rd argument to buffer.write var writeString = function(buffer, string, offset, len) { buffer.write(string, offset, len); } //overwrite function for older versions of node if(Buffer.prototype.write.length === 3) { writeString = function(buffer, string, offset, len) { buffer.write(string, offset); } } p.addCString = function(string) { //just write a 0 for empty or null strings if(!string) { this._ensure(1); } else { var len = Buffer.byteLength(string); this._ensure(len + 1); //+1 for null terminator writeString(this.buffer, string, this.offset, len); this.offset += len; } this.buffer[this.offset++] = 0; // null terminator return this; } p.addChar = function(char) { this._ensure(1); writeString(this.buffer, char, this.offset, 1); this.offset++; return this; } p.addString = function(string) { var string = string || ""; var len = Buffer.byteLength(string); this._ensure(len); this.buffer.write(string, this.offset); this.offset += len; return this; } p.getByteLength = function() { return this.offset - 5; } p.add = function(otherBuffer) { this._ensure(otherBuffer.length); otherBuffer.copy(this.buffer, this.offset); this.offset += otherBuffer.length; return this; } p.clear = function() { this.offset = 5; this.headerPosition = 0; this.lastEnd = 0; } //appends a header block to all the written data since the last //subsequent header or to the beginning if there is only one data block p.addHeader = function(code, last) { var origOffset = this.offset; this.offset = this.headerPosition; this.buffer[this.offset++] = code; //length is everything in this packet minus the code this.addInt32(origOffset - (this.headerPosition+1)) //set next header position this.headerPosition = origOffset; //make space for next header this.offset = origOffset; if(!last) { this._ensure(5); this.offset += 5; } } p.join = function(code) { if(code) { this.addHeader(code, true); } return this.buffer.slice(code ? 0 : 5, this.offset); } p.flush = function(code) { var result = this.join(code); this.clear(); return result; } module.exports = Writer; brianc-node-postgres-e5c48f3/package.json000066400000000000000000000011531176777237400204750ustar00rootroot00000000000000{ "name": "pg", "version": "0.7.1", "description": "PostgreSQL client - pure javascript & libpq with the same API", "keywords" : ["postgres", "pg", "libpq", "postgre", "database", "rdbms"], "homepage": "http://github.com/brianc/node-postgres", "repository" : { "type" : "git", "url" : "git://github.com/brianc/node-postgres.git" }, "author" : "Brian Carlson ", "main" : "./lib", "dependencies" : { "generic-pool" : "1.0.9" }, "scripts" : { "test" : "make test", "install" : "node-gyp rebuild || (exit 0)" }, "engines" : { "node": ">= 0.4.0" } } brianc-node-postgres-e5c48f3/script/000077500000000000000000000000001176777237400175135ustar00rootroot00000000000000brianc-node-postgres-e5c48f3/script/create-test-tables.js000066400000000000000000000033021176777237400235370ustar00rootroot00000000000000var args = require(__dirname + '/../test/cli'); var pg = require(__dirname + '/../lib'); var people = [ {name: 'Aaron', age: 10}, {name: 'Brian', age: 20}, {name: 'Chris', age: 30}, {name: 'David', age: 40}, {name: 'Elvis', age: 50}, {name: 'Frank', age: 60}, {name: 'Grace', age: 70}, {name: 'Haley', age: 80}, {name: 'Irma', age: 90}, {name: 'Jenny', age: 100}, {name: 'Kevin', age: 110}, {name: 'Larry', age: 120}, {name: 'Michelle', age: 130}, {name: 'Nancy', age: 140}, {name: 'Olivia', age: 150}, {name: 'Peter', age: 160}, {name: 'Quinn', age: 170}, {name: 'Ronda', age: 180}, {name: 'Shelley', age: 190}, {name: 'Tobias', age: 200}, {name: 'Uma', age: 210}, {name: 'Veena', age: 220}, {name: 'Wanda', age: 230}, {name: 'Xavier', age: 240}, {name: 'Yoyo', age: 250}, {name: 'Zanzabar', age: 260} ] var con = new pg.Client({ host: args.host, port: args.port, user: args.user, password: args.password, database: args.database }); con.connect(); if(args.down) { console.log("Dropping table 'person'") var query = con.query("drop table person"); query.on('end', function() { console.log("Dropped!"); con.end(); }); } else { console.log("Creating table 'person'"); con.query("create table person(id serial, name varchar(10), age integer)").on('end', function(){ console.log("Created!"); console.log("Filling it with people"); });; people.map(function(person) { return con.query("insert into person(name, age) values('"+person.name + "', '" + person.age + "')"); }).pop().on('end', function(){ console.log("Inserted 26 people"); con.end(); }); } brianc-node-postgres-e5c48f3/script/dump-db-types.js000066400000000000000000000007661176777237400225540ustar00rootroot00000000000000var pg = require(__dirname + '/../lib'); var args = require(__dirname + '/../test/cli'); var queries = [ "select CURRENT_TIMESTAMP", "select interval '1 day' + interval '1 hour'", "select TIMESTAMP 'today'"]; queries.forEach(function(query) { var client = new pg.Client({ user: args.user, database: args.database, password: args.password }); client.connect(); client .query(query) .on('row', function(row) { console.log(row); client.end(); }); }); brianc-node-postgres-e5c48f3/script/list-db-types.js000066400000000000000000000004351176777237400225530ustar00rootroot00000000000000var helper = require(__dirname + "/../test/integration/test-helper"); var pg = helper.pg; pg.connect(helper.config, assert.success(function(client) { var query = client.query('select oid, typname from pg_type where typtype = \'b\' order by oid'); query.on('row', console.log); })) brianc-node-postgres-e5c48f3/script/test-connection.js000066400000000000000000000016641176777237400231740ustar00rootroot00000000000000var helper = require(__dirname + '/../test/test-helper'); console.log(); console.log("testing ability to connect to '%j'", helper.config); var pg = require(__dirname + '/../lib'); pg.connect(helper.config, function(err, client) { if(err !== null) { console.error("Recieved connection error when attempting to contact PostgreSQL:"); console.error(err); process.exit(255); } console.log("Checking for existance of required test table 'person'") client.query("SELECT COUNT(name) FROM person", function(err, callback) { if(err != null) { console.error("Recieved error when executing query 'SELECT COUNT(name) FROM person'") console.error("It is possible you have not yet run the table create script under script/create-test-tables") console.error("Consult the postgres-node wiki under the 'Testing' section for more information") console.error(err); process.exit(255); } pg.end(); }) }) brianc-node-postgres-e5c48f3/src/000077500000000000000000000000001176777237400167765ustar00rootroot00000000000000brianc-node-postgres-e5c48f3/src/binding.cc000066400000000000000000000454041176777237400207260ustar00rootroot00000000000000#include #include #include #include #include #define LOG(msg) printf("%s\n",msg); #define TRACE(msg) //printf("%s\n", msg); #define THROW(msg) return ThrowException(Exception::Error(String::New(msg))); using namespace v8; using namespace node; static Persistent severity_symbol; static Persistent code_symbol; static Persistent detail_symbol; static Persistent hint_symbol; static Persistent position_symbol; static Persistent internalPosition_symbol; static Persistent internalQuery_symbol; static Persistent where_symbol; static Persistent file_symbol; static Persistent line_symbol; static Persistent routine_symbol; static Persistent name_symbol; static Persistent value_symbol; static Persistent type_symbol; static Persistent channel_symbol; static Persistent payload_symbol; static Persistent emit_symbol; static Persistent command_symbol; class Connection : public ObjectWrap { public: //creates the V8 objects & attaches them to the module (target) static void Init (Handle target) { HandleScope scope; Local t = FunctionTemplate::New(New); t->InstanceTemplate()->SetInternalFieldCount(1); t->SetClassName(String::NewSymbol("Connection")); emit_symbol = NODE_PSYMBOL("emit"); severity_symbol = NODE_PSYMBOL("severity"); code_symbol = NODE_PSYMBOL("code"); detail_symbol = NODE_PSYMBOL("detail"); hint_symbol = NODE_PSYMBOL("hint"); position_symbol = NODE_PSYMBOL("position"); internalPosition_symbol = NODE_PSYMBOL("internalPosition"); internalQuery_symbol = NODE_PSYMBOL("internalQuery"); where_symbol = NODE_PSYMBOL("where"); file_symbol = NODE_PSYMBOL("file"); line_symbol = NODE_PSYMBOL("line"); routine_symbol = NODE_PSYMBOL("routine"); name_symbol = NODE_PSYMBOL("name"); value_symbol = NODE_PSYMBOL("value"); type_symbol = NODE_PSYMBOL("type"); channel_symbol = NODE_PSYMBOL("channel"); payload_symbol = NODE_PSYMBOL("payload"); command_symbol = NODE_PSYMBOL("command"); NODE_SET_PROTOTYPE_METHOD(t, "connect", Connect); NODE_SET_PROTOTYPE_METHOD(t, "_sendQuery", SendQuery); NODE_SET_PROTOTYPE_METHOD(t, "_sendQueryWithParams", SendQueryWithParams); NODE_SET_PROTOTYPE_METHOD(t, "_sendPrepare", SendPrepare); NODE_SET_PROTOTYPE_METHOD(t, "_sendQueryPrepared", SendQueryPrepared); NODE_SET_PROTOTYPE_METHOD(t, "cancel", Cancel); NODE_SET_PROTOTYPE_METHOD(t, "end", End); target->Set(String::NewSymbol("Connection"), t->GetFunction()); TRACE("created class"); } //static function called by libev as callback entrypoint static void io_event(EV_P_ ev_io *w, int revents) { TRACE("Received IO event"); Connection *connection = static_cast(w->data); connection->HandleIOEvent(revents); } //v8 entry point into Connection#connect static Handle Connect(const Arguments& args) { HandleScope scope; Connection *self = ObjectWrap::Unwrap(args.This()); if(args.Length() == 0 || !args[0]->IsString()) { THROW("Must include connection string as only argument to connect"); } String::Utf8Value conninfo(args[0]->ToString()); bool success = self->Connect(*conninfo); if(!success) { self -> EmitLastError(); self -> DestroyConnection(); } return Undefined(); } //v8 entry point into Connection#cancel static Handle Cancel(const Arguments& args) { HandleScope scope; Connection *self = ObjectWrap::Unwrap(args.This()); bool success = self->Cancel(); if(!success) { self -> EmitLastError(); self -> DestroyConnection(); } return Undefined(); } //v8 entry point into Connection#_sendQuery static Handle SendQuery(const Arguments& args) { HandleScope scope; Connection *self = ObjectWrap::Unwrap(args.This()); if(!args[0]->IsString()) { THROW("First parameter must be a string query"); } char* queryText = MallocCString(args[0]); int result = self->Send(queryText); free(queryText); if(result == 0) { THROW("PQsendQuery returned error code"); } //TODO should we flush before throw? self->Flush(); return Undefined(); } //v8 entry point into Connection#_sendQueryWithParams static Handle SendQueryWithParams(const Arguments& args) { HandleScope scope; //dispatch non-prepared parameterized query return DispatchParameterizedQuery(args, false); } //v8 entry point into Connection#_sendPrepare(string queryName, string queryText, int nParams) static Handle SendPrepare(const Arguments& args) { HandleScope scope; Connection *self = ObjectWrap::Unwrap(args.This()); String::Utf8Value queryName(args[0]); String::Utf8Value queryText(args[1]); int length = args[2]->Int32Value(); self->SendPrepare(*queryName, *queryText, length); return Undefined(); } //v8 entry point into Connection#_sendQueryPrepared(string queryName, string[] paramValues) static Handle SendQueryPrepared(const Arguments& args) { HandleScope scope; //dispatch prepared parameterized query return DispatchParameterizedQuery(args, true); } static Handle DispatchParameterizedQuery(const Arguments& args, bool isPrepared) { HandleScope scope; Connection *self = ObjectWrap::Unwrap(args.This()); String::Utf8Value queryName(args[0]); //TODO this is much copy/pasta code if(!args[0]->IsString()) { THROW("First parameter must be a string"); } if(!args[1]->IsArray()) { THROW("Values must be an array"); } Handle params = args[1]; Local jsParams = Local::Cast(args[1]); int len = jsParams->Length(); char** paramValues = ArgToCStringArray(jsParams); if(!paramValues) { THROW("Unable to allocate char **paramValues from Local of v8 params"); } char* queryText = MallocCString(args[0]); int result = 0; if(isPrepared) { result = self->SendPreparedQuery(queryText, len, paramValues); } else { result = self->SendQueryParams(queryText, len, paramValues); } free(queryText); ReleaseCStringArray(paramValues, len); if(result == 1) { return Undefined(); } self->EmitLastError(); THROW("Postgres returned non-1 result from query dispatch."); } //v8 entry point into Connection#end static Handle End(const Arguments& args) { HandleScope scope; Connection *self = ObjectWrap::Unwrap(args.This()); self->End(); return Undefined(); } ev_io read_watcher_; ev_io write_watcher_; PGconn *connection_; bool connecting_; Connection () : ObjectWrap () { connection_ = NULL; connecting_ = false; TRACE("Initializing ev watchers"); ev_init(&read_watcher_, io_event); read_watcher_.data = this; ev_init(&write_watcher_, io_event); write_watcher_.data = this; } ~Connection () { } protected: //v8 entry point to constructor static Handle New (const Arguments& args) { HandleScope scope; Connection *connection = new Connection(); connection->Wrap(args.This()); return args.This(); } int Send(const char *queryText) { int rv = PQsendQuery(connection_, queryText); StartWrite(); return rv; } int SendQueryParams(const char *command, const int nParams, const char * const *paramValues) { int rv = PQsendQueryParams(connection_, command, nParams, NULL, paramValues, NULL, NULL, 0); StartWrite(); return rv; } int SendPrepare(const char *name, const char *command, const int nParams) { int rv = PQsendPrepare(connection_, name, command, nParams, NULL); StartWrite(); return rv; } int SendPreparedQuery(const char *name, int nParams, const char * const *paramValues) { int rv = PQsendQueryPrepared(connection_, name, nParams, paramValues, NULL, NULL, 0); StartWrite(); return rv; } int Cancel() { PGcancel* pgCancel = PQgetCancel(connection_); char errbuf[256]; int result = PQcancel(pgCancel, errbuf, 256); StartWrite(); PQfreeCancel(pgCancel); return result; } //flushes socket void Flush() { if(PQflush(connection_) == 1) { TRACE("Flushing"); ev_io_start(EV_DEFAULT_ &write_watcher_); } } //safely destroys the connection at most 1 time void DestroyConnection() { if(connection_ != NULL) { PQfinish(connection_); connection_ = NULL; } } //initializes initial async connection to postgres via libpq //and hands off control to libev bool Connect(const char* conninfo) { connection_ = PQconnectStart(conninfo); if (!connection_) { LOG("Connection couldn't be created"); } if (PQsetnonblocking(connection_, 1) == -1) { LOG("Unable to set connection to non-blocking"); return false; } ConnStatusType status = PQstatus(connection_); if(CONNECTION_BAD == status) { LOG("Bad connection status"); return false; } int fd = PQsocket(connection_); if(fd < 0) { LOG("socket fd was negative. error"); return false; } assert(PQisnonblocking(connection_)); PQsetNoticeProcessor(connection_, NoticeReceiver, this); TRACE("Setting watchers to socket"); ev_io_set(&read_watcher_, fd, EV_READ); ev_io_set(&write_watcher_, fd, EV_WRITE); connecting_ = true; StartWrite(); Ref(); return true; } static void NoticeReceiver(void *arg, const char *message) { Connection *self = (Connection*)arg; self->HandleNotice(message); } void HandleNotice(const char *message) { HandleScope scope; Handle notice = String::New(message); Emit("notice", ¬ice); } //called to process io_events from libev void HandleIOEvent(int revents) { if(revents & EV_ERROR) { LOG("Connection error."); return; } if(connecting_) { TRACE("Processing connecting_ io"); HandleConnectionIO(); return; } if(revents & EV_READ) { TRACE("revents & EV_READ"); if(PQconsumeInput(connection_) == 0) { End(); EmitLastError(); LOG("Something happened, consume input is 0"); return; } //declare handlescope as this method is entered via a libev callback //and not part of the public v8 interface HandleScope scope; if (PQisBusy(connection_) == 0) { PGresult *result; bool didHandleResult = false; while ((result = PQgetResult(connection_))) { HandleResult(result); didHandleResult = true; PQclear(result); } //might have fired from notification if(didHandleResult) { Emit("_readyForQuery"); } } PGnotify *notify; while ((notify = PQnotifies(connection_))) { Local result = Object::New(); result->Set(channel_symbol, String::New(notify->relname)); result->Set(payload_symbol, String::New(notify->extra)); Handle res = (Handle)result; Emit("notification", &res); PQfreemem(notify); } } if(revents & EV_WRITE) { TRACE("revents & EV_WRITE"); if (PQflush(connection_) == 0) { StopWrite(); } } } void HandleResult(PGresult* result) { ExecStatusType status = PQresultStatus(result); switch(status) { case PGRES_TUPLES_OK: { HandleTuplesResult(result); EmitCommandMetaData(result); } break; case PGRES_FATAL_ERROR: HandleErrorResult(result); break; case PGRES_COMMAND_OK: case PGRES_EMPTY_QUERY: EmitCommandMetaData(result); break; default: printf("Unrecogized query status: %s\n", PQresStatus(status)); break; } } void EmitCommandMetaData(PGresult* result) { HandleScope scope; Local info = Object::New(); info->Set(command_symbol, String::New(PQcmdStatus(result))); info->Set(value_symbol, String::New(PQcmdTuples(result))); Handle e = (Handle)info; Emit("_cmdStatus", &e); } //maps the postgres tuple results to v8 objects //and emits row events //TODO look at emitting fewer events because the back & forth between //javascript & c++ might introduce overhead (requires benchmarking) void HandleTuplesResult(const PGresult* result) { HandleScope scope; int rowCount = PQntuples(result); for(int rowNumber = 0; rowNumber < rowCount; rowNumber++) { //create result object for this row Local row = Array::New(); int fieldCount = PQnfields(result); for(int fieldNumber = 0; fieldNumber < fieldCount; fieldNumber++) { Local field = Object::New(); //name of field char* fieldName = PQfname(result, fieldNumber); field->Set(name_symbol, String::New(fieldName)); //oid of type of field int fieldType = PQftype(result, fieldNumber); field->Set(type_symbol, Integer::New(fieldType)); //value of field if(PQgetisnull(result, rowNumber, fieldNumber)) { field->Set(value_symbol, Null()); } else { char* fieldValue = PQgetvalue(result, rowNumber, fieldNumber); field->Set(value_symbol, String::New(fieldValue)); } row->Set(Integer::New(fieldNumber), field); } Handle e = (Handle)row; Emit("_row", &e); } } void HandleErrorResult(const PGresult* result) { HandleScope scope; //instantiate the return object as an Error with the summary Postgres message Local msg = Local::Cast(Exception::Error(String::New(PQresultErrorField(result, PG_DIAG_MESSAGE_PRIMARY)))); //add the other information returned by Postgres to the error object AttachErrorField(result, msg, severity_symbol, PG_DIAG_SEVERITY); AttachErrorField(result, msg, code_symbol, PG_DIAG_SQLSTATE); AttachErrorField(result, msg, detail_symbol, PG_DIAG_MESSAGE_DETAIL); AttachErrorField(result, msg, hint_symbol, PG_DIAG_MESSAGE_HINT); AttachErrorField(result, msg, position_symbol, PG_DIAG_STATEMENT_POSITION); AttachErrorField(result, msg, internalPosition_symbol, PG_DIAG_INTERNAL_POSITION); AttachErrorField(result, msg, internalQuery_symbol, PG_DIAG_INTERNAL_QUERY); AttachErrorField(result, msg, where_symbol, PG_DIAG_CONTEXT); AttachErrorField(result, msg, file_symbol, PG_DIAG_SOURCE_FILE); AttachErrorField(result, msg, line_symbol, PG_DIAG_SOURCE_LINE); AttachErrorField(result, msg, routine_symbol, PG_DIAG_SOURCE_FUNCTION); Handle m = msg; Emit("_error", &m); } void AttachErrorField(const PGresult *result, const Local msg, const Persistent symbol, int fieldcode) { char *val = PQresultErrorField(result, fieldcode); if(val) { msg->Set(symbol, String::New(val)); } } void End() { StopRead(); StopWrite(); DestroyConnection(); } private: //EventEmitter was removed from c++ in node v0.5.x void Emit(const char* message) { HandleScope scope; Handle args[1] = { String::New(message) }; Emit(1, args); } void Emit(const char* message, Handle* arg) { HandleScope scope; Handle args[2] = { String::New(message), *arg }; Emit(2, args); } void Emit(int length, Handle *args) { HandleScope scope; Local emit_v = this->handle_->Get(emit_symbol); assert(emit_v->IsFunction()); Local emit_f = emit_v.As(); TryCatch tc; emit_f->Call(this->handle_, length, args); if(tc.HasCaught()) { FatalException(tc); } } void HandleConnectionIO() { PostgresPollingStatusType status = PQconnectPoll(connection_); switch(status) { case PGRES_POLLING_READING: TRACE("Polled: PGRES_POLLING_READING"); StopWrite(); StartRead(); break; case PGRES_POLLING_WRITING: TRACE("Polled: PGRES_POLLING_WRITING"); StopRead(); StartWrite(); break; case PGRES_POLLING_FAILED: StopRead(); StopWrite(); TRACE("Polled: PGRES_POLLING_FAILED"); EmitLastError(); break; case PGRES_POLLING_OK: TRACE("Polled: PGRES_POLLING_OK"); connecting_ = false; StartRead(); Emit("connect"); default: //printf("Unknown polling status: %d\n", status); break; } } void EmitError(const char *message) { Local exception = Exception::Error(String::New(message)); Emit("_error", &exception); } void EmitLastError() { EmitError(PQerrorMessage(connection_)); } void StopWrite() { TRACE("Stoping write watcher"); ev_io_stop(EV_DEFAULT_ &write_watcher_); } void StartWrite() { TRACE("Starting write watcher"); ev_io_start(EV_DEFAULT_ &write_watcher_); } void StopRead() { TRACE("Stoping read watcher"); ev_io_stop(EV_DEFAULT_ &read_watcher_); } void StartRead() { TRACE("Starting read watcher"); ev_io_start(EV_DEFAULT_ &read_watcher_); } //Converts a v8 array to an array of cstrings //the result char** array must be free() when it is no longer needed //if for any reason the array cannot be created, returns 0 static char** ArgToCStringArray(Local params) { int len = params->Length(); char** paramValues = new char*[len]; for(int i = 0; i < len; i++) { Handle val = params->Get(i); if(val->IsString()) { char* cString = MallocCString(val); //will be 0 if could not malloc if(!cString) { LOG("ArgToCStringArray: OUT OF MEMORY OR SOMETHING BAD!"); ReleaseCStringArray(paramValues, i-1); return 0; } paramValues[i] = cString; } else if(val->IsNull()) { paramValues[i] = NULL; } else { //a paramter was not a string LOG("Parameter not a string"); ReleaseCStringArray(paramValues, i-1); return 0; } } return paramValues; } //helper function to release cString arrays static void ReleaseCStringArray(char **strArray, int len) { for(int i = 0; i < len; i++) { free(strArray[i]); } delete [] strArray; } //helper function to malloc new string from v8string static char* MallocCString(v8::Handle v8String) { String::Utf8Value utf8String(v8String->ToString()); char *cString = (char *) malloc(strlen(*utf8String) + 1); if(!cString) { return cString; } strcpy(cString, *utf8String); return cString; } }; extern "C" void init (Handle target) { HandleScope scope; Connection::Init(target); } brianc-node-postgres-e5c48f3/test/000077500000000000000000000000001176777237400171665ustar00rootroot00000000000000brianc-node-postgres-e5c48f3/test/buffer-list.js000066400000000000000000000027421176777237400217530ustar00rootroot00000000000000BufferList = function() { this.buffers = []; }; var p = BufferList.prototype; p.add = function(buffer, front) { this.buffers[front ? "unshift" : "push"](buffer); return this; }; p.addInt16 = function(val, front) { return this.add(Buffer([(val >>> 8),(val >>> 0)]),front); }; p.getByteLength = function(initial) { return this.buffers.reduce(function(previous, current){ return previous + current.length; },initial || 0); }; p.addInt32 = function(val, first) { return this.add(Buffer([ (val >>> 24 & 0xFF), (val >>> 16 & 0xFF), (val >>> 8 & 0xFF), (val >>> 0 & 0xFF) ]),first); }; p.addCString = function(val, front) { var len = Buffer.byteLength(val); var buffer = new Buffer(len+1); buffer.write(val); buffer[len] = 0; return this.add(buffer, front); }; p.addChar = function(char, first) { return this.add(Buffer(char,'utf8'), first); }; p.join = function(appendLength, char) { var length = this.getByteLength(); if(appendLength) { this.addInt32(length+4, true); return this.join(false, char); } if(char) { this.addChar(char, true); length++; } var result = Buffer(length); var index = 0; this.buffers.forEach(function(buffer) { buffer.copy(result, index, 0); index += buffer.length; }); return result; }; BufferList.concat = function() { var total = new BufferList(); for(var i = 0; i < arguments.length; i++) { total.add(arguments[i]); } return total.join(); }; module.exports = BufferList; brianc-node-postgres-e5c48f3/test/cli.js000066400000000000000000000005251176777237400202750ustar00rootroot00000000000000var config = require(__dirname + '/../lib/utils').parseConnectionString(process.argv[2]) for(var i = 0; i < process.argv.length; i++) { switch(process.argv[i].toLowerCase()) { case 'native': config.native = true; break; case 'binary': config.binary = true; break; default: break; } } module.exports = config; brianc-node-postgres-e5c48f3/test/integration/000077500000000000000000000000001176777237400215115ustar00rootroot00000000000000brianc-node-postgres-e5c48f3/test/integration/client/000077500000000000000000000000001176777237400227675ustar00rootroot00000000000000brianc-node-postgres-e5c48f3/test/integration/client/api-tests.js000066400000000000000000000130731176777237400252420ustar00rootroot00000000000000var helper = require(__dirname + '/../test-helper'); var pg = require(__dirname + '/../../../lib'); if(helper.args.native) { pg = require(__dirname + '/../../../lib').native; } var log = function() { //console.log.apply(console, arguments); } var sink = new helper.Sink(5, 10000, function() { log("ending connection pool: %j", helper.config); pg.end(helper.config); }); test('api', function() { log("connecting to %j", helper.config) pg.connect(helper.config, assert.calls(function(err, client) { assert.equal(err, null, "Failed to connect: " + helper.sys.inspect(err)); client.query('CREATE TEMP TABLE band(name varchar(100))'); ['the flaming lips', 'wolf parade', 'radiohead', 'bright eyes', 'the beach boys', 'dead black hearts'].forEach(function(bandName) { var query = client.query("INSERT INTO band (name) VALUES ('"+ bandName +"')") }); test('simple query execution',assert.calls( function() { log("executing simple query") client.query("SELECT * FROM band WHERE name = 'the beach boys'", assert.calls(function(err, result) { assert.lengthIs(result.rows, 1) assert.equal(result.rows.pop().name, 'the beach boys') log("simple query executed") })); })) test('prepared statement execution',assert.calls( function() { log("executing prepared statement 1") client.query('SELECT * FROM band WHERE name = $1', ['dead black hearts'],assert.calls( function(err, result) { log("Prepared statement 1 finished") assert.lengthIs(result.rows, 1); assert.equal(result.rows.pop().name, 'dead black hearts'); })) log("executing prepared statement two") client.query('SELECT * FROM band WHERE name LIKE $1 ORDER BY name', ['the %'], assert.calls(function(err, result) { log("prepared statement two finished") assert.lengthIs(result.rows, 2); assert.equal(result.rows.pop().name, 'the flaming lips'); assert.equal(result.rows.pop().name, 'the beach boys'); sink.add(); })) })) })) }) test('executing nested queries', function() { pg.connect(helper.config, assert.calls(function(err, client) { assert.isNull(err); log("connected for nested queriese") client.query('select now as now from NOW()', assert.calls(function(err, result) { assert.equal(new Date().getYear(), result.rows[0].now.getYear()) client.query('select now as now_again FROM NOW()', assert.calls(function() { client.query('select * FROM NOW()', assert.calls(function() { log('all nested queries recieved') assert.ok('all queries hit') sink.add(); })) })) })) })) }) test('raises error if cannot connect', function() { var connectionString = "pg://sfalsdkf:asdf@localhost/ieieie"; log("trying to connect to invalid place for error") pg.connect(connectionString, assert.calls(function(err, client) { assert.ok(err, 'should have raised an error') log("invalid connection supplied error to callback") sink.add(); })) }) test("query errors are handled and do not bubble if callback is provded", function() { pg.connect(helper.config, assert.calls(function(err, client) { assert.isNull(err) log("checking for query error") client.query("SELECT OISDJF FROM LEIWLISEJLSE", assert.calls(function(err, result) { assert.ok(err); log("query error supplied error to callback") sink.add(); })) })) }) test('callback is fired once and only once', function() { pg.connect(helper.config, assert.calls(function(err, client) { assert.isNull(err); client.query("CREATE TEMP TABLE boom(name varchar(10))"); var callCount = 0; client.query([ "INSERT INTO boom(name) VALUES('hai')", "INSERT INTO boom(name) VALUES('boom')", "INSERT INTO boom(name) VALUES('zoom')", ].join(";"), function(err, callback) { assert.equal(callCount++, 0, "Call count should be 0. More means this callback fired more than once."); sink.add(); }) })) }) test('can provide callback and config object', function() { pg.connect(helper.config, assert.calls(function(err, client) { assert.isNull(err); client.query({ name: 'boom', text: 'select NOW()' }, assert.calls(function(err, result) { assert.isNull(err); assert.equal(result.rows[0].now.getYear(), new Date().getYear()) })) })) }) test('can provide callback and config and parameters', function() { pg.connect(helper.config, assert.calls(function(err, client) { assert.isNull(err); var config = { text: 'select $1::text as val' }; client.query(config, ['hi'], assert.calls(function(err, result) { assert.isNull(err); assert.equal(result.rows.length, 1); assert.equal(result.rows[0].val, 'hi'); })) })) }) test('null and undefined are both inserted as NULL', function() { pg.connect(helper.config, assert.calls(function(err, client) { assert.isNull(err); client.query("CREATE TEMP TABLE my_nulls(a varchar(1), b varchar(1), c integer, d integer, e date, f date)"); client.query("INSERT INTO my_nulls(a,b,c,d,e,f) VALUES ($1,$2,$3,$4,$5,$6)", [ null, undefined, null, undefined, null, undefined ]); client.query("SELECT * FROM my_nulls", assert.calls(function(err, result) { assert.isNull(err); assert.equal(result.rows.length, 1); assert.isNull(result.rows[0].a); assert.isNull(result.rows[0].b); assert.isNull(result.rows[0].c); assert.isNull(result.rows[0].d); assert.isNull(result.rows[0].e); assert.isNull(result.rows[0].f); })) })) }) brianc-node-postgres-e5c48f3/test/integration/client/array-tests.js000066400000000000000000000102341176777237400256030ustar00rootroot00000000000000var helper = require(__dirname + "/test-helper"); var pg = helper.pg; test('parsing array results', function() { pg.connect(helper.config, assert.calls(function(err, client) { assert.isNull(err); client.query("CREATE TEMP TABLE why(names text[], numbors integer[])"); client.query('INSERT INTO why(names, numbors) VALUES(\'{"aaron", "brian","a b c" }\', \'{1, 2, 3}\')').on('error', console.log); test('numbers', function() { // client.connection.on('message', console.log) client.query('SELECT numbors FROM why', assert.success(function(result) { assert.lengthIs(result.rows[0].numbors, 3); assert.equal(result.rows[0].numbors[0], 1); assert.equal(result.rows[0].numbors[1], 2); assert.equal(result.rows[0].numbors[2], 3); })) }) test('parses string arrays', function() { client.query('SELECT names FROM why', assert.success(function(result) { var names = result.rows[0].names; assert.lengthIs(names, 3); assert.equal(names[0], 'aaron'); assert.equal(names[1], 'brian'); assert.equal(names[2], "a b c"); pg.end(); })) }) test('empty array', function(){ client.query("SELECT '{}'::text[] as names", assert.success(function(result) { var names = result.rows[0].names; assert.lengthIs(names, 0); pg.end(); })) }) test('element containing comma', function(){ client.query("SELECT '{\"joe,bob\",jim}'::text[] as names", assert.success(function(result) { var names = result.rows[0].names; assert.lengthIs(names, 2); assert.equal(names[0], 'joe,bob'); assert.equal(names[1], 'jim'); pg.end(); })) }) test('bracket in quotes', function(){ client.query("SELECT '{\"{\",\"}\"}'::text[] as names", assert.success(function(result) { var names = result.rows[0].names; assert.lengthIs(names, 2); assert.equal(names[0], '{'); assert.equal(names[1], '}'); pg.end(); })) }) test('null value', function(){ client.query("SELECT '{joe,null,bob,\"NULL\"}'::text[] as names", assert.success(function(result) { var names = result.rows[0].names; assert.lengthIs(names, 4); assert.equal(names[0], 'joe'); assert.equal(names[1], null); assert.equal(names[2], 'bob'); assert.equal(names[3], 'NULL'); pg.end(); })) }) test('element containing quote char', function(){ client.query("SELECT '{\"joe''\",jim'',\"bob\\\\\"\"}'::text[] as names", assert.success(function(result) { var names = result.rows[0].names; assert.lengthIs(names, 3); assert.equal(names[0], 'joe\''); assert.equal(names[1], 'jim\''); assert.equal(names[2], 'bob"'); pg.end(); })) }) test('nested array', function(){ client.query("SELECT '{{1,joe},{2,bob}}'::text[] as names", assert.success(function(result) { var names = result.rows[0].names; assert.lengthIs(names, 2); assert.lengthIs(names[0], 2); assert.equal(names[0][0], '1'); assert.equal(names[0][1], 'joe'); assert.lengthIs(names[1], 2); assert.equal(names[1][0], '2'); assert.equal(names[1][1], 'bob'); pg.end(); })) }) test('integer array', function(){ client.query("SELECT '{1,2,3}'::integer[] as names", assert.success(function(result) { var names = result.rows[0].names; assert.lengthIs(names, 3); assert.equal(names[0], 1); assert.equal(names[1], 2); assert.equal(names[2], 3); pg.end(); })) }) test('integer nested array', function(){ client.query("SELECT '{{1,100},{2,100},{3,100}}'::integer[] as names", assert.success(function(result) { var names = result.rows[0].names; assert.lengthIs(names, 3); assert.equal(names[0][0], 1); assert.equal(names[0][1], 100); assert.equal(names[1][0], 2); assert.equal(names[1][1], 100); assert.equal(names[2][0], 3); assert.equal(names[2][1], 100); pg.end(); })) }) })) }) brianc-node-postgres-e5c48f3/test/integration/client/big-simple-query-tests.js000066400000000000000000000247451176777237400276740ustar00rootroot00000000000000var helper = require(__dirname+"/test-helper"); /* Test to trigger a bug. I really dont know what's wrong but it seams that its triggered by multable big queryies with supplied values. The test bellow can trigger the bug. */ // Big query with a where clouse from supplied value var big_query_rows_1 = []; var big_query_rows_2 = []; var big_query_rows_3 = []; // Works test('big simple query 1',function() { var client = helper.client(); client.query("select 'aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa' as bla from person where name = '' or 1 = 1") .on('row', function(row) { big_query_rows_1.push(row); }) .on('error', function(error) { console.log("big simple query 1 error"); console.log(error); }); client.on('drain', client.end.bind(client)); }); // Works test('big simple query 2',function() { var client = helper.client(); client.query("select 'aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa' as bla from person where name = $1 or 1 = 1",['']) .on('row', function(row) { big_query_rows_2.push(row); }) .on('error', function(error) { console.log("big simple query 2 error"); console.log(error); }); client.on('drain', client.end.bind(client)); }); // Fails most of the time with 'invalid byte sequence for encoding "UTF8": 0xb9' or 'insufficient data left in message' // If test 1 and 2 are commented out it works test('big simple query 3',function() { var client = helper.client(); client.query("select 'aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa' as bla from person where name = $1 or 1 = 1",['']) .on('row', function(row) { big_query_rows_3.push(row); }) .on('error', function(error) { console.log("big simple query 3 error"); console.log(error); }); client.on('drain', client.end.bind(client)); }); process.on('exit', function() { assert.equal(big_query_rows_1.length, 26,'big simple query 1 should return 26 rows'); assert.equal(big_query_rows_2.length, 26,'big simple query 2 should return 26 rows'); assert.equal(big_query_rows_3.length, 26,'big simple query 3 should return 26 rows'); }); var runBigQuery = function(client) { var rows = []; var q = client.query("select 'aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa' as bla from person where name = $1 or 1 = 1",[''], function(err, result) { if(err != null) { console.log(err); throw Err; } assert.lengthIs(result.rows, 26); }); q.on('row', function(row) { rows.push(row); }) assert.emits(q, 'end', function() { //query ended assert.lengthIs(rows, 26); }) } test('many times', function() { var client = helper.client(); for(var i = 0; i < 20; i++) { runBigQuery(client); } client.on('drain', function() { client.end(); setTimeout(function() { //let client disconnect fully }, 100) }); }) brianc-node-postgres-e5c48f3/test/integration/client/cancel-query-tests.js000066400000000000000000000021231176777237400270530ustar00rootroot00000000000000var helper = require(__dirname+"/test-helper"); //before running this test make sure you run the script create-test-tables test("cancellation of a query", function() { var client = helper.client(); var qry = client.query("select name from person order by name"); client.on('drain', client.end.bind(client)); var rows1 = 0, rows2 = 0, rows3 = 0, rows4 = 0; var query1 = client.query(qry); query1.on('row', function(row) { rows1++; }); var query2 = client.query(qry); query2.on('row', function(row) { rows2++; }); var query3 = client.query(qry); query3.on('row', function(row) { rows3++; }); var query4 = client.query(qry); query4.on('row', function(row) { rows4++; }); helper.pg.cancel(helper.config, client, query1); helper.pg.cancel(helper.config, client, query2); helper.pg.cancel(helper.config, client, query4); setTimeout(function() { assert.equal(rows1, 0); assert.equal(rows2, 0); assert.equal(rows4, 0); }, 2000); assert.emits(query3, 'end', function() { test("returned right number of rows", function() { assert.equal(rows3, 26); }); }); }); brianc-node-postgres-e5c48f3/test/integration/client/configuration-tests.js000066400000000000000000000017741176777237400273450ustar00rootroot00000000000000var helper = require(__dirname + '/test-helper'); var pg = helper.pg; test('default values', function() { assert.same(pg.defaults,{ user: process.env.USER, database: process.env.USER, password: null, port: 5432, rows: 0, poolSize: 10 }) test('are used in new clients', function() { var client = new pg.Client(); assert.same(client,{ user: process.env.USER, database: process.env.USER, password: null, port: 5432 }) }) }) if(!helper.args.native) { test('modified values', function() { pg.defaults.user = 'boom' pg.defaults.password = 'zap' pg.defaults.database = 'pow' pg.defaults.port = 1234 pg.defaults.host = 'blam' pg.defaults.rows = 10 pg.defaults.poolSize = 0 test('are passed into created clients', function() { var client = new Client(); assert.same(client,{ user: 'boom', password: 'zap', database: 'pow', port: 1234, host: 'blam' }) }) }) } brianc-node-postgres-e5c48f3/test/integration/client/drain-tests.js000066400000000000000000000031001176777237400255540ustar00rootroot00000000000000var helper = require(__dirname + '/test-helper'); var pg = require(__dirname + '/../../../lib'); if(helper.args.native) { pg = require(__dirname + '/../../../lib').native; } var testDrainOfClientWithPendingQueries = function() { pg.connect(helper.config, assert.success(function(client) { test('when there are pending queries and client is resumed', function() { var drainCount = 0; client.on('drain', function() { drainCount++; }); client.pauseDrain(); client.query('SELECT NOW()', function() { client.query('SELECT NOW()', function() { assert.equal(drainCount, 0); process.nextTick(function() { assert.equal(drainCount, 1); pg.end(); }); }); client.resumeDrain(); assert.equal(drainCount, 0); }); }); })); }; pg.connect(helper.config, assert.success(function(client) { var drainCount = 0; client.on('drain', function() { drainCount++; }); test('pauseDrain and resumeDrain on simple client', function() { client.pauseDrain(); client.resumeDrain(); process.nextTick(assert.calls(function() { assert.equal(drainCount, 0); test('drain is paused', function() { client.pauseDrain(); client.query('SELECT NOW()', assert.success(function() { process.nextTick(function() { assert.equal(drainCount, 0); client.resumeDrain(); assert.equal(drainCount, 1); testDrainOfClientWithPendingQueries(); }); })); }); })); }); })); brianc-node-postgres-e5c48f3/test/integration/client/empty-query-tests.js000066400000000000000000000006561176777237400267750ustar00rootroot00000000000000var helper = require(__dirname+'/test-helper'); var client = helper.client(); test("empty query message handling", function() { assert.emits(client, 'drain', function() { client.end(); }); client.query({text: "", binary: false}); }); test('callback supported', assert.calls(function() { client.query({text: "", binary: false}, function(err, result) { assert.isNull(err); assert.empty(result.rows); }) })) brianc-node-postgres-e5c48f3/test/integration/client/error-handling-tests.js000066400000000000000000000101451176777237400274010ustar00rootroot00000000000000var helper = require(__dirname + '/test-helper'); var util = require('util'); var createErorrClient = function() { var client = helper.client(); client.on('error', function(err) { assert.ok(false, "client should not throw query error: " + util.inspect(err)); }); client.on('drain', client.end.bind(client)); return client; }; test('error handling', function(){ test('within a simple query', function() { var client = createErorrClient(); var query = client.query("select omfg from yodas_dsflsd where pixistix = 'zoiks!!!'"); assert.emits(query, 'error', function(error) { test('error is a psql error', function() { assert.equal(error.severity, "ERROR"); }); }); }); test('within a prepared statement', function() { var client = createErorrClient(); var q = client.query({text: "CREATE TEMP TABLE boom(age integer); INSERT INTO boom (age) VALUES (28);", binary: false}); test("when query is parsing", function() { //this query wont parse since there ain't no table named bang var ensureFuture = function(testClient) { test("client can issue more queries successfully", function() { var goodQuery = testClient.query("select age from boom"); assert.emits(goodQuery, 'row', function(row) { assert.equal(row.age, 28); }); }); }; var query = client.query({ text: "select * from bang where name = $1", values: ['0'] }); test("query emits the error", function() { assert.emits(query, 'error', function(err) { ensureFuture(client); }); }); test("when a query is binding", function() { var query = client.query({ text: 'select * from boom where age = $1', values: ['asldkfjasdf'] }); test("query emits the error", function() { assert.emits(query, 'error', function(err) { test('error has right severity', function() { assert.equal(err.severity, "ERROR"); }) ensureFuture(client); }); }); //TODO how to test for errors during execution? }); }); }); test('non-query error', function() { return false; var client = new Client({ user:'asldkfjsadlfkj' }); assert.emits(client, 'error'); client.connect(); }); test('non-query error with callback', function() { return false; var client = new Client({ user:'asldkfjsadlfkj' }); client.connect(assert.calls(function(error, client) { assert.ok(error); })); }); }); test('non-error calls supplied callback', function() { var client = new Client({ user: helper.args.user, password: helper.args.password, host: helper.args.host, port: helper.args.port, database: helper.args.database }); client.connect(assert.calls(function(err) { assert.isNull(err); client.end(); })) }); test('when connecting to invalid host', function() { return false; var client = new Client({ user: 'brian', password: '1234', host: 'asldkfjasdf!!#1308140.com' }); assert.emits(client, 'error'); client.connect(); }); test('when connecting to invalid host with callback', function() { return false; var client = new Client({ user: 'brian', password: '1234', host: 'asldkfjasdf!!#1308140.com' }); client.connect(function(error, client) { assert.ok(error); }); }); test('multiple connection errors (gh#31)', function() { return false; test('with single client', function() { //don't run yet...this test fails...need to think of fix var client = new Client({ user: 'blaksdjf', password: 'omfsadfas', host: helper.args.host, port: helper.args.port, database: helper.args.database }); client.connect(); assert.emits(client, 'error', function(e) { client.connect(); assert.emits(client, 'error'); }); }); test('with callback method', function() { var badConString = "tcp://aslkdfj:oi14081@"+helper.args.host+":"+helper.args.port+"/"+helper.args.database; return false; }); }); brianc-node-postgres-e5c48f3/test/integration/client/huge-numeric-tests.js000066400000000000000000000012601176777237400270540ustar00rootroot00000000000000var helper = require(__dirname + '/test-helper'); helper.pg.connect(helper.config, assert.success(function(client) { var types = require(__dirname + '/../../../lib/types'); //1231 = numericOID types.setTypeParser(1700, function(){ return 'yes'; }) types.setTypeParser(1700, 'binary', function(){ return 'yes'; }) var bignum = '294733346389144765940638005275322203805'; client.query('CREATE TEMP TABLE bignumz(id numeric)'); client.query('INSERT INTO bignumz(id) VALUES ($1)', [bignum]); client.query('SELECT * FROM bignumz', assert.success(function(result) { assert.equal(result.rows[0].id, 'yes') helper.pg.end(); })) })); //custom type converter brianc-node-postgres-e5c48f3/test/integration/client/no-data-tests.js000066400000000000000000000014551176777237400260150ustar00rootroot00000000000000var helper = require(__dirname + '/test-helper'); test("noData message handling", function() { var client = helper.client(); var q = client.query({ name: 'boom', text: 'create temp table boom(id serial, size integer)' }); client.query({ name: 'insert', text: 'insert into boom(size) values($1)', values: [100] }, function(err, result) { if(err) { console.log(err); throw err; } }); client.query({ name: 'insert', text: 'insert into boom(size) values($1)', values: [101] }); var query = client.query({ name: 'fetch', text: 'select size from boom where size < $1', values: [101] }); assert.emits(query, 'row', function(row) { assert.strictEqual(row.size,100) }); client.on('drain', client.end.bind(client)); }); brianc-node-postgres-e5c48f3/test/integration/client/notice-tests.js000066400000000000000000000025641176777237400257550ustar00rootroot00000000000000var helper = require(__dirname + '/test-helper'); test('emits notice message', function() { var client = helper.client(); client.query('create temp table boom(id serial, size integer)'); assert.emits(client, 'notice', function(notice) { assert.ok(notice != null); //TODO ending connection after notice generates weird errors process.nextTick(function() { client.end(); }) }); }) test('emits notify message', function() { var client = helper.client(); client.query('LISTEN boom', assert.calls(function() { var otherClient = helper.client(); otherClient.query('LISTEN boom', assert.calls(function() { assert.emits(client, 'notification', function(msg) { //make sure PQfreemem doesn't invalidate string pointers setTimeout(function() { assert.equal(msg.channel, 'boom'); assert.ok(msg.payload == 'omg!' /*9.x*/ || msg.payload == '' /*8.x*/, "expected blank payload or correct payload but got " + msg.message) client.end() }, 100) }); assert.emits(otherClient, 'notification', function(msg) { assert.equal(msg.channel, 'boom'); otherClient.end(); }); client.query("NOTIFY boom, 'omg!'", function(err, q) { if(err) { //notify not supported with payload on 8.x client.query("NOTIFY boom") } }); })); })); }) brianc-node-postgres-e5c48f3/test/integration/client/prepared-statement-tests.js000066400000000000000000000105501176777237400302720ustar00rootroot00000000000000var helper = require(__dirname +'/test-helper'); test("simple, unnamed prepared statement", function(){ var client = helper.client(); var query = client.query({ text: 'select age from person where name = $1', values: ['Brian'] }); assert.emits(query, 'row', function(row) { assert.equal(row.age, 20); }); assert.emits(query, 'end', function() { client.end(); }); }); test("named prepared statement", function() { var client = helper.client(); client.on('drain', client.end.bind(client)); var queryName = "user by age and like name"; var parseCount = 0; test("first named prepared statement",function() { var query = client.query({ text: 'select name from person where age <= $1 and name LIKE $2', values: [20, 'Bri%'], name: queryName }); assert.emits(query, 'row', function(row) { assert.equal(row.name, 'Brian'); }); assert.emits(query, 'end', function() { }); }); test("second named prepared statement with same name & text", function() { var cachedQuery = client.query({ text: 'select name from person where age <= $1 and name LIKE $2', name: queryName, values: [10, 'A%'] }); assert.emits(cachedQuery, 'row', function(row) { assert.equal(row.name, 'Aaron'); }); assert.emits(cachedQuery, 'end', function() { }); }); test("with same name, but the query text not even there batman!", function() { var q = client.query({ name: queryName, values: [30, '%n%'] }); test("gets first row", function() { assert.emits(q, 'row', function(row) { assert.equal(row.name, "Aaron"); test("gets second row", function() { assert.emits(q, 'row', function(row) { assert.equal(row.name, "Brian"); }); }); }); }); assert.emits(q, 'end', function() { }); }); }); test("prepared statements on different clients", function() { var statementName = "differ"; var statement1 = "select count(*) as count from person"; var statement2 = "select count(*) as count from person where age < $1"; var client1Finished = false; var client2Finished = false; var client1 = helper.client(); var client2 = helper.client(); test("client 1 execution", function() { var query = client1.query({ name: statementName, text: statement1 }); test('gets right data back', function() { assert.emits(query, 'row', function(row) { assert.equal(row.count, 26); }); }); assert.emits(query, 'end', function() { if(client2Finished) { client1.end(); client2.end(); } else { client1Finished = true; } }); }); test('client 2 execution', function() { var query = client2.query({ name: statementName, text: statement2, values: [11] }); test('gets right data', function() { assert.emits(query, 'row', function(row) { assert.equal(row.count, 1); }); }); assert.emits(query, 'end', function() { if(client1Finished) { client1.end(); client2.end(); } else { client2Finished = true; } }); }); }); test('prepared statement', function() { var client = helper.client(); client.on('drain', client.end.bind(client)); client.query('CREATE TEMP TABLE zoom(name varchar(100));'); client.query("INSERT INTO zoom (name) VALUES ('zed')"); client.query("INSERT INTO zoom (name) VALUES ('postgres')"); client.query("INSERT INTO zoom (name) VALUES ('node postgres')"); var checkForResults = function(q) { test('row callback fires for each result', function() { assert.emits(q, 'row', function(row) { assert.equal(row.name, 'node postgres'); assert.emits(q, 'row', function(row) { assert.equal(row.name, 'postgres'); assert.emits(q, 'row', function(row) { assert.equal(row.name, 'zed'); }) }); }) }) }; test('with small row count', function() { var query = client.query({ name: 'get names', text: "SELECT name FROM zoom ORDER BY name", rows: 1 }); checkForResults(query); }) test('with large row count', function() { var query = client.query({ name: 'get names', text: 'SELECT name FROM zoom ORDER BY name', rows: 1000 }) checkForResults(query); }) }) brianc-node-postgres-e5c48f3/test/integration/client/result-metadata-tests.js000066400000000000000000000016121176777237400275610ustar00rootroot00000000000000var helper = require(__dirname + "/test-helper"); var pg = helper.pg; test('should return insert metadata', function() { pg.connect(helper.config, assert.calls(function(err, client) { assert.isNull(err); client.query("CREATE TEMP TABLE zugzug(name varchar(10))", assert.calls(function(err, result) { assert.isNull(err); assert.equal(result.oid, null); assert.equal(result.command, 'CREATE'); client.query("INSERT INTO zugzug(name) VALUES('more work?')", assert.calls(function(err, result) { assert.equal(result.command, "INSERT"); assert.equal(result.rowCount, 1); client.query('SELECT * FROM zugzug', assert.calls(function(err, result) { assert.isNull(err); assert.equal(result.rowCount, 1); assert.equal(result.command, 'SELECT'); process.nextTick(pg.end.bind(pg)); })) })) })) })) }) brianc-node-postgres-e5c48f3/test/integration/client/simple-query-tests.js000066400000000000000000000044231176777237400271240ustar00rootroot00000000000000var helper = require(__dirname+"/test-helper"); //before running this test make sure you run the script create-test-tables test("simple query interface", function() { var client = helper.client(); var query = client.query("select name from person order by name"); client.on('drain', client.end.bind(client)); var rows = []; query.on('row', function(row) { rows.push(row['name']) }); query.once('row', function(row) { test('Can iterate through columns', function () { var columnCount = 0; for (column in row) { columnCount++; }; if ('length' in row) { assert.lengthIs(row, columnCount, 'Iterating through the columns gives a different length from calling .length.'); } }); }); assert.emits(query, 'end', function() { test("returned right number of rows", function() { assert.lengthIs(rows, 26); }); test("row ordering", function(){ assert.equal(rows[0], "Aaron"); assert.equal(rows[25], "Zanzabar"); }); }); }); test("multiple simple queries", function() { var client = helper.client(); client.query({ text: "create temp table bang(id serial, name varchar(5));insert into bang(name) VALUES('boom');", binary: false }) client.query("insert into bang(name) VALUES ('yes');"); var query = client.query("select name from bang"); assert.emits(query, 'row', function(row) { assert.equal(row['name'], 'boom'); assert.emits(query, 'row', function(row) { assert.equal(row['name'],'yes'); }); }); client.on('drain', client.end.bind(client)); }); test("multiple select statements", function() { var client = helper.client(); client.query({text: "create temp table boom(age integer); insert into boom(age) values(1); insert into boom(age) values(2); insert into boom(age) values(3)", binary: false}); client.query({text: "create temp table bang(name varchar(5)); insert into bang(name) values('zoom');", binary: false}); var result = client.query({text: "select age from boom where age < 2; select name from bang", binary: false}); assert.emits(result, 'row', function(row) { assert.strictEqual(row['age'], 1); assert.emits(result, 'row', function(row) { assert.strictEqual(row['name'], 'zoom'); }); }); client.on('drain', client.end.bind(client)); }); brianc-node-postgres-e5c48f3/test/integration/client/test-helper.js000066400000000000000000000001151176777237400255560ustar00rootroot00000000000000var helper = require(__dirname+'/../test-helper'); module.exports = helper; brianc-node-postgres-e5c48f3/test/integration/client/transaction-tests.js000066400000000000000000000035111176777237400270120ustar00rootroot00000000000000var helper = require(__dirname + '/test-helper'); var sink = new helper.Sink(2, function() { helper.pg.end(); }); test('a single connection transaction', function() { helper.pg.connect(helper.config, assert.calls(function(err, client) { assert.isNull(err); client.query('begin'); var getZed = { text: 'SELECT * FROM person WHERE name = $1', values: ['Zed'] }; test('Zed should not exist in the database', function() { client.query(getZed, assert.calls(function(err, result) { assert.isNull(err); assert.empty(result.rows); })) }) client.query("INSERT INTO person(name, age) VALUES($1, $2)", ['Zed', 270], assert.calls(function(err, result) { assert.isNull(err) })); test('Zed should exist in the database', function() { client.query(getZed, assert.calls(function(err, result) { assert.isNull(err); assert.equal(result.rows[0].name, 'Zed'); })) }) client.query('rollback'); test('Zed should not exist in the database', function() { client.query(getZed, assert.calls(function(err, result) { assert.isNull(err); assert.empty(result.rows); sink.add(); })) }) })) }) test('gh#36', function() { helper.pg.connect(helper.config, function(err, client) { if(err) throw err; client.query("BEGIN"); client.query({ name: 'X', text: "SELECT $1::INTEGER", values: [0] }, assert.calls(function(err, result) { if(err) throw err; assert.equal(result.rows.length, 1); })) client.query({ name: 'X', text: "SELECT $1::INTEGER", values: [0] }, assert.calls(function(err, result) { if(err) throw err; assert.equal(result.rows.length, 1); })) client.query("COMMIT", function() { sink.add(); }) }) }) brianc-node-postgres-e5c48f3/test/integration/client/type-coercion-tests.js000066400000000000000000000075131176777237400272530ustar00rootroot00000000000000var helper = require(__dirname + '/test-helper'); var sink; var testForTypeCoercion = function(type){ helper.pg.connect(helper.config, function(err, client) { assert.isNull(err); client.query("create temp table test_type(col " + type.name + ")", assert.calls(function(err, result) { assert.isNull(err); test("Coerces " + type.name, function() { type.values.forEach(function(val) { var insertQuery = client.query('insert into test_type(col) VALUES($1)',[val],assert.calls(function(err, result) { assert.isNull(err); })); var query = client.query({ name: 'get type ' + type.name , text: 'select col from test_type' }); query.on('error', function(err) { console.log(err); throw err; }); assert.emits(query, 'row', function(row) { assert.strictEqual(row.col, val, "expected " + type.name + " of " + val + " but got " + row.col); }, "row should have been called for " + type.name + " of " + val); client.query('delete from test_type'); }); client.query('drop table test_type', function() { sink.add(); }); }) })); }) }; var types = [{ name: 'integer', values: [1, -1, null] },{ name: 'smallint', values: [-1, 0, 1, null] },{ name: 'bigint', values: [-10000, 0, 10000, null] },{ name: 'varchar(5)', values: ['yo', '', 'zomg!', null] },{ name: 'oid', values: [0, 204410, null] },{ name: 'bool', values: [true, false, null] },{ //TODO get some actual huge numbers here name: 'numeric', values: [-12.34, 0, 12.34, null] },{ name: 'real', values: [101.1, 0, -101.3, null] },{ name: 'double precision', values: [-1.2, 0, 1.2, null] },{ name: 'timestamptz', values: [null] },{ name: 'timestamp', values: [null] },{ name: 'timetz', values: ['13:11:12.1234-05:30',null] },{ name: 'time', values: ['13:12:12.321', null] }]; // ignore some tests in binary mode if (helper.config.binary) { types = types.filter(function(type) { return !(type.name in {'real':1, 'timetz':1, 'time':1}); }); } var valueCount = 0; types.forEach(function(type) { valueCount += type.values.length; }) sink = new helper.Sink(types.length + 1, function() { helper.pg.end(); }) types.forEach(function(type) { testForTypeCoercion(type) }); test("timestampz round trip", function() { var now = new Date(); var client = helper.client(); client.on('error', function(err) { console.log(err); client.end(); }); client.query("create temp table date_tests(name varchar(10), tstz timestamptz(3))"); client.query({ text: "insert into date_tests(name, tstz)VALUES($1, $2)", name: 'add date', values: ['now', now] }); var result = client.query({ name: 'get date', text: 'select * from date_tests where name = $1', values: ['now'] }); assert.emits(result, 'row', function(row) { var date = row.tstz; assert.equal(date.getYear(),now.getYear()); assert.equal(date.getMonth(), now.getMonth()); assert.equal(date.getDate(), now.getDate()); assert.equal(date.getHours(), now.getHours()); assert.equal(date.getMinutes(), now.getMinutes()); assert.equal(date.getSeconds(), now.getSeconds()); test("milliseconds are equal", function() { assert.equal(date.getMilliseconds(), now.getMilliseconds()); }); }); client.on('drain', client.end.bind(client)); }); helper.pg.connect(helper.config, assert.calls(function(err, client) { assert.isNull(err); client.query('select null as res;', assert.calls(function(err, res) { assert.isNull(err); assert.strictEqual(res.rows[0].res, null) })) client.query('select 7 <> $1 as res;',[null], function(err, res) { assert.isNull(err); assert.strictEqual(res.rows[0].res, null); sink.add(); }) })) brianc-node-postgres-e5c48f3/test/integration/connection-pool/000077500000000000000000000000001176777237400246175ustar00rootroot00000000000000brianc-node-postgres-e5c48f3/test/integration/connection-pool/double-connection-tests.js000066400000000000000000000001111176777237400317150ustar00rootroot00000000000000var helper = require(__dirname + "/test-helper") helper.testPoolSize(2); brianc-node-postgres-e5c48f3/test/integration/connection-pool/ending-pool-tests.js000066400000000000000000000012321176777237400305260ustar00rootroot00000000000000var helper = require(__dirname + '/test-helper') var called = false; test('disconnects', function() { var sink = new helper.Sink(4, function() { called = true; //this should exit the process, killing each connection pool helper.pg.end(); }); [helper.config, helper.config, helper.config, helper.config].forEach(function(config) { helper.pg.connect(config, function(err, client) { assert.isNull(err); client.query("SELECT * FROM NOW()", function(err, result) { process.nextTick(function() { assert.equal(called, false, "Should not have disconnected yet") sink.add(); }) }) }) }) }) brianc-node-postgres-e5c48f3/test/integration/connection-pool/error-tests.js000066400000000000000000000017351176777237400274540ustar00rootroot00000000000000var helper = require(__dirname + "/../test-helper"); var pg = require(__dirname + "/../../../lib"); helper.pg = pg; //first make pool hold 2 clients helper.pg.defaults.poolSize = 2; var killIdleQuery = 'SELECT procpid, (SELECT pg_terminate_backend(procpid)) AS killed FROM pg_stat_activity WHERE current_query LIKE \'\''; //get first client helper.pg.connect(helper.config, assert.success(function(client) { client.id = 1; helper.pg.connect(helper.config, assert.success(function(client2) { client2.id = 2; //subscribe to the pg error event assert.emits(helper.pg, 'error', function(error, brokenClient) { assert.ok(error); assert.ok(brokenClient); assert.equal(client.id, brokenClient.id); helper.pg.end(); }); //kill the connection from client client2.query(killIdleQuery, assert.success(function(res) { //check to make sure client connection actually was killed assert.lengthIs(res.rows, 1); })); })); })); brianc-node-postgres-e5c48f3/test/integration/connection-pool/idle-timeout-tests.js000066400000000000000000000005241176777237400307170ustar00rootroot00000000000000var helper = require(__dirname + '/test-helper'); helper.pg.defaults.poolIdleTimeout = 200; test('idle timeout', function() { helper.pg.connect(helper.config, assert.calls(function(err, client) { assert.isNull(err); client.query('SELECT NOW()'); //just let this one time out //test will hang if pool doesn't timeout })); }); brianc-node-postgres-e5c48f3/test/integration/connection-pool/max-connection-tests.js000066400000000000000000000001431176777237400312350ustar00rootroot00000000000000var helper = require(__dirname + "/test-helper") helper.testPoolSize(10); helper.testPoolSize(11); brianc-node-postgres-e5c48f3/test/integration/connection-pool/optional-config-tests.js000066400000000000000000000006641176777237400314130ustar00rootroot00000000000000var helper = require(__dirname + '/test-helper'); //setup defaults helper.pg.defaults.user = helper.args.user; helper.pg.defaults.password = helper.args.password; helper.pg.defaults.host = helper.args.host; helper.pg.defaults.port = helper.args.port; helper.pg.defaults.database = helper.args.database; helper.pg.defaults.poolSize = 1; helper.pg.connect(assert.calls(function(err, client) { assert.isNull(err); client.end(); })); brianc-node-postgres-e5c48f3/test/integration/connection-pool/single-connection-tests.js000066400000000000000000000001111176777237400317240ustar00rootroot00000000000000var helper = require(__dirname + "/test-helper") helper.testPoolSize(1); brianc-node-postgres-e5c48f3/test/integration/connection-pool/test-helper.js000066400000000000000000000016051176777237400274130ustar00rootroot00000000000000var helper = require(__dirname + "/../test-helper"); helper.testPoolSize = function(max) { var sink = new helper.Sink(max, function() { helper.pg.end(); }); test("can pool " + max + " times", function() { for(var i = 0; i < max; i++) { helper.pg.poolSize = 10; test("connection #" + i + " executes", function() { helper.pg.connect(helper.config, function(err, client) { assert.isNull(err); client.query("select * from person", function(err, result) { assert.lengthIs(result.rows, 26) }) client.query("select count(*) as c from person", function(err, result) { assert.equal(result.rows[0].c, 26) }) var query = client.query("SELECT * FROM NOW()") query.on('end',function() { sink.add() }) }) }) } }) } module.exports = helper; brianc-node-postgres-e5c48f3/test/integration/connection-pool/unique-name-tests.js000066400000000000000000000031211176777237400305360ustar00rootroot00000000000000var helper = require(__dirname + '/test-helper'); helper.pg.defaults.poolSize = 1; helper.pg.defaults.user = helper.args.user; helper.pg.defaults.password = helper.args.password; helper.pg.defaults.database = helper.args.database; helper.pg.defaults.port = helper.args.port; helper.pg.defaults.host = helper.args.host; helper.pg.defaults.binary = helper.args.binary; helper.pg.defaults.poolIdleTimeout = 100; var moreArgs = {}; for (c in helper.config) { moreArgs[c] = helper.config[c]; } moreArgs.zomg = true; var badArgs = {}; for (c in helper.config) { badArgs[c] = helper.config[c]; } badArgs.user = badArgs.user + 'laksdjfl'; badArgs.password = badArgs.password + 'asldkfjlas'; badArgs.zomg = true; test('connecting with complete config', function() { helper.pg.connect(helper.config, assert.calls(function(err, client) { assert.isNull(err); client.iGotAccessed = true; client.query("SELECT NOW()") })); }); test('connecting with different config object', function() { helper.pg.connect(moreArgs, assert.calls(function(err, client) { assert.isNull(err); assert.ok(client.iGotAccessed === true) client.query("SELECT NOW()"); })) }); test('connecting with all defaults', function() { helper.pg.connect(assert.calls(function(err, client) { assert.isNull(err); assert.ok(client.iGotAccessed === true); client.end(); })); }); test('connecting with invalid config', function() { helper.pg.connect(badArgs, assert.calls(function(err, client) { assert.ok(err != null, "Expected connection error using invalid connection credentials"); })); }); brianc-node-postgres-e5c48f3/test/integration/connection-pool/waiting-connection-tests.js000066400000000000000000000001131176777237400321070ustar00rootroot00000000000000var helper = require(__dirname + "/test-helper") helper.testPoolSize(200); brianc-node-postgres-e5c48f3/test/integration/connection/000077500000000000000000000000001176777237400236505ustar00rootroot00000000000000brianc-node-postgres-e5c48f3/test/integration/connection/bound-command-tests.js000066400000000000000000000024771176777237400301030ustar00rootroot00000000000000var helper = require(__dirname + '/test-helper'); http://developer.postgresql.org/pgdocs/postgres/protocol-flow.html#PROTOCOL-FLOW-EXT-QUERY test('flushing once', function() { helper.connect(function(con) { con.parse({ text: 'select * from ids' }); con.bind(); con.execute(); con.flush(); assert.emits(con, 'parseComplete'); assert.emits(con, 'bindComplete'); assert.emits(con, 'dataRow'); assert.emits(con, 'commandComplete', function(){ con.sync(); }); assert.emits(con, 'readyForQuery', function(){ con.end(); }); }); }); test("sending many flushes", function() { helper.connect(function(con) { assert.emits(con, 'parseComplete', function(){ con.bind(); con.flush(); }); assert.emits(con, 'bindComplete', function(){ con.execute(); con.flush(); }); assert.emits(con, 'dataRow', function(msg){ assert.equal(msg.fields[0], 1); assert.emits(con, 'dataRow', function(msg){ assert.equal(msg.fields[0], 2); assert.emits(con, 'commandComplete', function(){ con.sync(); }); assert.emits(con, 'readyForQuery', function(){ con.end(); }); }); }); con.parse({ text: "select * from ids order by id" }); con.flush(); }); }); brianc-node-postgres-e5c48f3/test/integration/connection/notification-tests.js000066400000000000000000000010131176777237400300270ustar00rootroot00000000000000var helper = require(__dirname + '/test-helper'); //http://www.postgresql.org/docs/8.3/static/libpq-notify.html test('recieves notification from same connection with no payload', function() { helper.connect(function(con) { con.query('LISTEN boom'); assert.emits(con, 'readyForQuery', function() { con.query("NOTIFY boom"); assert.emits(con, 'notification', function(msg) { assert.equal(msg.payload, ""); assert.equal(msg.channel, 'boom') con.end(); }); }); }); }); brianc-node-postgres-e5c48f3/test/integration/connection/query-tests.js000066400000000000000000000013041176777237400265110ustar00rootroot00000000000000var helper = require(__dirname+"/test-helper"); var assert = require('assert'); var rows = []; //testing the low level 1-1 mapping api of client to postgres messages //it's cumbersome to use the api this way test('simple query', function() { helper.connect(function(con) { con.query('select * from ids'); assert.emits(con, 'dataRow'); con.on('dataRow', function(msg) { rows.push(msg.fields); }); assert.emits(con, 'readyForQuery', function() { con.end(); }); }); }); process.on('exit', function() { assert.equal(rows.length, 2); assert.equal(rows[0].length, 1); assert.strictEqual(String(rows[0] [0]), '1'); assert.strictEqual(String(rows[1] [0]), '2'); }); brianc-node-postgres-e5c48f3/test/integration/connection/test-helper.js000066400000000000000000000026411176777237400264450ustar00rootroot00000000000000var net = require('net'); var helper = require(__dirname+'/../test-helper'); var Connection = require(__dirname + '/../../../lib/connection'); var connect = function(callback) { var username = helper.args.user; var database = helper.args.database; var con = new Connection({stream: new net.Stream()}); con.on('error', function(error){ console.log(error); throw new Error("Connection error"); }); con.connect(helper.args.port || '5432', helper.args.host || 'localhost'); con.once('connect', function() { con.startup({ user: username, database: database }); con.once('authenticationCleartextPassword', function(){ con.password(helper.args.password); }); con.once('authenticationMD5Password', function(msg){ //need js client even if native client is included var client = require(__dirname +"/../../../lib/client"); var inner = client.md5(helper.args.password+helper.args.user); var outer = client.md5(inner + msg.salt.toString('binary')); con.password("md5"+outer); }); con.once('readyForQuery', function() { con.query('create temp table ids(id integer)'); con.once('readyForQuery', function() { con.query('insert into ids(id) values(1); insert into ids(id) values(2);'); con.once('readyForQuery', function() { callback(con); }); }); }); }); }; module.exports = { connect: connect }; brianc-node-postgres-e5c48f3/test/integration/gh-issues/000077500000000000000000000000001176777237400234205ustar00rootroot00000000000000brianc-node-postgres-e5c48f3/test/integration/gh-issues/130.js000066400000000000000000000010371176777237400242620ustar00rootroot00000000000000var helper = require(__dirname + '/../test-helper'); var exec = require('child_process').exec; helper.pg.defaults.poolIdleTimeout = 1000; helper.pg.connect(helper.config, function(err,client) { client.query("SELECT pg_backend_pid()", function(err, result) { var pid = result.rows[0].pg_backend_pid; exec('psql -c "select pg_terminate_backend('+pid+')" template1', assert.calls(function (error, stdout, stderr) { assert.isNull(error); })); }); }); helper.pg.on('error', function(err, client) { //swallow errors }); brianc-node-postgres-e5c48f3/test/integration/gh-issues/131.js000066400000000000000000000015331176777237400242640ustar00rootroot00000000000000var helper = require(__dirname + "/../test-helper"); var pg = helper.pg; test('parsing array results', function() { pg.connect(helper.config, assert.calls(function(err, client) { assert.isNull(err); client.query("CREATE TEMP TABLE why(names text[], numbors integer[], decimals double precision[])"); client.query('INSERT INTO why(names, numbors, decimals) VALUES(\'{"aaron", "brian","a b c" }\', \'{1, 2, 3}\', \'{.1, 0.05, 3.654}\')').on('error', console.log); test('decimals', function() { client.query('SELECT decimals FROM why', assert.success(function(result) { assert.lengthIs(result.rows[0].decimals, 3); assert.equal(result.rows[0].decimals[0], 0.1); assert.equal(result.rows[0].decimals[1], 0.05); assert.equal(result.rows[0].decimals[2], 3.654); pg.end(); })) }) })) }) brianc-node-postgres-e5c48f3/test/integration/test-helper.js000066400000000000000000000006201176777237400243010ustar00rootroot00000000000000var helper = require(__dirname + '/../test-helper'); if(helper.args.native) { Client = require(__dirname + '/../../lib/native'); helper.Client = Client; helper.pg = helper.pg.native; } //creates a client from cli parameters helper.client = function() { var client = new Client(helper.config); client.connect(); return client; }; //export parent helper stuffs module.exports = helper; brianc-node-postgres-e5c48f3/test/native/000077500000000000000000000000001176777237400204545ustar00rootroot00000000000000brianc-node-postgres-e5c48f3/test/native/callback-api-tests.js000066400000000000000000000010771176777237400244620ustar00rootroot00000000000000var helper = require(__dirname + "/../test-helper"); var Client = require(__dirname + "/../../lib/native"); test('fires callback with results', function() { var client = new Client(helper.config); client.connect(); client.query('SELECT 1 as num', assert.calls(function(err, result) { assert.isNull(err); assert.equal(result.rows[0].num, 1); client.query('SELECT * FROM person WHERE name = $1', ['Brian'], assert.calls(function(err, result) { assert.isNull(err); assert.equal(result.rows[0].name, 'Brian'); client.end(); })) })); }) brianc-node-postgres-e5c48f3/test/native/connection-tests.js000066400000000000000000000011231176777237400243060ustar00rootroot00000000000000var helper = require(__dirname + "/../test-helper"); var Client = require(__dirname + "/../../lib/native"); test('connecting with wrong parameters', function() { var con = new Client("user=asldfkj hostaddr=127.0.0.1 port=5432 dbname=asldkfj"); assert.emits(con, 'error', function(error) { assert.ok(error != null, "error should not be null"); con.end(); }); con.connect(); }); test('connects', function() { var con = new Client(helper.config); con.connect(); assert.emits(con, 'connect', function() { test('disconnects', function() { con.end(); }) }) }) brianc-node-postgres-e5c48f3/test/native/error-tests.js000066400000000000000000000025631176777237400233110ustar00rootroot00000000000000var helper = require(__dirname + "/../test-helper"); var Client = require(__dirname + "/../../lib/native"); test('query with non-text as first parameter throws error', function() { var client = new Client(helper.config); client.connect(); assert.emits(client, 'connect', function() { assert.throws(function() { client.query({text:{fail: true}}); }) client.end(); }) }) test('parameterized query with non-text as first parameter throws error', function() { var client = new Client(helper.config); client.connect(); assert.emits(client, 'connect', function() { assert.throws(function() { client.query({ text: {fail: true}, values: [1, 2] }) }) client.end(); }) }) var connect = function(callback) { var client = new Client(helper.config); client.connect(); assert.emits(client, 'connect', function() { callback(client); }) } test('parameterized query with non-array for second value', function() { test('inline', function() { connect(function(client) { assert.throws(function() { client.query("SELECT *", "LKSDJF") }) client.end(); }) }) test('config', function() { connect(function(client) { assert.throws(function() { client.query({ text: "SELECT *", values: "ALSDKFJ" }) }) client.end(); }) }) }) brianc-node-postgres-e5c48f3/test/native/evented-api-tests.js000066400000000000000000000066071176777237400243640ustar00rootroot00000000000000var helper = require(__dirname + "/../test-helper"); var Client = require(__dirname + "/../../lib/native"); var setupClient = function() { var client = new Client(helper.config); client.connect(); client.query("CREATE TEMP TABLE boom(name varchar(10), age integer)"); client.query("INSERT INTO boom(name, age) VALUES('Aaron', 26)"); client.query("INSERT INTO boom(name, age) VALUES('Brian', 28)"); return client; } test('connects', function() { var client = new Client(helper.config); client.connect(); test('good query', function() { var query = client.query("SELECT 1 as num, 'HELLO' as str"); assert.emits(query, 'row', function(row) { test('has integer data type', function() { assert.strictEqual(row.num, 1); }) test('has string data type', function() { assert.strictEqual(row.str, "HELLO") }) test('emits end AFTER row event', function() { assert.emits(query, 'end'); test('error query', function() { var query = client.query("LSKDJF"); assert.emits(query, 'error', function(err) { assert.ok(err != null, "Should not have emitted null error"); client.end(); }) }) }) }) }) }) test('multiple results', function() { test('queued queries', function() { var client = setupClient(); var q = client.query("SELECT name FROM BOOM"); assert.emits(q, 'row', function(row) { assert.equal(row.name, 'Aaron'); assert.emits(q, 'row', function(row) { assert.equal(row.name, "Brian"); }) }) assert.emits(q, 'end', function() { test('query with config', function() { var q = client.query({text:'SELECT 1 as num'}); assert.emits(q, 'row', function(row) { assert.strictEqual(row.num, 1); assert.emits(q, 'end', function() { client.end(); }) }) }) }) }) }) test('parameterized queries', function() { test('with a single string param', function() { var client = setupClient(); var q = client.query("SELECT * FROM boom WHERE name = $1", ['Aaron']); assert.emits(q, 'row', function(row) { assert.equal(row.name, 'Aaron'); }) assert.emits(q, 'end', function() { client.end(); }); }) test('with object config for query', function() { var client = setupClient(); var q = client.query({ text: "SELECT name FROM boom WHERE name = $1", values: ['Brian'] }); assert.emits(q, 'row', function(row) { assert.equal(row.name, 'Brian'); }) assert.emits(q, 'end', function() { client.end(); }) }) test('multiple parameters', function() { var client = setupClient(); var q = client.query('SELECT name FROM boom WHERE name = $1 or name = $2 ORDER BY name', ['Aaron', 'Brian']); assert.emits(q, 'row', function(row) { assert.equal(row.name, 'Aaron'); assert.emits(q, 'row', function(row) { assert.equal(row.name, 'Brian'); assert.emits(q, 'end', function() { client.end(); }) }) }) }) test('integer parameters', function() { var client = setupClient(); var q = client.query('SELECT * FROM boom WHERE age > $1', [27]); assert.emits(q, 'row', function(row) { assert.equal(row.name, 'Brian'); assert.equal(row.age, 28); }); assert.emits(q, 'end', function() { client.end(); }) }) }) brianc-node-postgres-e5c48f3/test/native/stress-tests.js000066400000000000000000000022271176777237400235000ustar00rootroot00000000000000var helper = require(__dirname + "/../test-helper"); var Client = require(__dirname + "/../../lib/native"); test('many rows', function() { var client = new Client(helper.config); client.connect(); var q = client.query("SELECT * FROM person"); var rows = []; q.on('row', function(row) { rows.push(row) }); assert.emits(q, 'end', function() { client.end(); assert.lengthIs(rows, 26); }) }); test('many queries', function() { var client = new Client(helper.config); client.connect(); var count = 0; var expected = 100; for(var i = 0; i < expected; i++) { var q = client.query("SELECT * FROM person"); assert.emits(q, 'end', function() { count++; }) } assert.emits(client, 'drain', function() { client.end(); assert.equal(count, expected); }) }) test('many clients', function() { var clients = []; for(var i = 0; i < 10; i++) { clients.push(new Client(helper.config)); } clients.forEach(function(client) { client.connect(); for(var i = 0; i < 20; i++) { client.query('SELECT * FROM person'); } assert.emits(client, 'drain', function() { client.end(); }) }) }) brianc-node-postgres-e5c48f3/test/test-buffers.js000066400000000000000000000054401176777237400221400ustar00rootroot00000000000000require(__dirname+'/test-helper'); //http://developer.postgresql.org/pgdocs/postgres/protocol-message-formats.html var buffers = {}; buffers.readyForQuery = function() { return new BufferList() .add(Buffer('I')) .join(true,'Z'); }; buffers.authenticationOk = function() { return new BufferList() .addInt32(0) .join(true, 'R'); }; buffers.authenticationCleartextPassword = function() { return new BufferList() .addInt32(3) .join(true, 'R'); }; buffers.authenticationMD5Password = function() { return new BufferList() .addInt32(5) .add(Buffer([1,2,3,4])) .join(true, 'R'); }; buffers.parameterStatus = function(name, value) { return new BufferList() .addCString(name) .addCString(value) .join(true, 'S'); }; buffers.backendKeyData = function(processID, secretKey) { return new BufferList() .addInt32(processID) .addInt32(secretKey) .join(true, 'K'); }; buffers.commandComplete = function(string) { return new BufferList() .addCString(string) .join(true, 'C'); }; buffers.rowDescription = function(fields) { fields = fields || []; var buf = new BufferList(); buf.addInt16(fields.length); fields.forEach(function(field) { buf.addCString(field.name) .addInt32(field.tableID || 0) .addInt16(field.attributeNumber || 0) .addInt32(field.dataTypeID || 0) .addInt16(field.dataTypeSize || 0) .addInt32(field.typeModifier || 0) .addInt16(field.formatCode || 0) }); return buf.join(true, 'T'); }; buffers.dataRow = function(columns) { columns = columns || []; var buf = new BufferList(); buf.addInt16(columns.length); columns.forEach(function(col) { if(col == null) { buf.addInt32(-1); } else { var strBuf = new Buffer(col, 'utf8'); buf.addInt32(strBuf.length); buf.add(strBuf); } }); return buf.join(true, 'D'); }; buffers.error = function(fields) { return errorOrNotice(fields).join(true, 'E'); }; buffers.notice = function(fields) { return errorOrNotice(fields).join(true, 'N'); }; var errorOrNotice = function(fields) { fields = fields || []; var buf = new BufferList(); fields.forEach(function(field) { buf.addChar(field.type); buf.addCString(field.value); }); return buf.add(Buffer([0]));//terminator } buffers.parseComplete = function() { return new BufferList().join(true, '1'); }; buffers.bindComplete = function() { return new BufferList().join(true, '2'); }; buffers.notification = function(id, channel, payload) { return new BufferList() .addInt32(id) .addCString(channel) .addCString(payload) .join(true, 'A') }; buffers.emptyQuery = function() { return new BufferList().join(true, 'I'); }; buffers.portalSuspended = function() { return new BufferList().join(true, 's'); }; module.exports = buffers; brianc-node-postgres-e5c48f3/test/test-helper.js000066400000000000000000000126621176777237400217670ustar00rootroot00000000000000//make assert a global... assert = require('assert'); var EventEmitter = require('events').EventEmitter; var sys = require('util'); var BufferList = require(__dirname+'/buffer-list') var Connection = require(__dirname + '/../lib/connection'); Client = require(__dirname + '/../lib').Client; process.on('uncaughtException', function(d) { if ('stack' in d && 'message' in d) { console.log("Message: " + d.message); console.log(d.stack); } else { console.log(d); } }); assert.same = function(actual, expected) { for(var key in expected) { assert.equal(actual[key], expected[key]); } }; assert.emits = function(item, eventName, callback, message) { var called = false; var id = setTimeout(function() { test("Should have called " + eventName, function() { assert.ok(called, message || "Expected '" + eventName + "' to be called.") }); },2000); item.once(eventName, function() { if (eventName === 'error') { // belt and braces test to ensure all error events return an error assert.ok(arguments[0] instanceof Error, "Expected error events to throw instances of Error but found: " + sys.inspect(arguments[0])); } called = true; clearTimeout(id); assert.ok(true); if(callback) { callback.apply(item, arguments); } }); }; assert.UTCDate = function(actual, year, month, day, hours, min, sec, milisecond) { var actualYear = actual.getUTCFullYear(); assert.equal(actualYear, year, "expected year " + year + " but got " + actualYear); var actualMonth = actual.getUTCMonth(); assert.equal(actualMonth, month, "expected month " + month + " but got " + actualMonth); var actualDate = actual.getUTCDate(); assert.equal(actualDate, day, "expected day " + day + " but got " + actualDate); var actualHours = actual.getUTCHours(); assert.equal(actualHours, hours, "expected hours " + hours + " but got " + actualHours); var actualMin = actual.getUTCMinutes(); assert.equal(actualMin, min, "expected min " + min + " but got " + actualMin); var actualSec = actual.getUTCSeconds(); assert.equal(actualSec, sec, "expected sec " + sec + " but got " + actualSec); var actualMili = actual.getUTCMilliseconds(); assert.equal(actualMili, milisecond, "expected milisecond " + milisecond + " but got " + actualMili); }; var spit = function(actual, expected) { console.log(""); console.log("actual " + sys.inspect(actual)); console.log("expect " + sys.inspect(expected)); console.log(""); } assert.equalBuffers = function(actual, expected) { if(actual.length != expected.length) { spit(actual, expected) assert.equal(actual.length, expected.length); } for(var i = 0; i < actual.length; i++) { if(actual[i] != expected[i]) { spit(actual, expected) } assert.equal(actual[i],expected[i]); } }; assert.empty = function(actual) { assert.lengthIs(actual, 0); }; assert.success = function(callback) { return assert.calls(function(err, arg) { if(err) { console.log(err); } assert.isNull(err); callback(arg); }) } assert.throws = function(offender) { try { offender(); } catch (e) { assert.ok(e instanceof Error, "Expected " + offender + " to throw instances of Error"); return; } assert.ok(false, "Expected " + offender + " to throw exception"); } assert.lengthIs = function(actual, expectedLength) { assert.equal(actual.length, expectedLength); }; var expect = function(callback, timeout) { var executed = false; var id = setTimeout(function() { assert.ok(executed, "Expected execution of function to be fired"); }, timeout || 2000) return function(err, queryResult) { clearTimeout(id); if (err) { assert.ok(err instanceof Error, "Expected errors to be instances of Error: " + sys.inspect(err)); } callback.apply(this, arguments) } } assert.calls = expect; assert.isNull = function(item, message) { message = message || "expected " + item + " to be null"; assert.ok(item === null, message); }; test = function(name, action) { test.testCount ++; var result = action(); if(result === false) { process.stdout.write('?'); }else{ process.stdout.write('.'); } }; //print out the filename process.stdout.write(require('path').basename(process.argv[1])); var args = require(__dirname + '/cli'); if(args.binary) process.stdout.write(' (binary)'); if(args.native) process.stdout.write(' (native)'); process.on('exit', console.log) process.on('uncaughtException', function(err) { console.error("\n %s", err.stack || err.toString()) //causes xargs to abort right away process.exit(255); }); var count = 0; var Sink = function(expected, timeout, callback) { var defaultTimeout = 1000; if(typeof timeout == 'function') { callback = timeout; timeout = defaultTimeout; } timeout = timeout || defaultTimeout; var internalCount = 0; var kill = function() { assert.ok(false, "Did not reach expected " + expected + " with an idle timeout of " + timeout); } var killTimeout = setTimeout(kill, timeout); return { add: function(count) { count = count || 1; internalCount += count; clearTimeout(killTimeout) if(internalCount < expected) { killTimeout = setTimeout(kill, timeout) } else { assert.equal(internalCount, expected); callback(); } } } } module.exports = { Sink: Sink, pg: require(__dirname + '/../lib/'), args: args, config: args, sys: sys, Client: Client }; brianc-node-postgres-e5c48f3/test/unit/000077500000000000000000000000001176777237400201455ustar00rootroot00000000000000brianc-node-postgres-e5c48f3/test/unit/client/000077500000000000000000000000001176777237400214235ustar00rootroot00000000000000brianc-node-postgres-e5c48f3/test/unit/client/cleartext-password-tests.js000066400000000000000000000007401176777237400267550ustar00rootroot00000000000000require(__dirname+'/test-helper'); test('cleartext password authentication', function(){ var client = createClient(); client.password = "!"; client.connection.stream.packets = []; client.connection.emit('authenticationCleartextPassword'); test('responds with password', function() { var packets = client.connection.stream.packets; assert.lengthIs(packets, 1); var packet = packets[0]; assert.equalBuffers(packet, [0x70, 0, 0, 0, 6, 33, 0]); }); }); brianc-node-postgres-e5c48f3/test/unit/client/configuration-tests.js000066400000000000000000000034031176777237400257700ustar00rootroot00000000000000require(__dirname+'/test-helper'); test('client settings', function() { test('defaults', function() { var client = new Client(); assert.equal(client.user, process.env.USER); assert.equal(client.database, process.env.USER); assert.equal(client.port, 5432); }); test('custom', function() { var user = 'brian'; var database = 'pgjstest'; var password = 'boom'; var client = new Client({ user: user, database: database, port: 321, password: password }); assert.equal(client.user, user); assert.equal(client.database, database); assert.equal(client.port, 321); assert.equal(client.password, password); }); }); test('initializing from a config string', function() { test('uses the correct values from the config string', function() { var client = new Client("pg://brian:pass@host1:333/databasename") assert.equal(client.user, 'brian') assert.equal(client.password, "pass") assert.equal(client.host, "host1") assert.equal(client.port, 333) assert.equal(client.database, "databasename") }) test('when not including all values the defaults are used', function() { var client = new Client("pg://host1") assert.equal(client.user, process.env.USER) assert.equal(client.password, null) assert.equal(client.host, "host1") assert.equal(client.port, 5432) assert.equal(client.database, process.env.USER) }) }) test('calls connect correctly on connection', function() { var client = new Client("/tmp"); var usedPort = ""; var usedHost = ""; client.connection.connect = function(port, host) { usedPort = port; usedHost = host; }; client.connect(); assert.equal(usedPort, "/tmp/.s.PGSQL.5432"); assert.strictEqual(usedHost, undefined) }) brianc-node-postgres-e5c48f3/test/unit/client/connection-string-tests.js000066400000000000000000000011011176777237400265550ustar00rootroot00000000000000require(__dirname + '/test-helper'); test("using connection string in client constructor", function() { var client = new Client("postgres://brian:pw@boom:381/lala"); test("parses user", function() { assert.equal(client.user,'brian'); }) test("parses password", function() { assert.equal(client.password, 'pw'); }) test("parses host", function() { assert.equal(client.host, 'boom'); }) test('parses port', function() { assert.equal(client.port, 381) }) test('parses database', function() { assert.equal(client.database, 'lala') }) }) brianc-node-postgres-e5c48f3/test/unit/client/md5-password-tests.js000066400000000000000000000013641176777237400254520ustar00rootroot00000000000000require(__dirname + '/test-helper') test('md5 authentication', function() { var client = createClient(); client.password = "!"; var salt = Buffer([1, 2, 3, 4]); client.connection.emit('authenticationMD5Password', {salt: salt}); test('responds', function() { assert.lengthIs(client.connection.stream.packets, 1); test('should have correct encrypted data', function() { var encrypted = Client.md5(client.password + client.user); encrypted = Client.md5(encrypted + salt.toString('binary')); var password = "md5" + encrypted //how do we want to test this? assert.equalBuffers(client.connection.stream.packets[0], new BufferList() .addCString(password).join(true,'p')) }); }); }); brianc-node-postgres-e5c48f3/test/unit/client/notification-tests.js000066400000000000000000000004151176777237400256070ustar00rootroot00000000000000var helper = require(__dirname + "/test-helper"); test('passes connection notification', function() { var client = helper.client(); assert.emits(client, 'notice', function(msg) { assert.equal(msg, "HAY!!"); }) client.connection.emit('notice', "HAY!!"); }) brianc-node-postgres-e5c48f3/test/unit/client/prepared-statement-tests.js000066400000000000000000000037701176777237400267340ustar00rootroot00000000000000var helper = require(__dirname + '/test-helper'); var client = helper.client(); var con = client.connection; var parseArg = null; con.parse = function(arg) { parseArg = arg; process.nextTick(function() { con.emit('parseComplete'); }); }; var bindArg = null; con.bind = function(arg) { bindArg = arg; process.nextTick(function(){ con.emit('bindComplete'); }); }; var executeArg = null; con.execute = function(arg) { executeArg = arg; process.nextTick(function() { con.emit('rowData',{ fields: [] }); con.emit('commandComplete', { text: "" }); }); }; var describeArg = null; con.describe = function(arg) { describeArg = arg; process.nextTick(function() { con.emit('rowDescription', { fields: [] }); }); }; var syncCalled = false; con.flush = function() { }; con.sync = function() { syncCalled = true; process.nextTick(function() { con.emit('readyForQuery'); }); }; test('bound command', function() { test('simple, unnamed bound command', function() { assert.ok(client.connection.emit('readyForQuery')); var query = client.query({ text: 'select * where name = $1', values: ['hi'] }); assert.emits(query,'end', function() { test('parse argument', function() { assert.equal(parseArg.name, null); assert.equal(parseArg.text, 'select * where name = $1'); assert.equal(parseArg.types, null); }); test('bind argument', function() { assert.equal(bindArg.statement, null); assert.equal(bindArg.portal, null); assert.lengthIs(bindArg.values, 1); assert.equal(bindArg.values[0], 'hi') }); test('describe argument', function() { assert.equal(describeArg.type, 'P'); assert.equal(describeArg.name, ""); }); test('execute argument', function() { assert.equal(executeArg.portal, null); assert.equal(executeArg.rows, null); }); test('sync called', function() { assert.ok(syncCalled); }); }); }); }); brianc-node-postgres-e5c48f3/test/unit/client/query-queue-tests.js000066400000000000000000000051631176777237400254150ustar00rootroot00000000000000var helper = require(__dirname + '/test-helper'); var Connection = require(__dirname + '/../../../lib/connection'); test('drain', function() { var con = new Connection({stream: "NO"}); var client = new Client({connection:con}); con.connect = function() { con.emit('connect'); }; con.query = function() { }; client.connect(); var raisedDrain = false; client.on('drain', function() { raisedDrain = true; }); client.query("hello"); client.query("sup"); client.query('boom'); test("with pending queries", function() { test("does not emit drain", function() { assert.equal(raisedDrain, false); }); }); test("after some queries executed", function() { con.emit('readyForQuery'); test("does not emit drain", function() { assert.equal(raisedDrain, false); }); }); test("when all queries are sent", function() { con.emit('readyForQuery'); con.emit('readyForQuery'); test("does not emit drain", function() { assert.equal(raisedDrain, false); }); }); test("after last query finishes", function() { con.emit('readyForQuery'); test("emits drain", function() { process.nextTick(function() { assert.ok(raisedDrain); }) }); }); }); test('with drain paused', function() { //mock out a fake connection var con = new Connection({stream: "NO"}); con.connect = function() { con.emit('connect'); }; con.query = function() { }; var client = new Client({connection:con}); client.connect(); var drainCount = 0; client.on('drain', function() { drainCount++; }); test('normally unpaused', function() { con.emit('readyForQuery'); client.query('boom'); assert.emits(client, 'drain', function() { assert.equal(drainCount, 1); }); con.emit('readyForQuery'); }); test('pausing', function() { test('unpaused with no queries in between', function() { client.pauseDrain(); client.resumeDrain(); assert.equal(drainCount, 1); }); test('paused', function() { test('resumeDrain after empty', function() { client.pauseDrain(); client.query('asdf'); con.emit('readyForQuery'); assert.equal(drainCount, 1); client.resumeDrain(); assert.equal(drainCount, 2); }); test('resumDrain while still pending', function() { client.pauseDrain(); client.query('asdf'); client.query('asdf1'); con.emit('readyForQuery'); client.resumeDrain(); assert.equal(drainCount, 2); con.emit('readyForQuery'); assert.equal(drainCount, 3); }); }); }); }); brianc-node-postgres-e5c48f3/test/unit/client/query-tests.js000066400000000000000000000044431176777237400242730ustar00rootroot00000000000000var helper = require(__dirname + '/test-helper'); var q = {}; q.dateParser = require(__dirname + "/../../../lib/types").getTypeParser(1114, 'text'); q.stringArrayParser = require(__dirname + "/../../../lib/types").getTypeParser(1009, 'text'); test("testing dateParser", function() { assert.equal(q.dateParser("2010-12-11 09:09:04").toUTCString(),new Date("2010-12-11 09:09:04 GMT").toUTCString()); }); var testForMs = function(part, expected) { var dateString = "2010-01-01 01:01:01" + part; test('testing for correcting parsing of ' + dateString, function() { var ms = q.dateParser(dateString).getMilliseconds(); assert.equal(ms, expected) }) } testForMs('.1', 100); testForMs('.01', 10); testForMs('.74', 740); test("testing 2dateParser", function() { var actual = "2010-12-11 09:09:04.1"; var expected = "\"2010-12-11T09:09:04.100Z\""; assert.equal(JSON.stringify(q.dateParser(actual)),expected); }); test("testing 2dateParser", function() { var actual = "2011-01-23 22:15:51.28-06"; var expected = "\"2011-01-24T04:15:51.280Z\""; assert.equal(JSON.stringify(q.dateParser(actual)),expected); }); test("testing 2dateParser", function() { var actual = "2011-01-23 22:15:51.280843-06"; var expected = "\"2011-01-24T04:15:51.280Z\""; assert.equal(JSON.stringify(q.dateParser(actual)),expected); }); test("testing empty array", function(){ var input = '{}'; var expected = []; assert.deepEqual(q.stringArrayParser(input), expected); }); test("testing empty string array", function(){ var input = '{""}'; var expected = [""]; assert.deepEqual(q.stringArrayParser(input), expected); }); test("testing numeric array", function(){ var input = '{1,2,3,4}'; var expected = [1,2,3,4]; assert.deepEqual(q.stringArrayParser(input), expected); }); test("testing stringy array", function(){ var input = '{a,b,c,d}'; var expected = ['a','b','c','d']; assert.deepEqual(q.stringArrayParser(input), expected); }); test("testing stringy array containing escaped strings", function(){ var input = '{"\\"\\"\\"","\\\\\\\\\\\\"}'; var expected = ['"""','\\\\\\']; assert.deepEqual(q.stringArrayParser(input), expected); }); test("testing NULL array", function(){ var input = '{NULL,NULL}'; var expected = [null,null]; assert.deepEqual(q.stringArrayParser(input), expected); }); brianc-node-postgres-e5c48f3/test/unit/client/result-metadata-tests.js000066400000000000000000000021311176777237400262120ustar00rootroot00000000000000var helper = require(__dirname + "/test-helper") var testForTag = function(tagText, callback) { test('includes command tag data for tag ' + tagText, function() { var client = helper.client(); client.connection.emit('readyForQuery') var query = client.query("whatever"); assert.lengthIs(client.connection.queries, 1) assert.emits(query, 'end', function(result) { assert.ok(result != null, "should pass something to this event") callback(result) }) client.connection.emit('commandComplete', { text: tagText }); client.connection.emit('readyForQuery'); }) } var check = function(oid, rowCount, command) { return function(result) { if(oid != null) { assert.equal(result.oid, oid); } assert.equal(result.rowCount, rowCount); assert.equal(result.command, command); } } testForTag("INSERT 0 3", check(0, 3, "INSERT")); testForTag("INSERT 841 1", check(841, 1, "INSERT")); testForTag("DELETE 10", check(null, 10, "DELETE")); testForTag("UPDATE 11", check(null, 11, "UPDATE")); testForTag("SELECT 20", check(null, 20, "SELECT")); brianc-node-postgres-e5c48f3/test/unit/client/simple-query-tests.js000066400000000000000000000074421176777237400255640ustar00rootroot00000000000000var helper = require(__dirname + "/test-helper"); test('executing query', function() { test("queing query", function() { test('when connection is ready', function() { var client = helper.client(); assert.empty(client.connection.queries); client.connection.emit('readyForQuery'); client.query('yes'); assert.lengthIs(client.connection.queries, 1); assert.equal(client.connection.queries, 'yes'); }); test('when connection is not ready', function() { var client = helper.client(); test('query is not sent', function() { client.query('boom'); assert.empty(client.connection.queries); }); test('sends query to connection once ready', function() { assert.ok(client.connection.emit('readyForQuery')); assert.lengthIs(client.connection.queries, 1); assert.equal(client.connection.queries[0], "boom"); }); }); test("multiple in the queue", function() { var client = helper.client(); var connection = client.connection; var queries = connection.queries; client.query('one'); client.query('two'); client.query('three'); assert.empty(queries); test("after one ready for query",function() { connection.emit('readyForQuery'); assert.lengthIs(queries, 1); assert.equal(queries[0], "one"); }); test('after two ready for query', function() { connection.emit('readyForQuery'); assert.lengthIs(queries, 2); }); test("after a bunch more", function() { connection.emit('readyForQuery'); connection.emit('readyForQuery'); connection.emit('readyForQuery'); assert.lengthIs(queries, 3); assert.equal(queries[0], "one"); assert.equal(queries[1], 'two'); assert.equal(queries[2], 'three'); }); }); }); test("query event binding and flow", function() { var client = helper.client(); var con = client.connection; var query = client.query('whatever'); test("has no queries sent before ready", function() { assert.empty(con.queries); }); test('sends query on readyForQuery event', function() { con.emit('readyForQuery'); assert.lengthIs(con.queries, 1); assert.equal(con.queries[0], 'whatever'); }); test('handles rowDescription message', function() { var handled = con.emit('rowDescription',{ fields: [{ name: 'boom' }] }); assert.ok(handled, "should have handlded rowDescritpion"); }); test('handles dataRow messages', function() { assert.emits(query, 'row', function(row) { assert.equal(row['boom'], "hi"); }); var handled = con.emit('dataRow', { fields: ["hi"] }); assert.ok(handled, "should have handled first data row message"); assert.emits(query, 'row', function(row) { assert.equal(row['boom'], "bye"); }); var handledAgain = con.emit('dataRow', { fields: ["bye"] }); assert.ok(handledAgain, "should have handled seciond data row message"); }); //multiple command complete messages will be sent //when multiple queries are in a simple command test('handles command complete messages', function() { con.emit('commandComplete', { text: 'INSERT 31 1' }); }); test('removes itself after another readyForQuery message', function() { return false; assert.emits(query, "end", function(msg) { //TODO do we want to check the complete messages? }); con.emit("readyForQuery"); //this would never actually happen ['dataRow','rowDescritpion', 'commandComplete'].forEach(function(msg) { assert.equal(con.emit(msg), false, "Should no longer be picking up '"+ msg +"' messages"); }); }); }); }); brianc-node-postgres-e5c48f3/test/unit/client/test-helper.js000066400000000000000000000010431176777237400242130ustar00rootroot00000000000000var helper = require(__dirname+'/../test-helper'); var Connection = require(__dirname + '/../../../lib/connection'); var makeClient = function() { var connection = new Connection({stream: "no"}); connection.startup = function() {}; connection.connect = function() {}; connection.query = function(text) { this.queries.push(text); }; connection.queries = []; var client = new Client({connection: connection}); client.connect(); client.connection.emit('connect'); return client; }; module.exports = { client: makeClient }; brianc-node-postgres-e5c48f3/test/unit/client/typed-query-results-tests.js000066400000000000000000000141331176777237400271120ustar00rootroot00000000000000var helper = require(__dirname + '/test-helper'); //http://www.postgresql.org/docs/8.4/static/datatype.html test('typed results', function() { var client = helper.client(); var con = client.connection; con.emit('readyForQuery'); var query = client.query("the bums lost"); //TODO refactor to this style var tests = [{ name: 'string/varchar', format: 'text', dataTypeID: 1043, actual: 'bang', expected: 'bang' },{ name: 'integer/int4', format: 'text', dataTypeID: 23, actual: '100', expected: 100 },{ name: 'smallint/int2', format: 'text', dataTypeID: 21, actual: '101', expected: 101 },{ name: 'bigint/int8', format: 'text', dataTypeID: 20, actual: '102', expected: 102 },{ name: 'oid', format: 'text', dataTypeID: 26, actual: '103', expected: 103 },{ name: 'numeric', format: 'text', dataTypeID: 1700, actual: '12.34', expected: 12.34 },{ name: 'real/float4', dataTypeID: 700, format: 'text', actual: '123.456', expected: 123.456 },{ name: 'double precision / float8', format: 'text', dataTypeID: 701, actual: '1.2', expected: 1.2 },{ name: 'boolean true', format: 'text', dataTypeID: 16, actual: 't', expected: true },{ name: 'boolean false', format: 'text', dataTypeID: 16, actual: 'f', expected: false },{ name: 'boolean null', format: 'text', dataTypeID: 16, actual: null, expected: null },{ name: 'timestamptz with minutes in timezone', format: 'text', dataTypeID: 1184, actual: '2010-10-31 14:54:13.74-0530', expected: function(val) { assert.UTCDate(val, 2010, 9, 31, 20, 24, 13, 740); } },{ name: 'timestamptz with other milisecond digits dropped', format: 'text', dataTypeID: 1184, actual: '2011-01-23 22:05:00.68-06', expected: function(val) { assert.UTCDate(val, 2011, 0, 24, 4, 5, 00, 680); } }, { name: 'timestampz with huge miliseconds in UTC', format: 'text', dataTypeID: 1184, actual: '2010-10-30 14:11:12.730838Z', expected: function(val) { assert.UTCDate(val, 2010, 9, 30, 14, 11, 12, 730); } },{ name: 'timestampz with no miliseconds', format: 'text', dataTypeID: 1184, actual: '2010-10-30 13:10:01+05', expected: function(val) { assert.UTCDate(val, 2010, 9, 30, 8, 10, 01, 0); } },{ name: 'timestamp', format: 'text', dataTypeID: 1114, actual: '2010-10-31 00:00:00', expected: function(val) { assert.UTCDate(val, 2010, 9, 31, 0, 0, 0, 0); } },{ name: 'interval time', format: 'text', dataTypeID: 1186, actual: '01:02:03', expected: function(val) { assert.deepEqual(val, {'hours':1, 'minutes':2, 'seconds':3}) } },{ name: 'interval long', format: 'text', dataTypeID: 1186, actual: '1 year -32 days', expected: function(val) { assert.deepEqual(val, {'years':1, 'days':-32}) } },{ name: 'interval combined negative', format: 'text', dataTypeID: 1186, actual: '1 day -00:00:03', expected: function(val) { assert.deepEqual(val, {'days':1, 'seconds':-3}) } },{ name: 'bytea', format: 'text', dataTypeID: 17, actual: 'foo\\000\\200\\\\\\377', expected: function(val) { assert.deepEqual(val, new Buffer([102, 111, 111, 0, 128, 92, 255])); } },{ name: 'empty bytea', format: 'text', dataTypeID: 17, actual: '', expected: function(val) { assert.deepEqual(val, new Buffer(0)); } }, { name: 'binary-string/varchar', format: 'binary', dataTypeID: 1043, actual: 'bang', expected: 'bang' },{ name: 'binary-integer/int4', format: 'binary', dataTypeID: 23, actual: [0, 0, 0, 100], expected: 100 },{ name: 'binary-smallint/int2', format: 'binary', dataTypeID: 21, actual: [0, 101], expected: 101 },{ name: 'binary-bigint/int8', format: 'binary', dataTypeID: 20, actual: [0, 0, 0, 0, 0, 0, 0, 102], expected: 102 },{ name: 'binary-bigint/int8-full', format: 'binary', dataTypeID: 20, actual: [1, 0, 0, 0, 0, 0, 0, 102], expected: 72057594037928030 },{ name: 'binary-oid', format: 'binary', dataTypeID: 26, actual: [0, 0, 0, 103], expected: 103 },{ name: 'binary-numeric', format: 'binary', dataTypeID: 1700, actual: [0,2,0,0,0,0,0,0x64,0,12,0xd,0x48,0,0,0,0], expected: 12.34 },{ name: 'binary-real/float4', dataTypeID: 700, format: 'binary', actual: [0x41, 0x48, 0x00, 0x00], expected: 12.5 },{ name: 'binary-double precision / float8', format: 'binary', dataTypeID: 701, actual: [0x3F,0xF3,0x33,0x33,0x33,0x33,0x33,0x33], expected: 1.2 },{ name: 'binary-boolean true', format: 'binary', dataTypeID: 16, actual: [1], expected: true },{ name: 'binary-boolean false', format: 'binary', dataTypeID: 16, actual: [0], expected: false },{ name: 'binary-boolean null', format: 'binary', dataTypeID: 16, actual: null, expected: null },{ name: 'binary-timestamp', format: 'binary', dataTypeID: 1184, actual: [0x00, 0x01, 0x36, 0xee, 0x3e, 0x66, 0x9f, 0xe0], expected: function(val) { assert.UTCDate(val, 2010, 9, 31, 20, 24, 13, 740); } },{ name: 'binary-string', format: 'binary', dataTypeID: 25, actual: new Buffer([0x73, 0x6c, 0x61, 0x64, 0x64, 0x61]), expected: 'sladda' }]; con.emit('rowDescription', { fieldCount: tests.length, fields: tests }); assert.emits(query, 'row', function(row) { for(var i = 0; i < tests.length; i++) { test('parses ' + tests[i].name, function() { var expected = tests[i].expected; if(typeof expected === 'function') { return expected(row[tests[i].name]); } assert.strictEqual(row[tests[i].name], expected); }); } }); assert.ok(con.emit('dataRow', { fields: tests.map(function(x) { return x.actual; }) })); }); brianc-node-postgres-e5c48f3/test/unit/connection/000077500000000000000000000000001176777237400223045ustar00rootroot00000000000000brianc-node-postgres-e5c48f3/test/unit/connection/error-tests.js000066400000000000000000000005761176777237400251430ustar00rootroot00000000000000var helper = require(__dirname + '/test-helper'); var Connection = require(__dirname + '/../../../lib/connection'); var con = new Connection({stream: new MemoryStream()}); test("connection emits stream errors", function() { assert.emits(con, 'error', function(err) { assert.equal(err.message, "OMG!"); }); con.connect(); con.stream.emit('error', new Error("OMG!")); }); brianc-node-postgres-e5c48f3/test/unit/connection/inbound-parser-tests.js000066400000000000000000000302671176777237400267420ustar00rootroot00000000000000require(__dirname+'/test-helper'); var Connection = require(__dirname + '/../../../lib/connection'); var buffers = require(__dirname + '/../../test-buffers'); var PARSE = function(buffer) { return new Parser(buffer).parse(); }; var authOkBuffer = buffers.authenticationOk(); var paramStatusBuffer = buffers.parameterStatus('client_encoding', 'UTF8'); var readyForQueryBuffer = buffers.readyForQuery(); var backendKeyDataBuffer = buffers.backendKeyData(1,2); var commandCompleteBuffer = buffers.commandComplete("SELECT 3"); var parseCompleteBuffer = buffers.parseComplete(); var bindCompleteBuffer = buffers.bindComplete(); var portalSuspendedBuffer = buffers.portalSuspended(); var addRow = function(bufferList, name, offset) { return bufferList.addCString(name) //field name .addInt32(offset++) //table id .addInt16(offset++) //attribute of column number .addInt32(offset++) //objectId of field's data type .addInt16(offset++) //datatype size .addInt32(offset++) //type modifier .addInt16(0) //format code, 0 => text }; var row1 = { name: 'id', tableID: 1, attributeNumber: 2, dataTypeID: 3, dataTypeSize: 4, typeModifier: 5, formatCode: 0 }; var oneRowDescBuff = new buffers.rowDescription([row1]); row1.name = 'bang'; var twoRowBuf = new buffers.rowDescription([row1,{ name: 'whoah', tableID: 10, attributeNumber: 11, dataTypeID: 12, dataTypeSize: 13, typeModifier: 14, formatCode: 0 }]) var emptyRowFieldBuf = new BufferList() .addInt16(0) .join(true, 'D'); var emptyRowFieldBuf = buffers.dataRow(); var oneFieldBuf = new BufferList() .addInt16(1) //number of fields .addInt32(5) //length of bytes of fields .addCString('test') .join(true, 'D'); var oneFieldBuf = buffers.dataRow(['test']); var expectedAuthenticationOkayMessage = { name: 'authenticationOk', length: 8 }; var expectedParameterStatusMessage = { name: 'parameterStatus', parameterName: 'client_encoding', parameterValue: 'UTF8', length: 25 }; var expectedBackendKeyDataMessage = { name: 'backendKeyData', processID: 1, secretKey: 2 }; var expectedReadyForQueryMessage = { name: 'readyForQuery', length: 5, status: 'I' }; var expectedCommandCompleteMessage = { length: 13, text: "SELECT 3" }; var emptyRowDescriptionBuffer = new BufferList() .addInt16(0) //number of fields .join(true,'T'); var expectedEmptyRowDescriptionMessage = { name: 'rowDescription', length: 6, fieldCount: 0 }; var expectedOneRowMessage = { name: 'rowDescription', length: 27, fieldCount: 1 }; var expectedTwoRowMessage = { name: 'rowDescription', length: 53, fieldCount: 2 }; var testForMessage = function(buffer, expectedMessage) { var lastMessage = {}; test('recieves and parses ' + expectedMessage.name, function() { var stream = new MemoryStream(); var client = new Connection({ stream: stream }); client.connect(); client.on('message',function(msg) { lastMessage = msg; }); client.on(expectedMessage.name, function() { client.removeAllListeners(expectedMessage.name); }); stream.emit('data', buffer); assert.same(lastMessage, expectedMessage); }); return lastMessage; }; var plainPasswordBuffer = buffers.authenticationCleartextPassword(); var md5PasswordBuffer = buffers.authenticationMD5Password(); var expectedPlainPasswordMessage = { name: 'authenticationCleartextPassword' }; var expectedMD5PasswordMessage = { name: 'authenticationMD5Password' }; var notificationResponseBuffer = buffers.notification(4, 'hi', 'boom'); var expectedNotificationResponseMessage = { name: 'notification', processId: 4, channel: 'hi', payload: 'boom' }; test('Connection', function() { testForMessage(authOkBuffer, expectedAuthenticationOkayMessage); testForMessage(plainPasswordBuffer, expectedPlainPasswordMessage); var msg = testForMessage(md5PasswordBuffer, expectedMD5PasswordMessage); test('md5 has right salt', function() { assert.equalBuffers(msg.salt, Buffer([1,2,3,4])); }); testForMessage(paramStatusBuffer, expectedParameterStatusMessage); testForMessage(backendKeyDataBuffer, expectedBackendKeyDataMessage); testForMessage(readyForQueryBuffer, expectedReadyForQueryMessage); testForMessage(commandCompleteBuffer,expectedCommandCompleteMessage); testForMessage(notificationResponseBuffer, expectedNotificationResponseMessage); test('empty row message', function() { var message = testForMessage(emptyRowDescriptionBuffer, expectedEmptyRowDescriptionMessage); test('has no fields', function() { assert.equal(message.fields.length, 0); }); }); test("no data message", function() { testForMessage(Buffer([0x6e, 0, 0, 0, 4]), { name: 'noData' }); }); test('one row message', function() { var message = testForMessage(oneRowDescBuff, expectedOneRowMessage); test('has one field', function() { assert.equal(message.fields.length, 1); }); test('has correct field info', function() { assert.same(message.fields[0], { name: 'id', tableID: 1, columnID: 2, dataTypeID: 3, dataTypeSize: 4, dataTypeModifier: 5, format: 'text' }); }); }); test('two row message', function() { var message = testForMessage(twoRowBuf, expectedTwoRowMessage); test('has two fields', function() { assert.equal(message.fields.length, 2); }); test('has correct first field', function() { assert.same(message.fields[0], { name: 'bang', tableID: 1, columnID: 2, dataTypeID: 3, dataTypeSize: 4, dataTypeModifier: 5, format: 'text' }) }); test('has correct second field', function() { assert.same(message.fields[1], { name: 'whoah', tableID: 10, columnID: 11, dataTypeID: 12, dataTypeSize: 13, dataTypeModifier: 14, format: 'text' }); }); }); test('parsing rows', function() { test('parsing empty row', function() { var message = testForMessage(emptyRowFieldBuf, { name: 'dataRow', fieldCount: 0 }); test('has 0 fields', function() { assert.equal(message.fields.length, 0); }); }); test('parsing data row with fields', function() { var message = testForMessage(oneFieldBuf, { name: 'dataRow', fieldCount: 1 }); test('has 1 field', function() { assert.equal(message.fields.length, 1); }); test('field is correct', function() { assert.equal(message.fields[0],'test'); }); }); }); test('notice message', function() { //this uses the same logic as error message var buff = buffers.notice([{type: 'C', value: 'code'}]); testForMessage(buff, { name: 'notice', code: 'code' }); }); test('error messages', function() { test('with no fields', function() { var msg = testForMessage(buffers.error(),{ name: 'error' }); }); test('with all the fields', function() { var buffer = buffers.error([{ type: 'S', value: 'ERROR' },{ type: 'C', value: 'code' },{ type: 'M', value: 'message' },{ type: 'D', value: 'details' },{ type: 'H', value: 'hint' },{ type: 'P', value: '100' },{ type: 'p', value: '101' },{ type: 'q', value: 'query' },{ type: 'W', value: 'where' },{ type: 'F', value: 'file' },{ type: 'L', value: 'line' },{ type: 'R', value: 'routine' },{ type: 'Z', //ignored value: 'alsdkf' }]); testForMessage(buffer,{ name: 'error', severity: 'ERROR', code: 'code', message: 'message', detail: 'details', hint: 'hint', position: '100', internalPosition: '101', internalQuery: 'query', where: 'where', file: 'file', line: 'line', routine: 'routine' }); }); }); test('parses parse complete command', function() { testForMessage(parseCompleteBuffer, { name: 'parseComplete' }); }); test('parses bind complete command', function() { testForMessage(bindCompleteBuffer, { name: 'bindComplete' }); }); test('parses portal suspended message', function() { testForMessage(portalSuspendedBuffer, { name: 'portalSuspended' }); }); }); //since the data message on a stream can randomly divide the incomming //tcp packets anywhere, we need to make sure we can parse every single //split on a tcp message test('split buffer, single message parsing', function() { var fullBuffer = buffers.dataRow([null, "bang", "zug zug", null, "!"]); var stream = new MemoryStream(); stream.readyState = 'open'; var client = new Connection({ stream: stream }); client.connect(); var message = null; client.on('message', function(msg) { message = msg; }); test('parses when full buffer comes in', function() { stream.emit('data', fullBuffer); assert.lengthIs(message.fields, 5); assert.equal(message.fields[0], null); assert.equal(message.fields[1], "bang"); assert.equal(message.fields[2], "zug zug"); assert.equal(message.fields[3], null); assert.equal(message.fields[4], "!"); }); var testMessageRecievedAfterSpiltAt = function(split) { var firstBuffer = new Buffer(fullBuffer.length-split); var secondBuffer = new Buffer(fullBuffer.length-firstBuffer.length); fullBuffer.copy(firstBuffer, 0, 0); fullBuffer.copy(secondBuffer, 0, firstBuffer.length); stream.emit('data', firstBuffer); stream.emit('data', secondBuffer); assert.lengthIs(message.fields, 5); assert.equal(message.fields[0], null); assert.equal(message.fields[1], "bang"); assert.equal(message.fields[2], "zug zug"); assert.equal(message.fields[3], null); assert.equal(message.fields[4], "!"); }; test('parses when split in the middle', function() { testMessageRecievedAfterSpiltAt(6); }); test('parses when split at end', function() { testMessageRecievedAfterSpiltAt(2); }); test('parses when split at beginning', function() { testMessageRecievedAfterSpiltAt(fullBuffer.length - 2); testMessageRecievedAfterSpiltAt(fullBuffer.length - 1); testMessageRecievedAfterSpiltAt(fullBuffer.length - 5); }); }); test('split buffer, multiple message parsing', function() { var dataRowBuffer = buffers.dataRow(['!']); var readyForQueryBuffer = buffers.readyForQuery(); var fullBuffer = new Buffer(dataRowBuffer.length + readyForQueryBuffer.length); dataRowBuffer.copy(fullBuffer, 0, 0); readyForQueryBuffer.copy(fullBuffer, dataRowBuffer.length, 0); var messages = []; var stream = new MemoryStream(); var client = new Connection({ stream: stream }); client.connect(); client.on('message', function(msg) { messages.push(msg); }); var verifyMessages = function() { assert.lengthIs(messages, 2); assert.same(messages[0],{ name: 'dataRow', fieldCount: 1 }); assert.equal(messages[0].fields[0],'!'); assert.same(messages[1],{ name: 'readyForQuery' }); messages = []; }; //sanity check test('recieves both messages when packet is not split', function() { stream.emit('data', fullBuffer); verifyMessages(); }); var splitAndVerifyTwoMessages = function(split) { var firstBuffer = new Buffer(fullBuffer.length-split); var secondBuffer = new Buffer(fullBuffer.length-firstBuffer.length); fullBuffer.copy(firstBuffer, 0, 0); fullBuffer.copy(secondBuffer, 0, firstBuffer.length); stream.emit('data', firstBuffer); stream.emit('data', secondBuffer); }; test('recieves both messages when packet is split', function() { test('in the middle', function() { splitAndVerifyTwoMessages(11); }); test('at the front', function() { splitAndVerifyTwoMessages(fullBuffer.length-1); splitAndVerifyTwoMessages(fullBuffer.length-4); splitAndVerifyTwoMessages(fullBuffer.length-6); }); test('at the end', function() { splitAndVerifyTwoMessages(8); splitAndVerifyTwoMessages(1); }); }); }); brianc-node-postgres-e5c48f3/test/unit/connection/outbound-sending-tests.js000066400000000000000000000105531176777237400272720ustar00rootroot00000000000000require(__dirname + "/test-helper"); var Connection = require(__dirname + '/../../../lib/connection'); var stream = new MemoryStream(); var con = new Connection({ stream: stream }); assert.received = function(stream, buffer) { assert.lengthIs(stream.packets, 1); var packet = stream.packets.pop(); assert.equalBuffers(packet, buffer); }; test("sends startup message", function() { con.startup({ user: 'brian', database: 'bang' }); assert.received(stream, new BufferList() .addInt16(3) .addInt16(0) .addCString('user') .addCString('brian') .addCString('database') .addCString('bang') .addCString('').join(true)) }); test('sends password message', function() { con.password("!"); assert.received(stream, new BufferList().addCString("!").join(true,'p')); }); test('sends query message', function() { var txt = 'select * from boom'; con.query(txt); assert.received(stream, new BufferList().addCString(txt).join(true,'Q')); }); test('sends parse message', function() { con.parse({text: '!'}); var expected = new BufferList() .addCString("") .addCString("!") .addInt16(0).join(true, 'P'); assert.received(stream, expected); }); test('sends parse message with named query', function() { con.parse({ name: 'boom', text: 'select * from boom', types: [] }); var expected = new BufferList() .addCString("boom") .addCString("select * from boom") .addInt16(0).join(true,'P'); assert.received(stream, expected); test('with multiple parameters', function() { con.parse({ name: 'force', text: 'select * from bang where name = $1', types: [1, 2, 3 ,4] }); var expected = new BufferList() .addCString("force") .addCString("select * from bang where name = $1") .addInt16(4) .addInt32(1) .addInt32(2) .addInt32(3) .addInt32(4).join(true,'P'); assert.received(stream, expected); }); }); test('bind messages', function() { test('with no values', function() { con.bind(); var expectedBuffer = new BufferList() .addCString("") .addCString("") .addInt16(0) .addInt16(0) .addInt16(0) .join(true,"B"); assert.received(stream, expectedBuffer); }); test('with named statement, portal, and values', function() { con.bind({ portal: 'bang', statement: 'woo', values: [1, 'hi', null, 'zing'] }); var expectedBuffer = new BufferList() .addCString('bang') //portal name .addCString('woo') //statement name .addInt16(0) .addInt16(4) .addInt32(1) .add(Buffer("1")) .addInt32(2) .add(Buffer("hi")) .addInt32(-1) .addInt32(4) .add(Buffer('zing')) .addInt16(0) .join(true, 'B'); assert.received(stream, expectedBuffer); }); }); test("sends execute message", function() { test("for unamed portal with no row limit", function() { con.execute(); var expectedBuffer = new BufferList() .addCString('') .addInt32(0) .join(true,'E'); assert.received(stream, expectedBuffer); }); test("for named portal with row limit", function() { con.execute({ portal: 'my favorite portal', rows: 100 }); var expectedBuffer = new BufferList() .addCString("my favorite portal") .addInt32(100) .join(true, 'E'); assert.received(stream, expectedBuffer); }); }); test('sends flush command', function() { con.flush(); var expected = new BufferList().join(true, 'H'); assert.received(stream, expected); }); test('sends sync command', function() { con.sync(); var expected = new BufferList().join(true,'S'); assert.received(stream, expected); }); test('sends end command', function() { con.end(); var expected = new Buffer([0x58, 0, 0, 0, 4]); assert.received(stream, expected); }); test('sends describe command',function() { test('describe statement', function() { con.describe({type: 'S', name: 'bang'}); var expected = new BufferList().addChar('S').addCString('bang').join(true, 'D') assert.received(stream, expected); }); test("describe unnamed portal", function() { con.describe({type: 'P'}); var expected = new BufferList().addChar('P').addCString("").join(true, "D"); assert.received(stream, expected); }); }); brianc-node-postgres-e5c48f3/test/unit/connection/startup-tests.js000066400000000000000000000027431176777237400255120ustar00rootroot00000000000000require(__dirname+'/test-helper'); var Connection = require(__dirname + '/../../../lib/connection'); test('connection can take existing stream', function() { var stream = new MemoryStream(); var con = new Connection({stream: stream}); assert.equal(con.stream, stream); }); test('using closed stream', function() { var stream = new MemoryStream(); stream.readyState = 'closed'; stream.connect = function(port, host) { this.connectCalled = true; this.port = port; this.host = host; } var con = new Connection({stream: stream}); con.connect(1234, 'bang'); test('makes stream connect', function() { assert.equal(stream.connectCalled, true); }); test('uses configured port', function() { assert.equal(stream.port, 1234); }); test('uses configured host', function() { assert.equal(stream.host, 'bang'); }); test('after stream connects client emits connected event', function() { var hit = false; con.once('connect', function() { hit = true; }); assert.ok(stream.emit('connect')); assert.ok(hit); }); }); test('using opened stream', function() { var stream = new MemoryStream(); stream.readyState = 'open'; stream.connect = function() { assert.ok(false, "Should not call open"); }; var con = new Connection({stream: stream}); test('does not call open', function() { var hit = false; con.once('connect', function() { hit = true; }); con.connect(); assert.ok(hit); }); }); brianc-node-postgres-e5c48f3/test/unit/connection/test-helper.js000066400000000000000000000000451176777237400250750ustar00rootroot00000000000000require(__dirname+'/../test-helper') brianc-node-postgres-e5c48f3/test/unit/test-helper.js000066400000000000000000000012151176777237400227360ustar00rootroot00000000000000var helper = require(__dirname+'/../test-helper'); var EventEmitter = require('events').EventEmitter; var Connection = require(__dirname + '/../../lib/connection'); MemoryStream = function() { EventEmitter.call(this); this.packets = []; }; helper.sys.inherits(MemoryStream, EventEmitter); var p = MemoryStream.prototype; p.write = function(packet) { this.packets.push(packet); }; p.writable = true; createClient = function() { var stream = new MemoryStream(); stream.readyState = "open"; var client = new Client({ connection: new Connection({stream: stream}) }); client.connect(); return client; }; module.exports = helper; brianc-node-postgres-e5c48f3/test/unit/utils-tests.js000066400000000000000000000110361176777237400230040ustar00rootroot00000000000000require(__dirname + '/test-helper'); var utils = require(__dirname + "/../../lib/utils"); var defaults = require(__dirname + "/../../lib").defaults; //this tests the monkey patching //to ensure comptability with older //versions of node test("EventEmitter.once", function() { //an event emitter var stream = new MemoryStream(); var callCount = 0; stream.once('single', function() { callCount++; }); stream.emit('single'); stream.emit('single'); assert.equal(callCount, 1); }); test('normalizing connection info', function() { test('with objects', function() { test('empty object uses defaults', function() { var input = {}; var output = utils.normalizeConnectionInfo(input); assert.equal(output.user, defaults.user); assert.equal(output.database, defaults.database); assert.equal(output.port, defaults.port); assert.equal(output.host, defaults.host); assert.equal(output.password, defaults.password); }); test('full object ignores defaults', function() { var input = { user: 'test1', database: 'test2', port: 'test3', host: 'test4', password: 'test5' }; assert.equal(utils.normalizeConnectionInfo(input), input); }); test('connection string', function() { test('non-unix socket', function() { test('uses defaults', function() { var input = ""; var output = utils.normalizeConnectionInfo(input); assert.equal(output.user, defaults.user); assert.equal(output.database, defaults.database); assert.equal(output.port, defaults.port); assert.equal(output.host, defaults.host); assert.equal(output.password, defaults.password); }); test('ignores defaults if string contains them all', function() { var input = "tcp://user1:pass2@host3:3333/databaseName"; var output = utils.normalizeConnectionInfo(input); assert.equal(output.user, 'user1'); assert.equal(output.database, 'databaseName'); assert.equal(output.port, 3333); assert.equal(output.host, 'host3'); assert.equal(output.password, 'pass2'); }) }); test('unix socket', function() { test('uses defaults', function() { var input = "/var/run/postgresql"; var output = utils.normalizeConnectionInfo(input); assert.equal(output.user, process.env.USER); assert.equal(output.host, '/var/run/postgresql'); assert.equal(output.database, process.env.USER); assert.equal(output.port, 5432); }); test('uses overridden defaults', function() { defaults.host = "/var/run/postgresql"; defaults.user = "boom"; defaults.password = "yeah"; defaults.port = 1234; var output = utils.normalizeConnectionInfo("asdf"); assert.equal(output.user, "boom"); assert.equal(output.password, "yeah"); assert.equal(output.port, 1234); assert.equal(output.host, "/var/run/postgresql"); }) }) }) }) }) test('libpq connection string building', function() { var checkForPart = function(array, part) { assert.ok(array.indexOf(part) > -1, array.join(" ") + " did not contain " + part); } test('builds simple string', function() { var config = { user: 'brian', password: 'xyz', port: 888, host: 'localhost', database: 'bam' } utils.buildLibpqConnectionString(config, assert.calls(function(err, constring) { assert.isNull(err) var parts = constring.split(" "); checkForPart(parts, "user='brian'") checkForPart(parts, "password='xyz'") checkForPart(parts, "port='888'") checkForPart(parts, "hostaddr=127.0.0.1") checkForPart(parts, "dbname='bam'") })) }) test('builds dns string', function() { var config = { user: 'brian', password: 'asdf', port: 5432, host: 'localhost' } utils.buildLibpqConnectionString(config, assert.calls(function(err, constring) { assert.isNull(err); var parts = constring.split(" "); checkForPart(parts, "user='brian'") checkForPart(parts, "hostaddr=127.0.0.1") })) }) test('error when dns fails', function() { var config = { user: 'brian', password: 'asf', port: 5432, host: 'asdlfkjasldfkksfd#!$!!!!..com' } utils.buildLibpqConnectionString(config, assert.calls(function(err, constring) { assert.ok(err); assert.isNull(constring) })) }) }) brianc-node-postgres-e5c48f3/test/unit/writer-tests.js000066400000000000000000000137211176777237400231630ustar00rootroot00000000000000require(__dirname + "/test-helper"); var Writer = require(__dirname + "/../../lib/writer"); test('adding int32', function() { var testAddingInt32 = function(int, expectedBuffer) { test('writes ' + int, function() { var subject = new Writer(); var result = subject.addInt32(int).join(); assert.equalBuffers(result, expectedBuffer); }) } testAddingInt32(0, [0, 0, 0, 0]); testAddingInt32(1, [0, 0, 0, 1]); testAddingInt32(256, [0, 0, 1, 0]); test('writes largest int32', function() { //todo need to find largest int32 when I have internet access return false; }) test('writing multiple int32s', function() { var subject = new Writer(); var result = subject.addInt32(1).addInt32(10).addInt32(0).join(); assert.equalBuffers(result, [0, 0, 0, 1, 0, 0, 0, 0x0a, 0, 0, 0, 0]); }) test('having to resize the buffer', function() { test('after resize correct result returned', function() { var subject = new Writer(10); subject.addInt32(1).addInt32(1).addInt32(1) assert.equalBuffers(subject.join(), [0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1]) }) }) }) test('int16', function() { test('writes 0', function() { var subject = new Writer(); var result = subject.addInt16(0).join(); assert.equalBuffers(result, [0,0]); }) test('writes 400', function() { var subject = new Writer(); var result = subject.addInt16(400).join(); assert.equalBuffers(result, [1, 0x90]) }) test('writes many', function() { var subject = new Writer(); var result = subject.addInt16(0).addInt16(1).addInt16(2).join(); assert.equalBuffers(result, [0, 0, 0, 1, 0, 2]) }) test('resizes if internal buffer fills up', function() { var subject = new Writer(3); var result = subject.addInt16(2).addInt16(3).join(); assert.equalBuffers(result, [0, 2, 0, 3]) }) }) test('cString', function() { test('writes empty cstring', function() { var subject = new Writer(); var result = subject.addCString().join(); assert.equalBuffers(result, [0]) }) test('writes two empty cstrings', function() { var subject = new Writer(); var result = subject.addCString("").addCString("").join(); assert.equalBuffers(result, [0, 0]) }) test('writes non-empty cstring', function() { var subject = new Writer(); var result = subject.addCString("!!!").join(); assert.equalBuffers(result, [33, 33, 33, 0]); }) test('resizes if reached end', function() { var subject = new Writer(3); var result = subject.addCString("!!!").join(); assert.equalBuffers(result, [33, 33, 33, 0]); }) test('writes multiple cstrings', function() { var subject = new Writer(); var result = subject.addCString("!").addCString("!").join(); assert.equalBuffers(result, [33, 0, 33, 0]); }) }) test('writes char', function() { var subject = new Writer(2); var result = subject.addChar('a').addChar('b').addChar('c').join(); assert.equalBuffers(result, [0x61, 0x62, 0x63]) }) test('gets correct byte length', function() { var subject = new Writer(5); assert.equal(subject.getByteLength(), 0) subject.addInt32(0) assert.equal(subject.getByteLength(), 4) subject.addCString("!") assert.equal(subject.getByteLength(), 6) }) test('can add arbitrary buffer to the end', function() { var subject = new Writer(4); subject.addCString("!!!") var result = subject.add(Buffer("@@@")).join(); assert.equalBuffers(result, [33, 33, 33, 0, 0x40, 0x40, 0x40]); }) test('can write normal string', function() { var subject = new Writer(4); var result = subject.addString("!").join(); assert.equalBuffers(result, [33]); test('can write cString too', function() { var result = subject.addCString("!").join(); assert.equalBuffers(result, [33, 33, 0]); test('can resize', function() { var result = subject.addString("!!").join(); assert.equalBuffers(result, [33, 33, 0, 33, 33]); }) }) }) test('clearing', function() { var subject = new Writer(); subject.addCString("@!!#!#"); subject.addInt32(10401); subject.clear(); assert.equalBuffers(subject.join(), []); test('can keep writing', function() { var joinedResult = subject.addCString("!").addInt32(9).addInt16(2).join(); assert.equalBuffers(joinedResult, [33, 0, 0, 0, 0, 9, 0, 2]); test('flush', function() { var flushedResult = subject.flush(); test('returns result', function() { assert.equalBuffers(flushedResult, [33, 0, 0, 0, 0, 9, 0, 2]) }) test('clears the writer', function() { assert.equalBuffers(subject.join(), []) assert.equalBuffers(subject.flush(), []) }) }) }) }) test("resizing to much larger", function() { var subject = new Writer(2); var string = "!!!!!!!!"; var result = subject.addCString(string).flush(); assert.equalBuffers(result, [33, 33, 33, 33, 33, 33, 33, 33, 0]) }) test("flush", function() { test('added as a hex code to a full writer', function() { var subject = new Writer(2); var result = subject.addCString("!").flush(0x50) assert.equalBuffers(result, [0x50, 0, 0, 0, 6, 33, 0]); }) test('added as a hex code to a non-full writer', function() { var subject = new Writer(10).addCString("!"); var joinedResult = subject.join(0x50); var result = subject.flush(0x50); assert.equalBuffers(result, [0x50, 0, 0, 0, 6, 33, 0]); }) test('added as a hex code to a buffer which requires resizing', function() { var result = new Writer(2).addCString("!!!!!!!!").flush(0x50); assert.equalBuffers(result, [0x50, 0, 0, 0, 0x0D, 33, 33, 33, 33, 33, 33, 33, 33, 0]); }) }) test("header", function() { test('adding two packets with headers', function() { var subject = new Writer(10).addCString("!"); subject.addHeader(0x50); subject.addCString("!!"); subject.addHeader(0x40); subject.addCString("!"); var result = subject.flush(0x10); assert.equalBuffers(result, [0x50, 0, 0, 0, 6, 33, 0, 0x40, 0, 0, 0, 7, 33, 33, 0, 0x10, 0, 0, 0, 6, 33, 0 ]); }) }) brianc-node-postgres-e5c48f3/wscript000066400000000000000000000017361176777237400176340ustar00rootroot00000000000000import Options, Utils from os import unlink, symlink, popen from os.path import exists srcdir = '.' blddir = 'build' VERSION = '0.0.1' def set_options(opt): opt.tool_options('compiler_cxx') def configure(conf): conf.check_tool('compiler_cxx') conf.check_tool('node_addon') pg_config = conf.find_program('pg_config', var='PG_CONFIG', mandatory=True) pg_libdir = popen("%s --libdir" % pg_config).readline().strip() conf.env.append_value("LIBPATH_PG", pg_libdir) conf.env.append_value("LIB_PG", "pq") pg_includedir = popen("%s --includedir" % pg_config).readline().strip() conf.env.append_value("CPPPATH_PG", pg_includedir) def build(bld): obj = bld.new_task_gen('cxx', 'shlib', 'node_addon') obj.cxxflags = ["-g", "-D_LARGEFILE_SOURCE", "-Wall"] obj.target = 'binding' obj.source = "./src/binding.cc" obj.uselib = "PG" def test(test): Utils.exec_command("node test/native/connection-tests.js") Utils.exec_command("node test/native/evented-api-tests.js")