Merge pull request #393 from brianc/issues/324
add support for result rows as arrays
This commit is contained in:
commit
f263fe1b0a
@ -171,6 +171,10 @@ var clientBuilder = function(config) {
|
|||||||
connection._pulseQueryQueue(true);
|
connection._pulseQueryQueue(true);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
connection.on('_rowDescription', function(rowDescription) {
|
||||||
|
connection._activeQuery.handleRowDescription(rowDescription);
|
||||||
|
});
|
||||||
|
|
||||||
//proxy some events to active query
|
//proxy some events to active query
|
||||||
connection.on('_row', function(row) {
|
connection.on('_row', function(row) {
|
||||||
connection._activeQuery.handleRow(row);
|
connection._activeQuery.handleRow(row);
|
||||||
|
@ -21,7 +21,8 @@ var NativeQuery = function(config, values, callback) {
|
|||||||
this.values = c.values;
|
this.values = c.values;
|
||||||
this.callback = c.callback;
|
this.callback = c.callback;
|
||||||
|
|
||||||
this._result = new Result();
|
this._result = new Result(config.rowMode);
|
||||||
|
this._addedFields = false;
|
||||||
//normalize values
|
//normalize values
|
||||||
if(this.values) {
|
if(this.values) {
|
||||||
for(var i = 0, len = this.values.length; i < len; i++) {
|
for(var i = 0, len = this.values.length; i < len; i++) {
|
||||||
@ -33,19 +34,12 @@ var NativeQuery = function(config, values, callback) {
|
|||||||
|
|
||||||
util.inherits(NativeQuery, EventEmitter);
|
util.inherits(NativeQuery, EventEmitter);
|
||||||
|
|
||||||
//maps from native rowdata into api compatible row object
|
NativeQuery.prototype.handleRowDescription = function(rowDescription) {
|
||||||
var mapRowData = function(row) {
|
this._result.addFields(rowDescription);
|
||||||
var result = {};
|
|
||||||
for(var i = 0, len = row.length; i < len; i++) {
|
|
||||||
var item = row[i];
|
|
||||||
result[item.name] = item.value === null ? null :
|
|
||||||
types.getTypeParser(item.type, 'text')(item.value);
|
|
||||||
}
|
|
||||||
return result;
|
|
||||||
};
|
};
|
||||||
|
|
||||||
NativeQuery.prototype.handleRow = function(rowData) {
|
NativeQuery.prototype.handleRow = function(rowData) {
|
||||||
var row = mapRowData(rowData);
|
var row = this._result.parseRow(rowData);
|
||||||
if(this.callback) {
|
if(this.callback) {
|
||||||
this._result.addRow(row);
|
this._result.addRow(row);
|
||||||
}
|
}
|
||||||
|
32
lib/query.js
32
lib/query.js
@ -23,7 +23,7 @@ var Query = function(config, values, callback) {
|
|||||||
this.callback = config.callback;
|
this.callback = config.callback;
|
||||||
this._fieldNames = [];
|
this._fieldNames = [];
|
||||||
this._fieldConverters = [];
|
this._fieldConverters = [];
|
||||||
this._result = new Result();
|
this._result = new Result(config.rowMode);
|
||||||
this.isPreparedStatement = false;
|
this.isPreparedStatement = false;
|
||||||
this._canceledDueToError = false;
|
this._canceledDueToError = false;
|
||||||
EventEmitter.call(this);
|
EventEmitter.call(this);
|
||||||
@ -55,36 +55,16 @@ var noParse = function(val) {
|
|||||||
//message with this query object
|
//message with this query object
|
||||||
//metadata used when parsing row results
|
//metadata used when parsing row results
|
||||||
Query.prototype.handleRowDescription = function(msg) {
|
Query.prototype.handleRowDescription = function(msg) {
|
||||||
this._fieldNames = [];
|
this._result.addFields(msg.fields);
|
||||||
this._fieldConverters = [];
|
|
||||||
var len = msg.fields.length;
|
|
||||||
for(var i = 0; i < len; i++) {
|
|
||||||
var field = msg.fields[i];
|
|
||||||
var format = field.format;
|
|
||||||
this._fieldNames[i] = field.name;
|
|
||||||
this._fieldConverters[i] = Types.getTypeParser(field.dataTypeID, format);
|
|
||||||
this._result.addField(field);
|
|
||||||
}
|
|
||||||
};
|
};
|
||||||
|
|
||||||
Query.prototype.handleDataRow = function(msg) {
|
Query.prototype.handleDataRow = function(msg) {
|
||||||
var self = this;
|
var row = this._result.parseRow(msg.fields);
|
||||||
var row = {};
|
this.emit('row', row, this._result);
|
||||||
for(var i = 0; i < msg.fields.length; i++) {
|
|
||||||
var rawValue = msg.fields[i];
|
|
||||||
if(rawValue === null) {
|
|
||||||
//leave null values alone
|
|
||||||
row[self._fieldNames[i]] = null;
|
|
||||||
} else {
|
|
||||||
//convert value to javascript
|
|
||||||
row[self._fieldNames[i]] = self._fieldConverters[i](rawValue);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
self.emit('row', row, self._result);
|
|
||||||
|
|
||||||
//if there is a callback collect rows
|
//if there is a callback collect rows
|
||||||
if(self.callback) {
|
if(this.callback) {
|
||||||
self._result.addRow(row);
|
this._result.addRow(row);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -1,12 +1,18 @@
|
|||||||
|
var types = require(__dirname + '/types/');
|
||||||
|
|
||||||
//result object returned from query
|
//result object returned from query
|
||||||
//in the 'end' event and also
|
//in the 'end' event and also
|
||||||
//passed as second argument to provided callback
|
//passed as second argument to provided callback
|
||||||
var Result = function() {
|
var Result = function(rowMode) {
|
||||||
this.command = null;
|
this.command = null;
|
||||||
this.rowCount = null;
|
this.rowCount = null;
|
||||||
this.oid = null;
|
this.oid = null;
|
||||||
this.rows = [];
|
this.rows = [];
|
||||||
this.fields = [];
|
this.fields = [];
|
||||||
|
this._parsers = [];
|
||||||
|
if(rowMode == "array") {
|
||||||
|
this.parseRow = this._parseRowAsArray;
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
var matchRegexp = /([A-Za-z]+) ?(\d+ )?(\d+)?/;
|
var matchRegexp = /([A-Za-z]+) ?(\d+ )?(\d+)?/;
|
||||||
@ -34,13 +40,55 @@ Result.prototype.addCommandComplete = function(msg) {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
Result.prototype._parseRowAsArray = function(rowData) {
|
||||||
|
var row = [];
|
||||||
|
for(var i = 0, len = rowData.length; i < len; i++) {
|
||||||
|
var rawValue = rowData[i];
|
||||||
|
if(rawValue !== null) {
|
||||||
|
row.push(this._parsers[i](rawValue));
|
||||||
|
} else {
|
||||||
|
row.push(null);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return row;
|
||||||
|
};
|
||||||
|
|
||||||
|
//rowData is an array of text or binary values
|
||||||
|
//this turns the row into a JavaScript object
|
||||||
|
Result.prototype.parseRow = function(rowData) {
|
||||||
|
var row = {};
|
||||||
|
for(var i = 0, len = rowData.length; i < len; i++) {
|
||||||
|
var rawValue = rowData[i];
|
||||||
|
var field = this.fields[i];
|
||||||
|
var fieldType = field.dataTypeID;
|
||||||
|
var parsedValue = null;
|
||||||
|
if(rawValue !== null) {
|
||||||
|
parsedValue = this._parsers[i](rawValue);
|
||||||
|
}
|
||||||
|
var fieldName = field.name;
|
||||||
|
row[fieldName] = parsedValue;
|
||||||
|
}
|
||||||
|
return row;
|
||||||
|
};
|
||||||
|
|
||||||
Result.prototype.addRow = function(row) {
|
Result.prototype.addRow = function(row) {
|
||||||
this.rows.push(row);
|
this.rows.push(row);
|
||||||
};
|
};
|
||||||
|
|
||||||
//Add a field definition to the result
|
Result.prototype.addFields = function(fieldDescriptions) {
|
||||||
Result.prototype.addField = function(field) {
|
//clears field definitions
|
||||||
this.fields.push(field);
|
//multiple query statements in 1 action can result in multiple sets
|
||||||
|
//of rowDescriptions...eg: 'select NOW(); select 1::int;'
|
||||||
|
//you need to reset the fields
|
||||||
|
if(this.fields.length) {
|
||||||
|
this.fields = [];
|
||||||
|
this._parsers = [];
|
||||||
|
}
|
||||||
|
for(var i = 0; i < fieldDescriptions.length; i++) {
|
||||||
|
var desc = fieldDescriptions[i];
|
||||||
|
this.fields.push(desc);
|
||||||
|
this._parsers.push(types.getTypeParser(desc.dataTypeID, desc.format || 'text'));
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
module.exports = Result;
|
module.exports = Result;
|
||||||
|
@ -61,7 +61,7 @@ public:
|
|||||||
routine_symbol = NODE_PSYMBOL("routine");
|
routine_symbol = NODE_PSYMBOL("routine");
|
||||||
name_symbol = NODE_PSYMBOL("name");
|
name_symbol = NODE_PSYMBOL("name");
|
||||||
value_symbol = NODE_PSYMBOL("value");
|
value_symbol = NODE_PSYMBOL("value");
|
||||||
type_symbol = NODE_PSYMBOL("type");
|
type_symbol = NODE_PSYMBOL("dataTypeID");
|
||||||
channel_symbol = NODE_PSYMBOL("channel");
|
channel_symbol = NODE_PSYMBOL("channel");
|
||||||
payload_symbol = NODE_PSYMBOL("payload");
|
payload_symbol = NODE_PSYMBOL("payload");
|
||||||
command_symbol = NODE_PSYMBOL("command");
|
command_symbol = NODE_PSYMBOL("command");
|
||||||
@ -522,6 +522,33 @@ protected:
|
|||||||
}
|
}
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
//maps the postgres tuple results to v8 objects
|
||||||
|
//and emits row events
|
||||||
|
//TODO look at emitting fewer events because the back & forth between
|
||||||
|
//javascript & c++ might introduce overhead (requires benchmarking)
|
||||||
|
void EmitRowDescription(const PGresult* result)
|
||||||
|
{
|
||||||
|
HandleScope scope;
|
||||||
|
Local<Array> row = Array::New();
|
||||||
|
int fieldCount = PQnfields(result);
|
||||||
|
for(int fieldNumber = 0; fieldNumber < fieldCount; fieldNumber++) {
|
||||||
|
Local<Object> field = Object::New();
|
||||||
|
//name of field
|
||||||
|
char* fieldName = PQfname(result, fieldNumber);
|
||||||
|
field->Set(name_symbol, String::New(fieldName));
|
||||||
|
|
||||||
|
//oid of type of field
|
||||||
|
int fieldType = PQftype(result, fieldNumber);
|
||||||
|
field->Set(type_symbol, Integer::New(fieldType));
|
||||||
|
|
||||||
|
row->Set(Integer::New(fieldNumber), field);
|
||||||
|
}
|
||||||
|
|
||||||
|
Handle<Value> e = (Handle<Value>)row;
|
||||||
|
Emit("_rowDescription", &e);
|
||||||
|
}
|
||||||
|
|
||||||
bool HandleResult(PGresult* result)
|
bool HandleResult(PGresult* result)
|
||||||
{
|
{
|
||||||
TRACE("PQresultStatus");
|
TRACE("PQresultStatus");
|
||||||
@ -529,6 +556,7 @@ protected:
|
|||||||
switch(status) {
|
switch(status) {
|
||||||
case PGRES_TUPLES_OK:
|
case PGRES_TUPLES_OK:
|
||||||
{
|
{
|
||||||
|
EmitRowDescription(result);
|
||||||
HandleTuplesResult(result);
|
HandleTuplesResult(result);
|
||||||
EmitCommandMetaData(result);
|
EmitCommandMetaData(result);
|
||||||
return true;
|
return true;
|
||||||
@ -592,24 +620,14 @@ protected:
|
|||||||
Local<Array> row = Array::New();
|
Local<Array> row = Array::New();
|
||||||
int fieldCount = PQnfields(result);
|
int fieldCount = PQnfields(result);
|
||||||
for(int fieldNumber = 0; fieldNumber < fieldCount; fieldNumber++) {
|
for(int fieldNumber = 0; fieldNumber < fieldCount; fieldNumber++) {
|
||||||
Local<Object> field = Object::New();
|
|
||||||
//name of field
|
|
||||||
char* fieldName = PQfname(result, fieldNumber);
|
|
||||||
field->Set(name_symbol, String::New(fieldName));
|
|
||||||
|
|
||||||
//oid of type of field
|
|
||||||
int fieldType = PQftype(result, fieldNumber);
|
|
||||||
field->Set(type_symbol, Integer::New(fieldType));
|
|
||||||
|
|
||||||
//value of field
|
//value of field
|
||||||
if(PQgetisnull(result, rowNumber, fieldNumber)) {
|
if(PQgetisnull(result, rowNumber, fieldNumber)) {
|
||||||
field->Set(value_symbol, Null());
|
row->Set(Integer::New(fieldNumber), Null());
|
||||||
} else {
|
} else {
|
||||||
char* fieldValue = PQgetvalue(result, rowNumber, fieldNumber);
|
char* fieldValue = PQgetvalue(result, rowNumber, fieldNumber);
|
||||||
field->Set(value_symbol, String::New(fieldValue));
|
row->Set(Integer::New(fieldNumber), String::New(fieldValue));
|
||||||
}
|
}
|
||||||
|
|
||||||
row->Set(Integer::New(fieldNumber), field);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
Handle<Value> e = (Handle<Value>)row;
|
Handle<Value> e = (Handle<Value>)row;
|
||||||
|
33
test/integration/client/results-as-array-tests.js
Normal file
33
test/integration/client/results-as-array-tests.js
Normal file
@ -0,0 +1,33 @@
|
|||||||
|
var util = require('util');
|
||||||
|
var helper = require('./test-helper');
|
||||||
|
|
||||||
|
var Client = helper.Client;
|
||||||
|
|
||||||
|
var conInfo = helper.config;
|
||||||
|
|
||||||
|
test('returns results as array', function() {
|
||||||
|
var client = new Client(conInfo);
|
||||||
|
var checkRow = function(row) {
|
||||||
|
assert(util.isArray(row), 'row should be an array');
|
||||||
|
assert.equal(row.length, 4);
|
||||||
|
assert.equal(row[0].getFullYear(), new Date().getFullYear());
|
||||||
|
assert.strictEqual(row[1], 1);
|
||||||
|
assert.strictEqual(row[2], 'hai');
|
||||||
|
assert.strictEqual(row[3], null);
|
||||||
|
}
|
||||||
|
client.connect(assert.success(function() {
|
||||||
|
var config = {
|
||||||
|
text: 'SELECT NOW(), 1::int, $1::text, null',
|
||||||
|
values: ['hai'],
|
||||||
|
rowMode: 'array'
|
||||||
|
};
|
||||||
|
var query = client.query(config, assert.success(function(result) {
|
||||||
|
assert.equal(result.rows.length, 1);
|
||||||
|
checkRow(result.rows[0]);
|
||||||
|
client.end();
|
||||||
|
}));
|
||||||
|
assert.emits(query, 'row', function(row) {
|
||||||
|
checkRow(row);
|
||||||
|
});
|
||||||
|
}));
|
||||||
|
});
|
37
test/integration/client/row-description-on-results-tests.js
Normal file
37
test/integration/client/row-description-on-results-tests.js
Normal file
@ -0,0 +1,37 @@
|
|||||||
|
var helper = require('./test-helper');
|
||||||
|
|
||||||
|
var Client = helper.Client;
|
||||||
|
|
||||||
|
var conInfo = helper.config;
|
||||||
|
|
||||||
|
var checkResult = function(result) {
|
||||||
|
assert(result.fields);
|
||||||
|
assert.equal(result.fields.length, 3);
|
||||||
|
var fields = result.fields;
|
||||||
|
assert.equal(fields[0].name, 'now');
|
||||||
|
assert.equal(fields[1].name, 'num');
|
||||||
|
assert.equal(fields[2].name, 'texty');
|
||||||
|
assert.equal(fields[0].dataTypeID, 1184);
|
||||||
|
assert.equal(fields[1].dataTypeID, 23);
|
||||||
|
assert.equal(fields[2].dataTypeID, 25);
|
||||||
|
};
|
||||||
|
|
||||||
|
test('row descriptions on result object', function() {
|
||||||
|
var client = new Client(conInfo);
|
||||||
|
client.connect(assert.success(function() {
|
||||||
|
client.query('SELECT NOW() as now, 1::int as num, $1::text as texty', ["hello"], assert.success(function(result) {
|
||||||
|
checkResult(result);
|
||||||
|
client.end();
|
||||||
|
}));
|
||||||
|
}));
|
||||||
|
});
|
||||||
|
|
||||||
|
test('row description on no rows', function() {
|
||||||
|
var client = new Client(conInfo);
|
||||||
|
client.connect(assert.success(function() {
|
||||||
|
client.query('SELECT NOW() as now, 1::int as num, $1::text as texty LIMIT 0', ["hello"], assert.success(function(result) {
|
||||||
|
checkResult(result);
|
||||||
|
client.end();
|
||||||
|
}));
|
||||||
|
}));
|
||||||
|
});
|
Loading…
Reference in New Issue
Block a user