package/Makefile000644 0000000350 3560116604 010716 0ustar00000000 000000 .PHONY: publish-patch test test: npm test patch: test npm version patch -m "Bump version" git push origin master --tags npm publish minor: test npm version minor -m "Bump version" git push origin master --tags npm publish package/src/addon.cc000644 0000007136 3560116604 011452 0ustar00000000 000000 #include "addon.h" // Initialize the node addon NAN_MODULE_INIT(InitAddon) { v8::Local tpl = Nan::New(Connection::Create); tpl->SetClassName(Nan::New("PQ").ToLocalChecked()); tpl->InstanceTemplate()->SetInternalFieldCount(1); //connection initialization & management functions Nan::SetPrototypeMethod(tpl, "$connectSync", Connection::ConnectSync); Nan::SetPrototypeMethod(tpl, "$connect", Connection::Connect); Nan::SetPrototypeMethod(tpl, "$finish", Connection::Finish); Nan::SetPrototypeMethod(tpl, "$getLastErrorMessage", Connection::GetLastErrorMessage); Nan::SetPrototypeMethod(tpl, "$resultErrorFields", Connection::ResultErrorFields); Nan::SetPrototypeMethod(tpl, "$socket", Connection::Socket); Nan::SetPrototypeMethod(tpl, "$serverVersion", Connection::ServerVersion); //sync query functions Nan::SetPrototypeMethod(tpl, "$exec", Connection::Exec); Nan::SetPrototypeMethod(tpl, "$execParams", Connection::ExecParams); Nan::SetPrototypeMethod(tpl, "$prepare", Connection::Prepare); Nan::SetPrototypeMethod(tpl, "$execPrepared", Connection::ExecPrepared); //async query functions Nan::SetPrototypeMethod(tpl, "$sendQuery", Connection::SendQuery); Nan::SetPrototypeMethod(tpl, "$sendQueryParams", Connection::SendQueryParams); Nan::SetPrototypeMethod(tpl, "$sendPrepare", Connection::SendPrepare); Nan::SetPrototypeMethod(tpl, "$sendQueryPrepared", Connection::SendQueryPrepared); Nan::SetPrototypeMethod(tpl, "$getResult", Connection::GetResult); //async i/o control functions Nan::SetPrototypeMethod(tpl, "$startRead", Connection::StartRead); Nan::SetPrototypeMethod(tpl, "$stopRead", Connection::StopRead); Nan::SetPrototypeMethod(tpl, "$startWrite", Connection::StartWrite); Nan::SetPrototypeMethod(tpl, "$consumeInput", Connection::ConsumeInput); Nan::SetPrototypeMethod(tpl, "$isBusy", Connection::IsBusy); Nan::SetPrototypeMethod(tpl, "$setNonBlocking", Connection::SetNonBlocking); Nan::SetPrototypeMethod(tpl, "$isNonBlocking", Connection::IsNonBlocking); Nan::SetPrototypeMethod(tpl, "$flush", Connection::Flush); //result accessor functions Nan::SetPrototypeMethod(tpl, "$clear", Connection::Clear); Nan::SetPrototypeMethod(tpl, "$ntuples", Connection::Ntuples); Nan::SetPrototypeMethod(tpl, "$nfields", Connection::Nfields); Nan::SetPrototypeMethod(tpl, "$fname", Connection::Fname); Nan::SetPrototypeMethod(tpl, "$ftype", Connection::Ftype); Nan::SetPrototypeMethod(tpl, "$getvalue", Connection::Getvalue); Nan::SetPrototypeMethod(tpl, "$getisnull", Connection::Getisnull); Nan::SetPrototypeMethod(tpl, "$cmdStatus", Connection::CmdStatus); Nan::SetPrototypeMethod(tpl, "$cmdTuples", Connection::CmdTuples); Nan::SetPrototypeMethod(tpl, "$resultStatus", Connection::ResultStatus); Nan::SetPrototypeMethod(tpl, "$resultErrorMessage", Connection::ResultErrorMessage); //string escaping functions #ifdef ESCAPE_SUPPORTED Nan::SetPrototypeMethod(tpl, "$escapeLiteral", Connection::EscapeLiteral); Nan::SetPrototypeMethod(tpl, "$escapeIdentifier", Connection::EscapeIdentifier); #endif //async notifications Nan::SetPrototypeMethod(tpl, "$notifies", Connection::Notifies); //COPY IN/OUT Nan::SetPrototypeMethod(tpl, "$putCopyData", Connection::PutCopyData); Nan::SetPrototypeMethod(tpl, "$putCopyEnd", Connection::PutCopyEnd); Nan::SetPrototypeMethod(tpl, "$getCopyData", Connection::GetCopyData); //Cancel Nan::SetPrototypeMethod(tpl, "$cancel", Connection::Cancel); Nan::Set(target, Nan::New("PQ").ToLocalChecked(), Nan::GetFunction(tpl).ToLocalChecked()); } NODE_MODULE(addon, InitAddon) package/src/connect-async-worker.cc000644 0000001361 3560116604 014432 0ustar00000000 000000 //helper class to perform async connection #include "addon.h" ConnectAsyncWorker::ConnectAsyncWorker(v8::Local paramString, Connection* conn, Nan::Callback* callback) : Nan::AsyncWorker(callback), conn(conn), paramString(paramString) { } ConnectAsyncWorker::~ConnectAsyncWorker() { } //this method fires within the threadpool and does not //block the main node run loop void ConnectAsyncWorker::Execute() { TRACE("ConnectAsyncWorker::Execute"); bool success = conn->ConnectDB(*paramString); if(!success) { SetErrorMessage(conn->ErrorMessage()); } } void ConnectAsyncWorker::HandleOKCallback() { Nan::HandleScope scope; conn->InitPollSocket(); callback->Call(0, NULL, async_resource); } package/src/connection.cc000644 0000050124 3560116604 012517 0ustar00000000 000000 #include "addon.h" Connection::Connection() : Nan::ObjectWrap() { TRACE("Connection::Constructor"); pq = NULL; lastResult = NULL; is_reading = false; is_reffed = false; is_success_poll_init = false; poll_watcher.data = this; } NAN_METHOD(Connection::Create) { TRACE("Building new instance"); Connection* conn = new Connection(); conn->Wrap(info.This()); info.GetReturnValue().Set(info.This()); } NAN_METHOD(Connection::ConnectSync) { TRACE("Connection::ConnectSync::begin"); Connection *self = Nan::ObjectWrap::Unwrap(info.This()); self->Ref(); self->is_reffed = true; bool success = self->ConnectDB(*Nan::Utf8String(info[0])); if (success) { self->InitPollSocket(); } info.GetReturnValue().Set(success); } NAN_METHOD(Connection::Connect) { TRACE("Connection::Connect"); Connection* self = NODE_THIS(); v8::Local callback = info[1].As(); LOG("About to make callback"); Nan::Callback* nanCallback = new Nan::Callback(callback); LOG("About to instantiate worker"); ConnectAsyncWorker* worker = new ConnectAsyncWorker(info[0].As(), self, nanCallback); LOG("Instantiated worker, running it..."); self->Ref(); self->is_reffed = true; worker->SaveToPersistent(Nan::New("PQConnectAsyncWorker").ToLocalChecked(), info.This()); Nan::AsyncQueueWorker(worker); } NAN_METHOD(Connection::Socket) { TRACE("Connection::Socket"); Connection *self = NODE_THIS(); int fd = PQsocket(self->pq); TRACEF("Connection::Socket::fd: %d\n", fd); info.GetReturnValue().Set(fd); } NAN_METHOD(Connection::GetLastErrorMessage) { Connection *self = NODE_THIS(); char* errorMessage = PQerrorMessage(self->pq); info.GetReturnValue().Set(Nan::New(errorMessage).ToLocalChecked()); } NAN_METHOD(Connection::Finish) { TRACE("Connection::Finish::finish"); Connection *self = NODE_THIS(); self->ReadStop(); if (self->is_success_poll_init) { uv_close((uv_handle_t*) &self->poll_watcher, NULL); self->is_success_poll_init = false; } self->ClearLastResult(); PQfinish(self->pq); self->pq = NULL; if(self->is_reffed) { self->is_reffed = false; self->Unref(); } } NAN_METHOD(Connection::ServerVersion) { TRACE("Connection::ServerVersion"); Connection* self = NODE_THIS(); info.GetReturnValue().Set(PQserverVersion(self->pq)); } NAN_METHOD(Connection::Exec) { Connection *self = NODE_THIS(); Nan::Utf8String commandText(info[0]); TRACEF("Connection::Exec: %s\n", *commandText); PGresult* result = PQexec(self->pq, *commandText); self->SetLastResult(result); } NAN_METHOD(Connection::ExecParams) { Connection *self = NODE_THIS(); Nan::Utf8String commandText(info[0]); TRACEF("Connection::Exec: %s\n", *commandText); v8::Local jsParams = v8::Local::Cast(info[1]); int numberOfParams = jsParams->Length(); char **parameters = NewCStringArray(jsParams); PGresult* result = PQexecParams( self->pq, *commandText, numberOfParams, NULL, //const Oid* paramTypes[], parameters, //const char* const* paramValues[] NULL, //const int* paramLengths[] NULL, //const int* paramFormats[], 0 //result format of text ); DeleteCStringArray(parameters, numberOfParams); self->SetLastResult(result); } NAN_METHOD(Connection::Prepare) { Connection *self = NODE_THIS(); Nan::Utf8String statementName(info[0]); Nan::Utf8String commandText(info[1]); int numberOfParams = Nan::To(info[2]).FromJust(); TRACEF("Connection::Prepare: %s\n", *statementName); PGresult* result = PQprepare( self->pq, *statementName, *commandText, numberOfParams, NULL //const Oid* paramTypes[] ); self->SetLastResult(result); } NAN_METHOD(Connection::ExecPrepared) { Connection *self = NODE_THIS(); Nan::Utf8String statementName(info[0]); TRACEF("Connection::ExecPrepared: %s\n", *statementName); v8::Local jsParams = v8::Local::Cast(info[1]); int numberOfParams = jsParams->Length(); char** parameters = NewCStringArray(jsParams); PGresult* result = PQexecPrepared( self->pq, *statementName, numberOfParams, parameters, //const char* const* paramValues[] NULL, //const int* paramLengths[] NULL, //const int* paramFormats[], 0 //result format of text ); DeleteCStringArray(parameters, numberOfParams); self->SetLastResult(result); } NAN_METHOD(Connection::Clear) { TRACE("Connection::Clear"); Connection *self = NODE_THIS(); self->ClearLastResult(); } NAN_METHOD(Connection::Ntuples) { TRACE("Connection::Ntuples"); Connection *self = NODE_THIS(); PGresult* res = self->lastResult; int numTuples = PQntuples(res); info.GetReturnValue().Set(numTuples); } NAN_METHOD(Connection::Nfields) { TRACE("Connection::Nfields"); Connection *self = NODE_THIS(); PGresult* res = self->lastResult; int numFields = PQnfields(res); info.GetReturnValue().Set(numFields); } NAN_METHOD(Connection::Fname) { TRACE("Connection::Fname"); Connection *self = NODE_THIS(); PGresult* res = self->lastResult; char* colName = PQfname(res, Nan::To(info[0]).FromJust()); if(colName == NULL) { return info.GetReturnValue().SetNull(); } info.GetReturnValue().Set(Nan::New(colName).ToLocalChecked()); } NAN_METHOD(Connection::Ftype) { TRACE("Connection::Ftype"); Connection *self = NODE_THIS(); PGresult* res = self->lastResult; int colName = PQftype(res, Nan::To(info[0]).FromJust()); info.GetReturnValue().Set(colName); } NAN_METHOD(Connection::Getvalue) { TRACE("Connection::Getvalue"); Connection *self = NODE_THIS(); PGresult* res = self->lastResult; int rowNumber = Nan::To(info[0]).FromJust(); int colNumber = Nan::To(info[1]).FromJust(); char* rowValue = PQgetvalue(res, rowNumber, colNumber); if(rowValue == NULL) { return info.GetReturnValue().SetNull(); } info.GetReturnValue().Set(Nan::New(rowValue).ToLocalChecked()); } NAN_METHOD(Connection::Getisnull) { TRACE("Connection::Getisnull"); Connection *self = NODE_THIS(); PGresult* res = self->lastResult; int rowNumber = Nan::To(info[0]).FromJust(); int colNumber = Nan::To(info[1]).FromJust(); int rowValue = PQgetisnull(res, rowNumber, colNumber); info.GetReturnValue().Set(rowValue == 1); } NAN_METHOD(Connection::CmdStatus) { TRACE("Connection::CmdStatus"); Connection *self = NODE_THIS(); PGresult* res = self->lastResult; char* status = PQcmdStatus(res); info.GetReturnValue().Set(Nan::New(status).ToLocalChecked()); } NAN_METHOD(Connection::CmdTuples) { TRACE("Connection::CmdTuples"); Connection *self = NODE_THIS(); PGresult* res = self->lastResult; char* tuples = PQcmdTuples(res); info.GetReturnValue().Set(Nan::New(tuples).ToLocalChecked()); } NAN_METHOD(Connection::ResultStatus) { TRACE("Connection::ResultStatus"); Connection *self = NODE_THIS(); PGresult* res = self->lastResult; char* status = PQresStatus(PQresultStatus(res)); info.GetReturnValue().Set(Nan::New(status).ToLocalChecked()); } NAN_METHOD(Connection::ResultErrorMessage) { TRACE("Connection::ResultErrorMessage"); Connection *self = NODE_THIS(); PGresult* res = self->lastResult; char* status = PQresultErrorMessage(res); info.GetReturnValue().Set(Nan::New(status).ToLocalChecked()); } # define SET_E(key, name) \ field = PQresultErrorField(self->lastResult, key); \ if(field != NULL) { \ Nan::Set(result, \ Nan::New(name).ToLocalChecked(), Nan::New(field).ToLocalChecked()); \ } NAN_METHOD(Connection::ResultErrorFields) { Connection *self = NODE_THIS(); if(self->lastResult == NULL) { return info.GetReturnValue().SetNull(); } v8::Local result = Nan::New(); char* field; SET_E(PG_DIAG_SEVERITY, "severity"); SET_E(PG_DIAG_SQLSTATE, "sqlState"); SET_E(PG_DIAG_MESSAGE_PRIMARY, "messagePrimary"); SET_E(PG_DIAG_MESSAGE_DETAIL, "messageDetail"); SET_E(PG_DIAG_MESSAGE_HINT, "messageHint"); SET_E(PG_DIAG_STATEMENT_POSITION, "statementPosition"); SET_E(PG_DIAG_INTERNAL_POSITION, "internalPosition"); SET_E(PG_DIAG_INTERNAL_QUERY, "internalQuery"); SET_E(PG_DIAG_CONTEXT, "context"); #ifdef MORE_ERROR_FIELDS_SUPPORTED SET_E(PG_DIAG_SCHEMA_NAME, "schemaName"); SET_E(PG_DIAG_TABLE_NAME, "tableName"); SET_E(PG_DIAG_COLUMN_NAME, "columnName"); SET_E(PG_DIAG_DATATYPE_NAME, "dataTypeName"); SET_E(PG_DIAG_CONSTRAINT_NAME, "constraintName"); #endif SET_E(PG_DIAG_SOURCE_FILE, "sourceFile"); SET_E(PG_DIAG_SOURCE_LINE, "sourceLine"); SET_E(PG_DIAG_SOURCE_FUNCTION, "sourceFunction"); info.GetReturnValue().Set(result); } NAN_METHOD(Connection::SendQuery) { TRACE("Connection::SendQuery"); Connection *self = NODE_THIS(); Nan::Utf8String commandText(info[0]); TRACEF("Connection::SendQuery: %s\n", *commandText); int success = PQsendQuery(self->pq, *commandText); info.GetReturnValue().Set(success == 1); } NAN_METHOD(Connection::SendQueryParams) { TRACE("Connection::SendQueryParams"); Connection *self = NODE_THIS(); Nan::Utf8String commandText(info[0]); TRACEF("Connection::SendQueryParams: %s\n", *commandText); v8::Local jsParams = v8::Local::Cast(info[1]); int numberOfParams = jsParams->Length(); char** parameters = NewCStringArray(jsParams); int success = PQsendQueryParams( self->pq, *commandText, numberOfParams, NULL, //const Oid* paramTypes[], parameters, //const char* const* paramValues[] NULL, //const int* paramLengths[] NULL, //const int* paramFormats[], 0 //result format of text ); DeleteCStringArray(parameters, numberOfParams); info.GetReturnValue().Set(success == 1); } NAN_METHOD(Connection::SendPrepare) { TRACE("Connection::SendPrepare"); Connection *self = NODE_THIS(); Nan::Utf8String statementName(info[0]); Nan::Utf8String commandText(info[1]); int numberOfParams = Nan::To(info[2]).FromJust(); TRACEF("Connection::SendPrepare: %s\n", *statementName); int success = PQsendPrepare( self->pq, *statementName, *commandText, numberOfParams, NULL //const Oid* paramTypes ); info.GetReturnValue().Set(success == 1); } NAN_METHOD(Connection::SendQueryPrepared) { TRACE("Connection::SendQueryPrepared"); Connection *self = NODE_THIS(); Nan::Utf8String statementName(info[0]); TRACEF("Connection::SendQueryPrepared: %s\n", *statementName); v8::Local jsParams = v8::Local::Cast(info[1]); int numberOfParams = jsParams->Length(); char** parameters = NewCStringArray(jsParams); int success = PQsendQueryPrepared( self->pq, *statementName, numberOfParams, parameters, //const char* const* paramValues[] NULL, //const int* paramLengths[] NULL, //const int* paramFormats[], 0 //result format of text ); DeleteCStringArray(parameters, numberOfParams); info.GetReturnValue().Set(success == 1); } NAN_METHOD(Connection::GetResult) { TRACE("Connection::GetResult"); Connection *self = NODE_THIS(); PGresult *result = PQgetResult(self->pq); if(result == NULL) { return info.GetReturnValue().Set(false); } self->SetLastResult(result); info.GetReturnValue().Set(true); } NAN_METHOD(Connection::ConsumeInput) { TRACE("Connection::ConsumeInput"); Connection *self = NODE_THIS(); int success = PQconsumeInput(self->pq); info.GetReturnValue().Set(success == 1); } NAN_METHOD(Connection::IsBusy) { TRACE("Connection::IsBusy"); Connection *self = NODE_THIS(); int isBusy = PQisBusy(self->pq); TRACEF("Connection::IsBusy: %d\n", isBusy); info.GetReturnValue().Set(isBusy == 1); } NAN_METHOD(Connection::StartRead) { TRACE("Connection::StartRead"); Connection* self = NODE_THIS(); self->ReadStart(); } NAN_METHOD(Connection::StopRead) { TRACE("Connection::StopRead"); Connection* self = NODE_THIS(); self->ReadStop(); } NAN_METHOD(Connection::StartWrite) { TRACE("Connection::StartWrite"); Connection* self = NODE_THIS(); self->WriteStart(); } NAN_METHOD(Connection::SetNonBlocking) { TRACE("Connection::SetNonBlocking"); Connection* self = NODE_THIS(); int ok = PQsetnonblocking(self->pq, Nan::To(info[0]).FromJust()); info.GetReturnValue().Set(ok == 0); } NAN_METHOD(Connection::IsNonBlocking) { TRACE("Connection::IsNonBlocking"); Connection* self = NODE_THIS(); int status = PQisnonblocking(self->pq); info.GetReturnValue().Set(status == 1); } NAN_METHOD(Connection::Flush) { TRACE("Connection::Flush"); Connection* self = NODE_THIS(); int status = PQflush(self->pq); info.GetReturnValue().Set(status); } #ifdef ESCAPE_SUPPORTED NAN_METHOD(Connection::EscapeLiteral) { TRACE("Connection::EscapeLiteral"); Connection* self = NODE_THIS(); Nan::Utf8String str(Nan::To(info[0]).ToLocalChecked()); TRACEF("Connection::EscapeLiteral:input %s\n", *str); char* result = PQescapeLiteral(self->pq, *str, str.length()); TRACEF("Connection::EscapeLiteral:output %s\n", result); if(result == NULL) { return info.GetReturnValue().SetNull(); } info.GetReturnValue().Set(Nan::New(result).ToLocalChecked()); PQfreemem(result); } NAN_METHOD(Connection::EscapeIdentifier) { TRACE("Connection::EscapeIdentifier"); Connection* self = NODE_THIS(); Nan::Utf8String str(Nan::To(info[0]).ToLocalChecked()); TRACEF("Connection::EscapeIdentifier:input %s\n", *str); char* result = PQescapeIdentifier(self->pq, *str, str.length()); TRACEF("Connection::EscapeIdentifier:output %s\n", result); if(result == NULL) { return info.GetReturnValue().SetNull(); } info.GetReturnValue().Set(Nan::New(result).ToLocalChecked()); PQfreemem(result); } #endif NAN_METHOD(Connection::Notifies) { LOG("Connection::Notifies"); Connection* self = NODE_THIS(); PGnotify* msg = PQnotifies(self->pq); if(msg == NULL) { LOG("No notification"); return; } v8::Local result = Nan::New(); Nan::Set(result, Nan::New("relname").ToLocalChecked(), Nan::New(msg->relname).ToLocalChecked()); Nan::Set(result, Nan::New("extra").ToLocalChecked(), Nan::New(msg->extra).ToLocalChecked()); Nan::Set(result, Nan::New("be_pid").ToLocalChecked(), Nan::New(msg->be_pid)); PQfreemem(msg); info.GetReturnValue().Set(result); }; NAN_METHOD(Connection::PutCopyData) { LOG("Connection::PutCopyData"); Connection* self = NODE_THIS(); v8::Local buffer = info[0].As(); char* data = node::Buffer::Data(buffer); int length = node::Buffer::Length(buffer); int result = PQputCopyData(self->pq, data, length); info.GetReturnValue().Set(result); } NAN_METHOD(Connection::PutCopyEnd) { LOG("Connection::PutCopyEnd"); Connection* self = NODE_THIS(); //optional error message bool sendErrorMessage = info.Length() > 0; int result; if(sendErrorMessage) { Nan::Utf8String msg(info[0]); TRACEF("Connection::PutCopyEnd:%s\n", *msg); result = PQputCopyEnd(self->pq, *msg); } else { result = PQputCopyEnd(self->pq, NULL); } info.GetReturnValue().Set(result); } static void FreeBuffer(char *buffer, void *) { PQfreemem(buffer); } NAN_METHOD(Connection::GetCopyData) { LOG("Connection::GetCopyData"); Connection* self = NODE_THIS(); char* buffer = NULL; int async = info[0]->IsTrue() ? 1 : 0; TRACEF("Connection::GetCopyData:async %d\n", async); int length = PQgetCopyData(self->pq, &buffer, async); //some sort of failure or not-ready condition if(length < 1) { return info.GetReturnValue().Set(length); } info.GetReturnValue().Set(Nan::NewBuffer(buffer, length, FreeBuffer, NULL).ToLocalChecked()); } NAN_METHOD(Connection::Cancel) { LOG("Connection::Cancel"); Connection* self = NODE_THIS(); PGcancel *cancelStuct = PQgetCancel(self->pq); if(cancelStuct == NULL) { info.GetReturnValue().Set(Nan::Error("Unable to allocate cancel struct")); return; } char* errBuff = new char[255]; LOG("PQcancel"); int result = PQcancel(cancelStuct, errBuff, 255); LOG("PQfreeCancel"); PQfreeCancel(cancelStuct); if(result == 1) { delete[] errBuff; return info.GetReturnValue().Set(true); } info.GetReturnValue().Set(Nan::New(errBuff).ToLocalChecked()); delete[] errBuff; } bool Connection::ConnectDB(const char* paramString) { TRACEF("Connection::ConnectDB:Connection parameters: %s\n", paramString); this->pq = PQconnectdb(paramString); ConnStatusType status = PQstatus(this->pq); if(status != CONNECTION_OK) { return false; } TRACE("Connection::Connect::Success"); return true; } void Connection::InitPollSocket() { int fd = PQsocket(this->pq); int socketInitStatus = uv_poll_init_socket(uv_default_loop(), &(this->poll_watcher), fd); if (socketInitStatus == 0) { is_success_poll_init = true; } } char * Connection::ErrorMessage() { return PQerrorMessage(this->pq); } void Connection::on_io_readable(uv_poll_t* handle, int status, int revents) { LOG("Connection::on_io_readable"); TRACEF("Connection::on_io_readable:status %d\n", status); TRACEF("Connection::on_io_readable:revents %d\n", revents); if(revents & UV_READABLE) { LOG("Connection::on_io_readable UV_READABLE"); Connection* self = (Connection*) handle->data; LOG("Got connection pointer"); self->Emit("readable"); } } void Connection::on_io_writable(uv_poll_t* handle, int status, int revents) { LOG("Connection::on_io_writable"); TRACEF("Connection::on_io_writable:status %d\n", status); TRACEF("Connection::on_io_writable:revents %d\n", revents); if(revents & UV_WRITABLE) { LOG("Connection::on_io_readable UV_WRITABLE"); Connection* self = (Connection*) handle->data; self->WriteStop(); self->Emit("writable"); } } void Connection::ReadStart() { LOG("Connection::ReadStart:starting read watcher"); is_reading = true; uv_poll_start(&poll_watcher, UV_READABLE, on_io_readable); LOG("Connection::ReadStart:started read watcher"); } void Connection::ReadStop() { LOG("Connection::ReadStop:stoping read watcher"); if(!is_reading) return; is_reading = false; uv_poll_stop(&poll_watcher); LOG("Connection::ReadStop:stopped read watcher"); } void Connection::WriteStart() { LOG("Connection::WriteStart:starting write watcher"); uv_poll_start(&poll_watcher, UV_WRITABLE, on_io_writable); LOG("Connection::WriteStart:started write watcher"); } void Connection::WriteStop() { LOG("Connection::WriteStop:stoping write watcher"); uv_poll_stop(&poll_watcher); } void Connection::ClearLastResult() { LOG("Connection::ClearLastResult"); if(lastResult == NULL) return; PQclear(lastResult); lastResult = NULL; } void Connection::SetLastResult(PGresult* result) { LOG("Connection::SetLastResult"); ClearLastResult(); lastResult = result; } char* Connection::NewCString(v8::Local val) { Nan::HandleScope scope; Nan::Utf8String str(val); char* buffer = new char[str.length() + 1]; strcpy(buffer, *str); return buffer; } char** Connection::NewCStringArray(v8::Local jsParams) { Nan::HandleScope scope; int numberOfParams = jsParams->Length(); char** parameters = new char*[numberOfParams]; for(int i = 0; i < numberOfParams; i++) { v8::Local val = Nan::Get(jsParams, i).ToLocalChecked(); if(val->IsNull()) { parameters[i] = NULL; continue; } //expect every other value to be a string... //make sure aggresive type checking is done //on the JavaScript side before calling parameters[i] = NewCString(val); } return parameters; } void Connection::DeleteCStringArray(char** array, int length) { for(int i = 0; i < length; i++) { delete [] array[i]; } delete [] array; } void Connection::Emit(const char* message) { Nan::HandleScope scope; v8::Local info[1] = { Nan::New(message).ToLocalChecked() }; TRACE("CALLING EMIT"); Nan::TryCatch tc; Nan::AsyncResource *async_emit_f = new Nan::AsyncResource("libpq:connection:emit"); async_emit_f->runInAsyncScope(handle(), "emit", 1, info); delete async_emit_f; if(tc.HasCaught()) { Nan::FatalException(tc); } } package/binding.gyp000644 0000002714 3560116604 011417 0ustar00000000 000000 { 'conditions': [ ['OS=="linux"', { 'variables' : { # Find the pull path to the pg_config command, since iy may not be on the PATH 'pgconfig': ' #include #include #if PG_VERSION_NUM > 90000 #define ESCAPE_SUPPORTED #endif #if PG_VERSION_NUM >= 93000 #define MORE_ERROR_FIELDS_SUPPORTED #endif #include "connection.h" #include "connect-async-worker.h" //#define LOG(msg) fprintf(stderr, "%s\n", msg); //#define TRACEF(format, arg) fprintf(stderr, format, arg); #define LOG(msg) ; #define TRACEF(format, arg) ; #define TRACE(msg) LOG(msg); #define NODE_THIS() Nan::ObjectWrap::Unwrap(info.This()); #endif package/src/connect-async-worker.h000644 0000000624 3560116604 014275 0ustar00000000 000000 #ifndef NODE_LIBPQ_CONNECT_ASYNC_WORKER #define NODE_LIBPQ_CONNECT_ASYNC_WORKER #include "addon.h" class ConnectAsyncWorker : public Nan::AsyncWorker { public: ConnectAsyncWorker(v8::Local paramString, Connection* conn, Nan::Callback* callback); ~ConnectAsyncWorker(); void Execute(); void HandleOKCallback(); private: Connection* conn; Nan::Utf8String paramString; }; #endif package/src/connection.h000644 0000004703 3560116604 012363 0ustar00000000 000000 #ifndef NODE_LIBPQ_CONNECTION #define NODE_LIBPQ_CONNECTION #include #include class Connection : public Nan::ObjectWrap { public: static NAN_METHOD(Create); static NAN_METHOD(ConnectSync); static NAN_METHOD(Connect); static NAN_METHOD(ServerVersion); static NAN_METHOD(Socket); static NAN_METHOD(GetLastErrorMessage); static NAN_METHOD(Finish); static NAN_METHOD(Exec); static NAN_METHOD(ExecParams); static NAN_METHOD(Prepare); static NAN_METHOD(ExecPrepared); static NAN_METHOD(Clear); static NAN_METHOD(Ntuples); static NAN_METHOD(Nfields); static NAN_METHOD(Fname); static NAN_METHOD(Ftype); static NAN_METHOD(Getvalue); static NAN_METHOD(Getisnull); static NAN_METHOD(CmdStatus); static NAN_METHOD(CmdTuples); static NAN_METHOD(ResultStatus); static NAN_METHOD(ResultErrorMessage); static NAN_METHOD(ResultErrorFields); static NAN_METHOD(SendQuery); static NAN_METHOD(SendQueryParams); static NAN_METHOD(SendPrepare); static NAN_METHOD(SendQueryPrepared); static NAN_METHOD(GetResult); static NAN_METHOD(ConsumeInput); static NAN_METHOD(IsBusy); static NAN_METHOD(StartRead); static NAN_METHOD(StopRead); static NAN_METHOD(StartWrite); static NAN_METHOD(SetNonBlocking); static NAN_METHOD(IsNonBlocking); static NAN_METHOD(Flush); #ifdef ESCAPE_SUPPORTED static NAN_METHOD(EscapeLiteral); static NAN_METHOD(EscapeIdentifier); #endif static NAN_METHOD(Notifies); static NAN_METHOD(PutCopyData); static NAN_METHOD(PutCopyEnd); static NAN_METHOD(GetCopyData); static NAN_METHOD(Cancel); bool ConnectDB(const char* paramString); void InitPollSocket(); char* ErrorMessage(); PGconn* pq; private: PGresult* lastResult; uv_poll_t poll_watcher; bool is_reffed; bool is_reading; bool is_success_poll_init; Connection(); static void on_io_readable(uv_poll_t* handle, int status, int revents); static void on_io_writable(uv_poll_t* handle, int status, int revents); void ReadStart(); void ReadStop(); void WriteStart(); void WriteStop(); void ClearLastResult(); void SetLastResult(PGresult* result); static char* NewCString(v8::Local val); static char** NewCStringArray(v8::Local jsParams); static void DeleteCStringArray(char** array, int length); void Emit(const char* message); }; #endif package/test/async-connection.js000644 0000002544 3560116604 014054 0ustar00000000 000000 var PQ = require('../'); var assert = require('assert'); describe('async connection', function () { it('works', function (done) { var pq = new PQ(); assert(!pq.connected, 'should have connected set to falsy'); pq.connect(function (err) { if (err) { console.log(err); } assert(!err); pq.exec('SELECT NOW()'); assert.equal(pq.connected, true, 'should have connected set to true'); assert.equal(pq.ntuples(), 1); done(); }); }); it('works with hard-coded connection parameters', function (done) { var pq = new PQ(); var conString = 'host=' + (process.env.PGHOST || 'localhost'); pq.connect(conString, done); }); it('returns an error to the callback if connection fails', function (done) { new PQ().connect('host=asldkfjasldkfjalskdfjasdf', function (err) { assert(err, 'should have passed an error'); done(); }); }); it('respects the active domain', function (done) { var pq = new PQ(); var domain = require('domain').create(); domain.run(function () { var activeDomain = process.domain; assert(activeDomain, 'Should have an active domain'); pq.connect(function (err) { assert.strictEqual( process.domain, activeDomain, 'Active domain is lost' ); done(); }); }); }); }); package/test/async-socket.js000644 0000006643 3560116604 013211 0ustar00000000 000000 var LibPQ = require('../'); var helper = require('./helper'); var assert = require('assert'); var consume = function (pq, cb) { if (!pq.isBusy()) return cb(); pq.startReader(); var onReadable = function () { assert(pq.consumeInput(), pq.errorMessage()); if (pq.isBusy()) { console.log('consuming a 2nd buffer of input later...'); return; } pq.removeListener('readable', onReadable); pq.stopReader(); cb(); }; pq.on('readable', onReadable); }; describe('async simple query', function () { helper.setupIntegration(); it('dispatches simple query', function (done) { var pq = this.pq; assert(this.pq.setNonBlocking(true)); this.pq.writable(function () { var success = pq.sendQuery('SELECT 1'); assert.strictEqual( pq.flush(), 0, 'Should have flushed all data to socket' ); assert(success, pq.errorMessage()); consume(pq, function () { assert(!pq.errorMessage()); assert(pq.getResult()); assert.strictEqual(pq.getResult(), false); assert.strictEqual(pq.ntuples(), 1); assert.strictEqual(pq.getvalue(0, 0), '1'); done(); }); }); }); it('dispatches parameterized query', function (done) { var pq = this.pq; var success = pq.sendQueryParams('SELECT $1::text as name', ['Brian']); assert(success, pq.errorMessage()); assert.strictEqual( pq.flush(), 0, 'Should have flushed query text & parameters' ); consume(pq, function () { assert(!pq.errorMessage()); assert(pq.getResult()); assert.strictEqual(pq.getResult(), false); assert.strictEqual(pq.ntuples(), 1); assert.equal(pq.getvalue(0, 0), 'Brian'); done(); }); }); it('throws on dispatching non-array second argument', function () { assert.throws(() => { this.pq.sendQueryParams('SELECT $1::text as name', 'Brian'); }); }); it('throws on dispatching non-array second argument to sendQueryPrepared', function () { assert.throws(() => { pq.sendQueryPrepared('test', ['Brian']); }); }); it('dispatches named query', function (done) { var pq = this.pq; var statementName = 'async-get-name'; var success = pq.sendPrepare(statementName, 'SELECT $1::text as name', 1); assert(success, pq.errorMessage()); assert.strictEqual(pq.flush(), 0, 'Should have flushed query text'); consume(pq, function () { assert(!pq.errorMessage()); //first time there should be a result assert(pq.getResult()); //call 'getResult' until it returns false indicating //there is no more input to consume assert.strictEqual(pq.getResult(), false); //since we only prepared a statement there should be //0 tuples in the result assert.equal(pq.ntuples(), 0); //now execute the previously prepared statement var success = pq.sendQueryPrepared(statementName, ['Brian']); assert(success, pq.errorMessage()); assert.strictEqual(pq.flush(), 0, 'Should have flushed parameters'); consume(pq, function () { assert(!pq.errorMessage()); //consume the result of the query execution assert(pq.getResult()); assert.equal(pq.ntuples(), 1); assert.equal(pq.getvalue(0, 0), 'Brian'); //call 'getResult' again to ensure we're finished assert.strictEqual(pq.getResult(), false); done(); }); }); }); }); package/test/cancel.js000644 0000001543 3560116604 012025 0ustar00000000 000000 var Libpq = require('../'); var assert = require('assert'); describe('cancel a request', function() { it('works', function(done) { var pq = new Libpq(); pq.connectSync(); var sent = pq.sendQuery('pg_sleep(5000)'); assert(sent, 'should have sent'); var canceled = pq.cancel(); assert.strictEqual(canceled, true, 'should have canceled'); var hasResult = pq.getResult(); assert(hasResult, 'should have a result'); assert.equal(pq.resultStatus(), 'PGRES_FATAL_ERROR'); assert.equal(pq.getResult(), false); pq.exec('SELECT NOW()'); done(); }); it('returns (not throws) an error if not connected', function(done) { var pq = new Libpq(); assert.doesNotThrow(function () { pq.cancel(function (err) { assert(err, 'should raise an error when not connected'); }); }); done(); }); }); package/test/construction.js000644 0000000635 3560116604 013333 0ustar00000000 000000 var PQ = require('../'); var async = require('async'); describe('Constructing multiple', function() { it('works all at once', function() { for(var i = 0; i < 1000; i++) { var pq = new PQ(); } }); it('connects and disconnects each client', function(done) { var connect = function(n, cb) { var pq = new PQ(); pq.connect(cb); }; async.times(30, connect, done); }); }) package/test/copy-in.js000644 0000002720 3560116604 012154 0ustar00000000 000000 var helper = require('./helper'); var assert = require('assert'); var bufferFrom = require('buffer-from') describe('COPY IN', function() { helper.setupIntegration(); it('check existing data assuptions', function() { this.pq.exec('SELECT COUNT(*) FROM test_data'); assert.equal(this.pq.getvalue(0, 0), 3); }); it('copies data in', function() { var success = this.pq.exec('COPY test_data FROM stdin'); assert.equal(this.pq.resultStatus(), 'PGRES_COPY_IN'); var buffer = bufferFrom("bob\t100\n", 'utf8'); var res = this.pq.putCopyData(buffer); assert.strictEqual(res, 1); var res = this.pq.putCopyEnd(); assert.strictEqual(res, 1); while(this.pq.getResult()) {} this.pq.exec('SELECT COUNT(*) FROM test_data'); assert.equal(this.pq.getvalue(0, 0), 4); }); it('can cancel copy data in', function() { var success = this.pq.exec('COPY test_data FROM stdin'); assert.equal(this.pq.resultStatus(), 'PGRES_COPY_IN'); var buffer = bufferFrom("bob\t100\n", 'utf8'); var res = this.pq.putCopyData(buffer); assert.strictEqual(res, 1); var res = this.pq.putCopyEnd('cancel!'); assert.strictEqual(res, 1); while(this.pq.getResult()) {} assert(this.pq.errorMessage()); assert(this.pq.errorMessage().indexOf('cancel!') > -1, this.pq.errorMessage() + ' should have contained "cancel!"'); this.pq.exec('SELECT COUNT(*) FROM test_data'); assert.equal(this.pq.getvalue(0, 0), 4); }); }); package/test/copy-out.js000644 0000001204 3560116604 012351 0ustar00000000 000000 var helper = require('./helper'); var assert = require('assert'); describe('COPY OUT', function() { helper.setupIntegration(); var getRow = function(pq, expected) { var result = pq.getCopyData(false); assert(result instanceof Buffer, 'Result should be a buffer'); assert.equal(result.toString('utf8'), expected); }; it('copies data out', function() { this.pq.exec('COPY test_data TO stdin'); assert.equal(this.pq.resultStatus(), 'PGRES_COPY_OUT'); getRow(this.pq, 'brian\t32\n'); getRow(this.pq, 'aaron\t30\n'); getRow(this.pq, '\t\\N\n'); assert.strictEqual(this.pq.getCopyData(), -1); }); }); package/test/error-conditions.js000644 0000001510 3560116604 014072 0ustar00000000 000000 var PQ = require('../') var assert = require('assert'); describe('without being connected', function() { it('exec fails', function() { var pq = new PQ(); pq.exec(); assert.equal(pq.resultStatus(), 'PGRES_FATAL_ERROR'); assert(pq.errorMessage()); }); it('fails on async query', function() { var pq = new PQ(); var success = pq.sendQuery('blah'); assert.strictEqual(success, false); assert.equal(pq.resultStatus(), 'PGRES_FATAL_ERROR'); assert(pq.errorMessage()); }); it('throws when reading while not connected', function() { var pq = new PQ(); assert.throws(function() { pq.startReader(); }); }); it('throws when writing while not connected', function() { var pq = new PQ(); assert.throws(function() { pq.writable(function() { }); }); }); }) package/test/error-info.js000644 0000003166 3560116604 012665 0ustar00000000 000000 var Libpq = require('../'); var assert = require('assert'); var helper = require('./helper'); describe('error info', function() { helper.setupIntegration(); describe('when there is no error', function() { it('everything is null', function() { var pq = this.pq; pq.exec('SELECT NOW()'); assert(!pq.errorMessage(), pq.errorMessage()); assert.equal(pq.ntuples(), 1); assert(pq.resultErrorFields(), null); }); }); describe('when there is an error', function() { it('sets all error codes', function() { var pq = this.pq; pq.exec('INSERT INTO test_data VALUES(1, NOW())'); assert(pq.errorMessage()); var err = pq.resultErrorFields(); assert.notEqual(err, null); assert.equal(err.severity, 'ERROR'); assert.equal(err.sqlState, 42804); assert.equal(err.messagePrimary, 'column "age" is of type integer but expression is of type timestamp with time zone'); assert.equal(err.messageDetail, undefined); assert.equal(err.messageHint, 'You will need to rewrite or cast the expression.'); assert.equal(err.statementPosition, 33); assert.equal(err.internalPosition, undefined); assert.equal(err.internalQuery, undefined); assert.equal(err.context, undefined); assert.equal(err.schemaName, undefined); assert.equal(err.tableName, undefined); assert.equal(err.dataTypeName, undefined); assert.equal(err.constraintName, undefined); assert.equal(err.sourceFile, "parse_target.c"); assert(parseInt(err.sourceLine)); assert.equal(err.sourceFunction, "transformAssignedExpr"); }); }); }); package/test/escaping.js000644 0000002174 3560116604 012372 0ustar00000000 000000 var Libpq = require('../'); var assert = require('assert'); describe('escapeLiteral', function() { it('fails to escape when the server is not connected', function() { var pq = new Libpq(); var result = pq.escapeLiteral('test'); assert.strictEqual(result, null); assert(pq.errorMessage()); }); it('escapes a simple string', function() { var pq = new Libpq(); pq.connectSync(); var result = pq.escapeLiteral('bang'); assert.equal(result, "'bang'"); }); it('escapes a bad string', function() { var pq = new Libpq(); pq.connectSync(); var result = pq.escapeLiteral("'; TRUNCATE TABLE blah;"); assert.equal(result, "'''; TRUNCATE TABLE blah;'"); }); }); describe('escapeIdentifier', function() { it('fails when the server is not connected', function() { var pq = new Libpq(); var result = pq.escapeIdentifier('test'); assert.strictEqual(result, null); assert(pq.errorMessage()); }); it('escapes a simple string', function() { var pq = new Libpq(); pq.connectSync(); var result = pq.escapeIdentifier('bang'); assert.equal(result, '"bang"'); }); }); package/test/helper.js000644 0000000751 3560116604 012057 0ustar00000000 000000 var PQ = require('../'); var createTable = function(pq) { pq.exec('CREATE TEMP TABLE test_data(name text, age int)') console.log(pq.resultErrorMessage()); pq.exec("INSERT INTO test_data(name, age) VALUES ('brian', 32), ('aaron', 30), ('', null);") }; module.exports = { setupIntegration: function() { before(function() { this.pq = new PQ(); this.pq.connectSync(); createTable(this.pq); }); after(function() { this.pq.finish(); }); } }; package/index.js000644 0000025607 3560116604 010737 0ustar00000000 000000 var PQ = (module.exports = require('bindings')('addon.node').PQ); var assert = require('assert'); //print out the include dir //if you want to include this in a binding.gyp file if (!module.parent) { var path = require('path'); console.log(path.normalize(__dirname + '/src')); } var EventEmitter = require('events').EventEmitter; var assert = require('assert'); for (var key in EventEmitter.prototype) { PQ.prototype[key] = EventEmitter.prototype[key]; } //SYNC connects to the server //throws an exception in the event of a connection error PQ.prototype.connectSync = function (paramString) { this.connected = true; if (!paramString) { paramString = ''; } var connected = this.$connectSync(paramString); if (!connected) { var err = new Error(this.errorMessage()); this.finish(); throw err; } }; //connects async using a background thread //calls the callback with an error if there was one PQ.prototype.connect = function (paramString, cb) { this.connected = true; if (typeof paramString == 'function') { cb = paramString; paramString = ''; } if (!paramString) { paramString = ''; } assert(cb, 'Must provide a connection callback'); if (process.domain) { cb = process.domain.bind(cb); } this.$connect(paramString, cb); }; PQ.prototype.errorMessage = function () { return this.$getLastErrorMessage(); }; //returns an int for the fd of the socket PQ.prototype.socket = function () { return this.$socket(); }; // return server version number e.g. 90300 PQ.prototype.serverVersion = function () { return this.$serverVersion(); }; //finishes the connection & closes it PQ.prototype.finish = function () { this.connected = false; this.$finish(); }; ////SYNC executes a plain text query //immediately stores the results within the PQ object for consumption with //ntuples, getvalue, etc... //returns false if there was an error //consume additional error details via PQ#errorMessage & friends PQ.prototype.exec = function (commandText) { if (!commandText) { commandText = ''; } this.$exec(commandText); }; //SYNC executes a query with parameters //immediately stores the results within the PQ object for consumption with //ntuples, getvalue, etc... //returns false if there was an error //consume additional error details via PQ#errorMessage & friends PQ.prototype.execParams = function (commandText, parameters) { if (!commandText) { commandText = ''; } if (!parameters) { parameters = []; } assert(Array.isArray(parameters), 'Parameters must be an array'); this.$execParams(commandText, parameters); }; //SYNC prepares a named query and stores the result //immediately stores the results within the PQ object for consumption with //ntuples, getvalue, etc... //returns false if there was an error //consume additional error details via PQ#errorMessage & friends PQ.prototype.prepare = function (statementName, commandText, nParams) { assert.equal(arguments.length, 3, 'Must supply 3 arguments'); if (!statementName) { statementName = ''; } if (!commandText) { commandText = ''; } nParams = Number(nParams) || 0; this.$prepare(statementName, commandText, nParams); }; //SYNC executes a named, prepared query and stores the result //immediately stores the results within the PQ object for consumption with //ntuples, getvalue, etc... //returns false if there was an error //consume additional error details via PQ#errorMessage & friends PQ.prototype.execPrepared = function (statementName, parameters) { if (!statementName) { statementName = ''; } if (!parameters) { parameters = []; } assert(Array.isArray(parameters), 'Parameters must be an array'); this.$execPrepared(statementName, parameters); }; //send a command to begin executing a query in async mode //returns true if sent, or false if there was a send failure PQ.prototype.sendQuery = function (commandText) { if (!commandText) { commandText = ''; } return this.$sendQuery(commandText); }; //send a command to begin executing a query with parameters in async mode //returns true if sent, or false if there was a send failure PQ.prototype.sendQueryParams = function (commandText, parameters) { if (!commandText) { commandText = ''; } if (!parameters) { parameters = []; } assert(Array.isArray(parameters), 'Parameters must be an array'); return this.$sendQueryParams(commandText, parameters); }; //send a command to prepare a named query in async mode //returns true if sent, or false if there was a send failure PQ.prototype.sendPrepare = function (statementName, commandText, nParams) { assert.equal(arguments.length, 3, 'Must supply 3 arguments'); if (!statementName) { statementName = ''; } if (!commandText) { commandText = ''; } nParams = Number(nParams) || 0; return this.$sendPrepare(statementName, commandText, nParams); }; //send a command to execute a named query in async mode //returns true if sent, or false if there was a send failure PQ.prototype.sendQueryPrepared = function (statementName, parameters) { if (!statementName) { statementName = ''; } if (!parameters) { parameters = []; } assert(Array.isArray(parameters), 'Parameters must be an array'); return this.$sendQueryPrepared(statementName, parameters); }; //'pops' a result out of the buffered //response data read during async command execution //and stores it on the c/c++ object so you can consume //the data from it. returns true if there was a pending result //or false if there was no pending result. if there was no pending result //the last found result is not overwritten so you can call getResult as many //times as you want, and you'll always have the last available result for consumption PQ.prototype.getResult = function () { return this.$getResult(); }; //returns a text of the enum associated with the result //usually just PGRES_COMMAND_OK or PGRES_FATAL_ERROR PQ.prototype.resultStatus = function () { return this.$resultStatus(); }; PQ.prototype.resultErrorMessage = function () { return this.$resultErrorMessage(); }; PQ.prototype.resultErrorFields = function () { return this.$resultErrorFields(); }; //free the memory associated with a result //this is somewhat handled for you within the c/c++ code //by never allowing the code to 'leak' a result. still, //if you absolutely want to free it yourself, you can use this. PQ.prototype.clear = function () { this.$clear(); }; //returns the number of tuples (rows) in the result set PQ.prototype.ntuples = function () { return this.$ntuples(); }; //returns the number of fields (columns) in the result set PQ.prototype.nfields = function () { return this.$nfields(); }; //returns the name of the field (column) at the given offset PQ.prototype.fname = function (offset) { return this.$fname(offset); }; //returns the Oid of the type for the given field PQ.prototype.ftype = function (offset) { return this.$ftype(offset); }; //returns a text value at the given row/col //if the value is null this still returns empty string //so you need to use PQ#getisnull to determine PQ.prototype.getvalue = function (row, col) { return this.$getvalue(row, col); }; //returns true/false if the value is null PQ.prototype.getisnull = function (row, col) { return this.$getisnull(row, col); }; //returns the status of the command PQ.prototype.cmdStatus = function () { return this.$cmdStatus(); }; //returns the tuples in the command PQ.prototype.cmdTuples = function () { return this.$cmdTuples(); }; //starts the 'read ready' libuv socket listener. //Once the socket becomes readable, the PQ instance starts //emitting 'readable' events. Similar to how node's readable-stream //works except to clear the SELECT() notification you need to call //PQ#consumeInput instead of letting node pull the data off the socket //http://www.postgresql.org/docs/9.1/static/libpq-async.html PQ.prototype.startReader = function () { assert(this.connected, 'Must be connected to start reader'); this.$startRead(); }; //suspends the libuv socket 'read ready' listener PQ.prototype.stopReader = function () { this.$stopRead(); }; PQ.prototype.writable = function (cb) { assert(this.connected, 'Must be connected to start writer'); this.$startWrite(); return this.once('writable', cb); }; //returns boolean - false indicates an error condition //e.g. a failure to consume input PQ.prototype.consumeInput = function () { return this.$consumeInput(); }; //returns true if PQ#getResult would cause //the process to block waiting on results //false indicates PQ#getResult can be called //with an assurance of not blocking PQ.prototype.isBusy = function () { return this.$isBusy(); }; //toggles the socket blocking on outgoing writes PQ.prototype.setNonBlocking = function (truthy) { return this.$setNonBlocking(truthy ? 1 : 0); }; //returns true if the connection is non-blocking on writes, otherwise false //note: connection is always non-blocking on reads if using the send* methods PQ.prototype.isNonBlocking = function () { return this.$isNonBlocking(); }; //returns 1 if socket is not write-ready //returns 0 if all data flushed to socket //returns -1 if there is an error PQ.prototype.flush = function () { return this.$flush(); }; //escapes a literal and returns the escaped string //I'm not 100% sure this doesn't do any I/O...need to check that PQ.prototype.escapeLiteral = function (input) { if (!input) return input; return this.$escapeLiteral(input); }; PQ.prototype.escapeIdentifier = function (input) { if (!input) return input; return this.$escapeIdentifier(input); }; //Checks for any notifications which may have arrivied //and returns them as a javascript object: {relname: 'string', extra: 'string', be_pid: int} //if there are no pending notifications this returns undefined PQ.prototype.notifies = function () { return this.$notifies(); }; //Sends a buffer of binary data to the server //returns 1 if the command was sent successfully //returns 0 if the command would block (use PQ#writable here if so) //returns -1 if there was an error PQ.prototype.putCopyData = function (buffer) { assert(buffer instanceof Buffer); return this.$putCopyData(buffer); }; //Sends a command to 'finish' the copy //if an error message is passed, it will be sent to the //backend and signal a request to cancel the copy in //returns 1 if sent succesfully //returns 0 if the command would block //returns -1 if there was an error PQ.prototype.putCopyEnd = function (errorMessage) { if (errorMessage) { return this.$putCopyEnd(errorMessage); } return this.$putCopyEnd(); }; //Gets a buffer of data from a copy out command //if async is passed as true it will not block waiting //for the result, otherwise this will BLOCK for a result. //returns a buffer if successful //returns 0 if copy is still in process (async only) //returns -1 if the copy is done //returns -2 if there was an error PQ.prototype.getCopyData = function (async) { return this.$getCopyData(!!async); }; PQ.prototype.cancel = function () { return this.$cancel(); }; package/test/index.js000644 0000000237 3560116604 011706 0ustar00000000 000000 var Client = require('../') describe('connecting', function() { it('works', function() { var client = new Client(); client.connectSync(); }); }); package/test/load.js000644 0000001010 3560116604 011504 0ustar00000000 000000 var Libpq = require('../') var async = require('async') var ok = require('okay') var blink = function(n, cb) { var connections = [] for(var i = 0; i < 30; i++) { connections.push(new Libpq()) } var connect = function(con, cb) { con.connect(cb) } async.each(connections, connect, ok(function() { connections.forEach(function(con) { con.finish() }) cb() })) } describe('many connections', function() { it('works', function(done) { async.timesSeries(10, blink, done) }) }) package/test/many-connections.js000644 0000002067 3560116604 014066 0ustar00000000 000000 var Libpq = require('../'); var _ = require('lodash'); var assert = require('assert'); describe('connectSync', function() { it('works 50 times in a row', function() { var pqs = _.times(50, function() { return new Libpq(); }); pqs.forEach(function(pq) { pq.connectSync(); }); pqs.forEach(function(pq) { pq.finish(); }); }); }); //doing a bunch of stuff here to //try and shake out a hard to track down segfault describe('connect async', function() { var total = 50; it('works ' + total + ' times in a row', function(done) { var pqs = _.times(total, function() { return new Libpq(); }); var count = 0; var connect = function(cb) { pqs.forEach(function(pq) { pq.connect(function(err) { assert(!err); count++; pq.startReader(); if(count == total) { cb(); } }); }); }; connect(function() { pqs.forEach(function(pq) { pq.stopReader(); pq.finish(); }); done(); }); }); }); package/test/multiple-queries.js000644 0000003060 3560116604 014102 0ustar00000000 000000 var Libpq = require('../'); var ok = require('okay') var queryText = "SELECT * FROM generate_series(1, 1000)" var query = function(pq, cb) { var sent = pq.sendQuery(queryText); if(!sent) return cb(new Error(pg.errorMessage())); console.log('sent query') //consume any outstanding results //while(!pq.isBusy() && pq.getResult()) { //console.log('consumed unused result') //} var cleanup = function() { pq.removeListener('readable', onReadable); pq.stopReader(); } var readError = function(message) { cleanup(); return cb(new Error(message || pq.errorMessage)); }; var onReadable = function() { //read waiting data from the socket //e.g. clear the pending 'select' if(!pq.consumeInput()) { return readError(); } //check if there is still outstanding data //if so, wait for it all to come in if(pq.isBusy()) { return; } //load our result object pq.getResult(); //"read until results return null" //or in our case ensure we only have one result if(pq.getResult()) { return readError('Only one result at a time is accepted'); } cleanup(); return cb(null, []) }; pq.on('readable', onReadable); pq.startReader(); }; describe('multiple queries', function() { var pq = new Libpq(); before(function(done) { pq.connect(done) }) it('first query works', function(done) { query(pq, done); }); it('second query works', function(done) { query(pq, done); }); it('third query works', function(done) { query(pq, done); }); }); package/test/non-blocking-controls.js000644 0000001030 3560116604 015010 0ustar00000000 000000 var helper = require('./helper') var assert = require('assert') describe('set & get non blocking', function() { helper.setupIntegration(); it('is initially set to false', function() { assert.strictEqual(this.pq.isNonBlocking(), false); }); it('can switch back and forth', function() { assert.strictEqual(this.pq.setNonBlocking(true), true); assert.strictEqual(this.pq.isNonBlocking(), true); assert.strictEqual(this.pq.setNonBlocking(), true); assert.strictEqual(this.pq.isNonBlocking(), false); }); }); package/test/notification.js000644 0000001640 3560116604 013264 0ustar00000000 000000 var Libpq = require('../'); var assert = require('assert'); describe('LISTEN/NOTIFY', function() { before(function() { this.listener = new Libpq(); this.notifier = new Libpq(); this.listener.connectSync(); this.notifier.connectSync(); }); it('works', function() { this.notifier.exec("NOTIFY testing, 'My Payload'"); var notice = this.listener.notifies(); assert.equal(notice, null); this.listener.exec('LISTEN testing'); this.notifier.exec("NOTIFY testing, 'My Second Payload'"); this.listener.exec('SELECT NOW()'); var notice = this.listener.notifies(); assert(notice, 'listener should have had a notification come in'); assert.equal(notice.relname, 'testing', 'missing relname == testing'); assert.equal(notice.extra, 'My Second Payload'); assert(notice.be_pid); }); after(function() { this.listener.finish(); this.notifier.finish(); }); }); package/test/result-accessors.js000644 0000001073 3560116604 014077 0ustar00000000 000000 var assert = require('assert'); var helper = require('./helper'); describe('result accessors', function() { helper.setupIntegration(); before(function() { this.pq.exec("INSERT INTO test_data(name, age) VALUES ('bob', 80) RETURNING *"); assert(!this.pq.errorMessage()); }); it('has ntuples', function() { assert.strictEqual(this.pq.ntuples(), 1); }); it('has cmdStatus', function() { assert.equal(this.pq.cmdStatus(), 'INSERT 0 1'); }); it('has command tuples', function() { assert.strictEqual(this.pq.cmdTuples(), '1'); }); }); package/test/server-version.js000644 0000001047 3560116604 013570 0ustar00000000 000000 var Libpq = require('../'); var assert = require('assert'); describe('Retrieve server version from connection', function() { it('return version number when connected', function() { var pq = new Libpq(); pq.connectSync(); var version = pq.serverVersion(); assert.equal(typeof version, 'number'); assert(version > 60000); }); it('return zero when not connected', function() { var pq = new Libpq(); var version = pq.serverVersion(); assert.equal(typeof version, 'number'); assert.equal(version, 0); }); });package/test/socket.js000644 0000000570 3560116604 012067 0ustar00000000 000000 var LibPQ = require('../') var helper = require('./helper') var assert = require('assert'); describe('getting socket', function() { helper.setupIntegration(); it('returns -1 when not connected', function() { var pq = new LibPQ(); assert.equal(pq.socket(), -1); }); it('returns value when connected', function() { assert(this.pq.socket() > 0); }); }); package/test/sync-integration.js000644 0000001602 3560116604 014071 0ustar00000000 000000 var PQ = require('../'); var assert = require('assert'); var helper = require('./helper'); describe('low-level query integration tests', function () { helper.setupIntegration(); describe('exec', function () { before(function () { this.pq.exec('SELECT * FROM test_data'); }); it('has correct tuples', function () { assert.strictEqual(this.pq.ntuples(), 3); }); it('has correct field count', function () { assert.strictEqual(this.pq.nfields(), 2); }); it('has correct rows', function () { assert.strictEqual(this.pq.getvalue(0, 0), 'brian'); assert.strictEqual(this.pq.getvalue(1, 1), '30'); assert.strictEqual(this.pq.getvalue(2, 0), ''); assert.strictEqual(this.pq.getisnull(2, 0), false); assert.strictEqual(this.pq.getvalue(2, 1), ''); assert.strictEqual(this.pq.getisnull(2, 1), true); }); }); }); package/test/sync-parameters.js000644 0000002300 3560116604 013705 0ustar00000000 000000 var assert = require('assert'); var helper = require('./helper'); describe('sync query with parameters', function () { helper.setupIntegration(); it('works with single string parameter', function () { var queryText = 'SELECT $1::text as name'; this.pq.execParams(queryText, ['Brian']); assert.strictEqual(this.pq.ntuples(), 1); assert.strictEqual(this.pq.getvalue(0, 0), 'Brian'); }); it('works with a number parameter', function () { var queryText = 'SELECT $1::int as age'; this.pq.execParams(queryText, [32]); assert.strictEqual(this.pq.ntuples(), 1); assert.strictEqual(this.pq.getvalue(0, 0), '32'); }); it('works with multiple parameters', function () { var queryText = 'INSERT INTO test_data(name, age) VALUES($1, $2)'; this.pq.execParams(queryText, ['Barkley', 4]); assert.equal(this.pq.resultErrorMessage(), ''); }); it('throws error when second argument is not an array', function () { var queryText = 'INSERT INTO test_data(name, age) VALUES($1, $2)'; assert.throws( function () { this.pq.execParams(queryText, 'Barkley', 4); assert.equal(this.pq.resultErrorMessage(), ''); }.bind(this) ); }); }); package/test/sync-prepare.js000644 0000002031 3560116604 013201 0ustar00000000 000000 var assert = require('assert'); var helper = require('./helper'); describe('prepare and execPrepared', function () { helper.setupIntegration(); var statementName = 'get-name'; describe('preparing a statement', function () { it('works properly', function () { this.pq.prepare(statementName, 'SELECT $1::text as name', 1); assert(!this.pq.resultErrorMessage()); assert.equal(this.pq.resultStatus(), 'PGRES_COMMAND_OK'); }); }); describe('executing a prepared statement', function () { it('works properly', function () { this.pq.execPrepared(statementName, ['Brian']); assert(!this.pq.resultErrorMessage()); assert.strictEqual(this.pq.ntuples(), 1); assert.strictEqual(this.pq.nfields(), 1); assert.strictEqual(this.pq.getvalue(0, 0), 'Brian'); }); it('throws an error when second argument is not an array', function () { assert.throws( function () { this.pq.execPrepared(statementName, 'Brian'); }.bind(this) ); }); }); }); package/test/sync.js000644 0000003363 3560116604 011556 0ustar00000000 000000 var PQ = require('../') var assert = require('assert'); describe('connecting with bad credentials', function() { it('throws an error', function() { try { new PQ().connectSync('asldkfjlasdf'); } catch(e) { assert.equal(e.toString().indexOf('connection pointer is NULL'), -1) return; } assert.fail('Should have thrown an exception'); }); }); describe('connecting with no credentials', function() { before(function() { this.pq = new PQ(); this.pq.connectSync(); }); it('is connected', function() { assert(this.pq.connected, 'should have connected == true'); }); after(function() { this.pq.finish(); assert(!this.pq.connected); }); }); describe('result checking', function() { before(function() { this.pq = new PQ(); this.pq.connectSync(); }); after(function() { this.pq.finish(); }); it('executes query', function() { this.pq.exec('SELECT NOW() as my_col'); assert.equal(this.pq.resultStatus(), 'PGRES_TUPLES_OK'); }) it('has 1 tuple', function() { assert.equal(this.pq.ntuples(), 1); }); it('has 1 field', function() { assert.strictEqual(this.pq.nfields(), 1); }); it('has column name', function() { assert.equal(this.pq.fname(0), 'my_col'); }); it('has oid type of timestamptz', function() { assert.strictEqual(this.pq.ftype(0), 1184); }); it('has value as a date', function() { var now = new Date(); var val = this.pq.getvalue(0); var date = new Date(Date.parse(val)); assert.equal(date.getFullYear(), now.getFullYear()); assert.equal(date.getMonth(), now.getMonth()); }); it('can manually clear result multiple times', function() { this.pq.clear(); this.pq.clear(); this.pq.clear(); }); }); package/package.json000644 0000001256 3560116604 011552 0ustar00000000 000000 { "name": "libpq", "version": "1.8.12", "description": "Low-level native bindings to PostgreSQL libpq", "main": "index.js", "keywords": [ "postgres", "libpq" ], "repository": { "type": "git", "url": "git://github.com/brianc/node-libpq.git" }, "scripts": { "pretest": "node-gyp rebuild", "test": "node_modules/.bin/mocha" }, "author": "Brian M. Carlson", "license": "MIT", "dependencies": { "bindings": "1.5.0", "nan": "^2.14.0" }, "devDependencies": { "async": "^2.6.2", "buffer-from": "^1.1.1", "lodash": "^4.17.11", "mocha": "5.2.0", "okay": "^1.0.0" }, "prettier": { "singleQuote": true } } package/README.md000644 0000042402 3560116604 010541 0ustar00000000 000000 # node-libpq [![Build Status](https://travis-ci.org/brianc/node-libpq.svg?branch=master)](https://travis-ci.org/brianc/node-libpq) Node native bindings to the PostgreSQL [libpq](http://www.postgresql.org/docs/9.3/interactive/libpq.html) C client library. This module attempts to mirror _as closely as possible_ the C API provided by libpq and provides the absolute minimum level of abstraction. It is intended to be extremely low level and allow you the same access as you would have to libpq directly from C, except in node.js! The obvious trade-off for being "close to the metal" is having to use a very "c style" API in JavaScript. If you have a good understanding of libpq or used it before hopefully the methods within node-libpq will be familiar; otherwise, you should probably spend some time reading [the official libpq C library documentation](http://www.postgresql.org/docs/9.3/interactive/libpq.html) to become a bit familiar. Referencing the libpq documentation directly should also provide you with more insight into the methods here. I will do my best to explain any differences from the C code for each method. I am also building some [higher level abstractions](https://github.com/brianc/node-pg-native) to eventually replace the `pg.native` portion of node-postgres. They should help as reference material. This module relies heavily on [nan](https://github.com/rvagg/nan) and wouldn't really be possible without it. Mucho thanks to the node-nan team. ## install You need libpq installed & the `pg_config` program should be in your path. You also need [node-gyp](https://github.com/TooTallNate/node-gyp) installed. ```bash $ npm install libpq ``` > Note: for Node.js equal or greater to version 10.16.0 you need to have at least `OpenSSL 1.1.1` installed. ## use ```js var Libpq = require('libpq'); var pq = new Libpq(); ``` ## API ### connection functions Libpq provides a few different connection functions, some of which are "not preferred" anymore. I've opted to simplify this interface a bit into a single __async__ and single __sync__ connnection function. The function accepts an connection string formatted as outlined [in this documentation in section 31.1.1](http://www.postgresql.org/docs/9.3/static/libpq-connect.html). If the parameters are not supplied, libpq will automatically use environment variables, a pgpass file, and other options. Consult the libpq documentation for a better rundown of all the ways it tries to determine your connection parameters. I personally __always__ connect with environment variables and skip supplying the optional `connectionParams`. Easier, more 12 factor app-ish, and you never risk hard coding any passwords. YMMV. :smile: ##### `pq.connect([connectionParams:string], callback:function)` Asyncronously attempts to connect to the postgres server. - `connectionParams` is an optional string - `callback` is mandatory. It is called when the connection has successfully been established. __async__ Connects to a PostgreSQL backend server process. This function actually calls the `PQconnectdb` blocking connection method in a background thread within node's internal thread-pool. There is a way to do non-blocking network I/O for some of the connecting with libpq directly, but it still blocks when your local file system looking for config files, SSL certificates, .pgpass file, and doing possible dns resolution. Because of this, the best way to get _fully_ non-blocking is to juse use `libuv_queue_work` and let node do it's magic and so that's what I do. This function _does not block_. ##### `pq.connectSync([connectionParams:string])` Attempts to connect to a PostgreSQL server. __BLOCKS__ until it either succeedes, or fails. If it fails it will throw an exception. - `connectionParams` is an optional string ##### `pq.finish()` Disconnects from the backend and cleans up all memory used by the libpq connection. ### Connection Status Functions ##### `pq.errorMessage():string` Retrieves the last error message from the connection. This is intended to be used after most functions which return an error code to get more detailed error information about the connection. You can also check this _before_ issuing queries to see if your connection has been lost. ##### `pq.socket():int` Returns an int representing the file descriptor for the socket used internally by the connection ### Sync Command Execution Functions ##### `pq.exec(commandText:string)` __sync__ sends a command to the backend and blocks until a result is received. - `commandText` is a required string of the query. ##### `pq.execParams(commandText:string, parameters:array[string])` __snyc__ sends a command and parameters to the backend and blocks until a result is received. - `commandText` is a required string of the query. - `parameters` is a required array of string values corresponding to each parameter in the commandText. ##### `pq.prepare(statementName:string, commandText:string, nParams:int)` __sync__ sends a named statement to the server to be prepared for later execution. blocks until a result from the prepare operation is received. - `statementName` is a required string of name of the statement to prepare. - `commandText` is a required string of the query. - `nParams` is a count of the number of parameters in the commandText. ##### `pq.execPrepared(statementName:string, parameters:array[string])` __sync__ sends a command to the server to execute a previously prepared statement. blocks until the results are returned. - `statementName` is a required string of the name of the prepared statement. - `parameters` are the parameters to pass to the prepared statement. ### Async Command Execution Functions In libpq the async command execution functions _only_ dispatch a request to the backend to run a query. They do not start result fetching on their own. Because libpq is a C api there is a somewhat complicated "dance" to retrieve the result information in a non-blocking way. node-libpq attempts to do as little as possible to abstract over this; therefore, the following functions are only part of the story. For a complete tutorial on how to dispatch & retrieve results from libpq in an async way you can [view the complete approach here](https://github.com/brianc/node-pg-native/blob/master/index.js#L105) ##### `pq.sendQuery(commandText:string):boolean` __async__ sends a query to the server to be processed. - `commandText` is a required string containing the query text. Returns `true` if the command was sent succesfully or `false` if it failed to send. ##### `pq.sendQueryParams(commandText:string, parameters:array[string]):boolean` __async__ sends a query and to the server to be processed. - `commandText` is a required string containing the query text. - `parameters` is an array of parameters as strings used in the parameterized query. Returns `true` if the command was sent succesfully or `false` if it failed to send. ##### `pq.sendPrepare(statementName:string, commandText:string, nParams:int):boolean` __async__ sends a request to the backend to prepare a named statement with the given name. - `statementName` is a required string of name of the statement to prepare. - `commandText` is a required string of the query. - `nParams` is a count of the number of parameters in the commandText. Returns `true` if the command was sent succesfully or `false` if it failed to send. ##### `pq.sendQueryPrepared(statementName:string, parameters:array[string]):boolean` __async__ sends a request to execute a previously prepared statement. - `statementName` is a required string of the name of the prepared statement. - `parameters` are the parameters to pass to the prepared statement. ##### `pq.getResult():boolean` Parses received data from the server into a `PGresult` struct and sets a pointer internally to the connection object to this result. __warning__: this function will __block__ if libpq is waiting on async results to be returned from the server. Call `pq.isBusy()` to determine if this command will block. Returns `true` if libpq was able to read buffered data & parse a result object. Returns `false` if there are no results waiting to be parsed. Generally doing async style queries you'll call this repeadedly until it returns false and then use the result accessor methods to pull results out of the current result set. ### Result accessor functions After a command is run in either sync or async mode & the results have been received, node-libpq stores the results internally and provides you access to the results via the standard libpq methods. The difference here is libpq will return a pointer to a PGresult structure which you access via libpq functions, but node-libpq stores the most recent result within itself and passes the opaque PGresult structure to the libpq methods. This is to avoid passing around a whole bunch of pointers to unmanaged memory and keeps the burden of properly allocating and freeing memory within node-libpq. ##### `pq.resultStatus():string` Returns either `PGRES_COMMAND_OK` or `PGRES_FATAL_ERROR` depending on the status of the last executed command. ##### `pq.resultErrorMessage():string` Retrieves the error message from the result. This will return `null` if the result does not have an error. ##### `pq.resultErrorFields():object` Retrieves detailed error information from the current result object. Very similar to `PQresultErrorField()` except instead of passing a fieldCode and retrieving a single field, retrieves all fields from the error at once on a single object. The object returned is a simple hash, _not_ an instance of an error object. Example: if you wanted to access `PG_DIAG_MESSAGE_DETAIL` you would do the following: ```js console.log(pq.errorFields().messageDetail) ``` ##### `pq.clear()` Manually frees the memory associated with a `PGresult` pointer. Generally this is called for you, but if you absolutely want to free the pointer yourself, you can. ##### `pq.ntuples():int` Retrieve the number of tuples (rows) from the result. ##### `pq.nfields():int` Retrieve the number of fields (columns) from the result. ##### `pq.fname(fieldNumber:int):string` Retrieve the name of the field (column) at the given offset. Offset starts at 0. ##### `pq.ftype(fieldNumber:int):int` Retrieve the `Oid` of the field (column) at the given offset. Offset starts at 0. ##### `pq.getvalue(tupleNumber:int, fieldNumber:int):string` Retrieve the text value at a given tuple (row) and field (column) offset. Both offsets start at 0. A null value is returned as the empty string `''`. ##### `pq.getisnull(tupleNumber:int, fieldNumber:int):boolean` Returns `true` if the value at the given offsets is actually `null`. Otherwise returns `false`. This is because `pq.getvalue()` returns an empty string for both an actual empty string and for a `null` value. Weird, huh? ##### `pq.cmdStatus():string` Returns the status string associated with a result. Something akin to `INSERT 3 0` if you inserted 3 rows. ##### `pq.cmdTuples():string` Returns the number of tuples (rows) affected by the command. Even though this is a number, it is returned as a string to mirror libpq's behavior. ### Async socket access These functions don't have a direct match within libpq. They exist to allow you to monitor the readability or writability of the libpq socket based on your platforms equivilant to `select()`. This allows you to perform async I/O completely from JavaScript. ##### `pq.startReader()` This uses libuv to start a read watcher on the socket open to the backend. As soon as this socket becomes readable the `pq` instance will emit a `readable` event. It is up to you to call `pq.consumeInput()` one or more times to clear this read notification or it will continue to emit read events over and over and over. The exact flow is outlined [here] under the documentation for `PQisBusy`. ##### `pq.stopReader()` Tells libuv to stop the read watcher on the connection socket. ##### `pq.writable(callback:function)` Call this to make sure the socket has flushed all data to the operating system. Once the socket is writable, your callback will be called. Usefully when using `PQsetNonBlocking` and `PQflush` for async writing. ### More async methods These are all documented in detail within the [libpq documentation](http://www.postgresql.org/docs/9.3/static/libpq-async.html) and function almost identically. ##### `pq.consumeInput():boolean` Reads waiting data from the socket. If the socket is not readable and you call this it will __block__ so be careful and only call it within the `readable` callback for the most part. Returns `true` if data was read. Returns `false` if there was an error. You can access error details with `pq.errorMessage()`. ##### `pq.isBusy():boolean` Returns `true` if calling `pq.consumeInput()` would block waiting for more data. Returns `false` if all data has been read from the socket. Once this returns `false` it is safe to call `pq.getResult()` ##### `pq.setNonBlocking(nonBlocking:boolean):boolean` Toggle the socket blocking on _write_. Returns `true` if the socket's state was succesfully toggled. Returns `false` if there was an error. - `nonBlocking` is `true` to set the connection to use non-blocking writes. `false` to use blocking writes. ##### `pq.flush():int` Flushes buffered data to the socket. Returns `1` if socket is not write-ready at which case you should call `pq.writable` with a callback and wait for the socket to be writable and then call `pq.flush()` again. Returns `0` if all data was flushed. Returns `-1` if there was an error. ### listen/notify ##### `pq.notifies():object` Checks for `NOTIFY` messages that have come in. If any have been received they will be in the following format: ```js var msg = { relname: 'name of channel', extra: 'message passed to notify command', be_pid: 130 } ``` ### COPY IN/OUT ##### `pq.putCopyData(buffer:Buffer):int` After issuing a successful command like `COPY table FROM stdin` you can start putting buffers directly into the databse with this function. - `buffer` Is a required node buffer of text data such as `Buffer('column1\tcolumn2\n')` Returns `1` if sent succesfully. Returns `0` if the command would block (only if you have called `pq.setNonBlocking(true)`). Returns `-1` if there was an error sending the command. ##### `pq.putCopyEnd([errorMessage:string])` Signals the backed your copy procedure is complete. If you pass `errorMessage` it will be sent to the backend and effectively cancel the copy operation. - `errorMessage` is an _optional_ string you can pass to cancel the copy operation. Returns `1` if sent succesfully. Returns `0` if the command would block (only if you have called `pq.setNonBlocking(true)`). Returns `-1` if there was an error sending the command. ##### `pq.getCopyData(async:boolean):Buffer or int` After issuing a successfuly command like `COPY table TO stdout` gets copy data from the connection. Returns a node buffer if there is data available. Returns `0` if the copy is still in progress (only if you have called `pq.setNonBlocking(true)`). Returns `-1` if the copy is completed. Returns `-2` if there was an error. - `async` is a boolean. Pass `false` to __block__ waiting for data from the backend. _defaults to `false`_ ### Misc Functions ##### `pq.escapeLiteral(input:string):string` Exact copy of the `PQescapeLiteral` function within libpq. Requires an established connection but does not perform any I/O. ##### `pq.escapeIdentifier(input:string):string` Exact copy of the `PQescapeIdentifier` function within libpq. Requires an established connection but does not perform any I/O. ##### `pq.cancel():true -or- string` Issues a request to cancel the currently executing query _on this instance of libpq_. Returns `true` if the cancel request was sent. Returns a `string` error message if the cancel request failed for any reason. The string will contain the error message provided by libpq. ##### `pq.serverVersion():number` Returns the version of the connected PostgreSQL backend server as a number. ## testing ```sh $ npm test ``` To run the tests you need a PostgreSQL backend reachable by typing `psql` with no connection parameters in your terminal. The tests use [environment variables](http://www.postgresql.org/docs/9.3/static/libpq-envars.html) to connect to the backend. An example of supplying a specific host the tests: ```sh $ PGHOST=blabla.mydatabasehost.com npm test ``` ## license The MIT License (MIT) Copyright (c) 2014 Brian M. Carlson Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. package/test/mocha.opts000644 0000000007 3560116604 012232 0ustar00000000 000000 --bail package/vendor/build.sh000644 0000000241 3560116604 012205 0ustar00000000 000000 #!/bin/sh BUILD_DIR="$(pwd)" source ./vendor/install_openssl.sh 1.1.1b sudo updatedb source ./vendor/install_libpq.sh sudo updatedb sudo ldconfig cd $BUILD_DIR package/vendor/install_libpq.sh000644 0000002125 3560116604 013746 0ustar00000000 000000 #!/bin/bash set -e OPENSSL_DIR="$(pwd)/openssl-1.1.1b" POSTGRES_VERSION="11.3" POSTGRES_DIR="$(pwd)/postgres-${POSTGRES_VERSION}" TMP_DIR="/tmp/postgres" JOBS="-j$(nproc || echo 1)" if [ -d "${TMP_DIR}" ]; then rm -rf "${TMP_DIR}" fi mkdir -p "${TMP_DIR}" curl https://ftp.postgresql.org/pub/source/v${POSTGRES_VERSION}/postgresql-${POSTGRES_VERSION}.tar.gz | \ tar -C "${TMP_DIR}" -xzf - cd "${TMP_DIR}/postgresql-${POSTGRES_VERSION}" if [ -d "${POSTGRES_DIR}" ]; then rm -rf "${POSTGRES_DIR}" fi mkdir -p $POSTGRES_DIR ./configure --prefix=$POSTGRES_DIR --with-openssl --with-includes=${OPENSSL_DIR}/include --with-libraries=${OPENSSL_DIR}/lib --without-readline cd src/interfaces/libpq; make; make install; cd - cd src/bin/pg_config; make install; cd - cd src/backend; make generated-headers; cd - cd src/include; make install; cd - export PATH="${POSTGRES_DIR}/bin:${PATH}" export CFLAGS="-I${POSTGRES_DIR}/include" export LDFLAGS="-L${POSTGRES_DIR}/lib" export LD_LIBRARY_PATH="${POSTGRES_DIR}/lib:$LD_LIBRARY_PATH" export PKG_CONFIG_PATH="${POSTGRES_DIR}/lib/pkgconfig:$PKG_CONFIG_PATH" package/vendor/install_openssl.sh000644 0000001574 3560116604 014331 0ustar00000000 000000 #!/bin/sh if [ ${#} -lt 1 ]; then echo "OpenSSL version required." 1>&2 exit 1 fi OPENSSL_VERSION="${1}" OPENSSL_DIR="$(pwd)/openssl-${OPENSSL_VERSION}" TMP_DIR="/tmp/openssl" JOBS="-j$(nproc)" if [ -d "${TMP_DIR}" ]; then rm -rf "${TMP_DIR}" fi mkdir -p "${TMP_DIR}" curl -s https://www.openssl.org/source/openssl-${OPENSSL_VERSION}.tar.gz | \ tar -C "${TMP_DIR}" -xzf - pushd "${TMP_DIR}/openssl-${OPENSSL_VERSION}" if [ -d "${OPENSSL_DIR}" ]; then rm -rf "${OPENSSL_DIR}" fi ./Configure \ --prefix=${OPENSSL_DIR} \ enable-crypto-mdebug enable-crypto-mdebug-backtrace \ linux-x86_64 make -s $JOBS make install_sw popd export PATH="${OPENSSL_DIR}/bin:${PATH}" export CFLAGS="-I${OPENSSL_DIR}/include" export LDFLAGS="-L${OPENSSL_DIR}/lib" export LD_LIBRARY_PATH="${OPENSSL_DIR}/lib:$LD_LIBRARY_PATH" export PKG_CONFIG_PATH="${OPENSSL_DIR}/lib/pkgconfig:$PKG_CONFIG_PATH" package/.github/workflows/ci.yaml000644 0000001611 3560116604 014133 0ustar00000000 000000 name: Node.js CI on: push: branches: [master] pull_request: branches: [master] jobs: build: runs-on: ubuntu-latest services: postgres: image: postgres:11 env: POSTGRES_USER: postgres POSTGRES_PASSWORD: postgres POSTGRES_DB: ci_db_test ports: - 5432:5432 options: --health-cmd pg_isready --health-interval 10s --health-timeout 5s --health-retries 5 strategy: matrix: node-version: [8.x, 10.x, 12.x, 14.x, 16.x] steps: - uses: actions/checkout@v3 - name: Use Node.js ${{ matrix.node-version }} uses: actions/setup-node@v3 with: node-version: ${{ matrix.node-version }} - run: npm ci - run: npm run build --if-present - run: PGTESTNOSSL=true PGUSER=postgres PGHOST=localhost PGPASSWORD=postgres PGDATABASE=ci_db_test npm test package/.travis.yml000644 0000000577 3560116604 011402 0ustar00000000 000000 language: node_js dist: xenial services: - postgresql sudo: true node_js: - "12" - "10" - "8" - "6" - "4" env: - CC=clang CXX=clang++ npm_config_clang=1 PGUSER=postgres PGDATABASE=postgres PGHOST=localhost before_install: - if [ $TRAVIS_OS_NAME == "linux" ]; then if [[ $(node -v) =~ v[1-9][0-9] ]]; then source ./vendor/build.sh; fi fi