diff --git a/NEWS.md b/NEWS.md index bddf7fa..c98f369 100644 --- a/NEWS.md +++ b/NEWS.md @@ -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 diff --git a/binding.gyp b/binding.gyp index 02c80a4..bdc9dfd 100644 --- a/binding.gyp +++ b/binding.gyp @@ -2,13 +2,16 @@ 'targets': [ { 'target_name': 'binding', + 'sources': ['src/binding.cc'], + 'include_dirs': [ + ' #include -#include -#include +#include #include #include #include @@ -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 severity_symbol; -static Persistent code_symbol; -static Persistent detail_symbol; -static Persistent hint_symbol; -static Persistent position_symbol; -static Persistent internalPosition_symbol; -static Persistent internalQuery_symbol; -static Persistent where_symbol; -static Persistent file_symbol; -static Persistent line_symbol; -static Persistent routine_symbol; -static Persistent name_symbol; -static Persistent value_symbol; -static Persistent type_symbol; -static Persistent channel_symbol; -static Persistent payload_symbol; -static Persistent emit_symbol; -static Persistent command_symbol; - class Connection : public ObjectWrap { public: @@ -49,30 +29,11 @@ public: static void Init (Handle target) { - HandleScope scope; + NanScope(); Local t = FunctionTemplate::New(New); t->InstanceTemplate()->SetInternalFieldCount(1); - t->SetClassName(String::NewSymbol("Connection")); - - emit_symbol = NODE_PSYMBOL("emit"); - severity_symbol = NODE_PSYMBOL("severity"); - code_symbol = NODE_PSYMBOL("code"); - detail_symbol = NODE_PSYMBOL("detail"); - hint_symbol = NODE_PSYMBOL("hint"); - position_symbol = NODE_PSYMBOL("position"); - internalPosition_symbol = NODE_PSYMBOL("internalPosition"); - internalQuery_symbol = NODE_PSYMBOL("internalQuery"); - where_symbol = NODE_PSYMBOL("where"); - file_symbol = NODE_PSYMBOL("file"); - line_symbol = NODE_PSYMBOL("line"); - routine_symbol = NODE_PSYMBOL("routine"); - name_symbol = NODE_PSYMBOL("name"); - value_symbol = NODE_PSYMBOL("value"); - type_symbol = NODE_PSYMBOL("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 - Connect(const Arguments& args) + static NAN_METHOD(Connect) { - HandleScope scope; + NanScope(); Connection *self = ObjectWrap::Unwrap(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 - Cancel(const Arguments& args) + static NAN_METHOD(Cancel) { - HandleScope scope; + NanScope(); Connection *self = ObjectWrap::Unwrap(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 - EscapeIdentifier(const Arguments& args) + static NAN_METHOD(EscapeIdentifier) { - HandleScope scope; + NanScope(); Connection *self = ObjectWrap::Unwrap(args.This()); char* inputStr = MallocCString(args[0]); @@ -166,14 +124,13 @@ public: Local jsStr = String::New(escapedStr, strlen(escapedStr)); PQfreemem(escapedStr); - return scope.Close(jsStr); + NanReturnValue(jsStr); } //v8 entry point into Connection#escapeLiteral - static Handle - EscapeLiteral(const Arguments& args) + static NAN_METHOD(EscapeLiteral) { - HandleScope scope; + NanScope(); Connection *self = ObjectWrap::Unwrap(args.This()); char* inputStr = MallocCString(args[0]); @@ -192,15 +149,14 @@ public: Local jsStr = String::New(escapedStr, strlen(escapedStr)); PQfreemem(escapedStr); - return scope.Close(jsStr); + NanReturnValue(jsStr); } #endif //v8 entry point into Connection#_sendQuery - static Handle - SendQuery(const Arguments& args) + static NAN_METHOD(SendQuery) { - HandleScope scope; + NanScope(); Connection *self = ObjectWrap::Unwrap(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 - 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 - SendPrepare(const Arguments& args) + static NAN_METHOD(SendPrepare) { - HandleScope scope; + NanScope(); Connection *self = ObjectWrap::Unwrap(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 - 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 - DispatchParameterizedQuery(const Arguments& args, bool isPrepared) + static void DispatchParameterizedQuery(_NAN_METHOD_ARGS, bool isPrepared) { - HandleScope scope; + NanScope(); Connection *self = ObjectWrap::Unwrap(args.This()); String::Utf8Value queryName(args[0]); //TODO this is much copy/pasta code if(!args[0]->IsString()) { - THROW("First parameter must be a string"); + 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 jsParams = Local::Cast(args[1]); @@ -277,7 +233,8 @@ public: char** paramValues = ArgToCStringArray(jsParams); if(!paramValues) { - THROW("Unable to allocate char **paramValues from Local of v8 params"); + NanThrowError("Unable to allocate char **paramValues from Local 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 - End(const Arguments& args) + static NAN_METHOD(End) { - HandleScope scope; + NanScope(); Connection *self = ObjectWrap::Unwrap(args.This()); self->End(); - return Undefined(); + NanReturnUndefined(); } uv_poll_t read_watcher_; @@ -340,20 +296,18 @@ public: { } - static Handle - SendCopyFromChunk(const Arguments& args) { - HandleScope scope; + static NAN_METHOD(SendCopyFromChunk) { + NanScope(); Connection *self = ObjectWrap::Unwrap(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 - EndCopyFrom(const Arguments& args) { - HandleScope scope; + static NAN_METHOD(EndCopyFrom) { + NanScope(); Connection *self = ObjectWrap::Unwrap(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 - 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 notice = String::New(message); Emit("notice", ¬ice); } @@ -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 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 res = (Handle)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 node_chunk = Local::New(chunk->handle_); + Local 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 row = Array::New(); int fieldCount = PQnfields(result); for(int fieldNumber = 0; fieldNumber < fieldCount; fieldNumber++) { Local field = Object::New(); //name of field char* fieldName = PQfname(result, fieldNumber); - field->Set(name_symbol, String::New(fieldName)); + 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 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 e = (Handle)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 msg = Local::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 m = msg; TRACE("EmitError"); Emit("_error", &m); } - void AttachErrorField(const PGresult *result, const Local msg, const Persistent symbol, int fieldcode) + void AttachErrorField(const PGresult *result, const Local msg, const Local 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 args[1] = { String::New(message) }; Emit(1, args); } void Emit(const char* message, Handle* arg) { - HandleScope scope; + NanScope(); Handle args[2] = { String::New(message), *arg }; Emit(2, args); } void Emit(int length, Handle *args) { - HandleScope scope; + NanScope(); - Local emit_v = this->handle_->Get(emit_symbol); + Local emit_v = NanObjectWrapHandle(this)->Get(NanSymbol("emit")); assert(emit_v->IsFunction()); Local emit_f = emit_v.As(); 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 exception = Exception::Error(String::New(message)); Emit("_error", &exception); } @@ -994,7 +947,7 @@ private: extern "C" void init (Handle target) { - HandleScope scope; + NanScope(); Connection::Init(target); } NODE_MODULE(binding, init) diff --git a/test/integration/client/type-coercion-tests.js b/test/integration/client/type-coercion-tests.js index 5c957fc..69d134e 100644 --- a/test/integration/client/type-coercion-tests.js +++ b/test/integration/client/type-coercion-tests.js @@ -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)); diff --git a/test/integration/connection-pool/ending-pool-tests.js b/test/integration/connection-pool/ending-pool-tests.js index da057a5..e227baa 100644 --- a/test/integration/connection-pool/ending-pool-tests.js +++ b/test/integration/connection-pool/ending-pool-tests.js @@ -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) }) }) }) diff --git a/test/unit/connection-parameters/creation-tests.js b/test/unit/connection-parameters/creation-tests.js index 0624c79..aaab121 100644 --- a/test/unit/connection-parameters/creation-tests.js +++ b/test/unit/connection-parameters/creation-tests.js @@ -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() {