Merge branch 'master' of https://github.com/brianc/node-postgres into feature/ssl

This commit is contained in:
brianc 2011-05-01 21:59:43 -05:00
commit 7c61bebfda
14 changed files with 187 additions and 132 deletions

View File

@ -107,6 +107,7 @@ Many thanks to the following:
* [pshc](https://github.com/pshc)
* [pjornblomqvist](https://github.com/bjornblomqvist)
* [JulianBirch](https://github.com/JulianBirch)
* [ef4](https://github.com/ef4)
## Documentation
@ -116,6 +117,11 @@ Still a work in progress, I am trying to flesh out the wiki...
### __PLEASE__ check out the WIKI
## Production Use
* [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.

View File

@ -4,62 +4,18 @@ var defaults = require(__dirname + '/defaults');
module.exports = {
init: function(Client) {
//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.removeListener('connect', 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.once('connect', onReady);
}
//connection pool global cache
var clientPools = {
}
var poolEnabled = function() {
return defaults.poolSize;
}
var log = function() {
//do nothing
}
//for testing
// var log = function() {
// console.log.apply(console, arguments);
// }
var getPooledClient = function(config, callback) {
var connect = function(config, callback) {
//lookup pool using config as key
//TODO this don't work so hot w/ object configs
var pool = clientPools[config];
//create pool if doesn't exist
if(!pool) {
//log("creating pool %s", config)
pool = clientPools[config] = new Pool(defaults.poolSize, function() {
//log("creating new client in pool %s", config)
var client = new Client(config);
client.connected = false;
return client;
@ -67,7 +23,6 @@ module.exports = {
}
pool.checkOut(function(err, client) {
//if client already connected just
//pass it along to the callback and return
if(client.connected) {
@ -92,9 +47,6 @@ module.exports = {
client.once('error', onError);
//TODO refactor
//i don't like reaching into the client's connection for attaching
//to specific events here
client.once('connect', onReady);
client.connect();

View File

@ -11,6 +11,7 @@ module.exports = {
defaults: defaults
}
//lazy require native module...the c++ may not have been compiled
module.exports.__defineGetter__("native", function() {
return require(__dirname + '/native');
})

View File

@ -8,34 +8,6 @@ var types = require(__dirname + "/types");
var Connection = binding.Connection;
var p = Connection.prototype;
var add = function(params, config, paramName) {
var value = config[paramName];
if(value) {
params.push(paramName+"='"+value+"'");
}
}
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') {
throw new Error("Need to use node to do async DNS on host");
}
params.push("hostaddr=127.0.0.1 ");
}
callback(params.join(" "));
} else {
throw new Error("Unrecognized config type for connection");
}
}
var nativeConnect = p.connect;
p.connect = function() {
@ -129,6 +101,7 @@ var NativeQuery = function(text, values, callback) {
this.text = text.text;
this.values = text.values;
this.name = text.name;
this.callback = values;
} else {
this.text = text;
this.values = values;
@ -147,12 +120,13 @@ var NativeQuery = function(text, values, callback) {
var item = this.values[i];
if(item instanceof Date) {
this.values[i] = JSON.stringify(item);
} else {
this.values[i] = item.toString();
}
}
}
EventEmitter.call(this);
this._translateValues();
};
sys.inherits(NativeQuery, EventEmitter);
@ -193,12 +167,32 @@ p.handleReadyForQuery = function() {
this.emit('end');
};
//translates values into strings
p._translateValues = function() {
if(this.values) {
this.values = this.values.map(function(val) {
return val.toString();
});
var add = function(params, config, paramName) {
var value = config[paramName];
if(value) {
params.push(paramName+"='"+value+"'");
}
}
//connection string helper
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') {
throw new Error("Need to use node to do async DNS on host");
}
params.push("hostaddr=127.0.0.1 ");
}
callback(params.join(" "));
} else {
throw new Error("Unrecognized config type for connection");
}
}

View File

@ -9,9 +9,6 @@ var Query = function(config) {
this.rows = config.rows;
this.types = config.types;
this.name = config.name;
//for code clarity purposes we'll declare this here though it's not
//set or used until a rowDescription message comes in
this.rowDescription = null;
this.callback = config.callback;
this._fieldNames = [];
this._fieldConverters = [];
@ -125,7 +122,7 @@ p.prepare = function(connection) {
connection.parsedStatements[this.name] = true;
}
//TODO is there some btter way to prepare values for the database?
//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;

View File

@ -18,6 +18,7 @@ var getStringTypeParser = function(oid) {
return typeParsers[oid] || noParse;
};
//parses PostgreSQL server formatted date strings into javascript date objects
var parseDate = function(isoDate) {
//TODO this could do w/ a refactor
@ -86,6 +87,37 @@ var parseStringArray = function(val) {
});
};
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]);
if (m[4]) i.months = parseInt(m[4]);
if (m[6]) i.days = parseInt(m[6]);
if (m[9]) i.hours = parseInt(m[9]);
if (m[10]) i.minutes = parseInt(m[10]);
if (m[11]) i.seconds = parseInt(m[11]);
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;
};
//default string type parser registrations
registerStringTypeParser(20, parseInt);
registerStringTypeParser(21, parseInt);
@ -99,8 +131,9 @@ registerStringTypeParser(1114, parseDate);
registerStringTypeParser(1184, parseDate);
registerStringTypeParser(1007, parseIntegerArray);
registerStringTypeParser(1009, parseStringArray);
registerStringTypeParser(1186, parseInterval);
module.exports = {
registerStringTypeParser: registerStringTypeParser,
getStringTypeParser: getStringTypeParser
getStringTypeParser: getStringTypeParser,
}

View File

@ -20,10 +20,15 @@ var Pool = function(maxSize, createFn) {
this.items = [];
this.waits = [];
}
sys.inherits(Pool, events.EventEmitter);
var p = Pool.prototype;
p.checkOut = function(callback) {
if(!this.maxSize) {
return callback(null, this.createFn());
}
var len = 0;
for(var i = 0, len = this.items.length; i < len; i++) {
var item = this.items[i];
@ -34,13 +39,7 @@ p.checkOut = function(callback) {
//check if we can create a new item
if(this.items.length < this.maxSize && this.createFn) {
var result = this.createFn();
var item = result;
//create function can return item conforming to interface
//of stored items to allow for create function to create
//checked out items
if(typeof item.checkedIn == "undefined") {
var item = {ref: result, checkedIn: true}
}
var item = {ref: result, checkedIn: true}
this.items.push(item);
if(item.checkedIn) {
return this._pulse(item, callback)

View File

@ -1,3 +1,6 @@
//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 = new Buffer(this.size + 5);

View File

@ -1,5 +1,5 @@
{ "name": "pg",
"version": "0.4.0",
"version": "0.4.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",

View File

@ -96,8 +96,8 @@ test("query errors are handled and do not bubble if callback is provded", functi
client.query("SELECT OISDJF FROM LEIWLISEJLSE", assert.calls(function(err, result) {
assert.ok(err);
log("query error supplied error to callback")
sink.add();
}))
sink.add();
}))
}))
})
@ -116,3 +116,16 @@ test('callback is fired once and only once', function() {
})
}))
})
test('can provide callback and config object', function() {
pg.connect(connectionString, 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())
}))
}))
})

