From 7622a48d7cc773a07c23f013a03c514f21571868 Mon Sep 17 00:00:00 2001 From: Lukas Lihotzki Date: Wed, 5 Apr 2017 18:42:14 +0200 Subject: [PATCH 1/4] Pass buffers as parameters Without this commit, buffers passed as parameters are processed like null-terminated strings. --- src/connection.cc | 100 ++++++++++++++++++++++++++++++---------------- src/connection.h | 3 +- 2 files changed, 66 insertions(+), 37 deletions(-) diff --git a/src/connection.cc b/src/connection.cc index fc8ae16..0951c5c 100644 --- a/src/connection.cc +++ b/src/connection.cc @@ -104,7 +104,12 @@ NAN_METHOD(Connection::ExecParams) { v8::Local jsParams = v8::Local::Cast(info[1]); int numberOfParams = jsParams->Length(); - char **parameters = NewCStringArray(jsParams); + + char** parameters = new char*[numberOfParams]; + int* lengths = new int[numberOfParams]; + int* formats = new int[numberOfParams]; + + ConvertParameters(jsParams, parameters, lengths, formats); PGresult* result = PQexecParams( self->pq, @@ -112,12 +117,14 @@ NAN_METHOD(Connection::ExecParams) { numberOfParams, NULL, //const Oid* paramTypes[], parameters, //const char* const* paramValues[] - NULL, //const int* paramLengths[] - NULL, //const int* paramFormats[], + lengths, //const int* paramLengths[] + formats, //const int* paramFormats[], 0 //result format of text ); DeleteCStringArray(parameters, numberOfParams); + delete[] lengths; + delete[] formats; self->SetLastResult(result); } @@ -152,19 +159,26 @@ NAN_METHOD(Connection::ExecPrepared) { v8::Local jsParams = v8::Local::Cast(info[1]); int numberOfParams = jsParams->Length(); - char** parameters = NewCStringArray(jsParams); + + char** parameters = new char*[numberOfParams]; + int* lengths = new int[numberOfParams]; + int* formats = new int[numberOfParams]; + + ConvertParameters(jsParams, parameters, lengths, formats); PGresult* result = PQexecPrepared( self->pq, *statementName, numberOfParams, parameters, //const char* const* paramValues[] - NULL, //const int* paramLengths[] - NULL, //const int* paramFormats[], + lengths, //const int* paramLengths[] + formats, //const int* paramFormats[], 0 //result format of text ); DeleteCStringArray(parameters, numberOfParams); + delete[] lengths; + delete[] formats; self->SetLastResult(result); } @@ -356,7 +370,12 @@ NAN_METHOD(Connection::SendQueryParams) { v8::Local jsParams = v8::Local::Cast(info[1]); int numberOfParams = jsParams->Length(); - char** parameters = NewCStringArray(jsParams); + + char** parameters = new char*[numberOfParams]; + int* lengths = new int[numberOfParams]; + int* formats = new int[numberOfParams]; + + ConvertParameters(jsParams, parameters, lengths, formats); int success = PQsendQueryParams( self->pq, @@ -364,12 +383,14 @@ NAN_METHOD(Connection::SendQueryParams) { numberOfParams, NULL, //const Oid* paramTypes[], parameters, //const char* const* paramValues[] - NULL, //const int* paramLengths[] - NULL, //const int* paramFormats[], + lengths, //const int* paramLengths[] + formats, //const int* paramFormats[], 0 //result format of text ); DeleteCStringArray(parameters, numberOfParams); + delete[] lengths; + delete[] formats; info.GetReturnValue().Set(success == 1); } @@ -406,19 +427,25 @@ NAN_METHOD(Connection::SendQueryPrepared) { v8::Local jsParams = v8::Local::Cast(info[1]); int numberOfParams = jsParams->Length(); - char** parameters = NewCStringArray(jsParams); + char** parameters = new char*[numberOfParams]; + int* lengths = new int[numberOfParams]; + int* formats = new int[numberOfParams]; + + ConvertParameters(jsParams, parameters, lengths, formats); int success = PQsendQueryPrepared( self->pq, *statementName, numberOfParams, parameters, //const char* const* paramValues[] - NULL, //const int* paramLengths[] - NULL, //const int* paramFormats[], + lengths, //const int* paramLengths[] + formats, //const int* paramFormats[], 0 //result format of text ); DeleteCStringArray(parameters, numberOfParams); + delete[] lengths; + delete[] formats; info.GetReturnValue().Set(success == 1); } @@ -747,43 +774,46 @@ void Connection::SetLastResult(PGresult* result) { lastResult = result; } -char* Connection::NewCString(v8::Local val) { - Nan::HandleScope scope; - - v8::Local str = Nan::To(val).ToLocalChecked(); - int len = str->Utf8Length() + 1; - char* buffer = new char[len]; - str->WriteUtf8(buffer, len); - return buffer; +void Connection::DeleteCStringArray(char** array, int length) { + for(int i = 0; i < length; i++) { + delete [] array[i]; + } + delete [] array; } -char** Connection::NewCStringArray(v8::Local jsParams) { +void Connection::ConvertParameters(v8::Local jsParams, char** parameters, int* lengths, int* formats) { 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]; + v8::Local bufferObj = val->ToObject(); + if (node::Buffer::HasInstance(val)) { + int len = node::Buffer::Length(bufferObj); + lengths[i] = len; + char* buffer = new char[len]; + memcpy(buffer, node::Buffer::Data(bufferObj), len); + parameters[i] = buffer; + formats[i] = 1; + } else { + //expect every other value to be a string... + //make sure aggresive type checking is done + //on the JavaScript side before calling + v8::Local str = Nan::To(val).ToLocalChecked(); + int len = str->Utf8Length() + 1; + lengths[i] = str->Utf8Length(); + char* buffer = new char[len]; + str->WriteUtf8(buffer, len); + parameters[i] = buffer; + formats[i] = 0; + } } - delete [] array; } void Connection::Emit(const char* message) { diff --git a/src/connection.h b/src/connection.h index d73b5e7..b3265b5 100644 --- a/src/connection.h +++ b/src/connection.h @@ -73,9 +73,8 @@ class Connection : public Nan::ObjectWrap { 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); + static void ConvertParameters(v8::Local jsParams, char** parameters, int* lengths, int* formats); void Emit(const char* message); }; From eb3ad7c23b2a3ab27dc9ef41d8a5d730f4f76180 Mon Sep 17 00:00:00 2001 From: Lukas Lihotzki Date: Thu, 6 Apr 2017 00:57:32 +0200 Subject: [PATCH 2/4] Add buffer parameter test --- test/sync-parameters.js | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/test/sync-parameters.js b/test/sync-parameters.js index 464206a..7aa19d6 100644 --- a/test/sync-parameters.js +++ b/test/sync-parameters.js @@ -23,4 +23,11 @@ describe('sync query with parameters', function() { this.pq.execParams(queryText, ['Barkley', 4]); assert.equal(this.pq.resultErrorMessage(), ''); }); + + it('works with buffer parameter', function() { + var queryText = 'SELECT $1::bytea as bin'; + this.pq.execParams(queryText, [Buffer.from([0x00, 0x2a, 0x80, 0xff])]); + assert.strictEqual(this.pq.ntuples(), 1); + assert.strictEqual(this.pq.getvalue(0, 0), '\\x002a80ff'); + }); }); From 8c1fbe65c00a1ea3e79a5d29ac59f8de6393b669 Mon Sep 17 00:00:00 2001 From: Lukas Lihotzki Date: Thu, 6 Apr 2017 01:11:52 +0200 Subject: [PATCH 3/4] Make buffer parameter test node 4 compatible --- test/sync-parameters.js | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/test/sync-parameters.js b/test/sync-parameters.js index 7aa19d6..fcb255f 100644 --- a/test/sync-parameters.js +++ b/test/sync-parameters.js @@ -26,7 +26,9 @@ describe('sync query with parameters', function() { it('works with buffer parameter', function() { var queryText = 'SELECT $1::bytea as bin'; - this.pq.execParams(queryText, [Buffer.from([0x00, 0x2a, 0x80, 0xff])]); + var octets = [0x00, 0x2a, 0x80, 0xff]; + var buffer = Buffer.from ? Buffer.from(octets) : new Buffer(octets); + this.pq.execParams(queryText, [buffer]); assert.strictEqual(this.pq.ntuples(), 1); assert.strictEqual(this.pq.getvalue(0, 0), '\\x002a80ff'); }); From ab8c80b2cc74de4dfb9c7c54bc4ae9145ebcd6a0 Mon Sep 17 00:00:00 2001 From: Lukas Lihotzki Date: Thu, 6 Apr 2017 01:35:44 +0200 Subject: [PATCH 4/4] Make buffer parameter test node 4 compatible for real --- test/sync-parameters.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/sync-parameters.js b/test/sync-parameters.js index fcb255f..ca5bf87 100644 --- a/test/sync-parameters.js +++ b/test/sync-parameters.js @@ -27,7 +27,7 @@ describe('sync query with parameters', function() { it('works with buffer parameter', function() { var queryText = 'SELECT $1::bytea as bin'; var octets = [0x00, 0x2a, 0x80, 0xff]; - var buffer = Buffer.from ? Buffer.from(octets) : new Buffer(octets); + var buffer = (Number(process.version.match(/^v(\d+)/)[1]) <= 4) ? new Buffer(octets) : Buffer.from(octets); this.pq.execParams(queryText, [buffer]); assert.strictEqual(this.pq.ntuples(), 1); assert.strictEqual(this.pq.getvalue(0, 0), '\\x002a80ff');