Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add support for notices. #97

Open
wants to merge 1 commit into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
23 changes: 22 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -243,6 +243,27 @@ var msg = {
}
```

##### `pq.on("notify", callback:function)`

Receives `DEBUG`, `LOG`, `INFO`, `NOTICE` and `WARNING` notifications.

- `callback` is mandatory. It is called with an object similar to what is
returned by the (yet undocumented) function `pq.resultErrorFields()`.

The format of the `notify` event payload is as follows:

```js
var notification = {
severity: 'WARNING',
sqlState: '01000',
messagePrimary: 'this is a warning message',
context: 'PL/pgSQL function inline_code_block line 1 at RAISE',
sourceFile: 'pl_exec.c',
sourceLine: '3917',
sourceFunction: 'exec_stmt_raise'
}
```

### COPY IN/OUT

##### `pq.putCopyData(buffer:Buffer):int`
Expand Down Expand Up @@ -296,7 +317,7 @@ Returns the version of the connected PostgreSQL backend server as a number.
$ 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.
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:

Expand Down
54 changes: 52 additions & 2 deletions src/connection.cc
Original file line number Diff line number Diff line change
Expand Up @@ -302,13 +302,17 @@ NAN_METHOD(Connection::ResultErrorMessage) {
info.GetReturnValue().Set(Nan::New<v8::String>(status).ToLocalChecked());
}

# define SET_E(key, name) \
field = PQresultErrorField(self->lastResult, key); \
// set error field from the pg_result "lastResult" parameter
# define SET_ER(key, name, lastResult) \
field = PQresultErrorField(lastResult, key); \
if(field != NULL) { \
Nan::Set(result, \
Nan::New(name).ToLocalChecked(), Nan::New(field).ToLocalChecked()); \
}

// set error field using the default "self->lastResult" pg_result
# define SET_E(key, name) SET_ER(key, name, self->lastResult)

NAN_METHOD(Connection::ResultErrorFields) {
Connection *self = NODE_THIS();

Expand Down Expand Up @@ -672,6 +676,8 @@ bool Connection::ConnectDB(const char* paramString) {
TRACEF("Connection::ConnectDB:Connection parameters: %s\n", paramString);
this->pq = PQconnectdb(paramString);

PQsetNoticeReceiver(this->pq, notice_receiver, this);

ConnStatusType status = PQstatus(this->pq);

if(status != CONNECTION_OK) {
Expand All @@ -696,6 +702,50 @@ char * Connection::ErrorMessage() {
return PQerrorMessage(this->pq);
}

void Connection::notice_receiver(void* connection, const pg_result* noticeResult) {
LOG("Connection::notice_received");

Connection* self = (Connection*) connection;
Nan::HandleScope scope;

v8::Local<v8::Object> result = Nan::New<v8::Object>();
char* field;
SET_ER(PG_DIAG_SEVERITY, "severity", noticeResult);
SET_ER(PG_DIAG_SQLSTATE, "sqlState", noticeResult);
SET_ER(PG_DIAG_MESSAGE_PRIMARY, "messagePrimary", noticeResult);
SET_ER(PG_DIAG_MESSAGE_DETAIL, "messageDetail", noticeResult);
SET_ER(PG_DIAG_MESSAGE_HINT, "messageHint", noticeResult);
SET_ER(PG_DIAG_STATEMENT_POSITION, "statementPosition", noticeResult);
SET_ER(PG_DIAG_INTERNAL_POSITION, "internalPosition", noticeResult);
SET_ER(PG_DIAG_INTERNAL_QUERY, "internalQuery", noticeResult);
SET_ER(PG_DIAG_CONTEXT, "context", noticeResult);
#ifdef MORE_ERROR_FIELDS_SUPPORTED
SET_ER(PG_DIAG_SCHEMA_NAME, "schemaName", noticeResult);
SET_ER(PG_DIAG_TABLE_NAME, "tableName", noticeResult);
SET_ER(PG_DIAG_COLUMN_NAME, "columnName", noticeResult);
SET_ER(PG_DIAG_DATATYPE_NAME, "dataTypeName", noticeResult);
SET_ER(PG_DIAG_CONSTRAINT_NAME, "constraintName", noticeResult);
#endif
SET_ER(PG_DIAG_SOURCE_FILE, "sourceFile", noticeResult);
SET_ER(PG_DIAG_SOURCE_LINE, "sourceLine", noticeResult);
SET_ER(PG_DIAG_SOURCE_FUNCTION, "sourceFunction", noticeResult);

v8::Local<v8::Value> info[2] = {
Nan::New<v8::String>("notice").ToLocalChecked(),
result,
};

TRACE("CALLING EMIT \"notice\"");

Nan::TryCatch tc;
Nan::AsyncResource *async_emit_f = new Nan::AsyncResource("libpq:connection:emit");
async_emit_f->runInAsyncScope(self->handle(), "emit", 2, info);
delete async_emit_f;
if(tc.HasCaught()) {
Nan::FatalException(tc);
}
}

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);
Expand Down
1 change: 1 addition & 0 deletions src/connection.h
Original file line number Diff line number Diff line change
Expand Up @@ -66,6 +66,7 @@ class Connection : public Nan::ObjectWrap {

Connection();

static void notice_receiver(void* connection, const pg_result* result);
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();
Expand Down
78 changes: 78 additions & 0 deletions test/notices.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,78 @@
var PQ = require('../');
var assert = require('assert');

describe('Receive notices', function() {
var notice = null;

before(function() {
this.pq = new PQ();
this.pq.connectSync();
this.pq.exec('SET client_min_messages TO DEBUG');

this.pq.on('notice', function (arg) {
notice = arg;
})
});

this.afterEach(function () {
notice = null;
})

it('works with "debug" messages', function() {
this.pq.exec('DO $$BEGIN RAISE DEBUG \'this is a debug message\'; END$$');

assert.notEqual(notice, null);
assert.equal(notice.severity, 'DEBUG');
assert.equal(notice.messagePrimary, 'this is a debug message');
});

it('works with "log" messages', function() {
this.pq.exec('DO $$BEGIN RAISE LOG \'this is a log message\'; END$$');

assert.notEqual(notice, null);
assert.equal(notice.severity, 'LOG');
assert.equal(notice.messagePrimary, 'this is a log message');
});

it('works with "info" messages', function() {
this.pq.exec('DO $$BEGIN RAISE INFO \'this is an info message\'; END$$');

assert.notEqual(notice, null);
assert.equal(notice.severity, 'INFO');
assert.equal(notice.messagePrimary, 'this is an info message');
});

it('works with "notice" messages', function() {
this.pq.exec('DO $$BEGIN RAISE NOTICE \'this is a notice message\'; END$$');

assert.notEqual(notice, null);
assert.equal(notice.severity, 'NOTICE');
assert.equal(notice.messagePrimary, 'this is a notice message');
});

it('works with "warning" messages', function() {
this.pq.exec('DO $$BEGIN RAISE WARNING \'this is a warning message\'; END$$');

assert.notEqual(notice, null);
assert.equal(notice.severity, 'WARNING');
assert.equal(notice.messagePrimary, 'this is a warning message');
});

it('ignores "exception" messages', function() {
this.pq.exec('DO $$BEGIN RAISE EXCEPTION \'this is an exception message\'; END$$');

assert.equal(notice, null);
});

it('works with internally-generated messages', function() {
this.pq.exec('ROLLBACK');

assert.notEqual(notice, null);
assert.equal(notice.severity, 'WARNING');
assert.equal(typeof notice.messagePrimary, 'string'); // might be localized
});

after(function() {
this.pq.finish();
});
});
Loading