View File

@ -0,0 +1,32 @@
var helper = require(__dirname + '/test-helper');
helper.pg.defaults.poolSize = 1;
var args = {
user: helper.args.user,
password: helper.args.password,
database: helper.args.database,
port: helper.args.port,
host: helper.args.host
}
helper.pg.connect(args, assert.calls(function(err, client) {
assert.isNull(err);
client.iGotAccessed = true;
client.query("SELECT NOW()")
}))
var moreArgs = {
user: helper.args.user + "2",
host: helper.args.host,
password: helper.args.password,
database: helper.args.database,
port: helper.args.port,
zomg: true
}
helper.pg.connect(moreArgs, assert.calls(function(err, client) {
assert.isNull(err);
assert.ok(client.iGotAccessed === true)
client.end();
}))

View File

@ -98,6 +98,27 @@ test('typed results', function() {
expected: function(val) {
assert.UTCDate(val, 2010, 9, 31, 0, 0, 0, 0);
}
},{
name: 'interval time',
dataTypeID: 1186,
actual: '01:02:03',
expected: function(val) {
assert.deepEqual(val, {'hours':1, 'minutes':2, 'seconds':3})
}
},{
name: 'interval long',
dataTypeID: 1186,
actual: '1 year -32 days',
expected: function(val) {
assert.deepEqual(val, {'years':1, 'days':-32})
}
},{
name: 'interval combined negative',
dataTypeID: 1186,
actual: '1 day -00:00:03',
expected: function(val) {
assert.deepEqual(val, {'days':1, 'seconds':-3})
}
}];

View File

