Skip to content

Commit 913fcbc

Browse files
committed
Merge pull request #382 from felixge/bignumber
Changes parseLengthCodedNumber to parse big numbers
2 parents e1cb77c + 04f2ab5 commit 913fcbc

File tree

6 files changed

+70
-33
lines changed

6 files changed

+70
-33
lines changed

Readme.md

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -145,6 +145,7 @@ When establishing a connection, you can set the following options:
145145
* `typeCast`: Determines if column values should be converted to native
146146
JavaScript types. (Default: `true`)
147147
* `queryFormat`: A custom query format function. See [Custom format](#custom-format).
148+
* `supportBigNumbers`: When dealing with big numbers in the database, you should enable this option.
148149
* `debug`: Prints protocol details to stdout. (Default: `false`)
149150
* `multipleStatements`: Allow multiple mysql statements per query. Be careful
150151
with this, it exposes you to SQL injection attacks. (Default: `false`)
@@ -372,6 +373,13 @@ connection.query('INSERT INTO posts SET ?', {title: 'test'}, function(err, resul
372373
});
373374
```
374375

376+
When dealing with big numbers (above JavaScript Number precision limit), you should
377+
consider enabling `supportBigNumbers` option to be able to read the insert id as a
378+
string, otherwise it will throw.
379+
380+
This option is also required when fetching big numbers from the database, otherwise
381+
you will get values rounded to hundreds or thousands due to the precision limit.
382+
375383
## Executing queries in parallel
376384

377385
The MySQL protocol is sequential, this means that you need multiple connections

lib/ConnectionConfig.js

Lines changed: 13 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -8,18 +8,19 @@ function ConnectionConfig(options) {
88
options = ConnectionConfig.parseUrl(options);
99
}
1010

11-
this.host = options.host || 'localhost';
12-
this.port = options.port || 3306;
13-
this.socketPath = options.socketPath;
14-
this.user = options.user || undefined;
15-
this.password = options.password || undefined;
16-
this.database = options.database;
17-
this.insecureAuth = options.insecureAuth || false;
18-
this.debug = options.debug;
19-
this.timezone = options.timezone || 'local';
20-
this.flags = options.flags || '';
21-
this.queryFormat = options.queryFormat;
22-
this.typeCast = (options.typeCast === undefined)
11+
this.host = options.host || 'localhost';
12+
this.port = options.port || 3306;
13+
this.socketPath = options.socketPath;
14+
this.user = options.user || undefined;
15+
this.password = options.password || undefined;
16+
this.database = options.database;
17+
this.insecureAuth = options.insecureAuth || false;
18+
this.supportBigNumbers = options.supportBigNumbers || false;
19+
this.debug = options.debug;
20+
this.timezone = options.timezone || 'local';
21+
this.flags = options.flags || '';
22+
this.queryFormat = options.queryFormat;
23+
this.typeCast = (options.typeCast === undefined)
2324
? true
2425
: options.typeCast;
2526

lib/protocol/Parser.js

Lines changed: 32 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,13 @@
11
var IEEE_754_BINARY_64_PRECISION = Math.pow(2, 53);
22
var MAX_PACKET_LENGTH = Math.pow(2, 24) - 1;
33
var PacketHeader = require('./PacketHeader');
4+
var BigNumber = require("bignumber.js");
45

56
module.exports = Parser;
67
function Parser(options) {
78
options = options || {};
89

10+
this._supportBigNumbers = options.config && options.config.supportBigNumbers;
911
this._buffer = new Buffer(0);
1012
this._longPacketBuffers = [];
1113
this._offset = 0;
@@ -145,29 +147,47 @@ Parser.prototype.parseLengthCodedBuffer = function() {
145147
};
146148

147149
Parser.prototype.parseLengthCodedNumber = function() {
148-
var byte = this._buffer[this._offset++];
150+
var bits = this._buffer[this._offset++];
149151

150-
if (byte <= 251) {
151-
return (byte === 251)
152+
if (bits <= 251) {
153+
return (bits === 251)
152154
? null
153-
: byte;
155+
: bits;
154156
}
155157

156158
var length;
157-
if (byte === 252) {
159+
var bigNumber = false;
160+
var value = 0;
161+
162+
if (bits === 252) {
158163
length = 2;
159-
} else if (byte === 253) {
164+
} else if (bits === 253) {
160165
length = 3;
161-
} else if (byte === 254) {
166+
} else if (bits === 254) {
162167
length = 8;
168+
169+
if (this._supportBigNumbers) {
170+
if (this._buffer[this._offset + 6] > 31 || this._buffer[this._offset + 7]) {
171+
value = new BigNumber(0);
172+
bigNumber = true;
173+
}
174+
}
163175
} else {
164-
throw new Error('parseLengthCodedNumber: Unexpected first byte: ' + byte);
176+
throw new Error('parseLengthCodedNumber: Unexpected first byte: ' + bits);
165177
}
166178

167-
var value = 0;
168179
for (var bytesRead = 0; bytesRead < length; bytesRead++) {
169-
var byte = this._buffer[this._offset++];
170-
value += Math.pow(256, bytesRead) * byte;
180+
bits = this._buffer[this._offset++];
181+
182+
if (bigNumber) {
183+
value = value.plus((new BigNumber(256)).pow(bytesRead).times(bits));
184+
} else {
185+
value += Math.pow(256, bytesRead) * bits;
186+
}
187+
}
188+
189+
if (bigNumber) {
190+
return value.toString();
171191
}
172192

173193
if (value >= IEEE_754_BINARY_64_PRECISION) {
@@ -194,7 +214,7 @@ Parser.prototype.parseNullTerminatedBuffer = function() {
194214

195215
Parser.prototype.parseNullTerminatedString = function() {
196216
var end = this._nullByteOffset();
197-
var value = this._buffer.toString(this._encoding, this._offset, end)
217+
var value = this._buffer.toString(this._encoding, this._offset, end);
198218
this._offset = end + 1;
199219

200220
return value;

lib/protocol/Protocol.js

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,6 @@ function Protocol(options) {
1616
this.readable = true;
1717
this.writable = true;
1818

19-
this._parser = new Parser({onPacket: this._parsePacket.bind(this)});
2019
this._config = options.config || {};
2120
this._connection = options.connection;
2221
this._callback = null;
@@ -26,6 +25,11 @@ function Protocol(options) {
2625
this._destroyed = false;
2726
this._queue = [];
2827
this._handshakeInitializationPacket = null;
28+
29+
this._parser = new Parser({
30+
onPacket : this._parsePacket.bind(this),
31+
config : this._config
32+
});
2933
}
3034

3135
Protocol.prototype.write = function(buffer) {

lib/protocol/packets/RowDataPacket.js

Lines changed: 10 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
1-
var Types = require('../constants/types');
2-
var Charsets = require('../constants/charsets');
3-
var Field = require('./Field');
1+
var Types = require('../constants/types');
2+
var Charsets = require('../constants/charsets');
3+
var Field = require('./Field');
4+
var IEEE_754_BINARY_64_PRECISION = Math.pow(2, 53);
45

56
module.exports = RowDataPacket;
67
function RowDataPacket() {
@@ -9,7 +10,7 @@ function RowDataPacket() {
910
RowDataPacket.prototype.parse = function(parser, fieldPackets, typeCast, nestTables, connection) {
1011
var self = this;
1112
var next = function () {
12-
return self._typeCast(fieldPacket, parser, connection.config.timezone);
13+
return self._typeCast(fieldPacket, parser, connection.config.timezone, connection.config.supportBigNumbers);
1314
};
1415

1516
for (var i = 0; i < fieldPackets.length; i++) {
@@ -20,7 +21,7 @@ RowDataPacket.prototype.parse = function(parser, fieldPackets, typeCast, nestTab
2021
value = typeCast.apply(connection, [ new Field({ packet: fieldPacket, parser: parser }), next ]);
2122
} else {
2223
value = (typeCast)
23-
? this._typeCast(fieldPacket, parser, connection.config.timezone)
24+
? this._typeCast(fieldPacket, parser, connection.config.timezone, connection.config.supportBigNumbers)
2425
: ( (fieldPacket.charsetNr === Charsets.BINARY)
2526
? parser.parseLengthCodedBuffer()
2627
: parser.parseLengthCodedString() );
@@ -37,7 +38,7 @@ RowDataPacket.prototype.parse = function(parser, fieldPackets, typeCast, nestTab
3738
}
3839
};
3940

40-
RowDataPacket.prototype._typeCast = function(field, parser, timeZone) {
41+
RowDataPacket.prototype._typeCast = function(field, parser, timeZone, supportBigNumbers) {
4142
switch (field.type) {
4243
case Types.TIMESTAMP:
4344
case Types.DATE:
@@ -69,7 +70,9 @@ RowDataPacket.prototype._typeCast = function(field, parser, timeZone) {
6970
var numberString = parser.parseLengthCodedString();
7071
return (numberString === null || (field.zeroFill && numberString[0] == "0"))
7172
? numberString
72-
: Number(numberString);
73+
: ((supportBigNumbers && Number(numberString) > IEEE_754_BINARY_64_PRECISION)
74+
? numberString
75+
: Number(numberString));
7376
case Types.BIT:
7477
return parser.parseLengthCodedBuffer();
7578
case Types.STRING:

package.json

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,8 @@
1414
"node": "*"
1515
},
1616
"dependencies": {
17-
"require-all": "0.0.3"
17+
"require-all": "0.0.3",
18+
"bignumber.js": "1.0.1"
1819
},
1920
"devDependencies": {
2021
"utest": "0.0.6",

0 commit comments

Comments
 (0)