Skip to content

Commit f263fe1

Browse files
committed
Merge pull request #393 from /issues/324
add support for result rows as arrays
2 parents 3f4a44e + 145666c commit f263fe1

File tree

7 files changed

+169
-55
lines changed

7 files changed

+169
-55
lines changed

lib/native/index.js

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -171,6 +171,10 @@ var clientBuilder = function(config) {
171171
connection._pulseQueryQueue(true);
172172
});
173173

174+
connection.on('_rowDescription', function(rowDescription) {
175+
connection._activeQuery.handleRowDescription(rowDescription);
176+
});
177+
174178
//proxy some events to active query
175179
connection.on('_row', function(row) {
176180
connection._activeQuery.handleRow(row);

lib/native/query.js

Lines changed: 6 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -16,12 +16,13 @@ var NativeQuery = function(config, values, callback) {
1616

1717
var c = utils.normalizeQueryConfig(config, values, callback);
1818

19-
this.name = c.name;
19+
this.name = c.name;
2020
this.text = c.text;
2121
this.values = c.values;
2222
this.callback = c.callback;
2323

24-
this._result = new Result();
24+
this._result = new Result(config.rowMode);
25+
this._addedFields = false;
2526
//normalize values
2627
if(this.values) {
2728
for(var i = 0, len = this.values.length; i < len; i++) {
@@ -33,19 +34,12 @@ var NativeQuery = function(config, values, callback) {
3334

3435
util.inherits(NativeQuery, EventEmitter);
3536

36-
//maps from native rowdata into api compatible row object
37-
var mapRowData = function(row) {
38-
var result = {};
39-
for(var i = 0, len = row.length; i < len; i++) {
40-
var item = row[i];
41-
result[item.name] = item.value === null ? null :
42-
types.getTypeParser(item.type, 'text')(item.value);
43-
}
44-
return result;
37+
NativeQuery.prototype.handleRowDescription = function(rowDescription) {
38+
this._result.addFields(rowDescription);
4539
};
4640

4741
NativeQuery.prototype.handleRow = function(rowData) {
48-
var row = mapRowData(rowData);
42+
var row = this._result.parseRow(rowData);
4943
if(this.callback) {
5044
this._result.addRow(row);
5145
}

lib/query.js

Lines changed: 6 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,7 @@ var Query = function(config, values, callback) {
2323
this.callback = config.callback;
2424
this._fieldNames = [];
2525
this._fieldConverters = [];
26-
this._result = new Result();
26+
this._result = new Result(config.rowMode);
2727
this.isPreparedStatement = false;
2828
this._canceledDueToError = false;
2929
EventEmitter.call(this);
@@ -55,36 +55,16 @@ var noParse = function(val) {
5555
//message with this query object
5656
//metadata used when parsing row results
5757
Query.prototype.handleRowDescription = function(msg) {
58-
this._fieldNames = [];
59-
this._fieldConverters = [];
60-
var len = msg.fields.length;
61-
for(var i = 0; i < len; i++) {
62-
var field = msg.fields[i];
63-
var format = field.format;
64-
this._fieldNames[i] = field.name;
65-
this._fieldConverters[i] = Types.getTypeParser(field.dataTypeID, format);
66-
this._result.addField(field);
67-
}
58+
this._result.addFields(msg.fields);
6859
};
6960

7061
Query.prototype.handleDataRow = function(msg) {
71-
var self = this;
72-
var row = {};
73-
for(var i = 0; i < msg.fields.length; i++) {
74-
var rawValue = msg.fields[i];
75-
if(rawValue === null) {
76-
//leave null values alone
77-
row[self._fieldNames[i]] = null;
78-
} else {
79-
//convert value to javascript
80-
row[self._fieldNames[i]] = self._fieldConverters[i](rawValue);
81-
}
82-
}
83-
self.emit('row', row, self._result);
62+
var row = this._result.parseRow(msg.fields);
63+
this.emit('row', row, this._result);
8464

8565
//if there is a callback collect rows
86-
if(self.callback) {
87-
self._result.addRow(row);
66+
if(this.callback) {
67+
this._result.addRow(row);
8868
}
8969
};
9070

lib/result.js

Lines changed: 52 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,18 @@
1+
var types = require(__dirname + '/types/');
2+
13
//result object returned from query
24
//in the 'end' event and also
35
//passed as second argument to provided callback
4-
var Result = function() {
6+
var Result = function(rowMode) {
57
this.command = null;
68
this.rowCount = null;
79
this.oid = null;
810
this.rows = [];
911
this.fields = [];
12+
this._parsers = [];
13+
if(rowMode == "array") {
14+
this.parseRow = this._parseRowAsArray;
15+
}
1016
};
1117

1218
var matchRegexp = /([A-Za-z]+) ?(\d+ )?(\d+)?/;
@@ -34,13 +40,55 @@ Result.prototype.addCommandComplete = function(msg) {
3440
}
3541
};
3642

43+
Result.prototype._parseRowAsArray = function(rowData) {
44+
var row = [];
45+
for(var i = 0, len = rowData.length; i < len; i++) {
46+
var rawValue = rowData[i];
47+
if(rawValue !== null) {
48+
row.push(this._parsers[i](rawValue));
49+
} else {
50+
row.push(null);
51+
}
52+
}
53+
return row;
54+
};
55+
56+
//rowData is an array of text or binary values
57+
//this turns the row into a JavaScript object
58+
Result.prototype.parseRow = function(rowData) {
59+
var row = {};
60+
for(var i = 0, len = rowData.length; i < len; i++) {
61+
var rawValue = rowData[i];
62+
var field = this.fields[i];
63+
var fieldType = field.dataTypeID;
64+
var parsedValue = null;
65+
if(rawValue !== null) {
66+
parsedValue = this._parsers[i](rawValue);
67+
}
68+
var fieldName = field.name;
69+
row[fieldName] = parsedValue;
70+
}
71+
return row;
72+
};
73+
3774
Result.prototype.addRow = function(row) {
3875
this.rows.push(row);
3976
};
4077

41-
//Add a field definition to the result
42-
Result.prototype.addField = function(field) {
43-
this.fields.push(field);
78+
Result.prototype.addFields = function(fieldDescriptions) {
79+
//clears field definitions
80+
//multiple query statements in 1 action can result in multiple sets
81+
//of rowDescriptions...eg: 'select NOW(); select 1::int;'
82+
//you need to reset the fields
83+
if(this.fields.length) {
84+
this.fields = [];
85+
this._parsers = [];
86+
}
87+
for(var i = 0; i < fieldDescriptions.length; i++) {
88+
var desc = fieldDescriptions[i];
89+
this.fields.push(desc);
90+
this._parsers.push(types.getTypeParser(desc.dataTypeID, desc.format || 'text'));
91+
}
4492
};
4593

4694
module.exports = Result;

src/binding.cc

Lines changed: 31 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -61,7 +61,7 @@ class Connection : public ObjectWrap {
6161
routine_symbol = NODE_PSYMBOL("routine");
6262
name_symbol = NODE_PSYMBOL("name");
6363
value_symbol = NODE_PSYMBOL("value");
64-
type_symbol = NODE_PSYMBOL("type");
64+
type_symbol = NODE_PSYMBOL("dataTypeID");
6565
channel_symbol = NODE_PSYMBOL("channel");
6666
payload_symbol = NODE_PSYMBOL("payload");
6767
command_symbol = NODE_PSYMBOL("command");
@@ -522,13 +522,41 @@ class Connection : public ObjectWrap {
522522
}
523523
return false;
524524
}
525+
526+
//maps the postgres tuple results to v8 objects
527+
//and emits row events
528+
//TODO look at emitting fewer events because the back & forth between
529+
//javascript & c++ might introduce overhead (requires benchmarking)
530+
void EmitRowDescription(const PGresult* result)
531+
{
532+
HandleScope scope;
533+
Local<Array> row = Array::New();
534+
int fieldCount = PQnfields(result);
535+
for(int fieldNumber = 0; fieldNumber < fieldCount; fieldNumber++) {
536+
Local<Object> field = Object::New();
537+
//name of field
538+
char* fieldName = PQfname(result, fieldNumber);
539+
field->Set(name_symbol, String::New(fieldName));
540+
541+
//oid of type of field
542+
int fieldType = PQftype(result, fieldNumber);
543+
field->Set(type_symbol, Integer::New(fieldType));
544+
545+
row->Set(Integer::New(fieldNumber), field);
546+
}
547+
548+
Handle<Value> e = (Handle<Value>)row;
549+
Emit("_rowDescription", &e);
550+
}
551+
525552
bool HandleResult(PGresult* result)
526553
{
527554
TRACE("PQresultStatus");
528555
ExecStatusType status = PQresultStatus(result);
529556
switch(status) {
530557
case PGRES_TUPLES_OK:
531558
{
559+
EmitRowDescription(result);
532560
HandleTuplesResult(result);
533561
EmitCommandMetaData(result);
534562
return true;
@@ -592,24 +620,14 @@ class Connection : public ObjectWrap {
592620
Local<Array> row = Array::New();
593621
int fieldCount = PQnfields(result);
594622
for(int fieldNumber = 0; fieldNumber < fieldCount; fieldNumber++) {
595-
Local<Object> field = Object::New();
596-
//name of field
597-
char* fieldName = PQfname(result, fieldNumber);
598-
field->Set(name_symbol, String::New(fieldName));
599-
600-
//oid of type of field
601-
int fieldType = PQftype(result, fieldNumber);
602-
field->Set(type_symbol, Integer::New(fieldType));
603623

604624
//value of field
605625
if(PQgetisnull(result, rowNumber, fieldNumber)) {
606-
field->Set(value_symbol, Null());
626+
row->Set(Integer::New(fieldNumber), Null());
607627
} else {
608628
char* fieldValue = PQgetvalue(result, rowNumber, fieldNumber);
609-
field->Set(value_symbol, String::New(fieldValue));
629+
row->Set(Integer::New(fieldNumber), String::New(fieldValue));
610630
}
611-
612-
row->Set(Integer::New(fieldNumber), field);
613631
}
614632

615633
Handle<Value> e = (Handle<Value>)row;
Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
var util = require('util');
2+
var helper = require('./test-helper');
3+
4+
var Client = helper.Client;
5+
6+
var conInfo = helper.config;
7+
8+
test('returns results as array', function() {
9+
var client = new Client(conInfo);
10+
var checkRow = function(row) {
11+
assert(util.isArray(row), 'row should be an array');
12+
assert.equal(row.length, 4);
13+
assert.equal(row[0].getFullYear(), new Date().getFullYear());
14+
assert.strictEqual(row[1], 1);
15+
assert.strictEqual(row[2], 'hai');
16+
assert.strictEqual(row[3], null);
17+
}
18+
client.connect(assert.success(function() {
19+
var config = {
20+
text: 'SELECT NOW(), 1::int, $1::text, null',
21+
values: ['hai'],
22+
rowMode: 'array'
23+
};
24+
var query = client.query(config, assert.success(function(result) {
25+
assert.equal(result.rows.length, 1);
26+
checkRow(result.rows[0]);
27+
client.end();
28+
}));
29+
assert.emits(query, 'row', function(row) {
30+
checkRow(row);
31+
});
32+
}));
33+
});
Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
var helper = require('./test-helper');
2+
3+
var Client = helper.Client;
4+
5+
var conInfo = helper.config;
6+
7+
var checkResult = function(result) {
8+
assert(result.fields);
9+
assert.equal(result.fields.length, 3);
10+
var fields = result.fields;
11+
assert.equal(fields[0].name, 'now');
12+
assert.equal(fields[1].name, 'num');
13+
assert.equal(fields[2].name, 'texty');
14+
assert.equal(fields[0].dataTypeID, 1184);
15+
assert.equal(fields[1].dataTypeID, 23);
16+
assert.equal(fields[2].dataTypeID, 25);
17+
};
18+
19+
test('row descriptions on result object', function() {
20+
var client = new Client(conInfo);
21+
client.connect(assert.success(function() {
22+
client.query('SELECT NOW() as now, 1::int as num, $1::text as texty', ["hello"], assert.success(function(result) {
23+
checkResult(result);
24+
client.end();
25+
}));
26+
}));
27+
});
28+
29+
test('row description on no rows', function() {
30+
var client = new Client(conInfo);
31+
client.connect(assert.success(function() {
32+
client.query('SELECT NOW() as now, 1::int as num, $1::text as texty LIMIT 0', ["hello"], assert.success(function(result) {
33+
checkResult(result);
34+
client.end();
35+
}));
36+
}));
37+
});

0 commit comments

Comments
 (0)