@ -5,7 +5,7 @@ var con = new Connection({
stream: stream
});
assert.recieved = function(stream, buffer) {
assert.received = function(stream, buffer) {
assert.length(stream.packets, 1);
var packet = stream.packets.pop();
assert.equalBuffers(packet, buffer);
@ -16,7 +16,7 @@ test("sends startup message", function() {
user: 'brian',
database: 'bang'
});
assert.recieved(stream, new BufferList()
assert.received(stream, new BufferList()
.addInt16(3)
.addInt16(0)
.addCString('user')
@ -28,13 +28,13 @@ test("sends startup message", function() {
test('sends password message', function() {
con.password("!");
assert.recieved(stream, new BufferList().addCString("!").join(true,'p'));
assert.received(stream, new BufferList().addCString("!").join(true,'p'));
});
test('sends query message', function() {
var txt = 'select * from boom';
con.query(txt);
assert.recieved(stream, new BufferList().addCString(txt).join(true,'Q'));
assert.received(stream, new BufferList().addCString(txt).join(true,'Q'));
});
test('sends parse message', function() {
@ -43,7 +43,7 @@ test('sends parse message', function() {
.addCString("")
.addCString("!")
.addInt16(0).join(true, 'P');
assert.recieved(stream, expected);
assert.received(stream, expected);
});
test('sends parse message with named query', function() {
@ -56,7 +56,7 @@ test('sends parse message with named query', function() {
.addCString("boom")
.addCString("select * from boom")
.addInt16(0).join(true,'P');
assert.recieved(stream, expected);
assert.received(stream, expected);
test('with multiple parameters', function() {
con.parse({
@ -72,7 +72,7 @@ test('sends parse message with named query', function() {
.addInt32(2)
.addInt32(3)
.addInt32(4).join(true,'P');
assert.recieved(stream, expected);
assert.received(stream, expected);
});
});
@ -87,7 +87,7 @@ test('bind messages', function() {
.addInt16(0)
.addInt16(0)
.join(true,"B");
assert.recieved(stream, expectedBuffer);
assert.received(stream, expectedBuffer);
});
test('with named statement, portal, and values', function() {
@ -110,7 +110,7 @@ test('bind messages', function() {
.add(Buffer('zing'))
.addInt16(0)
.join(true, 'B');
assert.recieved(stream, expectedBuffer);
assert.received(stream, expectedBuffer);
});
});
@ -123,7 +123,7 @@ test("sends execute message", function() {
.addCString('')
.addInt32(0)
.join(true,'E');
assert.recieved(stream, expectedBuffer);
assert.received(stream, expectedBuffer);
});
test("for named portal with row limit", function() {
@ -135,38 +135,38 @@ test("sends execute message", function() {
.addCString("my favorite portal")
.addInt32(100)
.join(true, 'E');
assert.recieved(stream, expectedBuffer);
assert.received(stream, expectedBuffer);
});
});
test('sends flush command', function() {
con.flush();
var expected = new BufferList().join(true, 'H');
assert.recieved(stream, expected);
assert.received(stream, expected);
});
test('sends sync command', function() {
con.sync();
var expected = new BufferList().join(true,'S');
assert.recieved(stream, expected);
assert.received(stream, expected);
});
test('sends end command', function() {
con.end();
var expected = new Buffer([0x58, 0, 0, 0, 4]);
assert.recieved(stream, expected);
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.recieved(stream, expected);
assert.received(stream, expected);
});
test("describe unnamed portal", function() {
con.describe({type: 'P'});
var expected = new BufferList().addChar('P').addCString("").join(true, "D");
assert.recieved(stream, expected);
assert.received(stream, expected);
});
});

View File

@ -91,24 +91,28 @@ test('an empty pool', function() {
})
})
test('when creating async new pool members', function() {
var count = 0;
var pool = new Pool(3, function() {
var item = {ref: {name: ++count}, checkedIn: false};
process.nextTick(function() {
pool.checkIn(item.ref)
})
return item;
test('a pool with size of zero', function() {
var index = 0;
var pool = new Pool(0, function() {
return index++;
})
test('one request recieves member', function() {
test('checkin does nothing', function() {
index = 0;
pool.checkIn(301813);
assert.equal(pool.checkOut(assert.calls(function(err, item) {
assert.equal(item, 0);
})));
})
test('always creates a new item', function() {
index = 0;
pool.checkOut(assert.calls(function(err, item) {
assert.equal(item.name, 1)
pool.checkOut(assert.calls(function(err, item) {
assert.equal(item.name, 2)
pool.checkOut(assert.calls(function(err, item) {
assert.equal(item.name, 3)
}))
}))
assert.equal(item, 0);
}))
pool.checkOut(assert.calls(function(err, item) {
assert.equal(item, 1);
}))
pool.checkOut(assert.calls(function(err, item) {
assert.equal(item, 2);
}))
})
})
@ -167,7 +171,7 @@ test('normalizing connection info', function() {
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";