Skip to content

Commit 1e61247

Browse files
committed
conversion pg text to binary protocol started
1 parent f5fce35 commit 1e61247

16 files changed

+388
-72
lines changed

lib/connection.js

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -148,7 +148,8 @@ p.bind = function(config) {
148148
buffer.addString(val);
149149
}
150150
}
151-
buffer.addInt16(0); //no format codes, use text
151+
buffer.addInt16(1); // format codes to use binary
152+
buffer.addInt16(1);
152153
//0x42 = 'B'
153154
this.send(0x42, buffer.flush());
154155
};
@@ -365,7 +366,7 @@ p.parseD = function(msg) {
365366
var fields = [];
366367
for(var i = 0; i < fieldCount; i++) {
367368
var length = this.parseInt32();
368-
fields[i] = (length === -1 ? null : this.readString(length))
369+
fields[i] = (length === -1 ? null : this.readBytes(length))
369370
};
370371
msg.fieldCount = fieldCount;
371372
msg.fields = fields;
@@ -434,6 +435,10 @@ p.readString = function(length) {
434435
return this.buffer.toString(this.encoding, this.offset, (this.offset += length));
435436
};
436437

438+
p.readBytes = function(length) {
439+
return this.buffer.slice(this.offset, this.offset += length);
440+
};
441+
437442
p.parseCString = function() {
438443
var start = this.offset;
439444
while(this.buffer[this.offset++]) { };

lib/query.js

Lines changed: 124 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,7 @@ p.submit = function(connection) {
3737
var names = [];
3838
var rows = [];
3939
var handleRowDescription = function(msg) {
40+
console.log(JSON.stringify(msg));
4041
for(var i = 0; i < msg.fields.length; i++) {
4142
converters[i] = dataTypeParsers[msg.fields[i].dataTypeID] || noParse;
4243
names[i] = msg.fields[i].name;
@@ -47,6 +48,7 @@ p.submit = function(connection) {
4748
for(var i = 0; i < msg.fields.length; i++) {
4849
var rawValue = msg.fields[i];
4950
result[names[i]] = rawValue === null ? null : converters[i](rawValue);
51+
console.log(names[i] + ": " + result[names[i]]);
5052
}
5153
self.emit('row', result);
5254

@@ -206,22 +208,135 @@ var dateParser = function(isoDate) {
206208
return date;
207209
};
208210

211+
function shl(a,b) {
212+
// Copyright (c) 1996 Henri Torgemane. All Rights Reserved.
213+
// fix for crappy <<
214+
for (var i=0;i<b;i++) {
215+
a=a%0x80000000;
216+
if (a&0x40000000==0x40000000)
217+
{
218+
a-=0x40000000;
219+
a*=2;
220+
a+=0x80000000;
221+
} else
222+
a*=2;
223+
};
224+
225+
return a;
226+
}
227+
228+
var parseFloat = function(data, precisionBits, exponentBits) {
229+
var bias = Math.pow(2, exponentBits - 1) - 1;
230+
var sign = parseBits(data, 1);
231+
var exponent = parseBits(data, exponentBits, 1);
232+
233+
if (exponent == 0)
234+
return 0;
235+
236+
// parse mantissa
237+
var precisionBitsCounter = 1;
238+
var parsePrecisionBits = function(lastValue, newValue, bits) {
239+
if (lastValue == 0) {
240+
lastValue = 1;
241+
}
242+
243+
for (var i = 1; i <= bits; i++) {
244+
precisionBitsCounter /= 2;
245+
if ((newValue & (0x1 << (bits - i))) > 0) {
246+
lastValue += precisionBitsCounter;
247+
}
248+
}
249+
250+
return lastValue;
251+
};
252+
253+
var mantissa = parseBits(data, precisionBits, exponentBits + 1, parsePrecisionBits);
254+
255+
// special cases
256+
if (exponent == (Math.pow(2, exponentBits + 1) - 1)) {
257+
if (mantissa == 0) {
258+
return (sign == 0) ? Infinity : -Infinity;
259+
}
260+
261+
return NaN;
262+
}
263+
264+
// normale number
265+
return ((sign == 0) ? 1 : -1) * Math.pow(2, exponent - bias) * mantissa;
266+
};
267+
268+
var parseBits = function(data, bits, offset, callback) {
269+
offset = offset || 0;
270+
callback = callback || function(lastValue, newValue, bits) { return (lastValue * Math.pow(2, bits)) + newValue; };
271+
var offsetBytes = offset >> 3;
272+
273+
// read first (maybe partial) byte
274+
var mask = 0xff;
275+
var firstBits = 8 - (offset % 8);
276+
if (bits < firstBits) {
277+
mask = (0xff << (8 - bits)) & 0xff;
278+
firstBits = bits;
279+
}
280+
281+
if (offset) {
282+
mask = mask >> (offset % 8);
283+
}
284+
var result = callback(0, data[offsetBytes] & mask, firstBits);
285+
286+
// read bytes
287+
var bytes = (bits + offset) >> 3;
288+
for (var i = offsetBytes + 1; i < bytes; i++) {
289+
result = callback(result, data[i], 8);
290+
}
291+
292+
// bits to read, that are not a complete byte
293+
var lastBits = (bits + offset) % 8;
294+
if (lastBits > 0) {
295+
result = callback(result, data[bytes] >> (8 - lastBits), lastBits);
296+
}
297+
298+
return result;
299+
}
300+
301+
var parseBinaryInt64 = function(value) {
302+
return parseBits(value, 64);
303+
}
304+
305+
var parseBinaryInt32 = function(value) {
306+
return parseBits(value, 32);
307+
}
308+
309+
var parseBinaryInt16 = function(value) {
310+
return parseBits(value, 16);
311+
}
312+
313+
var parseBinaryFloat32 = function(value) {
314+
return parseFloat(value, 23, 8);
315+
}
316+
317+
var parseBinaryFloat64 = function(value) {
318+
return parseFloat(value, 52, 11);
319+
}
320+
209321
// To help we test dateParser
210322
Query.dateParser = dateParser;
211323

212324
var dataTypeParsers = {
213-
20: parseInt,
214-
21: parseInt,
215-
23: parseInt,
216-
26: parseInt,
217-
1700: parseFloat,
218-
700: parseFloat,
219-
701: parseFloat,
220325
16: function(dbVal) { //boolean
221-
return dbVal === 't';
326+
console.log(JSON.stringify(dbVal));
327+
return value[0] == 1;
222328
},
223-
1114: dateParser,
329+
330+
20: parseBinaryInt64,
331+
21: parseBinaryInt16,
332+
23: parseBinaryInt32,
333+
26: parseBinaryInt64,
334+
700: parseBinaryFloat32,
335+
701: parseBinaryFloat64,
336+
// 1009: arrayParser,
337+
1114: parseBinaryInt64, // TOFIX: dateParser,
224338
1184: dateParser
339+
// 1700: parseFloat,
225340
};
226341

227342

lib/result.js

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
//result object returned from query
2+
//in the 'end' event and also
3+
//passed as second argument to provided callback
4+
var Result = function() {
5+
this.rows = [];
6+
};
7+
8+
var p = Result.prototype;
9+
10+
11+
var matchRegexp = /([A-Za-z]+) (\d+ )?(\d+)?/
12+
13+
//adds a command complete message
14+
p.addCommandComplete = function(msg) {
15+
var match = matchRegexp.exec(msg.text);
16+
if(match) {
17+
this.command = match[1];
18+
//match 3 will only be existing on insert commands
19+
if(match[3]) {
20+
this.rowCount = parseInt(match[3]);
21+
this.oid = parseInt(match[2]);
22+
} else {
23+
this.rowCount = parseInt(match[2]);
24+
}
25+
}
26+
};
27+
28+
p.addRow = function(row) {
29+
this.rows.push(row);
30+
};
31+
32+
module.exports = Result;

lib/writer.js

Lines changed: 25 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -1,25 +1,18 @@
11
var Writer = function(size) {
22
this.size = size || 1024;
3-
this.buffer = new Buffer(this.size);
4-
this.offset = 0;
3+
this.buffer = new Buffer(this.size + 5);
4+
this.offset = 5;
55
};
66

77
var p = Writer.prototype;
88

9-
p._remaining = function() {
10-
return this.buffer.length - this.offset;
11-
}
12-
13-
p._resize = function() {
14-
var oldBuffer = this.buffer;
15-
this.buffer = Buffer(oldBuffer.length + this.size);
16-
oldBuffer.copy(this.buffer);
17-
}
18-
199
//resizes internal buffer if not enough size left
2010
p._ensure = function(size) {
21-
if(this._remaining() < size) {
22-
this._resize()
11+
var remaining = this.buffer.length - this.offset;
12+
if(remaining < size) {
13+
var oldBuffer = this.buffer;
14+
this.buffer = Buffer(oldBuffer.length + size);
15+
oldBuffer.copy(this.buffer);
2316
}
2417
}
2518

@@ -46,7 +39,7 @@ p.addCString = function(string) {
4639
this.buffer.write(string, this.offset);
4740
this.offset += len;
4841
this.buffer[this.offset] = 0; //add null terminator
49-
return this;
42+
return this;
5043
}
5144

5245
p.addChar = function(char) {
@@ -56,10 +49,6 @@ p.addChar = function(char) {
5649
return this;
5750
}
5851

59-
p.join = function() {
60-
return this.buffer.slice(0, this.offset);
61-
}
62-
6352
p.addString = function(string) {
6453
var string = string || "";
6554
var len = Buffer.byteLength(string);
@@ -70,7 +59,7 @@ p.addString = function(string) {
7059
}
7160

7261
p.getByteLength = function() {
73-
return this.offset;
62+
return this.offset - 5;
7463
}
7564

7665
p.add = function(otherBuffer) {
@@ -81,11 +70,24 @@ p.add = function(otherBuffer) {
8170
}
8271

8372
p.clear = function() {
84-
this.offset=0;
73+
this.offset=5;
74+
}
75+
76+
p.join = function(code) {
77+
if(code) {
78+
var end = this.offset;
79+
this.offset = 0;
80+
this.buffer[this.offset++] = code;
81+
//write the length which is length of entire packet not including
82+
//message type code byte
83+
this.addInt32(end - 1);
84+
this.offset = end;
85+
}
86+
return this.buffer.slice(code ? 0 : 5, this.offset);
8587
}
8688

87-
p.flush = function() {
88-
var result = this.join();
89+
p.flush = function(code) {
90+
var result = this.join(code);
8991
this.clear();
9092
return result;
9193
}

package.json

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
{ "name": "pg",
2-
"version": "0.2.3",
2+
"version": "0.2.6",
33
"description": "Pure JavaScript PostgreSQL client",
44
"homepage": "http://github.com/brianc/node-postgres",
55
"repository" : {
@@ -9,6 +9,6 @@
99
"author" : "Brian Carlson <brian.m.carlson@gmail.com>",
1010
"main" : "./lib/index",
1111
"directories" : { "lib" : "./lib" },
12-
"scripts" : { "test" : "node ./test/run.js" },
12+
"scripts" : { "test" : "make test" },
1313
"engines" : { "node": ">= 0.2.2" }
1414
}

script/list-db-types.js

Lines changed: 6 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -1,24 +1,6 @@
1-
var net = require('net')
2-
var Connection = require(__dirname+'/../lib/connection');
3-
4-
var con = new Connection({stream: new net.Stream()});
5-
con.connect('5432', 'localhost');
6-
7-
con.on('connect', function() {
8-
con.startup({
9-
user: 'brian',
10-
database: 'postgres'
11-
});
12-
});
13-
14-
con.on('dataRow', function(msg) {
15-
console.log(msg.fields);
16-
});
17-
18-
con.on('readyForQuery', function() {
19-
con.query('select oid, typname from pg_type where typtype = \'b\' order by typname');
20-
});
21-
22-
con.on('commandComplete', function() {
23-
con.end();
24-
});
1+
var helper = require(__dirname + "/../test/integration/test-helper");
2+
var pg = helper.pg;
3+
pg.connect(helper.connectionString(), assert.success(function(client) {
4+
var query = client.query('select oid, typname from pg_type where typtype = \'b\' order by oid');
5+
query.on('row', console.log);
6+
}))

test/integration/client/api-tests.js

Lines changed: 17 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@ var log = function() {
66
//console.log.apply(console, arguments);
77
}
88

9-
var sink = new helper.Sink(4, 10000, function() {
9+
var sink = new helper.Sink(5, 10000, function() {
1010
log("ending connection pool: %s", connectionString);
1111
pg.end(connectionString);
1212
});
@@ -92,3 +92,19 @@ test("query errors are handled and do not bubble if callback is provded", functi
9292
}))
9393
}))
9494
})
95+
96+
test('callback is fired once and only once', function() {
97+
pg.connect(connectionString, assert.calls(function(err, client) {
98+
assert.isNull(err);
99+
client.query("CREATE TEMP TABLE boom(name varchar(10))");
100+
var callCount = 0;
101+
client.query([
102+
"INSERT INTO boom(name) VALUES('hai')",
103+
"INSERT INTO boom(name) VALUES('boom')",
104+
"INSERT INTO boom(name) VALUES('zoom')",
105+
].join(";"), function(err, callback) {
106+
assert.equal(callCount++, 0, "Call count should be 0. More means this callback fired more than once.");
107+
sink.add();
108+
})
109+
}))
110+
})

0 commit comments

Comments
 (0)