This commit is contained in:
Darwin 2011-01-08 16:41:48 +01:00
commit 7fcfbd8bb0
31 changed files with 778 additions and 323 deletions

2
.gitignore vendored
View File

@ -1 +1,3 @@
/.emacs-project
*.swp
*.log

29
Makefile Normal file
View File

@ -0,0 +1,29 @@
SHELL := /bin/bash
user=postgres
password=1234
host=localhost
port=5432
database=postgres
verbose=false
params := -u $(user) --password $(password) -p $(port) -d $(database) -h $(host) --verbose $(verbose)
node-command := xargs -n 1 -I file node file $(params)
.PHONY : test test-connection test-integration bench
test: test-unit
test-all: test-unit test-integration
bench:
@find benchmark -name "*-bench.js" | $(node-command)
test-unit:
@find test/unit -name "*-tests.js" | $(node-command)
test-connection:
@node script/test-connection.js $(params)
test-integration: test-connection
@find test/integration -name "*-tests.js" | $(node-command)

View File

@ -3,17 +3,15 @@
Non-blocking (async) pure JavaScript PostgreSQL client for node.js written
with love and TDD.
## alpha version
### Installation
## Installation
npm install pg
### Example
## Example
var pg = require('pg');
pg.connect("pg://user:password@host:port/database", function(err, client) {
var connectionString = "pg://user:password@host:port/database";
pg.connect(connectionString, function(err, client) {
if(err) {
//handle connection error
}
@ -40,14 +38,14 @@ with love and TDD.
}
}
### Philosophy
## Philosophy
* well tested
* no monkey patching
* no dependencies (...besides PostgreSQL)
* [in-depth documentation](http://github.com/brianc/node-postgres/wiki) (work in progress)
### features
## features
- prepared statement support
- parameters
@ -58,8 +56,9 @@ with love and TDD.
- float <-> double, numeric
- boolean <-> boolean
- notification message support
- tested like a Toyota
~1000 assertions executed on
- connection pooling
- mucho testing
~250 tests executed on
- ubuntu
- node v0.2.2, v0.2.3, v0.2.4, v0.2.5, v0.3.0, v0.3.1
- postgres 8.4.4
@ -67,21 +66,31 @@ with love and TDD.
- node v0.2.2, v0.2.3, v0.2.4, v0.2.5, v0.3.0, v0.3.1
- postgres v8.4.4, v9.0.1 installed both locally and on networked Windows 7
### Contributing
## Contributing
clone the repo:
git clone git://github.com/brianc/node-postgres
cd node-postgres
node test/run.js
make test
And just like magic, you're ready to contribute! <3
### 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)
## More info please
### Documentation
### [Documentation](node-postgres/wiki)
__PLEASE__ check out the [WIKI](node-postgres/wiki). __MUCH__ more information there.
### __PLEASE__ check out the WIKI
### Working?
@ -89,9 +98,7 @@ __PLEASE__ check out the [WIKI](node-postgres/wiki). __MUCH__ more information
### Why did you write this?
As soon as I saw node.js for the first time I knew I had found
something lovely and simple and _just what I always wanted!_. So...I
poked around for a while. I was excited. I still am!
As soon as I saw node.js for the first time I knew I had found something lovely and simple and _just what I always wanted!_. So...I poked around for a while. I was excited. I still am!
I drew major inspiration from [postgres-js](http://github.com/creationix/postgres-js).
@ -102,9 +109,7 @@ saw there.
### Plans for the future?
- transparent prepared statement caching
- connection pooling
- more testings of error scenarios
- streamline writing of buffers
## License

View File

@ -0,0 +1,58 @@
var 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)

View File

@ -12,7 +12,7 @@ var Connection = require(__dirname + '/connection');
var parseConnectionString = function(str) {
var result = url.parse(str);
result.host = result.hostname;
result.database = result.pathname.slice(1);
result.database = result.pathname ? result.pathname.slice(1) : null
var auth = (result.auth || ':').split(':');
result.user = auth[0];
result.password = auth[1];

View File

@ -16,6 +16,7 @@ var Connection = function(config) {
this.offset = null;
this.encoding = 'utf8';
this.parsedStatements = {};
this.writer = new Writer();
};
sys.inherits(Connection, EventEmitter);
@ -53,14 +54,14 @@ p.connect = function(port, host) {
};
p.startup = function(config) {
var bodyBuffer = new Writer()
var bodyBuffer = this.writer
.addInt16(3)
.addInt16(0)
.addCString('user')
.addCString(config.user)
.addCString('database')
.addCString(config.database)
.addCString('').join();
.addCString('').flush();
//this message is sent without a code
var length = bodyBuffer.length + 4;
@ -74,7 +75,7 @@ p.startup = function(config) {
p.password = function(password) {
//0x70 = 'p'
this.send(0x70, new Writer().addCString(password).join());
this.send(0x70, this.writer.addCString(password).flush());
};
p.send = function(code, bodyBuffer) {
@ -97,7 +98,7 @@ p.end = function() {
p.query = function(text) {
//0x51 = Q
this.send(0x51, new Writer().addCString(text).join());
this.send(0x51, this.writer.addCString(text).flush());
};
p.parse = function(query) {
@ -111,7 +112,7 @@ p.parse = function(query) {
//normalize null type array
query.types = query.types || [];
var len = query.types.length;
var buffer = new Writer()
var buffer = this.writer
.addCString(query.name) //name of query
.addCString(query.text) //actual query text
.addInt16(len);
@ -120,7 +121,7 @@ p.parse = function(query) {
}
//0x50 = 'P'
this.send(0x50, buffer.join());
this.send(0x50, buffer.flush());
return this;
};
@ -132,7 +133,7 @@ p.bind = function(config) {
config.statement = config.statement || '';
var values = config.values || [];
var len = values.length;
var buffer = new Writer()
var buffer = this.writer
.addCString(config.portal)
.addCString(config.statement)
.addInt16(0) //always use default text format
@ -144,22 +145,22 @@ p.bind = function(config) {
} else {
val = val.toString();
buffer.addInt32(Buffer.byteLength(val));
buffer.add(Buffer(val,this.encoding));
buffer.addString(val);
}
}
buffer.addInt16(0); //no format codes, use text
//0x42 = 'B'
this.send(0x42, buffer.join());
this.send(0x42, buffer.flush());
};
p.execute = function(config) {
config = config || {};
config.portal = config.portal || '';
config.rows = config.rows || '';
var buffer = new Writer()
var buffer = this.writer
.addCString(config.portal)
.addInt32(config.rows)
.join();
.flush();
//0x45 = 'E'
this.send(0x45, buffer);
@ -181,7 +182,7 @@ p.end = function() {
};
p.describe = function(msg) {
this.send(0x44, new Writer().addCString(msg.type + (msg.name || '')).join());
this.send(0x44, this.writer.addCString(msg.type + (msg.name || '')).flush());
};
//parsing methods

View File

@ -4,6 +4,36 @@ var net = require('net');
var Pool = require(__dirname + '/utils').Pool;
var Client = require(__dirname+'/client');
var defaults = require(__dirname + '/defaults');
//wrap up common connection management boilerplate
var connect = function(config, callback) {
if(poolEnabled()) {
return getPooledClient(config, callback)
}
var client = new Client(config);
client.connect();
var onError = function(error) {
client.connection.removeListener('readyForQuery', onReady);
callback(error);
}
var onReady = function() {
client.removeListener('error', onError);
callback(null, client);
client.on('drain', client.end.bind(client));
}
client.once('error', onError);
//TODO refactor
//i don't like reaching into the client's connection for attaching
//to specific events here
client.connection.once('readyForQuery', onReady);
}
//connection pool global cache
var clientPools = {
}
@ -13,12 +43,13 @@ var poolEnabled = function() {
}
var log = function() {
//do nothing
}
var log = function() {
console.log.apply(console, arguments);
}
//for testing
// var log = function() {
// console.log.apply(console, arguments);
// }
var getPooledClient = function(config, callback) {
//lookup pool using config as key
@ -27,16 +58,17 @@ var getPooledClient = function(config, callback) {
//create pool if doesn't exist
if(!pool) {
log("creating pool %s", config)
//log("creating pool %s", config)
pool = clientPools[config] = new Pool(defaults.poolSize, function() {
log("creating new client in pool %s", config)
//log("creating new client in pool %s", config)
var client = new Client(config);
client.connected = false;
return client;
})
}
pool.checkOut(function(err, client) {
//if client already connected just
//pass it along to the callback and return
if(client.connected) {
@ -72,56 +104,36 @@ var getPooledClient = function(config, callback) {
}
//destroys the world
var end = function(callback) {
for(var name in clientPools) {
var pool = clientPools[name];
log("destroying pool %s", name);
pool.waits.forEach(function(wait) {
wait(new Error("Client is being destroyed"))
})
pool.waits = [];
pool.items.forEach(function(item) {
var client = item.ref;
if(client.activeQuery) {
log("client is still active, waiting for it to complete");
client.on('drain', client.end.bind(client))
} else {
client.end();
}
})
//remove reference to pool lookup
clientPools[name] = null;
delete(clientPools[name])
//or optionally only a single pool
//mostly used for testing or
//a hard shutdown
var end = function(name) {
if(!name) {
for(var poolName in clientPools) {
end(poolName)
return
}
}
var pool = clientPools[name];
//log("destroying pool %s", name);
pool.waits.forEach(function(wait) {
wait(new Error("Client is being destroyed"))
})
pool.waits = [];
pool.items.forEach(function(item) {
var client = item.ref;
if(client.activeQuery) {
//log("client is still active, waiting for it to complete");
client.on('drain', client.end.bind(client))
} else {
client.end();
}
})
//remove reference to pool lookup
clientPools[name] = null;
delete(clientPools[name])
}
//wrap up common connection management boilerplate
var connect = function(config, callback) {
if(poolEnabled()) {
return getPooledClient(config, callback)
}
throw new Error("FUCK")
var client = new Client(config);
client.connect();
var onError = function(error) {
client.connection.removeListener('readyForQuery', onReady);
callback(error);
}
var onReady = function() {
client.removeListener('error', onError);
callback(null, client);
client.on('drain', client.end.bind(client));
}
client.once('error', onError);
//TODO refactor
//i don't like reaching into the client's connection for attaching
//to specific events here
client.connection.once('readyForQuery', onReady);
}
module.exports = {
Client: Client,

View File

@ -1,6 +1,5 @@
var EventEmitter = require('events').EventEmitter;
var sys = require('sys');var sys = require('sys');
var Row = require(__dirname + '/row');
var Query = function(config) {
this.text = config.text;
@ -44,7 +43,7 @@ p.submit = function(connection) {
};
};
var handleDatarow = function(msg) {
var result = new Row();
var result = {};
for(var i = 0; i < msg.fields.length; i++) {
var rawValue = msg.fields[i];
result[names[i]] = rawValue === null ? null : converters[i](rawValue);
@ -75,8 +74,9 @@ p.submit = function(connection) {
if(self.callback) {
self.callback(err);
connection.removeListener('commandComplete', onCommandComplete);
} else {
self.emit('error', err);
}
self.emit('error', err);
self.emit('end');
};
@ -180,7 +180,7 @@ var dateParser = function(isoDate) {
var seconds = /(\d{2})/.exec(end);
seconds = (seconds ? seconds[1] : 0);
seconds = parseInt(seconds,10);
var mili = /\.(\d{1,})/.exec(end+"000");
var mili = /\.(\d{1,})/.exec(end+"000");
mili = mili ? mili[1].slice(0,3) : 0;
var tZone = /([Z|+\-])(\d{2})?(\d{2})?/.exec(end);
//minutes to adjust for timezone

View File

@ -1,8 +0,0 @@
var sys = require('sys');
var Row = function() {
};
var p = Row.prototype;
module.exports = Row;

View File

@ -1,60 +1,93 @@
var Writer = function() {
this.buffers = [];
var Writer = function(size) {
this.size = size || 1024;
this.buffer = new Buffer(this.size);
this.offset = 0;
};
var p = Writer.prototype;
p.add = function(buffer) {
this.buffers.push(buffer);
p._remaining = function() {
return this.buffer.length - this.offset;
}
p._resize = function() {
var oldBuffer = this.buffer;
this.buffer = Buffer(oldBuffer.length + this.size);
oldBuffer.copy(this.buffer);
}
//resizes internal buffer if not enough size left
p._ensure = function(size) {
if(this._remaining() < size) {
this._resize()
}
}
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(val, front) {
return this.add(Buffer([
(val >>> 8),
(val >>> 0)
]));
};
p.addInt16 = function(num) {
this._ensure(2)
this.buffer[this.offset++] = (num >>> 8 & 0xFF)
this.buffer[this.offset++] = (num >>> 0 & 0xFF)
return this;
}
p.getByteLength = function(initial) {
return this.buffers.reduce(function(previous, current){
return previous + current.length;
},initial || 0);
};
p.addCString = function(string) {
var string = string || "";
var len = Buffer.byteLength(string) + 1;
this._ensure(len);
this.buffer.write(string, this.offset);
this.offset += len;
this.buffer[this.offset] = 0; //add null terminator
return this;
}
p.addInt32 = function(val, first) {
return this.add(Buffer([
(val >>> 24 & 0xFF),
(val >>> 16 & 0xFF),
(val >>> 8 & 0xFF),
(val >>> 0 & 0xFF)
]));
};
p.addCString = function(val) {
var len = Buffer.byteLength(val);
var buffer = new Buffer(len+1);
buffer.write(val);
buffer[len] = 0;
return this.add(buffer);
};
p.addChar = function(char, first) {
return this.add(Buffer(char,'utf8'), first);
};
p.addChar = function(char) {
this._ensure(1);
this.buffer.write(char, this.offset);
this.offset++;
return this;
}
p.join = function() {
var result = Buffer(this.getByteLength());
var index = 0;
var buffers = this.buffers;
var length = this.buffers.length;
for(var i = 0; i < length; i ++) {
var buffer = buffers[i];
buffer.copy(result, index, 0);
index += buffer.length;
}
return this.buffer.slice(0, this.offset);
}
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;
}
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=0;
}
p.flush = function() {
var result = this.join();
this.clear();
return result;
};
}
module.exports = Writer;

View File

@ -1,5 +1,5 @@
{ "name": "pg",
"version": "0.1.0",
"version": "0.2.2",
"description": "Pure JavaScript PostgreSQL client",
"homepage": "http://github.com/brianc/node-postgres",
"repository" : {

23
script/test-connection.js Normal file
View File

@ -0,0 +1,23 @@
var helper = require(__dirname + '/../test/test-helper');
var connectionString = helper.connectionString();
console.log();
console.log("testing ability to connect to '%s'", connectionString);
var pg = require(__dirname + '/../lib');
pg.connect(connectionString, 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();
})
})

View File

@ -17,6 +17,9 @@ for(var i = 0; i < args.length; i++) {
case '--password':
config.password = args[++i];
break;
case '--verbose':
config.verbose = (args[++i] == "true");
break;
case '-d':
case '--database':
config.database = args[++i];
@ -45,5 +48,5 @@ var log = function(keys) {
console.log(key + ": '" + config[key] + "'");
});
}
//log(['user','password','database','port','host'])
module.exports = config;

View File

@ -1,9 +1,20 @@
var helper = require(__dirname + '/../test-helper');
var pg = require(__dirname + '/../../../lib');
var connectionString = helper.connectionString(__filename);
var log = function() {
//console.log.apply(console, arguments);
}
var sink = new helper.Sink(4, 10000, function() {
log("ending connection pool: %s", connectionString);
pg.end(connectionString);
});
test('api', function() {
pg.connect(helper.args, assert.calls(function(err, client) {
assert.equal(err, null, "Failed to connect");
log("connecting to %s", connectionString)
pg.connect(connectionString, assert.calls(function(err, client) {
assert.equal(err, null, "Failed to connect: " + sys.inspect(err));
client.query('CREATE TEMP TABLE band(name varchar(100))');
@ -13,23 +24,30 @@ test('api', function() {
test('simple query execution',assert.calls( function() {
client.query("SELECT * FROM band WHERE name = 'the beach boys'", function(err, result) {
log("executing simple query")
client.query("SELECT * FROM band WHERE name = 'the beach boys'", assert.calls(function(err, result) {
assert.length(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.length(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.length(result.rows, 2);
assert.equal(result.rows.pop().name, 'the flaming lips');
assert.equal(result.rows.pop().name, 'the beach boys');
sink.add();
}))
}))
@ -37,12 +55,16 @@ test('api', function() {
})
test('executing nested queries', function() {
pg.connect(helper.args, assert.calls(function(err, client) {
pg.connect(connectionString, 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();
}))
}))
}))
@ -50,10 +72,23 @@ test('executing nested queries', function() {
})
test('raises error if cannot connect', function() {
var connectionString = "pg://asdf@seoiasfd:4884/ieieie";
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();
}))
})
pg.end();
test("query errors are handled and do not bubble if callback is provded", function() {
pg.connect(connectionString, 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();
}))
}))
})

View File

@ -4,7 +4,7 @@ test("simple query interface", function() {
var client = helper.client();
var query = client.query("select name from person");
var query = client.query("select name from person order by name");
client.on('drain', client.end.bind(client));
@ -12,6 +12,17 @@ test("simple query interface", function() {
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.length(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() {
@ -51,4 +62,3 @@ test("multiple select statements", function() {
});
client.on('drain', client.end.bind(client));
});

View File

@ -10,7 +10,11 @@ module.exports = {
host: helper.args.host,
port: helper.args.port
});
client.connect();
return client;
}
},
connectionString: helper.connectionString,
Sink: helper.Sink,
pg: helper.pg
};

View File

@ -0,0 +1,48 @@
var helper = require(__dirname + '/test-helper');
test('a single connection transaction', function() {
var connectionString = helper.connectionString();
var sink = new helper.Sink(1, function() {
helper.pg.end();
});
helper.pg.connect(connectionString, 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();
}))
})
}))
})

View File

@ -1,37 +1,39 @@
var helper = require(__dirname + '/test-helper');
var client = helper.client();
client.on('drain', client.end.bind(client));
var sink;
var connectionString = helper.connectionString();
var testForTypeCoercion = function(type){
client.query("create temp table test_type(col " + type.name + ")");
helper.pg.connect(connectionString, function(err, client) {
assert.isNull(err)
client.query("create temp table test_type(col " + type.name + ")");
test("Coerces " + type.name, function() {
type.values.forEach(function(val) {
test("Coerces " + type.name, function() {
type.values.forEach(function(val) {
var insertQuery = client.query({
name: 'insert type test ' + type.name,
text: 'insert into test_type(col) VALUES($1)',
values: [val]
var insertQuery = client.query({
name: 'insert type test ' + type.name,
text: 'insert into test_type(col) VALUES($1)',
values: [val]
});
var query = client.query({
name: 'get type ' + type.name ,
text: 'select col from test_type'
});
assert.emits(query, 'row', function(row) {
assert.strictEqual(row.col, val, "expected " + type.name + " of " + val + " but got " + row[0]);
});
client.query({
name: 'delete values',
text: 'delete from test_type'
});
sink.add();
});
var query = client.query({
name: 'get type ' + type.name ,
text: 'select col from test_type'
});
assert.emits(query, 'row', function(row) {
assert.strictEqual(row.col, val, "expected " + type.name + " of " + val + " but got " + row[0]);
});
client.query({
name: 'delete values',
text: 'delete from test_type'
});
client.query('drop table test_type');
});
client.query('drop table test_type');
});
})
};
var types = [{
@ -76,9 +78,18 @@ var types = [{
values: ['13:12:12.321', null]
}];
var valueCount = 0;
types.forEach(function(type) {
valueCount += type.values.length;
})
sink = new helper.Sink(valueCount, function() {
helper.pg.end();
})
types.forEach(testForTypeCoercion);
test("timestampz round trip", function() {
var now = new Date();
var client = helper.client();
client.on('error', function(err) {
@ -112,6 +123,7 @@ test("timestampz round trip", function() {
});
});
client.on('drain', client.end.bind(client));
});

View File

@ -0,0 +1,2 @@
var helper = require(__dirname + "/test-helper")
helper.testPoolSize(2);

View File

@ -0,0 +1,27 @@
var helper = require(__dirname + '/test-helper')
var conString1 = helper.connectionString();
var conString2 = helper.connectionString();
var conString3 = helper.connectionString();
var conString4 = helper.connectionString();
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();
});
[conString1, conString2, conString3, conString4].forEach(function() {
helper.pg.connect(conString1, 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();
})
})
})
})
})

View File

@ -0,0 +1,3 @@
var helper = require(__dirname + "/test-helper")
helper.testPoolSize(10);
helper.testPoolSize(11);

View File

@ -1,32 +1,2 @@
var helper = require(__dirname + "/test-helper")
setTimeout(function() {
helper.pg.defaults.poolSize = 10;
test('executes a single pooled connection/query', function() {
var args = helper.args;
var conString = "pg://"+args.user+":"+args.password+"@"+args.host+":"+args.port+"/"+args.database;
var queryCount = 0;
helper.pg.connect(conString, assert.calls(function(err, client) {
assert.isNull(err);
client.query("select * from NOW()", assert.calls(function(err, result) {
assert.isNull(err);
queryCount++;
}))
}))
var id = setTimeout(function() {
assert.equal(queryCount, 1)
}, 1000)
var check = function() {
setTimeout(function() {
if(queryCount == 1) {
clearTimeout(id)
helper.pg.end();
} else {
check();
}
}, 50)
}
check();
})
}, 1000)
helper.testPoolSize(1);

View File

@ -1,4 +1,40 @@
module.exports = {
args: require(__dirname + "/../test-helper").args,
pg: require(__dirname + "/../../../lib")
var helper = require(__dirname + "/../test-helper");
var pg = require(__dirname + "/../../../lib");
helper.pg = pg;
var testPoolSize = function(max) {
var conString = helper.connectionString();
var sink = new helper.Sink(max, function() {
helper.pg.end(conString);
});
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(conString, function(err, client) {
assert.isNull(err);
client.query("select * from person", function(err, result) {
assert.length(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 = {
args: helper.args,
pg: helper.pg,
connectionString: helper.connectionString,
Sink: helper.Sink,
testPoolSize: testPoolSize
}

View File

@ -0,0 +1,2 @@
var helper = require(__dirname + "/test-helper")
helper.testPoolSize(200);

View File

@ -1,3 +1,6 @@
var helper = require(__dirname + '/../test-helper');
//export parent helper stuffs
module.exports = { args: helper.args };
module.exports = helper;
if(helper.args.verbose) {
}

View File

@ -1,25 +0,0 @@
//executes all the unit tests
var fs = require('fs');
var args = require(__dirname + '/cli');
var runDir = function(dir) {
fs.readdirSync(dir).forEach(function(file) {
if(file.indexOf(".js") < 0) {
return runDir(fs.realpathSync(dir + file) + "/");
}
require(dir + file.split('.js') [0]);
});
};
var arg = args.test;
if(arg == 'all') {
runDir(__dirname+'/unit/');
runDir(__dirname+'/integration/');
}
else {
runDir(__dirname+'/' + arg + '/');
}

View File

@ -1,4 +1,3 @@
require.paths.unshift(__dirname + '/../lib/');
Client = require('client');
@ -11,6 +10,15 @@ buffers = require(__dirname + '/test-buffers');
Connection = require('connection');
var args = require(__dirname + '/cli');
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]);
@ -85,7 +93,7 @@ assert.length = function(actual, expectedLength) {
var expect = function(callback, timeout) {
var executed = false;
var id = setTimeout(function() {
assert.ok(executed, "Expected execution of " + callback + " fired");
assert.ok(executed, "Expected execution of funtion to be fired");
}, timeout || 2000)
return function(err, queryResult) {
@ -101,46 +109,93 @@ assert.isNull = function(item, message) {
assert.ok(item === null, message);
};
['equal', 'length', 'empty', 'strictEqual', 'emits', 'equalBuffers', 'same', 'calls', 'ok'].forEach(function(name) {
var old = assert[name];
assert[name] = function() {
test.assertCount++
return old.apply(this, arguments);
};
});
test = function(name, action) {
test.testCount ++;
if(args.verbose) {
console.log(name);
}
var result = action();
if(result === false) {
test.ignored.push(name);
process.stdout.write('?');
if(!args.verbose) {
process.stdout.write('?');
}
}else{
process.stdout.write('.');
if(!args.verbose) {
process.stdout.write('.');
}
}
};
test.assertCount = test.assertCount || 0;
//print out the filename
process.stdout.write(require('path').basename(process.argv[1]));
//print a new line since we'll be printing test names
if(args.verbose) {
console.log();
}
test.testCount = test.testCount || 0;
test.ignored = test.ignored || [];
test.errors = test.errors || [];
test.start = test.start || new Date();
process.on('exit', function() {
console.log('');
var duration = ((new Date() - test.start)/1000);
console.log(test.testCount + ' tests ' + test.assertCount + ' assertions in ' + duration + ' seconds');
test.ignored.forEach(function(name) {
console.log("Ignored: " + name);
});
test.errors.forEach(function(error) {
console.log("Error: " + error.name);
});
if(test.ignored.length || test.errors.length) {
test.ignored.forEach(function(name) {
console.log("Ignored: " + name);
});
test.errors.forEach(function(error) {
console.log("Error: " + error.name);
});
console.log('');
}
test.errors.forEach(function(error) {
throw error.e;
});
});
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 = {
args: args
args: args,
Sink: Sink,
pg: require('index'),
connectionString: function() {
return "pg"+(count++)+"://"+args.user+":"+args.password+"@"+args.host+":"+args.port+"/"+args.database;
}
};

View File

@ -19,7 +19,7 @@ test('client settings', function() {
port: 321,
password: password
});
assert.equal(client.user, user);
assert.equal(client.database, database);
assert.equal(client.port, 321);
@ -27,3 +27,26 @@ test('client settings', function() {
});
});
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, "")
assert.equal(client.password, "")
assert.equal(client.host, "host1")
assert.equal(client.port, 5432)
assert.equal(client.database, "")
})
})

View File

@ -1,4 +0,0 @@
//mostly just testing simple row api
require(__dirname + "/test-helper");
var Row = require('row');

View File

@ -1,5 +1,5 @@
require(__dirname + '/test-helper');
var Pool = require("utils").Pool;
var Pool = require(__dirname + "/../../lib/utils").Pool;
//this tests the monkey patching
//to ensure comptability with older

View File

@ -1,57 +1,153 @@
require(__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 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]);
})
})
})
BufferList.prototype.compare = function(expected) {
var buf = this.join();
assert.equalBuffers(buf, expected);
};
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('adds int16', function() {
new BufferList().addInt16(5).compare([0, 5]);
});
})
test('adds two int16s', function() {
new BufferList().addInt16(5).addInt16(3).compare([0,5,0,3]);
});
test('adds int32', function() {
new BufferList().addInt32(1).compare([0,0,0,1]);
new BufferList().addInt32(1).addInt32(3).compare([0,0,0,1,0,0,0,3]);
});
test('adds CStrings', function() {
new BufferList().addCString('').compare([0]);
new BufferList().addCString('!!').compare([33,33,0]);
new BufferList().addCString('!').addCString('!').compare([33,0,33,0]);
});
test('computes length', function() {
var buf = new BufferList().join(true);
assert.equalBuffers(buf, [0,0,0,4]);
});
test('appends character', function() {
var buf = new BufferList().join(false,'!');
assert.equalBuffers(buf,[33]);
});
test('appends char and length', function() {
var buf = new BufferList().join(true,'!');
assert.equalBuffers(buf,[33,0,0,0,4]);
});
test('does complicated buffer', function() {
var buf = new BufferList()
.addInt32(1)
.addInt16(2)
.addCString('!')
.join(true,'!');
assert.equalBuffers(buf, [33, 0, 0, 0, 0x0c, 0, 0, 0, 1, 0, 2, 33, 0]);
});
test('concats', function() {
var buf1 = new BufferList().addInt32(8).join(false,'!');
var buf2 = new BufferList().addInt16(1).join();
var result = BufferList.concat(buf1, buf2);
assert.equalBuffers(result, [33, 0, 0, 0, 8, 0, 1]);
});