Add field metadata to query result object

Refactored the way rows are built in the native bindings which should
result in a small performance improvement
This commit is contained in:
Brian Carlson 2013-07-08 09:19:30 -05:00
parent 05e9026aea
commit 3f96bbbc5c
4 changed files with 97 additions and 15 deletions

View File

@ -171,6 +171,10 @@ var clientBuilder = function(config) {
connection._pulseQueryQueue(true);
});
connection.on('_rowDescription', function(rowDescription) {
connection._activeQuery.handleRowDescription(rowDescription);
});
//proxy some events to active query
connection.on('_row', function(row) {
connection._activeQuery.handleRow(row);

View File

@ -22,6 +22,7 @@ var NativeQuery = function(config, values, callback) {
this.callback = c.callback;
this._result = new Result();
this._addedFields = false;
//normalize values
if(this.values) {
for(var i = 0, len = this.values.length; i < len; i++) {
@ -39,13 +40,35 @@ var mapRowData = function(row) {
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);
types.getTypeParser(item.dataTypeID, 'text')(item.value);
}
return result;
};
NativeQuery.prototype.handleRowDescription = function(rowDescription) {
//multiple query statements in 1 action can result in multiple sets
//of rowDescriptions...eg: 'select NOW(); select 1::int;'
if(this._result.fields.length) {
this._result.fields = [];
}
for(var i = 0, len = rowDescription.length; i < len; i++) {
this._result.addField(rowDescription[i]);
}
};
NativeQuery.prototype.handleRow = function(rowData) {
var row = mapRowData(rowData);
var row = {};
for(var i = 0, len = rowData.length; i < len; i++) {
var rawValue = rowData[i];
var field = this._result.fields[i];
var fieldType = field.dataTypeID;
var parsedValue = null;
if(rawValue !== null) {
parsedValue = types.getTypeParser(fieldType, 'text')(rawValue);
}
var fieldName = field.name;
row[fieldName] = parsedValue;
}
if(this.callback) {
this._result.addRow(row);
}

View File

@ -61,7 +61,7 @@ public:
routine_symbol = NODE_PSYMBOL("routine");
name_symbol = NODE_PSYMBOL("name");
value_symbol = NODE_PSYMBOL("value");
type_symbol = NODE_PSYMBOL("type");
type_symbol = NODE_PSYMBOL("dataTypeID");
channel_symbol = NODE_PSYMBOL("channel");
payload_symbol = NODE_PSYMBOL("payload");
command_symbol = NODE_PSYMBOL("command");
@ -522,6 +522,33 @@ protected:
}
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)
{
TRACE("PQresultStatus");
@ -529,6 +556,7 @@ protected:
switch(status) {
case PGRES_TUPLES_OK:
{
EmitRowDescription(result);
HandleTuplesResult(result);
EmitCommandMetaData(result);
return true;
@ -592,24 +620,14 @@ protected:
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));
//value of field
if(PQgetisnull(result, rowNumber, fieldNumber)) {
field->Set(value_symbol, Null());
row->Set(Integer::New(fieldNumber), Null());
} else {
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;

View 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();
}));
}));
});