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

Pass paramFormats and paramLengths onto libpq #56

Open
wants to merge 3 commits 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
32 changes: 28 additions & 4 deletions index.js
Original file line number Diff line number Diff line change
Expand Up @@ -91,7 +91,13 @@ PQ.prototype.execParams = function(commandText, parameters) {
if(!parameters) {
parameters = [];
}
this.$execParams(commandText, parameters);
var paramFormats = parameters.map(function(value) {
return value instanceof Buffer ? 1 : 0;
});
var paramLengths = parameters.map(function(value) {
return value instanceof Buffer ? value.length : 0;
});
this.$execParams(commandText, parameters, paramFormats, paramLengths);
};

//SYNC prepares a named query and stores the result
Expand Down Expand Up @@ -123,7 +129,13 @@ PQ.prototype.execPrepared = function(statementName, parameters) {
if(!parameters) {
parameters = [];
}
this.$execPrepared(statementName, parameters);
var paramFormats = parameters.map(function(value) {
return value instanceof Buffer ? 1 : 0;
});
var paramLengths = parameters.map(function(value) {
return value instanceof Buffer ? value.length : 0;
});
this.$execPrepared(statementName, parameters, paramFormats, paramLengths);
};

//send a command to begin executing a query in async mode
Expand All @@ -144,7 +156,13 @@ PQ.prototype.sendQueryParams = function(commandText, parameters) {
if(!parameters) {
parameters = [];
}
return this.$sendQueryParams(commandText, parameters);
var paramFormats = parameters.map(function(value) {
return value instanceof Buffer ? 1 : 0;
});
var paramLengths = parameters.map(function(value) {
return value instanceof Buffer ? value.length : 0;
});
return this.$sendQueryParams(commandText, parameters, paramFormats, paramLengths);
};

//send a command to prepare a named query in async mode
Expand All @@ -170,7 +188,13 @@ PQ.prototype.sendQueryPrepared = function(statementName, parameters) {
if(!parameters) {
parameters = [];
}
return this.$sendQueryPrepared(statementName, parameters);
var paramFormats = parameters.map(function(value) {
return value instanceof Buffer ? 1 : 0;
});
var paramLengths = parameters.map(function(value) {
return value instanceof Buffer ? value.length : 0;
});
return this.$sendQueryPrepared(statementName, parameters, paramFormats, paramLengths);
};

