Skip to content

Commit bd075b7

Browse files
committed
Allow storing of Integer when 'disableLosslessIntegers=true'
`Integer` will now be allowed in query parameters even when native JS numbers are configured on the driver level. This is done to not cut away a valid neo4j integer type. It will also result in better backwards compatibility - existing applications will not need to change all `Integer` query parameters when they set `disableLosslessIntegers=true`.
1 parent c3fb512 commit bd075b7

File tree

3 files changed

+42
-61
lines changed

3 files changed

+42
-61
lines changed

src/v1/internal/connector.js

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -164,8 +164,7 @@ class Connection {
164164
* @constructor
165165
* @param {NodeChannel|WebSocketChannel} channel - channel with a 'write' function and a 'onmessage' callback property.
166166
* @param {string} url - the hostname and port to connect to.
167-
* @param {boolean} disableLosslessIntegers if this connection should convert all received integers to native JS numbers
168-
* (including native {@link Number} type or our own {@link Integer}) as native {@link Number}.
167+
* @param {boolean} disableLosslessIntegers if this connection should convert all received integers to native JS numbers.
169168
*/
170169
constructor(channel, url, disableLosslessIntegers = false) {
171170
/**
@@ -181,7 +180,7 @@ class Connection {
181180
this._ch = channel;
182181
this._dechunker = new Dechunker();
183182
this._chunker = new Chunker( channel );
184-
this._packer = new Packer(this._chunker, disableLosslessIntegers);
183+
this._packer = new Packer(this._chunker);
185184
this._unpacker = new Unpacker(disableLosslessIntegers);
186185

187186
this._isHandlingFailure = false;

src/v1/internal/packstream.js

Lines changed: 12 additions & 35 deletions
Original file line numberDiff line numberDiff line change
@@ -80,13 +80,10 @@ class Packer {
8080
/**
8181
* @constructor
8282
* @param {Chunker} channel the chunker backed by a network channel.
83-
* @param {boolean} disableLosslessIntegers if this packer should convert all received integers to native JS numbers
84-
* (including native {@link Number} type or our own {@link Integer}) as native {@link Number}.
8583
*/
86-
constructor(channel, disableLosslessIntegers = false) {
84+
constructor(channel) {
8785
this._ch = channel;
8886
this._byteArraysSupported = true;
89-
this._disableLosslessIntegers = disableLosslessIntegers;
9087
}
9188

9289
/**
@@ -107,7 +104,7 @@ class Packer {
107104
} else if (typeof(x) == "string") {
108105
return () => this.packString(x, onError);
109106
} else if (isInt(x)) {
110-
return this._packableInteger(x, onError);
107+
return () => this.packInteger(x);
111108
} else if (x instanceof Int8Array) {
112109
return () => this.packBytes(x, onError);
113110
} else if (x instanceof Array) {
@@ -150,28 +147,6 @@ class Packer {
150147
}
151148
}
152149

153-
/**
154-
* Creates a packable function out of the provided {@link Integer} value.
155-
* @param {Integer} x the value to pack.
156-
* @param {function} onError the callback for the case when value cannot be packed.
157-
* @return {function}
158-
* @private
159-
*/
160-
_packableInteger(x, onError) {
161-
if (this._disableLosslessIntegers) {
162-
// pack Integer objects only when native numbers are not used, fail otherwise
163-
// Integer can't represent special values like Number.NEGATIVE_INFINITY
164-
// and should not be used at all when native numbers are enabled
165-
if (onError) {
166-
onError(newError(`Cannot pack Integer value ${x} (${JSON.stringify(x)}) when native numbers are enabled. ` +
167-
`Please use native Number instead or disable native number support on the driver level.`));
168-
}
169-
return () => undefined;
170-
} else {
171-
return () => this.packInteger(x);
172-
}
173-
}
174-
175150
/**
176151
* Packs a struct
177152
* @param signature the signature of the struct
@@ -209,6 +184,7 @@ class Packer {
209184
this._ch.writeInt32(low);
210185
}
211186
}
187+
212188
packFloat(x) {
213189
this._ch.writeUInt8(FLOAT_64);
214190
this._ch.writeFloat64(x);
@@ -343,8 +319,7 @@ class Unpacker {
343319

344320
/**
345321
* @constructor
346-
* @param {boolean} disableLosslessIntegers if this unpacker should convert all received integers to native JS numbers
347-
* (including native {@link Number} type or our own {@link Integer}) as native {@link Number}.
322+
* @param {boolean} disableLosslessIntegers if this unpacker should convert all received integers to native JS numbers.
348323
*/
349324
constructor(disableLosslessIntegers = false) {
350325
// Higher level layers can specify how to map structs to higher-level objects.
@@ -368,9 +343,12 @@ class Unpacker {
368343
return boolean;
369344
}
370345

371-
const number = this._unpackNumber(marker, buffer);
372-
if (number !== null) {
373-
return number;
346+
const numberOrInteger = this._unpackNumberOrInteger(marker, buffer);
347+
if (numberOrInteger !== null) {
348+
if (this._disableLosslessIntegers && isInt(numberOrInteger)) {
349+
return numberOrInteger.toNumberOrInfinity();
350+
}
351+
return numberOrInteger;
374352
}
375353

376354
const string = this._unpackString(marker, markerHigh, markerLow, buffer);
@@ -411,7 +389,7 @@ class Unpacker {
411389
}
412390
}
413391

414-
_unpackNumber(marker, buffer) {
392+
_unpackNumberOrInteger(marker, buffer) {
415393
if (marker == FLOAT_64) {
416394
return buffer.readFloat64();
417395
} else if (marker >= 0 && marker < 128) {
@@ -428,8 +406,7 @@ class Unpacker {
428406
} else if (marker == INT_64) {
429407
const high = buffer.readInt32();
430408
const low = buffer.readInt32();
431-
const integer = new Integer(low, high);
432-
return this._disableLosslessIntegers ? integer.toNumberOrInfinity() : integer;
409+
return new Integer(low, high);
433410
} else {
434411
return null;
435412
}

test/v1/driver.test.js

Lines changed: 28 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -339,27 +339,32 @@ describe('driver', () => {
339339
nativeNumbers.forEach(number => {
340340

341341
it(`should return native number ${number} when disableLosslessIntegers=true`, done => {
342-
testNativeNumberInReturnedRecord(number, done);
342+
testNumberInReturnedRecord(number, number, done);
343343
});
344344

345345
});
346346

347-
it('should fail to pack Integer when disableLosslessIntegers=true', done => {
348-
driver = neo4j.driver('bolt://localhost', sharedNeo4j.authToken, {disableLosslessIntegers: true});
349-
const session = driver.session();
347+
const integersWithNativeNumberEquivalent = [
348+
[neo4j.int(0), 0],
349+
[neo4j.int(42), 42],
350+
[neo4j.int(-100), -100],
351+
[neo4j.int(Number.MIN_SAFE_INTEGER), Number.MIN_SAFE_INTEGER],
352+
[neo4j.int(Number.MAX_SAFE_INTEGER), Number.MAX_SAFE_INTEGER],
353+
[neo4j.int('-9007199254740992'), Number.NEGATIVE_INFINITY], // Number.MIN_SAFE_INTEGER - 1
354+
[neo4j.int('9007199254740992'), Number.POSITIVE_INFINITY], // Number.MAX_SAFE_INTEGER + 1
355+
[neo4j.int('-9007199254749999'), Number.NEGATIVE_INFINITY], // Number.MIN_SAFE_INTEGER - 9007
356+
[neo4j.int('9007199254749999'), Number.POSITIVE_INFINITY], // Number.MAX_SAFE_INTEGER + 9008
357+
];
358+
359+
integersWithNativeNumberEquivalent.forEach(integerWithNativeNumber => {
360+
361+
const integer = integerWithNativeNumber[0];
362+
const nativeNumber = integerWithNativeNumber[1];
363+
364+
it(`should send Integer ${integer.toString()} and return native number ${nativeNumber} when disableLosslessIntegers=true`, done => {
365+
testNumberInReturnedRecord(integer, nativeNumber, done);
366+
});
350367

351-
session.run('RETURN $number', {number: neo4j.int(42)})
352-
.then(result => {
353-
done.fail(`Was somehow able to pack Integer and received result ${JSON.stringify(result)}`);
354-
})
355-
.catch(error => {
356-
const message = error.message;
357-
if (message.indexOf('Cannot pack Integer value') === -1) {
358-
done.fail(`Unexpected error message: ${message}`);
359-
} else {
360-
done();
361-
}
362-
});
363368
});
364369

365370
function testIPv6Connection(url, done) {
@@ -378,24 +383,24 @@ describe('driver', () => {
378383
});
379384
}
380385

381-
function testNativeNumberInReturnedRecord(number, done) {
386+
function testNumberInReturnedRecord(inputNumber, expectedNumber, done) {
382387
driver = neo4j.driver('bolt://localhost', sharedNeo4j.authToken, {disableLosslessIntegers: true});
383388

384389
const session = driver.session();
385-
session.run('RETURN $number AS n0, $number AS n1', {number: number}).then(result => {
390+
session.run('RETURN $number AS n0, $number AS n1', {number: inputNumber}).then(result => {
386391
session.close();
387392

388393
const records = result.records;
389394
expect(records.length).toEqual(1);
390395
const record = records[0];
391396

392-
expect(record.get('n0')).toEqual(number);
393-
expect(record.get('n1')).toEqual(number);
397+
expect(record.get('n0')).toEqual(expectedNumber);
398+
expect(record.get('n1')).toEqual(expectedNumber);
394399

395-
expect(record.get(0)).toEqual(number);
396-
expect(record.get(1)).toEqual(number);
400+
expect(record.get(0)).toEqual(expectedNumber);
401+
expect(record.get(1)).toEqual(expectedNumber);
397402

398-
expect(record.toObject()).toEqual({n0: number, n1: number});
403+
expect(record.toObject()).toEqual({n0: expectedNumber, n1: expectedNumber});
399404

400405
done();
401406
});

0 commit comments

Comments
 (0)