merge with upstream

This commit is contained in:
Hannes Hörl 2013-12-22 23:21:42 +01:00
commit 9ad0159037
9 changed files with 155 additions and 151 deletions

View File

@ -4,6 +4,9 @@ For richer information consult the commit log on github with referenced pull req
We do not include break-fix version release in this file.
### v2.9.0
- Add better support for [unix domain socket](https://github.com/brianc/node-postgres/pull/487) connections
### v2.8.0
- Add support for parsing JSON[] and UUID[] result types

View File

@ -2,13 +2,16 @@
'targets': [
{
'target_name': 'binding',
'sources': ['src/binding.cc'],
'include_dirs': [
'<!@(pg_config --includedir)',
'<!(node -e "require(\'nan\')")'
],
'conditions' : [
['OS=="win"', {
'conditions' : [
['"<!@(cmd /C where /Q pg_config || echo n)"!="n"',
{
'sources': ['src/binding.cc'],
'include_dirs': ['<!@(pg_config --includedir)'],
'libraries' : ['libpq.lib'],
'msvs_settings': {
'VCLinkerTool' : {
@ -24,8 +27,6 @@
'conditions' : [
['"y"!="n"', # ToDo: add pg_config existance condition that works on linux
{
'sources': ['src/binding.cc'],
'include_dirs': ['<!@(pg_config --includedir)'],
'libraries' : ['-lpq -L<!@(pg_config --libdir)']
}
]

View File

@ -12,16 +12,24 @@ var val = function(key, config) {
var url = require('url');
//parses a connection string
var parse = function(str) {
var config;
//unix socket
if(str.charAt(0) === '/') {
return { host: str };
config = str.split(' ');
return { host: config[0], database: config[1] };
}
// url parse expects spaces encoded as %20
str = encodeURI(str);
if(/ |%[^a-f0-9]|%[a-f0-9][^a-f0-9]/i.test(str)) str = encodeURI(str);
var result = url.parse(str, true);
var config = {};
config = {};
if(result.protocol == 'socket:') {
config.host = decodeURI(result.pathname);
config.database = result.query.db;
config.client_encoding = result.query.encoding;
return config;
}
config.host = result.hostname;
config.database = result.pathname ? result.pathname.slice(1) : null;
config.database = result.pathname ? decodeURI(result.pathname.slice(1)) : null;
var auth = (result.auth || ':').split(':');
config.user = auth[0];
config.password = auth[1];

View File

@ -3,12 +3,12 @@ var 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 dateMatcher = /(\d{1,})-(\d{2})-(\d{2}) (\d{2}):(\d{2}):(\d{2})(\.\d{1,})?/;
var match = dateMatcher.exec(isoDate);
//could not parse date
if(!match) {
dateMatcher = /^(\d{4})-(\d{2})-(\d{2})$/;
dateMatcher = /^(\d{1,})-(\d{2})-(\d{2})$/;
match = dateMatcher.test(isoDate);
if(!match) {
return null;

View File

@ -1,6 +1,6 @@
{
"name": "pg",
"version": "2.8.3",
"version": "2.9.0",
"description": "PostgreSQL client - pure javascript & libpq with the same API",
"keywords": [
"postgres",
@ -20,7 +20,8 @@
"dependencies": {
"generic-pool": "2.0.3",
"buffer-writer": "1.0.0",
"pgpass": "0.0.1"
"pgpass": "0.0.1",
"nan": "~0.6.0"
},
"devDependencies": {
"jshint": "1.1.0",

View File

@ -1,7 +1,6 @@
#include <pg_config.h>
#include <libpq-fe.h>
#include <node.h>
#include <node_buffer.h>
#include <nan.h>
#include <string.h>
#include <assert.h>
#include <stdlib.h>
@ -17,30 +16,11 @@
#define SINGLE_ROW_SUPPORTED
#endif
#define THROW(msg) return ThrowException(Exception::Error(String::New(msg)));
#define THROW(msg) NanThrowError(msg); NanReturnUndefined();
using namespace v8;
using namespace node;
static Persistent<String> severity_symbol;
static Persistent<String> code_symbol;
static Persistent<String> detail_symbol;
static Persistent<String> hint_symbol;
static Persistent<String> position_symbol;
static Persistent<String> internalPosition_symbol;
static Persistent<String> internalQuery_symbol;
static Persistent<String> where_symbol;
static Persistent<String> file_symbol;
static Persistent<String> line_symbol;
static Persistent<String> routine_symbol;
static Persistent<String> name_symbol;
static Persistent<String> value_symbol;
static Persistent<String> type_symbol;
static Persistent<String> channel_symbol;
static Persistent<String> payload_symbol;
static Persistent<String> emit_symbol;
static Persistent<String> command_symbol;
class Connection : public ObjectWrap {
public:
@ -49,30 +29,11 @@ public:
static void
Init (Handle<Object> target)
{
HandleScope scope;
NanScope();
Local<FunctionTemplate> 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("dataTypeID");
channel_symbol = NODE_PSYMBOL("channel");
payload_symbol = NODE_PSYMBOL("payload");
command_symbol = NODE_PSYMBOL("command");
t->SetClassName(NanSymbol("Connection"));
NODE_SET_PROTOTYPE_METHOD(t, "connect", Connect);
#ifdef ESCAPE_SUPPORTED
@ -107,10 +68,9 @@ public:
}
//v8 entry point into Connection#connect
static Handle<Value>
Connect(const Arguments& args)
static NAN_METHOD(Connect)
{
HandleScope scope;
NanScope();
Connection *self = ObjectWrap::Unwrap<Connection>(args.This());
if(args.Length() == 0 || !args[0]->IsString()) {
THROW("Must include connection string as only argument to connect");
@ -123,14 +83,13 @@ public:
self -> DestroyConnection();
}
return Undefined();
NanReturnUndefined();
}
//v8 entry point into Connection#cancel
static Handle<Value>
Cancel(const Arguments& args)
static NAN_METHOD(Cancel)
{
HandleScope scope;
NanScope();
Connection *self = ObjectWrap::Unwrap<Connection>(args.This());
bool success = self->Cancel();
@ -139,15 +98,14 @@ public:
self -> DestroyConnection();
}
return Undefined();
NanReturnUndefined();
}
#ifdef ESCAPE_SUPPORTED
//v8 entry point into Connection#escapeIdentifier
static Handle<Value>
EscapeIdentifier(const Arguments& args)
static NAN_METHOD(EscapeIdentifier)
{
HandleScope scope;
NanScope();
Connection *self = ObjectWrap::Unwrap<Connection>(args.This());
char* inputStr = MallocCString(args[0]);
@ -166,14 +124,13 @@ public:
Local<Value> jsStr = String::New(escapedStr, strlen(escapedStr));
PQfreemem(escapedStr);
return scope.Close(jsStr);
NanReturnValue(jsStr);
}
//v8 entry point into Connection#escapeLiteral
static Handle<Value>
EscapeLiteral(const Arguments& args)
static NAN_METHOD(EscapeLiteral)
{
HandleScope scope;
NanScope();
Connection *self = ObjectWrap::Unwrap<Connection>(args.This());
char* inputStr = MallocCString(args[0]);
@ -192,15 +149,14 @@ public:
Local<Value> jsStr = String::New(escapedStr, strlen(escapedStr));
PQfreemem(escapedStr);
return scope.Close(jsStr);
NanReturnValue(jsStr);
}
#endif
//v8 entry point into Connection#_sendQuery
static Handle<Value>
SendQuery(const Arguments& args)
static NAN_METHOD(SendQuery)
{
HandleScope scope;
NanScope();
Connection *self = ObjectWrap::Unwrap<Connection>(args.This());
const char *lastErrorMessage;
if(!args[0]->IsString()) {
@ -218,23 +174,22 @@ public:
}
//TODO should we flush before throw?
self->Flush();
return Undefined();
NanReturnUndefined();
}
//v8 entry point into Connection#_sendQueryWithParams
static Handle<Value>
SendQueryWithParams(const Arguments& args)
static NAN_METHOD(SendQueryWithParams)
{
HandleScope scope;
NanScope();
//dispatch non-prepared parameterized query
return DispatchParameterizedQuery(args, false);
DispatchParameterizedQuery(args, false);
NanReturnUndefined();
}
//v8 entry point into Connection#_sendPrepare(string queryName, string queryText, int nParams)
static Handle<Value>
SendPrepare(const Arguments& args)
static NAN_METHOD(SendPrepare)
{
HandleScope scope;
NanScope();
Connection *self = ObjectWrap::Unwrap<Connection>(args.This());
String::Utf8Value queryName(args[0]);
@ -243,32 +198,33 @@ public:
bool singleRowMode = (bool)args[3]->Int32Value();
self->SendPrepare(*queryName, *queryText, length, singleRowMode);
return Undefined();
NanReturnUndefined();
}
//v8 entry point into Connection#_sendQueryPrepared(string queryName, string[] paramValues)
static Handle<Value>
SendQueryPrepared(const Arguments& args)
static NAN_METHOD(SendQueryPrepared)
{
HandleScope scope;
NanScope();
//dispatch prepared parameterized query
return DispatchParameterizedQuery(args, true);
DispatchParameterizedQuery(args, true);
NanReturnUndefined();
}
static Handle<Value>
DispatchParameterizedQuery(const Arguments& args, bool isPrepared)
static void DispatchParameterizedQuery(_NAN_METHOD_ARGS, bool isPrepared)
{
HandleScope scope;
NanScope();
Connection *self = ObjectWrap::Unwrap<Connection>(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");
NanThrowError("First parameter must be a string");
return;
}
if(!args[1]->IsArray()) {
THROW("Values must be an array");
NanThrowError("Values must be an array");
return;
}
Local<Array> jsParams = Local<Array>::Cast(args[1]);
@ -277,7 +233,8 @@ public:
char** paramValues = ArgToCStringArray(jsParams);
if(!paramValues) {
THROW("Unable to allocate char **paramValues from Local<Array> of v8 params");
NanThrowError("Unable to allocate char **paramValues from Local<Array> of v8 params");
return;
}
char* queryText = MallocCString(args[0]);
@ -293,22 +250,21 @@ public:
free(queryText);
ReleaseCStringArray(paramValues, len);
if(result == 1) {
return Undefined();
return;
}
self->EmitLastError();
THROW("Postgres returned non-1 result from query dispatch.");
NanThrowError("Postgres returned non-1 result from query dispatch.");
}
//v8 entry point into Connection#end
static Handle<Value>
End(const Arguments& args)
static NAN_METHOD(End)
{
HandleScope scope;
NanScope();
Connection *self = ObjectWrap::Unwrap<Connection>(args.This());
self->End();
return Undefined();
NanReturnUndefined();
}
uv_poll_t read_watcher_;
@ -340,20 +296,18 @@ public:
{
}
static Handle<Value>
SendCopyFromChunk(const Arguments& args) {
HandleScope scope;
static NAN_METHOD(SendCopyFromChunk) {
NanScope();
Connection *self = ObjectWrap::Unwrap<Connection>(args.This());
//TODO handle errors in some way
if (args.Length() < 1 && !Buffer::HasInstance(args[0])) {
THROW("SendCopyFromChunk requires 1 Buffer argument");
}
self->SendCopyFromChunk(args[0]->ToObject());
return Undefined();
NanReturnUndefined();
}
static Handle<Value>
EndCopyFrom(const Arguments& args) {
HandleScope scope;
static NAN_METHOD(EndCopyFrom) {
NanScope();
Connection *self = ObjectWrap::Unwrap<Connection>(args.This());
char * error_msg = NULL;
if (args[0]->IsString()) {
@ -362,19 +316,18 @@ public:
//TODO handle errors in some way
self->EndCopyFrom(error_msg);
free(error_msg);
return Undefined();
NanReturnUndefined();
}
protected:
//v8 entry point to constructor
static Handle<Value>
New (const Arguments& args)
static NAN_METHOD(New)
{
HandleScope scope;
NanScope();
Connection *connection = new Connection();
connection->Wrap(args.This());
return args.This();
NanReturnValue(args.This());
}
#ifdef ESCAPE_SUPPORTED
@ -523,7 +476,7 @@ protected:
void HandleNotice(const char *message)
{
HandleScope scope;
NanScope();
Handle<Value> notice = String::New(message);
Emit("notice", &notice);
}
@ -552,7 +505,7 @@ protected:
//declare handlescope as this method is entered via a libuv callback
//and not part of the public v8 interface
HandleScope scope;
NanScope();
if (this->copyOutMode_) {
this->HandleCopyOut();
}
@ -584,8 +537,8 @@ protected:
TRACE("PQnotifies");
while ((notify = PQnotifies(connection_))) {
Local<Object> result = Object::New();
result->Set(channel_symbol, String::New(notify->relname));
result->Set(payload_symbol, String::New(notify->extra));
result->Set(NanSymbol("channel"), String::New(notify->relname));
result->Set(NanSymbol("payload"), String::New(notify->extra));
Handle<Value> res = (Handle<Value>)result;
Emit("notification", &res);
PQfreemem(notify);
@ -604,11 +557,9 @@ protected:
bool HandleCopyOut () {
char * buffer = NULL;
int copied;
Buffer * chunk;
copied = PQgetCopyData(connection_, &buffer, 1);
while (copied > 0) {
chunk = Buffer::New(buffer, copied);
Local<Value> node_chunk = Local<Value>::New(chunk->handle_);
Local<Value> node_chunk = NanNewBufferHandle(buffer, copied);
Emit("copyData", &node_chunk);
PQfreemem(buffer);
copied = PQgetCopyData(connection_, &buffer, 1);
@ -633,18 +584,18 @@ protected:
//javascript & c++ might introduce overhead (requires benchmarking)
void EmitRowDescription(const PGresult* result)
{
HandleScope scope;
NanScope();
Local<Array> row = Array::New();
int fieldCount = PQnfields(result);
for(int fieldNumber = 0; fieldNumber < fieldCount; fieldNumber++) {
Local<Object> field = Object::New();
//name of field
char* fieldName = PQfname(result, fieldNumber);
field->Set(name_symbol, String::New(fieldName));
field->Set(NanSymbol("name"), String::New(fieldName));
//oid of type of field
int fieldType = PQftype(result, fieldNumber);
field->Set(type_symbol, Integer::New(fieldType));
field->Set(NanSymbol("dataTypeID"), Integer::New(fieldType));
row->Set(Integer::New(fieldNumber), field);
}
@ -706,10 +657,10 @@ protected:
void EmitCommandMetaData(PGresult* result)
{
HandleScope scope;
NanScope();
Local<Object> info = Object::New();
info->Set(command_symbol, String::New(PQcmdStatus(result)));
info->Set(value_symbol, String::New(PQcmdTuples(result)));
info->Set(NanSymbol("command"), String::New(PQcmdStatus(result)));
info->Set(NanSymbol("value"), String::New(PQcmdTuples(result)));
Handle<Value> e = (Handle<Value>)info;
Emit("_cmdStatus", &e);
}
@ -720,7 +671,7 @@ protected:
//javascript & c++ might introduce overhead (requires benchmarking)
void HandleTuplesResult(const PGresult* result)
{
HandleScope scope;
NanScope();
int rowCount = PQntuples(result);
for(int rowNumber = 0; rowNumber < rowCount; rowNumber++) {
//create result object for this row
@ -744,7 +695,7 @@ protected:
void HandleErrorResult(const PGresult* result)
{
HandleScope scope;
NanScope();
//instantiate the return object as an Error with the summary Postgres message
TRACE("ReadResultField");
const char* errorMessage = PQresultErrorField(result, PG_DIAG_MESSAGE_PRIMARY);
@ -756,24 +707,25 @@ protected:
Local<Object> msg = Local<Object>::Cast(Exception::Error(String::New(errorMessage)));
TRACE("AttachErrorFields");
//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);
AttachErrorField(result, msg, NanSymbol("severity"), PG_DIAG_SEVERITY);
AttachErrorField(result, msg, NanSymbol("code"), PG_DIAG_SQLSTATE);
AttachErrorField(result, msg, NanSymbol("detail"), PG_DIAG_MESSAGE_DETAIL);
AttachErrorField(result, msg, NanSymbol("hint"), PG_DIAG_MESSAGE_HINT);
AttachErrorField(result, msg, NanSymbol("position"), PG_DIAG_STATEMENT_POSITION);
AttachErrorField(result, msg, NanSymbol("internalPosition"), PG_DIAG_INTERNAL_POSITION);
AttachErrorField(result, msg, NanSymbol("internalQuery"), PG_DIAG_INTERNAL_QUERY);
AttachErrorField(result, msg, NanSymbol("where"), PG_DIAG_CONTEXT);
AttachErrorField(result, msg, NanSymbol("file"), PG_DIAG_SOURCE_FILE);
AttachErrorField(result, msg, NanSymbol("line"), PG_DIAG_SOURCE_LINE);
AttachErrorField(result, msg, NanSymbol("routine"), PG_DIAG_SOURCE_FUNCTION);
Handle<Value> m = msg;
TRACE("EmitError");
Emit("_error", &m);
}
void AttachErrorField(const PGresult *result, const Local<Object> msg, const Persistent<String> symbol, int fieldcode)
void AttachErrorField(const PGresult *result, const Local<Object> msg, const Local<String> symbol, int fieldcode)
{
NanScope();
char *val = PQresultErrorField(result, fieldcode);
if(val) {
msg->Set(symbol, String::New(val));
@ -793,26 +745,26 @@ protected:
private:
//EventEmitter was removed from c++ in node v0.5.x
void Emit(const char* message) {
HandleScope scope;
NanScope();
Handle<Value> args[1] = { String::New(message) };
Emit(1, args);
}
void Emit(const char* message, Handle<Value>* arg) {
HandleScope scope;
NanScope();
Handle<Value> args[2] = { String::New(message), *arg };
Emit(2, args);
}
void Emit(int length, Handle<Value> *args) {
HandleScope scope;
NanScope();
Local<Value> emit_v = this->handle_->Get(emit_symbol);
Local<Value> emit_v = NanObjectWrapHandle(this)->Get(NanSymbol("emit"));
assert(emit_v->IsFunction());
Local<Function> emit_f = emit_v.As<Function>();
TryCatch tc;
emit_f->Call(this->handle_, length, args);
emit_f->Call(NanObjectWrapHandle(this), length, args);
if(tc.HasCaught()) {
FatalException(tc);
}
@ -849,6 +801,7 @@ private:
void EmitError(const char *message)
{
NanScope();
Local<Value> exception = Exception::Error(String::New(message));
Emit("_error", &exception);
}
@ -994,7 +947,7 @@ private:
extern "C" void init (Handle<Object> target)
{
HandleScope scope;
NanScope();
Connection::Init(target);
}
NODE_MODULE(binding, init)

View File

@ -151,19 +151,23 @@ test("timestampz round trip", function() {
});
if(!helper.config.binary) {
test('early AD & BC date', function() {
test('date range extremes', function() {
var client = helper.client();
client.on('error', function(err) {
console.log(err);
client.end();
});
client.query('SELECT $1::TIMESTAMPTZ as when', ["0062-03-08 14:32:00"], assert.success(function(res) {
assert.equal(res.rows[0].when.getFullYear(), 62);
// PostgreSQL supports date range of 4713 BCE to 294276 CE
// http://www.postgresql.org/docs/9.2/static/datatype-datetime.html
// ECMAScript supports date range of Apr 20 271821 BCE to Sep 13 275760 CE
// http://ecma-international.org/ecma-262/5.1/#sec-15.9.1.1
client.query('SELECT $1::TIMESTAMPTZ as when', ["275760-09-13 00:00:00 GMT"], assert.success(function(res) {
assert.equal(res.rows[0].when.getFullYear(), 275760);
}))
client.query('SELECT $1::TIMESTAMPTZ as when', ["0062-03-08 14:32:00 BC"], assert.success(function(res) {
assert.equal(res.rows[0].when.getFullYear(), -62);
client.query('SELECT $1::TIMESTAMPTZ as when', ["4713-12-31 12:31:59 BC GMT"], assert.success(function(res) {
assert.equal(res.rows[0].when.getFullYear(), -4713);
}))
client.on('drain', client.end.bind(client));

View File

@ -11,11 +11,11 @@ test('disconnects', function() {
helper.pg.connect(config, function(err, client, done) {
assert.isNull(err);
client.query("SELECT * FROM NOW()", function(err, result) {
process.nextTick(function() {
setTimeout(function() {
assert.equal(called, false, "Should not have disconnected yet")
sink.add();
done();
})
}, 0)
})
})
})

View File

@ -47,10 +47,44 @@ test('ConnectionParameters initializing from config', function() {
assert.ok(subject.isDomainSocket === false);
});
test('escape spaces if present', function() {
subject = new ConnectionParameters('postgres://localhost/post gres');
assert.equal(subject.database, 'post gres');
});
test('do not double escape spaces', function() {
subject = new ConnectionParameters('postgres://localhost/post%20gres');
assert.equal(subject.database, 'post gres');
});
test('initializing with unix domain socket', function() {
var subject = new ConnectionParameters('/var/run/');
assert.ok(subject.isDomainSocket);
assert.equal(subject.host, '/var/run/');
assert.equal(subject.database, defaults.user);
});
test('initializing with unix domain socket and a specific database, the simple way', function() {
var subject = new ConnectionParameters('/var/run/ mydb');
assert.ok(subject.isDomainSocket);
assert.equal(subject.host, '/var/run/');
assert.equal(subject.database, 'mydb');
});
test('initializing with unix domain socket, the health way', function() {
var subject = new ConnectionParameters('socket:/some path/?db=my[db]&encoding=utf8');
assert.ok(subject.isDomainSocket);
assert.equal(subject.host, '/some path/');
assert.equal(subject.database, 'my[db]', 'must to be escaped and unescaped trough "my%5Bdb%5D"');
assert.equal(subject.client_encoding, 'utf8');
});
test('initializing with unix domain socket, the escaped health way', function() {
var subject = new ConnectionParameters('socket:/some%20path/?db=my%2Bdb&encoding=utf8');
assert.ok(subject.isDomainSocket);
assert.equal(subject.host, '/some path/');
assert.equal(subject.database, 'my+db');
assert.equal(subject.client_encoding, 'utf8');
});
test('libpq connection string building', function() {