Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Introduce 'disableLosslessIntegers' config option #323

Merged
merged 5 commits into from
Feb 2, 2018
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

12 changes: 12 additions & 0 deletions src/v1/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -157,6 +157,18 @@ const USER_AGENT = "neo4j-javascript/" + VERSION;
* // result in no timeout being applied. Connection establishment will be then bound by the timeout configured
* // on the operating system level. Default value is 5000, which is 5 seconds.
* connectionTimeout: 5000, // 5 seconds
*
* // Make this driver always return native JavaScript numbers for integer values, instead of the
* // dedicated {@link Integer} class. Values that do not fit in native number bit range will be represented as
* // <code>Number.NEGATIVE_INFINITY</code> or <code>Number.POSITIVE_INFINITY</code>.
* // <b>Warning:</b> It is not always safe to enable this setting when JavaScript applications are not the only ones
* // interacting with the database. Stored numbers might in such case be not representable by native
* // {@link Number} type and thus driver will return lossy values. This might also happen when data was
* // initially imported using neo4j import tool and contained numbers larger than
* // <code>Number.MAX_SAFE_INTEGER</code>. Driver will then return positive infinity, which is lossy.
* // Default value for this option is <code>false</code> because native JavaScript numbers might result
* // in loss of precision in the general case.
* disableLosslessIntegers: false,
* }
*
* @param {string} url The URL for the Neo4j database, for instance "bolt://localhost"
Expand Down
15 changes: 15 additions & 0 deletions src/v1/integer.js
Original file line number Diff line number Diff line change
Expand Up @@ -88,6 +88,21 @@ class Integer {
*/
toNumber(){ return this.high * TWO_PWR_32_DBL + (this.low >>> 0); }

/**
* Converts the Integer to native number or -Infinity/+Infinity when it does not fit.
* @return {number}
* @package
*/
toNumberOrInfinity() {
if (this.lessThan(Integer.MIN_SAFE_VALUE)) {
return Number.NEGATIVE_INFINITY;
} else if (this.greaterThan(Integer.MAX_SAFE_VALUE)) {
return Number.POSITIVE_INFINITY;
} else {
return this.toNumber();
}
}

/**
* Converts the Integer to a string written in the specified radix.
* @param {number=} radix Radix (2-36), defaults to 10
Expand Down
14 changes: 7 additions & 7 deletions src/v1/internal/connector.js
Original file line number Diff line number Diff line change
Expand Up @@ -162,11 +162,11 @@ class Connection {

/**
* @constructor
* @param channel - channel with a 'write' function and a 'onmessage'
* callback property
* @param url - url to connect to
* @param {NodeChannel|WebSocketChannel} channel - channel with a 'write' function and a 'onmessage' callback property.
* @param {string} url - the hostname and port to connect to.
* @param {boolean} disableLosslessIntegers if this connection should convert all received integers to native JS numbers.
*/
constructor (channel, url) {
constructor(channel, url, disableLosslessIntegers = false) {
/**
* An ordered queue of observers, each exchange response (zero or more
* RECORD messages followed by a SUCCESS message) we recieve will be routed
Expand All @@ -180,8 +180,8 @@ class Connection {
this._ch = channel;
this._dechunker = new Dechunker();
this._chunker = new Chunker( channel );
this._packer = new Packer( this._chunker );
this._unpacker = new Unpacker();
this._packer = new Packer(this._chunker);
this._unpacker = new Unpacker(disableLosslessIntegers);

this._isHandlingFailure = false;
this._currentFailure = null;
Expand Down Expand Up @@ -588,7 +588,7 @@ function connect(url, config = {}, connectionErrorCode = null) {
const Ch = config.channel || Channel;
const parsedUrl = urlUtil.parseBoltUrl(url);
const channelConfig = new ChannelConfig(parsedUrl, config, connectionErrorCode);
return new Connection(new Ch(channelConfig), parsedUrl.hostAndPort);
return new Connection(new Ch(channelConfig), parsedUrl.hostAndPort, config.disableLosslessIntegers);
}

export {
Expand Down
36 changes: 26 additions & 10 deletions src/v1/internal/packstream.js
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@
import utf8 from './utf8';
import Integer, {int, isInt} from '../integer';
import {newError} from './../error';
import {Chunker} from './chunking';

const TINY_STRING = 0x80;
const TINY_LIST = 0x90;
Expand Down Expand Up @@ -75,7 +76,12 @@ class Structure {
* @access private
*/
class Packer {
constructor (channel) {

/**
* @constructor
* @param {Chunker} channel the chunker backed by a network channel.
*/
constructor(channel) {
this._ch = channel;
this._byteArraysSupported = true;
}
Expand All @@ -98,7 +104,7 @@ class Packer {
} else if (typeof(x) == "string") {
return () => this.packString(x, onError);
} else if (isInt(x)) {
return () => this.packInteger( x );
return () => this.packInteger(x);
} else if (x instanceof Int8Array) {
return () => this.packBytes(x, onError);
} else if (x instanceof Array) {
Expand Down Expand Up @@ -178,6 +184,7 @@ class Packer {
this._ch.writeInt32(low);
}
}

packFloat(x) {
this._ch.writeUInt8(FLOAT_64);
this._ch.writeFloat64(x);
Expand Down Expand Up @@ -309,11 +316,17 @@ class Packer {
* @access private
*/
class Unpacker {
constructor () {

/**
* @constructor
* @param {boolean} disableLosslessIntegers if this unpacker should convert all received integers to native JS numbers.
*/
constructor(disableLosslessIntegers = false) {
// Higher level layers can specify how to map structs to higher-level objects.
// If we recieve a struct that has a signature that does not have a mapper,
// If we receive a struct that has a signature that does not have a mapper,
// we simply return a Structure object.
this.structMappers = {};
this._disableLosslessIntegers = disableLosslessIntegers;
}

unpack(buffer) {
Expand All @@ -330,9 +343,12 @@ class Unpacker {
return boolean;
}

const number = this._unpackNumber(marker, buffer);
if (number !== null) {
return number;
const numberOrInteger = this._unpackNumberOrInteger(marker, buffer);
if (numberOrInteger !== null) {
if (this._disableLosslessIntegers && isInt(numberOrInteger)) {
return numberOrInteger.toNumberOrInfinity();
}
return numberOrInteger;
}

const string = this._unpackString(marker, markerHigh, markerLow, buffer);
Expand Down Expand Up @@ -373,7 +389,7 @@ class Unpacker {
}
}

_unpackNumber(marker, buffer) {
_unpackNumberOrInteger(marker, buffer) {
if (marker == FLOAT_64) {
return buffer.readFloat64();
} else if (marker >= 0 && marker < 128) {
Expand All @@ -388,8 +404,8 @@ class Unpacker {
let b = buffer.readInt32();
return int(b);
} else if (marker == INT_64) {
let high = buffer.readInt32();
let low = buffer.readInt32();
const high = buffer.readInt32();
const low = buffer.readInt32();
return new Integer(low, high);
} else {
return null;
Expand Down
14 changes: 4 additions & 10 deletions test/types/v1/driver.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,16 +17,7 @@
* limitations under the License.
*/

import Driver, {
AuthToken,
Config,
EncryptionLevel,
LoadBalancingStrategy,
READ,
SessionMode,
TrustStrategy,
WRITE
} from "../../../types/v1/driver";
import Driver, {AuthToken, Config, EncryptionLevel, LoadBalancingStrategy, READ, SessionMode, TrustStrategy, WRITE} from "../../../types/v1/driver";
import {Parameters} from "../../../types/v1/statement-runner";
import Session from "../../../types/v1/session";
import {Neo4jError} from "../../../types/v1/error";
Expand Down Expand Up @@ -59,6 +50,9 @@ const connectionPoolSize: undefined | number = config.connectionPoolSize;
const maxTransactionRetryTime: undefined | number = config.maxTransactionRetryTime;
const loadBalancingStrategy1: undefined | LoadBalancingStrategy = config.loadBalancingStrategy;
const loadBalancingStrategy2: undefined | string = config.loadBalancingStrategy;
const maxConnectionLifetime: undefined | number = config.maxConnectionLifetime;
const connectionTimeout: undefined | number = config.connectionTimeout;
const disableLosslessIntegers: undefined | boolean = config.disableLosslessIntegers;

const sessionMode: SessionMode = dummy;
const sessionModeStr: string = sessionMode;
Expand Down
25 changes: 25 additions & 0 deletions test/types/v1/graph-types.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,9 @@ const node1Id: Integer = node1.identity;
const node1Labels: string[] = node1.labels;
const node1Props: object = node1.properties;

const node2: Node<number> = new Node(2, ["Person", "Employee"], {name: "Alice"});
const node2Id: number = node2.identity;

const rel1: Relationship = new Relationship(int(1), int(2), int(3), "KNOWS", {since: 12345});
const rel1String: string = rel1.toString();
const rel1Id: Integer = rel1.identity;
Expand All @@ -41,13 +44,35 @@ const rel2Id: Integer = rel2.identity;
const rel2Type: string = rel2.type;
const rel2Props: object = rel2.properties;

const rel4: Relationship<number> = new Relationship(2, 3, 4, "KNOWS", {since: 12345});
const rel4Id: number = rel4.identity;
const rel4Start: number = rel4.start;
const rel4End: number = rel4.end;

const rel5: UnboundRelationship<number> = new UnboundRelationship(5, "KNOWS", {since: 12345});
const rel5Id: number = rel5.identity;
const rel6 = rel5.bind(24, 42);
const rel6Id: number = rel6.identity;
const rel6Start: number = rel6.start;
const rel6End: number = rel6.end;

const pathSegment1: PathSegment = new PathSegment(node1, rel1, node1);
const pathSegment1Start: Node = pathSegment1.start;
const pathSegment1Rel: Relationship = pathSegment1.relationship;
const pathSegment1End: Node = pathSegment1.end;

const pathSegment2: PathSegment<number> = new PathSegment(node2, rel4, node2);
const pathSegment2Start: Node<number> = pathSegment2.start;
const pathSegment2Rel: Relationship<number> = pathSegment2.relationship;
const pathSegment2End: Node<number> = pathSegment2.end;

const path1: Path = new Path(node1, node1, [pathSegment1]);
const path1Start: Node = path1.start;
const path1End: Node = path1.end;
const path1Segments: PathSegment[] = path1.segments;
const path1Length: number = path1.length;

const path2: Path<number> = new Path(node2, node2, [pathSegment2]);
const path2Start: Node<number> = path2.start;
const path2End: Node<number> = path2.end;
const path2Segments: PathSegment<number>[] = path2.segments;
41 changes: 21 additions & 20 deletions test/types/v1/result-summary.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,27 +17,20 @@
* limitations under the License.
*/

import ResultSummary, {
Notification,
NotificationPosition,
Plan,
ProfiledPlan,
ServerInfo,
StatementStatistic
} from "../../../types/v1/result-summary";
import ResultSummary, {Notification, NotificationPosition, Plan, ProfiledPlan, ServerInfo, StatementStatistic} from "../../../types/v1/result-summary";
import Integer from "../../../types/v1/integer";

const dummy: any = null;

const sum: ResultSummary = dummy;
const sum1: ResultSummary = dummy;

const stmt = sum.statement;
const stmt = sum1.statement;
const stmtText: string = stmt.text;
const stmtParams: object = stmt.parameters;

const str: string = sum.statementType;
const str: string = sum1.statementType;

const counters: StatementStatistic = sum.counters;
const counters: StatementStatistic = sum1.counters;

const containsUpdates: boolean = counters.containsUpdates();
const nodesCreated: number = counters.nodesCreated();
Expand All @@ -52,21 +45,21 @@ const indexesRemoved: number = counters.indexesRemoved();
const constraintsAdded: number = counters.constraintsAdded();
const constraintsRemoved: number = counters.constraintsRemoved();

const plan: Plan = sum.plan;
const plan: Plan = sum1.plan;
const planOperatorType: string = plan.operatorType;
const planIdentifiers: string[] = plan.identifiers;
const planArguments: { [key: string]: string } = plan.arguments;
const planChildren: Plan[] = plan.children;

const profile: ProfiledPlan = sum.profile;
const profile: ProfiledPlan = sum1.profile;
const profileOperatorType: string = profile.operatorType;
const profileIdentifiers: string[] = profile.identifiers;
const profileArguments: { [key: string]: string } = profile.arguments;
const profileDbHits: number = profile.dbHits;
const profileRows: number = profile.rows;
const profileChildren: ProfiledPlan[] = profile.children;

const notifications: Notification[] = sum.notifications;
const notifications: Notification[] = sum1.notifications;
const notification: Notification = notifications[0];
const code: string = notification.code;
const title: string = notification.title;
Expand All @@ -78,12 +71,20 @@ const offset: number = position2.offset;
const line: number = position2.line;
const column: number = position2.column;

const server: ServerInfo = sum.server;
const server: ServerInfo = sum1.server;
const address: string = server.address;
const version: string = server.version;

const resultConsumedAfter: Integer = sum.resultConsumedAfter;
const resultAvailableAfter: Integer = sum.resultAvailableAfter;
const resultConsumedAfter1: Integer = sum1.resultConsumedAfter;
const resultAvailableAfter1: Integer = sum1.resultAvailableAfter;

const hasPlan: boolean = sum.hasPlan();
const hasProfile: boolean = sum.hasProfile();
const hasPlan: boolean = sum1.hasPlan();
const hasProfile: boolean = sum1.hasProfile();

const sum2: ResultSummary<number> = dummy;
const resultConsumedAfter2: number = sum2.resultConsumedAfter;
const resultAvailableAfter2: number = sum2.resultAvailableAfter;

const sum3: ResultSummary<Integer> = dummy;
const resultConsumedAfter3: Integer = sum3.resultConsumedAfter;
const resultAvailableAfter3: Integer = sum3.resultAvailableAfter;
Loading