mirror of
https://github.com/brianc/node-postgres.git
synced 2025-12-08 20:16:25 +00:00
Merge branch 'master' of https://github.com/brianc/node-postgres into feature/ssl
This commit is contained in:
commit
7c61bebfda
@ -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.
|
||||
|
||||
@ -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();
|
||||
|
||||
@ -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');
|
||||
})
|
||||
|
||||
@ -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");
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@ -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;
|
||||
|
||||
35
lib/types.js
35
lib/types.js
@ -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,
|
||||
}
|
||||
|
||||
13
lib/utils.js
13
lib/utils.js
@ -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)
|
||||
|
||||
@ -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);
|
||||
|
||||
@ -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",
|
||||
|
||||
@ -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())
|
||||
}))
|
||||
}))
|
||||
})
|
||||
|
||||
32
test/integration/connection-pool/unique-name-tests.js
Normal file
32
test/integration/connection-pool/unique-name-tests.js
Normal 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();
|
||||
}))
|
||||
@ -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})
|
||||
}
|
||||
}];
|
||||
|
||||
|
||||
|
||||
@ -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);
|
||||
});
|
||||
});
|
||||
|
||||
@ -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";
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user