//'pops' a result out of the buffered
Expand Down
67 changes: 57 additions & 10 deletions src/connection.cc
Original file line number Diff line number Diff line change
Expand Up @@ -102,22 +102,29 @@ NAN_METHOD(Connection::ExecParams) {
TRACEF("Connection::Exec: %s\n", *commandText);

v8::Local<v8::Array> jsParams = v8::Local<v8::Array>::Cast(info[1]);
v8::Local<v8::Array> jsParamFormats = v8::Local<v8::Array>::Cast(info[2]);
v8::Local<v8::Array> jsParamLengths = v8::Local<v8::Array>::Cast(info[3]);

int numberOfParams = jsParams->Length();

char **parameters = NewCStringArray(jsParams);
int *paramFormats = NewCIntArray(jsParamFormats);
int *paramLengths = NewCIntArray(jsParamLengths);

PGresult* result = PQexecParams(
self->pq,
*commandText,
numberOfParams,
NULL, //const Oid* paramTypes[],
parameters, //const char* const* paramValues[]
NULL, //const int* paramLengths[]
NULL, //const int* paramFormats[],
paramLengths, //const int* paramLengths[]
paramFormats, //const int* paramFormats[],
0 //result format of text
);

DeleteCStringArray(parameters, numberOfParams);
delete [] paramFormats;
delete [] paramLengths;

self->SetLastResult(result);
}
Expand Down Expand Up @@ -150,21 +157,28 @@ NAN_METHOD(Connection::ExecPrepared) {
TRACEF("Connection::ExecPrepared: %s\n", *statementName);

v8::Local<v8::Array> jsParams = v8::Local<v8::Array>::Cast(info[1]);
v8::Local<v8::Array> jsParamFormats = v8::Local<v8::Array>::Cast(info[2]);
v8::Local<v8::Array> jsParamLengths = v8::Local<v8::Array>::Cast(info[3]);

int numberOfParams = jsParams->Length();
char** parameters = NewCStringArray(jsParams);

char **parameters = NewCStringArray(jsParams);
int *paramFormats = NewCIntArray(jsParamFormats);
int *paramLengths = NewCIntArray(jsParamLengths);

PGresult* result = PQexecPrepared(
self->pq,
*statementName,
numberOfParams,
parameters, //const char* const* paramValues[]
NULL, //const int* paramLengths[]
NULL, //const int* paramFormats[],
paramLengths, //const int* paramLengths[]
paramFormats, //const int* paramFormats[],
0 //result format of text
);

DeleteCStringArray(parameters, numberOfParams);
delete [] paramFormats;
delete [] paramLengths;

self->SetLastResult(result);
}
Expand Down Expand Up @@ -354,22 +368,29 @@ NAN_METHOD(Connection::SendQueryParams) {
TRACEF("Connection::SendQueryParams: %s\n", *commandText);

v8::Local<v8::Array> jsParams = v8::Local<v8::Array>::Cast(info[1]);
v8::Local<v8::Array> jsParamFormats = v8::Local<v8::Array>::Cast(info[2]);
v8::Local<v8::Array> jsParamLengths = v8::Local<v8::Array>::Cast(info[3]);

int numberOfParams = jsParams->Length();

char** parameters = NewCStringArray(jsParams);
int *paramFormats = NewCIntArray(jsParamFormats);
int *paramLengths = NewCIntArray(jsParamLengths);

int success = PQsendQueryParams(
self->pq,
*commandText,
numberOfParams,
NULL, //const Oid* paramTypes[],
parameters, //const char* const* paramValues[]
NULL, //const int* paramLengths[]
NULL, //const int* paramFormats[],
paramLengths, //const int* paramLengths[]
paramFormats, //const int* paramFormats[],
0 //result format of text
);

DeleteCStringArray(parameters, numberOfParams);
delete [] paramFormats;
delete [] paramLengths;

info.GetReturnValue().Set(success == 1);
}
Expand Down Expand Up @@ -404,21 +425,28 @@ NAN_METHOD(Connection::SendQueryPrepared) {
TRACEF("Connection::SendQueryPrepared: %s\n", *statementName);

v8::Local<v8::Array> jsParams = v8::Local<v8::Array>::Cast(info[1]);
v8::Local<v8::Array> jsParamFormats = v8::Local<v8::Array>::Cast(info[2]);
v8::Local<v8::Array> jsParamLengths = v8::Local<v8::Array>::Cast(info[3]);

int numberOfParams = jsParams->Length();
char** parameters = NewCStringArray(jsParams);

char **parameters = NewCStringArray(jsParams);
int *paramFormats = NewCIntArray(jsParamFormats);
int *paramLengths = NewCIntArray(jsParamLengths);

int success = PQsendQueryPrepared(
self->pq,
*statementName,
numberOfParams,
parameters, //const char* const* paramValues[]
NULL, //const int* paramLengths[]
NULL, //const int* paramFormats[],
paramLengths, //const int* paramLengths[]
paramFormats, //const int* paramFormats[],
0 //result format of text
);

DeleteCStringArray(parameters, numberOfParams);
delete [] paramFormats;
delete [] paramLengths;

info.GetReturnValue().Set(success == 1);
}
Expand Down Expand Up @@ -786,6 +814,25 @@ void Connection::DeleteCStringArray(char** array, int length) {
delete [] array;
}

int* Connection::NewCIntArray(v8::Local<v8::Array> jsParams) {
Nan::HandleScope scope;

int len = jsParams->Length();

int* array = new int[len];

for(int i = 0; i < len; i++) {
v8::Local<v8::Value> val = Nan::Get(jsParams, i).ToLocalChecked();
if(val->IsNull()) {
array[i] = 0;
continue;
}
array[i] = Nan::To<int>(val).FromJust();
}

return array;
}

void Connection::Emit(const char* message) {
Nan::HandleScope scope;

Expand Down
1 change: 1 addition & 0 deletions src/connection.h
Original file line number Diff line number Diff line change
Expand Up @@ -76,6 +76,7 @@ class Connection : public Nan::ObjectWrap {
static char* NewCString(v8::Local<v8::Value> val);
static char** NewCStringArray(v8::Local<v8::Array> jsParams);
static void DeleteCStringArray(char** array, int length);
static int* NewCIntArray(v8::Local<v8::Array> jsParams);
void Emit(const char* message);
};

Expand Down
60 changes: 60 additions & 0 deletions test/async-socket.js
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,24 @@ describe('async simple query', function() {
})
});

