diff --git a/lib/native/index.js b/lib/native/index.js index d6dd4b22..f89740db 100644 --- a/lib/native/index.js +++ b/lib/native/index.js @@ -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); diff --git a/lib/native/query.js b/lib/native/query.js index e358caf5..081c5503 100644 --- a/lib/native/query.js +++ b/lib/native/query.js @@ -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); } diff --git a/src/binding.cc b/src/binding.cc index a1c56853..9e26b266 100644 --- a/src/binding.cc +++ b/src/binding.cc @@ -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 row = Array::New(); + int fieldCount = PQnfields(result); + for(int fieldNumber = 0; fieldNumber < fieldCount; fieldNumber++) { + Local 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 e = (Handle)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 row = Array::New(); int fieldCount = PQnfields(result); for(int fieldNumber = 0; fieldNumber < fieldCount; fieldNumber++) { - Local 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 e = (Handle)row; diff --git a/test/integration/client/row-description-on-results-tests.js b/test/integration/client/row-description-on-results-tests.js new file mode 100644 index 00000000..22c92965 --- /dev/null +++ b/test/integration/client/row-description-on-results-tests.js @@ -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(); + })); + })); +});