it('dispatches query with binary parameter', function(done) {
var pq = this.pq;
var string = 'fo\\o';
var buffer = helper.createBuffer(string, 'utf8');
var success = pq.sendQueryParams('SELECT $1::bytea as value', [buffer]);
assert(success, pq.errorMessage());
assert.strictEqual(pq.flush(), 0, 'Should have flushed query text & parameters');
consume(pq, function() {
assert.ifError(pq.errorMessage());
assert(pq.getResult());
assert.strictEqual(pq.getResult(), false);
assert.strictEqual(pq.ntuples(), 1);
var value = helper.parseHexOutput(pq.getvalue(0, 0));
assert.equal(value, string);
done();
})
});

it('dispatches named query', function(done) {
var pq = this.pq;
var statementName = 'async-get-name';
Expand Down Expand Up @@ -92,4 +110,46 @@ describe('async simple query', function() {
});
});
});

it('dispatches named query with binary parameter', function(done) {
var pq = this.pq;
var statementName = 'async-get-binary-param';
var string = 'fo\\o';
var buffer = helper.createBuffer(string, 'utf8');
var success = pq.sendPrepare(statementName, 'SELECT $1::bytea as value', 1);
assert(success, pq.errorMessage());
assert.strictEqual(pq.flush(), 0, 'Should have flushed query text');
consume(pq, function() {
assert.ifError(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, [buffer]);
assert(success, pq.errorMessage());
assert.strictEqual(pq.flush(), 0, 'Should have flushed parameters');
consume(pq, function() {
assert.ifError(pq.errorMessage());

//consume the result of the query execution
assert(pq.getResult());
assert.equal(pq.ntuples(), 1);
var value = helper.parseHexOutput(pq.getvalue(0, 0));
assert.equal(value, string);

//call 'getResult' again to ensure we're finished
assert.strictEqual(pq.getResult(), false);
done();
});
});
});
});
15 changes: 15 additions & 0 deletions test/helper.js
Original file line number Diff line number Diff line change
Expand Up @@ -17,5 +17,20 @@ module.exports = {
after(function() {
this.pq.finish();
});
},
parseHexOutput: function(hexx) {
var hex = hexx.toString().substring(2); // remove leading \x
var str = '';
for (var i = 0; i < hex.length; i += 2) {
str += String.fromCharCode(parseInt(hex.substr(i, 2), 16));
}
return str;
},
createBuffer: function(string, encoding) {
if (Number(process.version.match(/^v(\d+)/)[1]) <= 4) {
return new Buffer(string, encoding);
} else {
return Buffer.from(string, encoding);
}
}
};
20 changes: 20 additions & 0 deletions test/sync-parameters.js
Original file line number Diff line number Diff line change
Expand Up @@ -23,4 +23,24 @@ describe('sync query with parameters', function() {
this.pq.execParams(queryText, ['Barkley', 4]);
assert.equal(this.pq.resultErrorMessage(), '');
});

it('works with non-escaped binary parameter', function() {
var queryText = 'SELECT $1::bytea as value';
var string = 'foo';
var buffer = helper.createBuffer(string, 'utf8');
this.pq.execParams(queryText, [buffer]);
assert.strictEqual(this.pq.ntuples(), 1);
var value = helper.parseHexOutput(this.pq.getvalue(0, 0));
assert.strictEqual(value, string);
});

it('works with escaped binary parameter', function() {
var queryText = 'SELECT $1::bytea as value';
var string = 'fo\\o';
var buffer = helper.createBuffer(string, 'utf8');
this.pq.execParams(queryText, [buffer]);
assert.strictEqual(this.pq.ntuples(), 1);
var value = helper.parseHexOutput(this.pq.getvalue(0, 0));
assert.strictEqual(value, string);
});
});
22 changes: 22 additions & 0 deletions test/sync-prepare.js
Original file line number Diff line number Diff line change
Expand Up @@ -25,3 +25,25 @@ describe('prepare and execPrepared', function() {
});
});
});

describe('prepare and execPrepared with binary parameter', function() {

helper.setupIntegration();

var statementName = 'get-binary-param';

it('works properly', function() {
this.pq.prepare(statementName, 'SELECT $1::bytea as value', 1);
assert.ifError(this.pq.resultErrorMessage());
assert.equal(this.pq.resultStatus(), 'PGRES_COMMAND_OK');

var string = 'fo\\o';
var buffer = helper.createBuffer(string, 'utf8');
this.pq.execPrepared(statementName, [buffer]);
assert.ifError(this.pq.resultErrorMessage());
assert.strictEqual(this.pq.ntuples(), 1)
assert.strictEqual(this.pq.nfields(), 1);
var value = helper.parseHexOutput(this.pq.getvalue(0, 0));
assert.strictEqual(value, string);
});
});