From b4f3038daac475b2d373de892f39473cc5eb2364 Mon Sep 17 00:00:00 2001 From: Antonio Barcelos Date: Tue, 1 Mar 2022 13:36:52 +0100 Subject: [PATCH 01/17] Enabling BoltProtocol v5 and the new elementId in the graph types MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The format of the identifiers used by the kernel is changing from the current long that represents a file offset to a more flexible format needed by the new Freki storage engine. In 5.0 the new storage engine will be ‘off’ by default, at some point in the future it will be enabled by default. The drivers will need to be able to support the old format alongside the new one in 5.0. To do this the existing long will remain and a new parallel identifier will be added that is of type string. The new string version will contain the long value for servers using the older non Freki storage engine e.g. versions < 5.0 and versions 5.x with Freki disabled. The following changes in the graph types was done for accomodating the elementId: - in `Node`: - Add `elementId: string` for identifying the element; - Deprecate `identity: NumberOrInteger` - in `Relationship`: - Add `elementId: string` for identifiying the element; - Deprecate `identity: NumberOrInteger` - Add `startNodeElementId: string` for identifiying the start node; - Deprecate `start: NumberOrInteger` - Add `endNodeElementId: string` for identifiying the end node; - Deprecate `end: NumberOrInteger` - in `UnboundRelationship`: - Add `elementId: string` for identifying the element; - Deprecate `identity: NumberOrInteger` --- packages/core/src/graph-types.ts | 11 ++- packages/core/test/graph-types.test.ts | 101 +++++++++++++++++++++++++ 2 files changed, 110 insertions(+), 2 deletions(-) create mode 100644 packages/core/test/graph-types.test.ts diff --git a/packages/core/src/graph-types.ts b/packages/core/src/graph-types.ts index dbd442533..78259eb38 100644 --- a/packages/core/src/graph-types.ts +++ b/packages/core/src/graph-types.ts @@ -48,14 +48,16 @@ class Node} labels - Array for all labels * @param {Properties} properties - Map with node properties + * @param {string} elementId - Element identifier */ - constructor(identity: T, labels: string[], properties: P) { + constructor(identity: T, labels: string[], properties: P, elementId?: string) { /** * Identity of the node. * @type {Integer|number} @@ -71,13 +73,18 @@ class Node { + test('should have identity', () => { + const node = new Node(1, [], {}) + + expect(node.identity).toEqual(1) + }) + + test('should have labels', () => { + const node = new Node(1, ['label'], {}) + + expect(node.labels).toEqual(['label']) + }) + + test('should have properties', () => { + const node = new Node(1, [], { + property: 'value' + }) + + expect(node.properties).toEqual({ + property: 'value' + }) + }) + + test('should have elementId', () => { + const node = new Node(1, [], {}, 'elementId') + + expect(node.elementId).toEqual('elementId') + }) + + test.each([ + [10, '10'], + [int(12), '12'], + [BigInt(32), '32'], + ])('should have elementId default to identity when it is not set', (identity, expected) => { + const node = new Node(identity, [], {}) + + expect(node.elementId).toEqual(expected) + }) + + test.each(validNodes())('should be serialized as string', node => { + expect(node.toString()).toMatchSnapshot() + }) + + test.each(validNodes())('should be consider a node', (node: any) => { + expect(isNode(node)).toBe(true) + }) + + test.each(nonNodes())('should not consider a non-node object as node', nonNode => { + expect(isNode(nonNode)).toBe(false) + }) + + function validNodes(): any[] { + return [ + [new Node(1, ['label'], {}, 'elementId')], + [new Node(1, ['label'], {})], + [new Node(1, [], {})], + [new Node(1, [], { 'property': 'value' })], + [new Node(1, ['label'], { 'property': 'value' })], + ] + } + + function nonNodes(): any[] { + return [ + [undefined], + [null], + [{ identity: 1, labels: ['label'], properties: { 'property': 'value' } }], + [{ identity: 1, labels: ['label'], properties: { 'property': 'value' }, elementId: 'elementId' }], + [{}], + [{ 'property': 'value' }], + [{ 'property': 'value', 'labels': ['label'] }], + [{ 'property': 'value', 'labels': ['label'], 'identity': 1 }], + ] + } +}) From 627f03ecd79c7fe99737efd850509aee9487db8f Mon Sep 17 00:00:00 2001 From: Antonio Barcelos Date: Tue, 1 Mar 2022 13:41:00 +0100 Subject: [PATCH 02/17] Deprecate Node#identity --- packages/core/src/graph-types.ts | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/packages/core/src/graph-types.ts b/packages/core/src/graph-types.ts index 78259eb38..8420f2560 100644 --- a/packages/core/src/graph-types.ts +++ b/packages/core/src/graph-types.ts @@ -52,7 +52,7 @@ class Node} labels - Array for all labels * @param {Properties} properties - Map with node properties * @param {string} elementId - Element identifier @@ -60,7 +60,8 @@ class Node Date: Tue, 1 Mar 2022 14:20:51 +0100 Subject: [PATCH 03/17] Add support for `elementid` in Relationship --- packages/core/src/graph-types.ts | 63 ++++++++--- packages/core/test/graph-types.test.ts | 139 +++++++++++++++++++++++-- 2 files changed, 183 insertions(+), 19 deletions(-) diff --git a/packages/core/src/graph-types.ts b/packages/core/src/graph-types.ts index 8420f2560..18c43e17b 100644 --- a/packages/core/src/graph-types.ts +++ b/packages/core/src/graph-types.ts @@ -20,6 +20,9 @@ import Integer from './integer' import { stringify } from './json' type StandardDate = Date +/** + * @typedef {number | Integer | bigint} NumberOrInteger + */ type NumberOrInteger = number | Integer | bigint type Properties = { [key: string]: any } @@ -48,14 +51,14 @@ class Node} labels - Array for all labels * @param {Properties} properties - Map with node properties - * @param {string} elementId - Element identifier + * @param {string} elementId - Node element identifier */ constructor(identity: T, labels: string[], properties: P, elementId?: string) { /** @@ -75,10 +78,10 @@ class Node identity.toString()) } /** @@ -127,30 +130,42 @@ class Relationship identity.toString()) + + /** + * The Start Node element identifier. + * @type {string} + */ + this.startNodeElementId = _valueOrGetDefault(startNodeElementId, () => start.toString()) + + /** + * The End Node element identifier. + * @type {string} + */ + this.endNodeElementId = _valueOrGetDefault(endNodeElementId, () => end.toString()) } /** * @ignore */ toString(): string { - let s = '(' + this.start + ')-[:' + this.type + let s = '(' + this.startNodeElementId + ')-[:' + this.type const keys = Object.keys(this.properties) if (keys.length > 0) { s += ' {' @@ -179,7 +212,7 @@ class Relationship (value: T|undefined|null, getDefault: () => T): T { + return value === undefined || value === null ? getDefault() : value +} + export { Node, isNode, diff --git a/packages/core/test/graph-types.test.ts b/packages/core/test/graph-types.test.ts index 3d92c1941..9f3dac6bf 100644 --- a/packages/core/test/graph-types.test.ts +++ b/packages/core/test/graph-types.test.ts @@ -18,7 +18,9 @@ */ import { Node, - isNode + isNode, + Relationship, + isRelationship, } from '../src/graph-types' import { @@ -54,11 +56,9 @@ describe('Node', () => { expect(node.elementId).toEqual('elementId') }) - test.each([ - [10, '10'], - [int(12), '12'], - [BigInt(32), '32'], - ])('should have elementId default to identity when it is not set', (identity, expected) => { + test.each( + validIdentityAndExpectedElementIds() + )('should have elementId default to identity when it is not set', (identity, expected) => { const node = new Node(identity, [], {}) expect(node.elementId).toEqual(expected) @@ -99,3 +99,130 @@ describe('Node', () => { ] } }) + +describe('Relationship', () => { + test('should have identity', () => { + const relationship = new Relationship(1, 2, 3, 'Rel', {}) + + expect(relationship.identity).toEqual(1) + }) + + test('should have start', () => { + const relationship = new Relationship(1, 2, 3, 'Rel', {}) + + expect(relationship.start).toEqual(2) + }) + + test('should have end', () => { + const relationship = new Relationship(1, 2, 3, 'Rel', {}) + + expect(relationship.end).toEqual(3) + }) + + test('should have type', () => { + const relationship = new Relationship(1, 2, 3, 'Rel', {}) + + expect(relationship.type).toEqual('Rel') + }) + + test('should have properties', () => { + const relationship = new Relationship(1, 2, 3, 'Rel', { 'property': 'value' }) + + expect(relationship.properties).toEqual({ 'property': 'value' }) + }) + + test('should have elementId', () => { + const relationship = new Relationship(1, 2, 3, 'Rel', {}, 'elementId') + + expect(relationship.elementId).toEqual('elementId') + }) + + test.each( + validIdentityAndExpectedElementIds() + )('should default elementId to indentity when it is not set', (identity, expected) => { + const relationship = new Relationship(identity, 2, 3, 'Rel', {}) + + expect(relationship.elementId).toEqual(expected) + }) + + test('should have startNodeElementId', () => { + const relationship = new Relationship(1, 2, 3, 'Rel', {}, 'elementId', 'startNodeElementId') + + expect(relationship.startNodeElementId).toEqual('startNodeElementId') + }) + + test.each( + validIdentityAndExpectedElementIds() + )('should default startNodeElementId to start when it is not set', (identity, expected) => { + const relationship = new Relationship(1, identity, 3, 'Rel', {}) + + expect(relationship.startNodeElementId).toEqual(expected) + }) + + test('should have endNodeElementId', () => { + const relationship = new Relationship(1, 2, 3, 'Rel', {}, 'elementId', 'startNodeElementId', 'endNodeElementId') + + expect(relationship.endNodeElementId).toEqual('endNodeElementId') + }) + + test.each( + validIdentityAndExpectedElementIds() + )('should default endNodeElementId to start when it is not set', (identity, expected) => { + const relationship = new Relationship(1, 2, identity, 'Rel', {}) + + expect(relationship.endNodeElementId).toEqual(expected) + }) + + test.each(validRelationships())('should be serialized as string', relationship => { + expect(relationship.toString()).toMatchSnapshot() + }) + + test.each(validRelationships())('should be consider a node', relationship => { + expect(isRelationship(relationship)).toBe(true) + }) + + test.each(nonRelationships())('should not consider a non-node object as node', nonRelationship => { + expect(isRelationship(nonRelationship)).toBe(false) + }) + + function validRelationships (): any[] { + return [ + [new Relationship(1, 2, 3, 'Rel', {}, 'elementId', 'startNodeElementId', 'endNodeElementId')], + [new Relationship(1, 2, 3, 'Rel', {}, 'elementId', 'startNodeElementId')], + [new Relationship(1, 2, 3, 'Rel', {}, 'elementId')], + [new Relationship(1, 2, 3, 'Rel', {})], + [new Relationship(1, 2, 3, 'Rel', { 'property': 'value' })], + ] + } + + function nonRelationships (): any[] { + return [ + [undefined], + [null], + ['Relationship'], + [{}], + [{ 'property': 'value' }], + [{ + identity: 1, start: 2, end: 3, type: 'Rel', + properties: { 'property': 'value' } + }], + [{ + identity: 1, start: 2, end: 3, type: 'Rel', + properties: { 'property': 'value' }, elementId: 'elementId' + }], + [{ + identity: 1, start: 2, end: 3, type: 'Rel', + properties: { 'property': 'value' }, elementId: 'elementId', + startNodeElementId: 'startNodeElementId', endNodeElementId: 'endNodeElementId' + }], + ] + } +}) + +function validIdentityAndExpectedElementIds (): any[] { + return [ + [10, '10'], + [int(12), '12'], + [BigInt(32), '32'], + ] +} From 7156c0f1f58fb44e610f192cf622d713b4ba007e Mon Sep 17 00:00:00 2001 From: Antonio Barcelos Date: Tue, 1 Mar 2022 14:55:27 +0100 Subject: [PATCH 04/17] Add elementId to UnboundRelationship --- packages/core/src/graph-types.ts | 40 ++++++++- packages/core/test/graph-types.test.ts | 112 ++++++++++++++++++++++++- 2 files changed, 145 insertions(+), 7 deletions(-) diff --git a/packages/core/src/graph-types.ts b/packages/core/src/graph-types.ts index 18c43e17b..06f648979 100644 --- a/packages/core/src/graph-types.ts +++ b/packages/core/src/graph-types.ts @@ -240,18 +240,21 @@ class UnboundRelationship identity.toString()) } /** * Bind relationship * * @protected + * @deprecated use {@link UnboundRelationship#bindTo} instead * @param {Integer} start - Identity of start node * @param {Integer} end - Identity of end node * @return {Relationship} - Created relationship @@ -280,7 +290,29 @@ class UnboundRelationship, end: Node): Relationship { + return new Relationship( + this.identity, + start.identity, + end.identity, + this.type, + this.properties, + this.elementId, + start.elementId, + end.elementId, ) } diff --git a/packages/core/test/graph-types.test.ts b/packages/core/test/graph-types.test.ts index 9f3dac6bf..2aecf9913 100644 --- a/packages/core/test/graph-types.test.ts +++ b/packages/core/test/graph-types.test.ts @@ -21,6 +21,8 @@ import { isNode, Relationship, isRelationship, + UnboundRelationship, + isUnboundRelationship, } from '../src/graph-types' import { @@ -177,11 +179,11 @@ describe('Relationship', () => { expect(relationship.toString()).toMatchSnapshot() }) - test.each(validRelationships())('should be consider a node', relationship => { + test.each(validRelationships())('should be consider a relationship', relationship => { expect(isRelationship(relationship)).toBe(true) }) - test.each(nonRelationships())('should not consider a non-node object as node', nonRelationship => { + test.each(nonRelationships())('should not consider a non-relationship object as relationship', nonRelationship => { expect(isRelationship(nonRelationship)).toBe(false) }) @@ -219,9 +221,113 @@ describe('Relationship', () => { } }) +describe('UnboundRelationship', () => { + test('should have identity', () => { + const relationship = new UnboundRelationship(1, 'Rel', {}) + + expect(relationship.identity).toEqual(1) + }) + + test('should have type', () => { + const relationship = new UnboundRelationship(1, 'Rel', {}) + + expect(relationship.type).toEqual('Rel') + }) + + test('should have properties', () => { + const relationship = new UnboundRelationship(1, 'Rel', { 'property': 'value' }) + + expect(relationship.properties).toEqual({ 'property': 'value' }) + }) + + test.each(validUnboundRelationships())('should be serialized as string', relationship => { + expect(relationship.toString()).toMatchSnapshot() + }) + + test.each(validUnboundRelationships())('should be consider a unbound relationship', relationship => { + expect(isUnboundRelationship(relationship)).toBe(true) + }) + + test.each( + nonUnboundRelationships() + )('should not consider a non-unbound relationship object as unbound relationship', nonUnboundRelationship => { + expect(isUnboundRelationship(nonUnboundRelationship)).toBe(false) + }) + + test.each( + bindUnboundRelationshipFixture() + )('should bind with node identity', (rel, startNode, endNode) => { + expect(rel.bind(startNode.identity, endNode.identity)) + .toEqual( + new Relationship( + rel.identity, + startNode.identity, + endNode.identity, + rel.type, + rel.properties, + rel.elementId + ) + ) + }) + + test.each( + bindUnboundRelationshipFixture() + )('should bind to nodes', (rel, startNode, endNode) => { + expect(rel.bindTo(startNode, endNode)) + .toEqual( + new Relationship( + rel.identity, + startNode.identity, + endNode.identity, + rel.type, + rel.properties, + rel.elementId, + startNode.elementId, + endNode.elementId + ) + ) + }) + + function validUnboundRelationships (): any[] { + return [ + [new UnboundRelationship(1, 'Rel', {}, 'elementId')], + [new UnboundRelationship(1, 'Rel', {})], + [new UnboundRelationship(1, 'Rel', { 'property': 'value' })], + ] + } + + function nonUnboundRelationships (): any[] { + return [ + [undefined], + [null], + ['Relationship'], + [{}], + [{ 'property': 'value' }], + [{ + identity: 1, type: 'Rel', + properties: { 'property': 'value' } + }], + [{ + identity: 1, type: 'Rel', + properties: { 'property': 'value' }, elementId: 'elementId' + }] + ] + } + + function bindUnboundRelationshipFixture (): any[] { + return [ + [new UnboundRelationship(0, 'Rel', {}), new Node(1, ['Node'], {}), new Node(2, ['Node'], {})], + [new UnboundRelationship(0, 'Rel', {}, 'elementId'), new Node(1, ['Node'], {}), new Node(2, ['Node'], {})], + [new UnboundRelationship(0, 'Rel', {}), new Node(1, ['Node'], {}, 'nodeElementId'), new Node(2, ['Node'], {})], + [new UnboundRelationship(0, 'Rel', {}), new Node(1, ['Node'], {}, 'nodeElementId'), new Node(2, ['Node'], {}), 'nodeElementId2'], + [new UnboundRelationship(0, 'Rel', {}, 'elementId'), new Node(1, ['Node'], {}, 'nodeElementId'), new Node(2, ['Node'], {}), 'nodeElementId2'], + ] + } +}) + function validIdentityAndExpectedElementIds (): any[] { return [ - [10, '10'], + [10, '10'], [int(12), '12'], [BigInt(32), '32'], ] From fe216148bf45b43a42e0571a4602338fde5179dc Mon Sep 17 00:00:00 2001 From: Antonio Barcelos Date: Tue, 1 Mar 2022 14:57:28 +0100 Subject: [PATCH 05/17] Add snapshots --- .../__snapshots__/graph-types.test.ts.snap | 27 +++++++++++++++++++ 1 file changed, 27 insertions(+) create mode 100644 packages/core/test/__snapshots__/graph-types.test.ts.snap diff --git a/packages/core/test/__snapshots__/graph-types.test.ts.snap b/packages/core/test/__snapshots__/graph-types.test.ts.snap new file mode 100644 index 000000000..e024c43bb --- /dev/null +++ b/packages/core/test/__snapshots__/graph-types.test.ts.snap @@ -0,0 +1,27 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`Node should be serialized as string 1`] = `"(elementId:label)"`; + +exports[`Node should be serialized as string 2`] = `"(1:label)"`; + +exports[`Node should be serialized as string 3`] = `"(1)"`; + +exports[`Node should be serialized as string 4`] = `"(1 {property:\\"value\\"})"`; + +exports[`Node should be serialized as string 5`] = `"(1:label {property:\\"value\\"})"`; + +exports[`Relationship should be serialized as string 1`] = `"(startNodeElementId)-[:Rel]->(endNodeElementId)"`; + +exports[`Relationship should be serialized as string 2`] = `"(startNodeElementId)-[:Rel]->(3)"`; + +exports[`Relationship should be serialized as string 3`] = `"(2)-[:Rel]->(3)"`; + +exports[`Relationship should be serialized as string 4`] = `"(2)-[:Rel]->(3)"`; + +exports[`Relationship should be serialized as string 5`] = `"(2)-[:Rel {property:\\"value\\"}]->(3)"`; + +exports[`UnboundRelationship should be serialized as string 1`] = `"-[:Rel]->"`; + +exports[`UnboundRelationship should be serialized as string 2`] = `"-[:Rel]->"`; + +exports[`UnboundRelationship should be serialized as string 3`] = `"-[:Rel {property:\\"value\\"}]->"`; From f595d73370d2fd98782f1d16cd371a74d3097889 Mon Sep 17 00:00:00 2001 From: Antonio Barcelos Date: Tue, 1 Mar 2022 16:03:37 +0100 Subject: [PATCH 06/17] Add support for packing and unpack v5 nodes --- .../src/packstream/packstream-v3.js | 52 ++++ .../test/packstream/packstream-v3.test.js | 262 ++++++++++++++++++ 2 files changed, 314 insertions(+) create mode 100644 packages/bolt-connection/src/packstream/packstream-v3.js create mode 100644 packages/bolt-connection/test/packstream/packstream-v3.test.js diff --git a/packages/bolt-connection/src/packstream/packstream-v3.js b/packages/bolt-connection/src/packstream/packstream-v3.js new file mode 100644 index 000000000..74f31c4b4 --- /dev/null +++ b/packages/bolt-connection/src/packstream/packstream-v3.js @@ -0,0 +1,52 @@ +/** + * Copyright (c) "Neo4j" + * Neo4j Sweden AB [http://neo4j.com] + * + * This file is part of Neo4j. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import * as v2 from './packstream-v2'; +import { + Node +} from 'neo4j-driver-core' + +const NODE_STRUCT_SIZE = 4 + +export class Packer extends v2.Packer { + // This implementation is the same +} + +export class Unpacker extends v2.Unpacker { + /** + * @constructor + * @param {boolean} disableLosslessIntegers if this unpacker should convert all received integers to native JS numbers. + * @param {boolean} useBigInt if this unpacker should convert all received integers to Bigint + */ + constructor (disableLosslessIntegers = false, useBigInt = false) { + super(disableLosslessIntegers, useBigInt) + } + + _unpackNode (structSize, buffer) { + this._verifyStructSize('Node', NODE_STRUCT_SIZE, structSize) + + return new Node( + this.unpack(buffer), // Identity + this.unpack(buffer), // Labels + this.unpack(buffer), // Properties, + this.unpack(buffer) // ElementId + ) + } + +} diff --git a/packages/bolt-connection/test/packstream/packstream-v3.test.js b/packages/bolt-connection/test/packstream/packstream-v3.test.js new file mode 100644 index 000000000..d9e0b6525 --- /dev/null +++ b/packages/bolt-connection/test/packstream/packstream-v3.test.js @@ -0,0 +1,262 @@ +/** + * Copyright (c) "Neo4j" + * Neo4j Sweden AB [http://neo4j.com] + * + * This file is part of Neo4j. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + + import { int, Integer } from 'neo4j-driver-core' + import { alloc } from '../../src/channel' + import { Packer, Unpacker } from '../../src/packstream/packstream-v3' + import { Structure } from '../../src/packstream/packstream-v1' + import { Node, int } from 'neo4j-driver-core' + + describe('#unit PackStreamV3', () => { + it('should pack integers with small numbers', () => { + let n, i + // test small numbers + for (n = -999; n <= 999; n += 1) { + i = int(n) + expect(packAndUnpack(i).toString()).toBe(i.toString()) + expect( + packAndUnpack(i, { disableLosslessIntegers: true }).toString() + ).toBe(i.toString()) + expect(packAndUnpack(i, { useBigInt: true }).toString()).toBe( + i.toString() + ) + } + }) + + it('should pack integers with small numbers created with Integer', () => { + let n, i + // test small numbers + for (n = -10; n <= 10; n += 1) { + i = new Integer(n, 0) + expect(packAndUnpack(i).toString()).toBe(i.toString()) + expect( + packAndUnpack(i, { disableLosslessIntegers: true }).toString() + ).toBe(i.toString()) + expect(packAndUnpack(i, { useBigInt: true }).toString()).toBe( + i.toString() + ) + } + }) + + it('should pack integers with positive numbers', () => { + let n, i + // positive numbers + for (n = 16; n <= 16; n += 1) { + i = int(Math.pow(2, n)) + expect(packAndUnpack(i).toString()).toBe(i.toString()) + + const unpackedLossyInteger = packAndUnpack(i, { + disableLosslessIntegers: true + }) + expect(typeof unpackedLossyInteger).toBe('number') + expect(unpackedLossyInteger.toString()).toBe( + i.inSafeRange() ? i.toString() : 'Infinity' + ) + + const bigint = packAndUnpack(i, { useBigInt: true }) + expect(typeof bigint).toBe('bigint') + expect(bigint.toString()).toBe(i.toString()) + } + }) + + it('should pack integer with negative numbers', () => { + let n, i + // negative numbers + for (n = 0; n <= 63; n += 1) { + i = int(-Math.pow(2, n)) + expect(packAndUnpack(i).toString()).toBe(i.toString()) + + const unpackedLossyInteger = packAndUnpack(i, { + disableLosslessIntegers: true + }) + expect(typeof unpackedLossyInteger).toBe('number') + expect(unpackedLossyInteger.toString()).toBe( + i.inSafeRange() ? i.toString() : '-Infinity' + ) + + const bigint = packAndUnpack(i, { useBigInt: true }) + expect(typeof bigint).toBe('bigint') + expect(bigint.toString()).toBe(i.toString()) + } + }) + + it('should pack BigInt with small numbers', () => { + let n, i + // test small numbers + for (n = -999; n <= 999; n += 1) { + i = BigInt(n) + expect(packAndUnpack(i).toString()).toBe(i.toString()) + expect( + packAndUnpack(i, { disableLosslessIntegers: true }).toString() + ).toBe(i.toString()) + expect(packAndUnpack(i, { useBigInt: true }).toString()).toBe( + i.toString() + ) + } + }) + + it('should pack BigInt with positive numbers', () => { + let n, i + // positive numbers + for (n = 16; n <= 16; n += 1) { + i = BigInt(Math.pow(2, n)) + expect(packAndUnpack(i).toString()).toBe(i.toString()) + + const unpackedLossyInteger = packAndUnpack(i, { + disableLosslessIntegers: true + }) + expect(typeof unpackedLossyInteger).toBe('number') + expect(unpackedLossyInteger.toString()).toBe( + int(i).inSafeRange() ? i.toString() : 'Infinity' + ) + + const bigint = packAndUnpack(i, { useBigInt: true }) + expect(typeof bigint).toBe('bigint') + expect(bigint.toString()).toBe(i.toString()) + } + }) + + it('should pack BigInt with negative numbers', () => { + let n, i + // negative numbers + for (n = 0; n <= 63; n += 1) { + i = BigInt(-Math.pow(2, n)) + expect(packAndUnpack(i).toString()).toBe(i.toString()) + + const unpackedLossyInteger = packAndUnpack(i, { + disableLosslessIntegers: true + }) + expect(typeof unpackedLossyInteger).toBe('number') + expect(unpackedLossyInteger.toString()).toBe( + int(i).inSafeRange() ? i.toString() : '-Infinity' + ) + + const bigint = packAndUnpack(i, { useBigInt: true }) + expect(typeof bigint).toBe('bigint') + expect(bigint.toString()).toBe(i.toString()) + } + }) + + it('should pack strings', () => { + expect(packAndUnpack('')).toBe('') + expect(packAndUnpack('abcdefg123567')).toBe('abcdefg123567') + const str = Array(65536 + 1).join('a') // 2 ^ 16 + 1 + expect(packAndUnpack(str, { bufferSize: str.length + 8 })).toBe(str) + }) + + it('should pack structures', () => { + expect(packAndUnpack(new Structure(1, ['Hello, world!!!'])).fields[0]).toBe( + 'Hello, world!!!' + ) + }) + + it('should pack lists', () => { + const list = ['a', 'b'] + const unpacked = packAndUnpack(list) + expect(unpacked[0]).toBe(list[0]) + expect(unpacked[1]).toBe(list[1]) + }) + + it('should pack long lists', () => { + const listLength = 256 + const list = [] + for (let i = 0; i < listLength; i++) { + list.push(null) + } + const unpacked = packAndUnpack(list, { bufferSize: 1400 }) + expect(unpacked[0]).toBe(list[0]) + expect(unpacked[1]).toBe(list[1]) + }) + + it.each( + validNodesAndConfig() + )('should unpack Nodes', (struct, expectedNode, config) => { + const node = packAndUnpack(struct, config) + + expect(node).toEqual(expectedNode) + }) + + it.each( + invalidNodesConfig() + )('should thrown error for unpacking invalid Nodes', (struct) => { + expect(() => packAndUnpack(struct)).toThrow() + }) + + function validNodesAndConfig () { + function validWithNumber () { + const identity = 1 + const labels = ['a', 'b'] + const properties = { 'a': 1, 'b': 2 } + const elementId = 'element_id_1' + const expectedNode = new Node(identity, labels, properties, elementId) + const nodeStruct = new Structure(0x4e, [ + identity, labels, properties, elementId + ]) + return [nodeStruct, expectedNode, { disableLosslessIntegers: true, useBigInt: false }] + } + + function validWithInt () { + const identity = int(1) + const labels = ['a', 'b'] + const properties = { 'a': int(1), 'b': int(2) } + const elementId = 'element_id_1' + const expectedNode = new Node(identity, labels, properties, elementId) + const nodeStruct = new Structure(0x4e, [ + identity, labels, properties, elementId + ]) + return [nodeStruct, expectedNode, { disableLosslessIntegers: false, useBigInt: false }] + } + + function validWithBigInt () { + const identity = BigInt(1) + const labels = ['a', 'b'] + const properties = { 'a': BigInt(1), 'b': BigInt(2) } + const elementId = 'element_id_1' + const expectedNode = new Node(identity, labels, properties, elementId) + const nodeStruct = new Structure(0x4e, [ + identity, labels, properties, elementId + ]) + return [nodeStruct, expectedNode, { disableLosslessIntegers: false, useBigInt: true }] + } + + return [ + validWithNumber(), + validWithInt(), + validWithBigInt() + ] + } + + function invalidNodesConfig () { + return [ + [ new Structure(0x4e, [1, ['a', 'b'], { 'a': 1, 'b': 2 }]) ], + [ new Structure(0x4e, [1, ['a', 'b'], { 'a': 1, 'b': 2 }, 'elementId', 'myId']) ], + ] + } + }) + + function packAndUnpack ( + val, + { bufferSize = 128, disableLosslessIntegers = false, useBigInt = false } = {} + ) { + const buffer = alloc(bufferSize) + new Packer(buffer).packable(val)() + buffer.reset() + return new Unpacker(disableLosslessIntegers, useBigInt).unpack(buffer) + } + \ No newline at end of file From 97f657c53780d5831ff9984543a42053d8b5acb9 Mon Sep 17 00:00:00 2001 From: Antonio Barcelos Date: Tue, 1 Mar 2022 17:05:39 +0100 Subject: [PATCH 07/17] Add `Relationship` v5 unpack --- .../src/packstream/packstream-v3.js | 30 ++++--- .../test/packstream/packstream-v3.test.js | 88 ++++++++++++++++++- 2 files changed, 106 insertions(+), 12 deletions(-) diff --git a/packages/bolt-connection/src/packstream/packstream-v3.js b/packages/bolt-connection/src/packstream/packstream-v3.js index 74f31c4b4..27f9778b2 100644 --- a/packages/bolt-connection/src/packstream/packstream-v3.js +++ b/packages/bolt-connection/src/packstream/packstream-v3.js @@ -19,25 +19,17 @@ import * as v2 from './packstream-v2'; import { - Node + Node, + Relationship } from 'neo4j-driver-core' const NODE_STRUCT_SIZE = 4 - +const RELATIONSHIP_STRUCT_SIZE = 8 export class Packer extends v2.Packer { // This implementation is the same } export class Unpacker extends v2.Unpacker { - /** - * @constructor - * @param {boolean} disableLosslessIntegers if this unpacker should convert all received integers to native JS numbers. - * @param {boolean} useBigInt if this unpacker should convert all received integers to Bigint - */ - constructor (disableLosslessIntegers = false, useBigInt = false) { - super(disableLosslessIntegers, useBigInt) - } - _unpackNode (structSize, buffer) { this._verifyStructSize('Node', NODE_STRUCT_SIZE, structSize) @@ -48,5 +40,21 @@ export class Unpacker extends v2.Unpacker { this.unpack(buffer) // ElementId ) } + + _unpackRelationship (structSize, buffer) { + this._verifyStructSize('Relationship', RELATIONSHIP_STRUCT_SIZE, structSize) + + return new Relationship( + this.unpack(buffer), // Identity + this.unpack(buffer), // Start Node Identity + this.unpack(buffer), // End Node Identity + this.unpack(buffer), // Type + this.unpack(buffer), // Properties, + this.unpack(buffer), // ElementId + this.unpack(buffer), // Start Node Element Id + this.unpack(buffer) // End Node Element Id + ) + } + } diff --git a/packages/bolt-connection/test/packstream/packstream-v3.test.js b/packages/bolt-connection/test/packstream/packstream-v3.test.js index d9e0b6525..c688bbd4e 100644 --- a/packages/bolt-connection/test/packstream/packstream-v3.test.js +++ b/packages/bolt-connection/test/packstream/packstream-v3.test.js @@ -21,7 +21,7 @@ import { alloc } from '../../src/channel' import { Packer, Unpacker } from '../../src/packstream/packstream-v3' import { Structure } from '../../src/packstream/packstream-v1' - import { Node, int } from 'neo4j-driver-core' + import { Node, int, Relationship } from 'neo4j-driver-core' describe('#unit PackStreamV3', () => { it('should pack integers with small numbers', () => { @@ -198,6 +198,20 @@ expect(() => packAndUnpack(struct)).toThrow() }) + it.each( + validRelationshipsAndConfig() + )('should unpack Relationships', (struct, expectedRelationship, config) => { + const releationship = packAndUnpack(struct, config) + + expect(releationship).toEqual(expectedRelationship) + }) + + it.each( + invalidRelationshipsConfig() + )('should thrown error for unpacking invalid Relationships', (struct) => { + expect(() => packAndUnpack(struct)).toThrow() + }) + function validNodesAndConfig () { function validWithNumber () { const identity = 1 @@ -248,6 +262,78 @@ [ new Structure(0x4e, [1, ['a', 'b'], { 'a': 1, 'b': 2 }, 'elementId', 'myId']) ], ] } + + function validRelationshipsAndConfig () { + function validWithNumber () { + const identity = 1 + const start = 2 + const end = 3 + const type = 'KNOWS' + const properties = { 'a': 1, 'b': 2 } + const elementId = 'element_id_1' + const startNodeElementId = 'element_id_2' + const endNodeElementId = 'element_id_3' + const expectedRel = new Relationship( + identity, start, end, type, properties, + elementId, startNodeElementId, endNodeElementId) + const relStruct = new Structure(0x52, [ + identity, start, end, type, properties, elementId, + startNodeElementId, endNodeElementId + ]) + return [relStruct, expectedRel, { disableLosslessIntegers: true, useBigInt: false }] + } + + function validWithInt () { + const identity = int(1) + const start = int(2) + const end = int(3) + const type = 'KNOWS' + const properties = { 'a': int(1), 'b': int(2) } + const elementId = 'element_id_1' + const startNodeElementId = 'element_id_2' + const endNodeElementId = 'element_id_3' + const expectedRel = new Relationship( + identity, start, end, type, properties, + elementId, startNodeElementId, endNodeElementId) + const relStruct = new Structure(0x52, [ + identity, start, end, type, properties, elementId, + startNodeElementId, endNodeElementId + ]) + return [relStruct, expectedRel, { disableLosslessIntegers: false, useBigInt: false }] + } + + function validWithBigInt () { + const identity = BigInt(1) + const start = BigInt(2) + const end = BigInt(3) + const type = 'KNOWS' + const properties = { 'a': BigInt(1), 'b': BigInt(2) } + const elementId = 'element_id_1' + const startNodeElementId = 'element_id_2' + const endNodeElementId = 'element_id_3' + const expectedRel = new Relationship( + identity, start, end, type, properties, + elementId, startNodeElementId, endNodeElementId) + const relStruct = new Structure(0x52, [ + identity, start, end, type, properties, elementId, + startNodeElementId, endNodeElementId + ]) + return [relStruct, expectedRel, { disableLosslessIntegers: false, useBigInt: true }] + } + + return [ + validWithNumber(), + validWithInt(), + validWithBigInt() + ] + } + + function invalidRelationshipsConfig () { + return [ + [ new Structure(0x52, [1, 2, 3, 'rel', { 'a': 1, 'b': 2 }, 'elementId', 'startNodeId'])], + [ new Structure(0x52, [1, 2, 3, 'rel', { 'a': 1, 'b': 2 }, 'elementId', 'startNodeId', 'endNodeId', 'myId'])], + ] + } }) function packAndUnpack ( From 8f1b440be7869a551fa74aa45834b34bba52b887 Mon Sep 17 00:00:00 2001 From: Antonio Barcelos Date: Tue, 1 Mar 2022 17:07:01 +0100 Subject: [PATCH 08/17] Format file --- .../test/packstream/packstream-v3.test.js | 511 +++++++++--------- 1 file changed, 255 insertions(+), 256 deletions(-) diff --git a/packages/bolt-connection/test/packstream/packstream-v3.test.js b/packages/bolt-connection/test/packstream/packstream-v3.test.js index c688bbd4e..5c6e15096 100644 --- a/packages/bolt-connection/test/packstream/packstream-v3.test.js +++ b/packages/bolt-connection/test/packstream/packstream-v3.test.js @@ -17,188 +17,188 @@ * limitations under the License. */ - import { int, Integer } from 'neo4j-driver-core' - import { alloc } from '../../src/channel' - import { Packer, Unpacker } from '../../src/packstream/packstream-v3' - import { Structure } from '../../src/packstream/packstream-v1' - import { Node, int, Relationship } from 'neo4j-driver-core' - - describe('#unit PackStreamV3', () => { - it('should pack integers with small numbers', () => { - let n, i - // test small numbers - for (n = -999; n <= 999; n += 1) { - i = int(n) - expect(packAndUnpack(i).toString()).toBe(i.toString()) - expect( - packAndUnpack(i, { disableLosslessIntegers: true }).toString() - ).toBe(i.toString()) - expect(packAndUnpack(i, { useBigInt: true }).toString()).toBe( - i.toString() - ) - } - }) - - it('should pack integers with small numbers created with Integer', () => { - let n, i - // test small numbers - for (n = -10; n <= 10; n += 1) { - i = new Integer(n, 0) - expect(packAndUnpack(i).toString()).toBe(i.toString()) - expect( - packAndUnpack(i, { disableLosslessIntegers: true }).toString() - ).toBe(i.toString()) - expect(packAndUnpack(i, { useBigInt: true }).toString()).toBe( - i.toString() - ) - } - }) - - it('should pack integers with positive numbers', () => { - let n, i - // positive numbers - for (n = 16; n <= 16; n += 1) { - i = int(Math.pow(2, n)) - expect(packAndUnpack(i).toString()).toBe(i.toString()) - - const unpackedLossyInteger = packAndUnpack(i, { - disableLosslessIntegers: true - }) - expect(typeof unpackedLossyInteger).toBe('number') - expect(unpackedLossyInteger.toString()).toBe( - i.inSafeRange() ? i.toString() : 'Infinity' - ) - - const bigint = packAndUnpack(i, { useBigInt: true }) - expect(typeof bigint).toBe('bigint') - expect(bigint.toString()).toBe(i.toString()) - } - }) - - it('should pack integer with negative numbers', () => { - let n, i - // negative numbers - for (n = 0; n <= 63; n += 1) { - i = int(-Math.pow(2, n)) - expect(packAndUnpack(i).toString()).toBe(i.toString()) - - const unpackedLossyInteger = packAndUnpack(i, { - disableLosslessIntegers: true - }) - expect(typeof unpackedLossyInteger).toBe('number') - expect(unpackedLossyInteger.toString()).toBe( - i.inSafeRange() ? i.toString() : '-Infinity' - ) - - const bigint = packAndUnpack(i, { useBigInt: true }) - expect(typeof bigint).toBe('bigint') - expect(bigint.toString()).toBe(i.toString()) - } - }) - - it('should pack BigInt with small numbers', () => { - let n, i - // test small numbers - for (n = -999; n <= 999; n += 1) { - i = BigInt(n) - expect(packAndUnpack(i).toString()).toBe(i.toString()) - expect( - packAndUnpack(i, { disableLosslessIntegers: true }).toString() - ).toBe(i.toString()) - expect(packAndUnpack(i, { useBigInt: true }).toString()).toBe( - i.toString() - ) - } - }) - - it('should pack BigInt with positive numbers', () => { - let n, i - // positive numbers - for (n = 16; n <= 16; n += 1) { - i = BigInt(Math.pow(2, n)) - expect(packAndUnpack(i).toString()).toBe(i.toString()) - - const unpackedLossyInteger = packAndUnpack(i, { - disableLosslessIntegers: true - }) - expect(typeof unpackedLossyInteger).toBe('number') - expect(unpackedLossyInteger.toString()).toBe( - int(i).inSafeRange() ? i.toString() : 'Infinity' - ) - - const bigint = packAndUnpack(i, { useBigInt: true }) - expect(typeof bigint).toBe('bigint') - expect(bigint.toString()).toBe(i.toString()) - } - }) - - it('should pack BigInt with negative numbers', () => { - let n, i - // negative numbers - for (n = 0; n <= 63; n += 1) { - i = BigInt(-Math.pow(2, n)) - expect(packAndUnpack(i).toString()).toBe(i.toString()) - - const unpackedLossyInteger = packAndUnpack(i, { - disableLosslessIntegers: true - }) - expect(typeof unpackedLossyInteger).toBe('number') - expect(unpackedLossyInteger.toString()).toBe( - int(i).inSafeRange() ? i.toString() : '-Infinity' - ) - - const bigint = packAndUnpack(i, { useBigInt: true }) - expect(typeof bigint).toBe('bigint') - expect(bigint.toString()).toBe(i.toString()) - } - }) - - it('should pack strings', () => { - expect(packAndUnpack('')).toBe('') - expect(packAndUnpack('abcdefg123567')).toBe('abcdefg123567') - const str = Array(65536 + 1).join('a') // 2 ^ 16 + 1 - expect(packAndUnpack(str, { bufferSize: str.length + 8 })).toBe(str) - }) - - it('should pack structures', () => { - expect(packAndUnpack(new Structure(1, ['Hello, world!!!'])).fields[0]).toBe( - 'Hello, world!!!' - ) - }) - - it('should pack lists', () => { - const list = ['a', 'b'] - const unpacked = packAndUnpack(list) - expect(unpacked[0]).toBe(list[0]) - expect(unpacked[1]).toBe(list[1]) - }) - - it('should pack long lists', () => { - const listLength = 256 - const list = [] - for (let i = 0; i < listLength; i++) { - list.push(null) - } - const unpacked = packAndUnpack(list, { bufferSize: 1400 }) - expect(unpacked[0]).toBe(list[0]) - expect(unpacked[1]).toBe(list[1]) - }) - - it.each( - validNodesAndConfig() - )('should unpack Nodes', (struct, expectedNode, config) => { - const node = packAndUnpack(struct, config) - - expect(node).toEqual(expectedNode) - }) - - it.each( - invalidNodesConfig() - )('should thrown error for unpacking invalid Nodes', (struct) => { - expect(() => packAndUnpack(struct)).toThrow() - }) - - it.each( +import { int, Integer } from 'neo4j-driver-core' +import { alloc } from '../../src/channel' +import { Packer, Unpacker } from '../../src/packstream/packstream-v3' +import { Structure } from '../../src/packstream/packstream-v1' +import { Node, int, Relationship } from 'neo4j-driver-core' + +describe('#unit PackStreamV3', () => { + it('should pack integers with small numbers', () => { + let n, i + // test small numbers + for (n = -999; n <= 999; n += 1) { + i = int(n) + expect(packAndUnpack(i).toString()).toBe(i.toString()) + expect( + packAndUnpack(i, { disableLosslessIntegers: true }).toString() + ).toBe(i.toString()) + expect(packAndUnpack(i, { useBigInt: true }).toString()).toBe( + i.toString() + ) + } + }) + + it('should pack integers with small numbers created with Integer', () => { + let n, i + // test small numbers + for (n = -10; n <= 10; n += 1) { + i = new Integer(n, 0) + expect(packAndUnpack(i).toString()).toBe(i.toString()) + expect( + packAndUnpack(i, { disableLosslessIntegers: true }).toString() + ).toBe(i.toString()) + expect(packAndUnpack(i, { useBigInt: true }).toString()).toBe( + i.toString() + ) + } + }) + + it('should pack integers with positive numbers', () => { + let n, i + // positive numbers + for (n = 16; n <= 16; n += 1) { + i = int(Math.pow(2, n)) + expect(packAndUnpack(i).toString()).toBe(i.toString()) + + const unpackedLossyInteger = packAndUnpack(i, { + disableLosslessIntegers: true + }) + expect(typeof unpackedLossyInteger).toBe('number') + expect(unpackedLossyInteger.toString()).toBe( + i.inSafeRange() ? i.toString() : 'Infinity' + ) + + const bigint = packAndUnpack(i, { useBigInt: true }) + expect(typeof bigint).toBe('bigint') + expect(bigint.toString()).toBe(i.toString()) + } + }) + + it('should pack integer with negative numbers', () => { + let n, i + // negative numbers + for (n = 0; n <= 63; n += 1) { + i = int(-Math.pow(2, n)) + expect(packAndUnpack(i).toString()).toBe(i.toString()) + + const unpackedLossyInteger = packAndUnpack(i, { + disableLosslessIntegers: true + }) + expect(typeof unpackedLossyInteger).toBe('number') + expect(unpackedLossyInteger.toString()).toBe( + i.inSafeRange() ? i.toString() : '-Infinity' + ) + + const bigint = packAndUnpack(i, { useBigInt: true }) + expect(typeof bigint).toBe('bigint') + expect(bigint.toString()).toBe(i.toString()) + } + }) + + it('should pack BigInt with small numbers', () => { + let n, i + // test small numbers + for (n = -999; n <= 999; n += 1) { + i = BigInt(n) + expect(packAndUnpack(i).toString()).toBe(i.toString()) + expect( + packAndUnpack(i, { disableLosslessIntegers: true }).toString() + ).toBe(i.toString()) + expect(packAndUnpack(i, { useBigInt: true }).toString()).toBe( + i.toString() + ) + } + }) + + it('should pack BigInt with positive numbers', () => { + let n, i + // positive numbers + for (n = 16; n <= 16; n += 1) { + i = BigInt(Math.pow(2, n)) + expect(packAndUnpack(i).toString()).toBe(i.toString()) + + const unpackedLossyInteger = packAndUnpack(i, { + disableLosslessIntegers: true + }) + expect(typeof unpackedLossyInteger).toBe('number') + expect(unpackedLossyInteger.toString()).toBe( + int(i).inSafeRange() ? i.toString() : 'Infinity' + ) + + const bigint = packAndUnpack(i, { useBigInt: true }) + expect(typeof bigint).toBe('bigint') + expect(bigint.toString()).toBe(i.toString()) + } + }) + + it('should pack BigInt with negative numbers', () => { + let n, i + // negative numbers + for (n = 0; n <= 63; n += 1) { + i = BigInt(-Math.pow(2, n)) + expect(packAndUnpack(i).toString()).toBe(i.toString()) + + const unpackedLossyInteger = packAndUnpack(i, { + disableLosslessIntegers: true + }) + expect(typeof unpackedLossyInteger).toBe('number') + expect(unpackedLossyInteger.toString()).toBe( + int(i).inSafeRange() ? i.toString() : '-Infinity' + ) + + const bigint = packAndUnpack(i, { useBigInt: true }) + expect(typeof bigint).toBe('bigint') + expect(bigint.toString()).toBe(i.toString()) + } + }) + + it('should pack strings', () => { + expect(packAndUnpack('')).toBe('') + expect(packAndUnpack('abcdefg123567')).toBe('abcdefg123567') + const str = Array(65536 + 1).join('a') // 2 ^ 16 + 1 + expect(packAndUnpack(str, { bufferSize: str.length + 8 })).toBe(str) + }) + + it('should pack structures', () => { + expect(packAndUnpack(new Structure(1, ['Hello, world!!!'])).fields[0]).toBe( + 'Hello, world!!!' + ) + }) + + it('should pack lists', () => { + const list = ['a', 'b'] + const unpacked = packAndUnpack(list) + expect(unpacked[0]).toBe(list[0]) + expect(unpacked[1]).toBe(list[1]) + }) + + it('should pack long lists', () => { + const listLength = 256 + const list = [] + for (let i = 0; i < listLength; i++) { + list.push(null) + } + const unpacked = packAndUnpack(list, { bufferSize: 1400 }) + expect(unpacked[0]).toBe(list[0]) + expect(unpacked[1]).toBe(list[1]) + }) + + it.each( + validNodesAndConfig() + )('should unpack Nodes', (struct, expectedNode, config) => { + const node = packAndUnpack(struct, config) + + expect(node).toEqual(expectedNode) + }) + + it.each( + invalidNodesConfig() + )('should thrown error for unpacking invalid Nodes', (struct) => { + expect(() => packAndUnpack(struct)).toThrow() + }) + + it.each( validRelationshipsAndConfig() )('should unpack Relationships', (struct, expectedRelationship, config) => { const releationship = packAndUnpack(struct, config) @@ -209,62 +209,62 @@ it.each( invalidRelationshipsConfig() )('should thrown error for unpacking invalid Relationships', (struct) => { - expect(() => packAndUnpack(struct)).toThrow() + expect(() => packAndUnpack(struct)).toThrow() }) - function validNodesAndConfig () { - function validWithNumber () { - const identity = 1 - const labels = ['a', 'b'] - const properties = { 'a': 1, 'b': 2 } - const elementId = 'element_id_1' - const expectedNode = new Node(identity, labels, properties, elementId) - const nodeStruct = new Structure(0x4e, [ - identity, labels, properties, elementId - ]) - return [nodeStruct, expectedNode, { disableLosslessIntegers: true, useBigInt: false }] - } - - function validWithInt () { - const identity = int(1) - const labels = ['a', 'b'] - const properties = { 'a': int(1), 'b': int(2) } - const elementId = 'element_id_1' - const expectedNode = new Node(identity, labels, properties, elementId) - const nodeStruct = new Structure(0x4e, [ - identity, labels, properties, elementId - ]) - return [nodeStruct, expectedNode, { disableLosslessIntegers: false, useBigInt: false }] - } - - function validWithBigInt () { - const identity = BigInt(1) - const labels = ['a', 'b'] - const properties = { 'a': BigInt(1), 'b': BigInt(2) } - const elementId = 'element_id_1' - const expectedNode = new Node(identity, labels, properties, elementId) - const nodeStruct = new Structure(0x4e, [ - identity, labels, properties, elementId - ]) - return [nodeStruct, expectedNode, { disableLosslessIntegers: false, useBigInt: true }] - } - - return [ - validWithNumber(), - validWithInt(), - validWithBigInt() - ] - } - - function invalidNodesConfig () { - return [ - [ new Structure(0x4e, [1, ['a', 'b'], { 'a': 1, 'b': 2 }]) ], - [ new Structure(0x4e, [1, ['a', 'b'], { 'a': 1, 'b': 2 }, 'elementId', 'myId']) ], - ] - } - - function validRelationshipsAndConfig () { - function validWithNumber () { + function validNodesAndConfig() { + function validWithNumber() { + const identity = 1 + const labels = ['a', 'b'] + const properties = { 'a': 1, 'b': 2 } + const elementId = 'element_id_1' + const expectedNode = new Node(identity, labels, properties, elementId) + const nodeStruct = new Structure(0x4e, [ + identity, labels, properties, elementId + ]) + return [nodeStruct, expectedNode, { disableLosslessIntegers: true, useBigInt: false }] + } + + function validWithInt() { + const identity = int(1) + const labels = ['a', 'b'] + const properties = { 'a': int(1), 'b': int(2) } + const elementId = 'element_id_1' + const expectedNode = new Node(identity, labels, properties, elementId) + const nodeStruct = new Structure(0x4e, [ + identity, labels, properties, elementId + ]) + return [nodeStruct, expectedNode, { disableLosslessIntegers: false, useBigInt: false }] + } + + function validWithBigInt() { + const identity = BigInt(1) + const labels = ['a', 'b'] + const properties = { 'a': BigInt(1), 'b': BigInt(2) } + const elementId = 'element_id_1' + const expectedNode = new Node(identity, labels, properties, elementId) + const nodeStruct = new Structure(0x4e, [ + identity, labels, properties, elementId + ]) + return [nodeStruct, expectedNode, { disableLosslessIntegers: false, useBigInt: true }] + } + + return [ + validWithNumber(), + validWithInt(), + validWithBigInt() + ] + } + + function invalidNodesConfig() { + return [ + [new Structure(0x4e, [1, ['a', 'b'], { 'a': 1, 'b': 2 }])], + [new Structure(0x4e, [1, ['a', 'b'], { 'a': 1, 'b': 2 }, 'elementId', 'myId'])], + ] + } + + function validRelationshipsAndConfig() { + function validWithNumber() { const identity = 1 const start = 2 const end = 3 @@ -274,7 +274,7 @@ const startNodeElementId = 'element_id_2' const endNodeElementId = 'element_id_3' const expectedRel = new Relationship( - identity, start, end, type, properties, + identity, start, end, type, properties, elementId, startNodeElementId, endNodeElementId) const relStruct = new Structure(0x52, [ identity, start, end, type, properties, elementId, @@ -283,7 +283,7 @@ return [relStruct, expectedRel, { disableLosslessIntegers: true, useBigInt: false }] } - function validWithInt () { + function validWithInt() { const identity = int(1) const start = int(2) const end = int(3) @@ -293,7 +293,7 @@ const startNodeElementId = 'element_id_2' const endNodeElementId = 'element_id_3' const expectedRel = new Relationship( - identity, start, end, type, properties, + identity, start, end, type, properties, elementId, startNodeElementId, endNodeElementId) const relStruct = new Structure(0x52, [ identity, start, end, type, properties, elementId, @@ -302,7 +302,7 @@ return [relStruct, expectedRel, { disableLosslessIntegers: false, useBigInt: false }] } - function validWithBigInt () { + function validWithBigInt() { const identity = BigInt(1) const start = BigInt(2) const end = BigInt(3) @@ -312,7 +312,7 @@ const startNodeElementId = 'element_id_2' const endNodeElementId = 'element_id_3' const expectedRel = new Relationship( - identity, start, end, type, properties, + identity, start, end, type, properties, elementId, startNodeElementId, endNodeElementId) const relStruct = new Structure(0x52, [ identity, start, end, type, properties, elementId, @@ -328,21 +328,20 @@ ] } - function invalidRelationshipsConfig () { + function invalidRelationshipsConfig() { return [ - [ new Structure(0x52, [1, 2, 3, 'rel', { 'a': 1, 'b': 2 }, 'elementId', 'startNodeId'])], - [ new Structure(0x52, [1, 2, 3, 'rel', { 'a': 1, 'b': 2 }, 'elementId', 'startNodeId', 'endNodeId', 'myId'])], + [new Structure(0x52, [1, 2, 3, 'rel', { 'a': 1, 'b': 2 }, 'elementId', 'startNodeId'])], + [new Structure(0x52, [1, 2, 3, 'rel', { 'a': 1, 'b': 2 }, 'elementId', 'startNodeId', 'endNodeId', 'myId'])], ] } - }) - - function packAndUnpack ( - val, - { bufferSize = 128, disableLosslessIntegers = false, useBigInt = false } = {} - ) { - const buffer = alloc(bufferSize) - new Packer(buffer).packable(val)() - buffer.reset() - return new Unpacker(disableLosslessIntegers, useBigInt).unpack(buffer) - } - \ No newline at end of file +}) + +function packAndUnpack( + val, + { bufferSize = 128, disableLosslessIntegers = false, useBigInt = false } = {} +) { + const buffer = alloc(bufferSize) + new Packer(buffer).packable(val)() + buffer.reset() + return new Unpacker(disableLosslessIntegers, useBigInt).unpack(buffer) +} From a4253ee628a385782c2dc113c796c8243e3f4a40 Mon Sep 17 00:00:00 2001 From: Antonio Barcelos Date: Tue, 1 Mar 2022 17:20:38 +0100 Subject: [PATCH 09/17] Unpack UnboundRelationship v5 and fix path contruction --- .../src/packstream/packstream-v1.js | 12 ++-- .../src/packstream/packstream-v3.js | 22 +++++- .../test/packstream/packstream-v3.test.js | 67 ++++++++++++++++++- 3 files changed, 91 insertions(+), 10 deletions(-) diff --git a/packages/bolt-connection/src/packstream/packstream-v1.js b/packages/bolt-connection/src/packstream/packstream-v1.js index b1361cac5..5f5893e9d 100644 --- a/packages/bolt-connection/src/packstream/packstream-v1.js +++ b/packages/bolt-connection/src/packstream/packstream-v1.js @@ -646,18 +646,18 @@ class Unpacker { // information about their start and end nodes, that's instead // inferred from the path sequence. This is us inferring (and, // for performance reasons remembering) the start/end of a rel. - rels[relIndex - 1] = rel = rel.bind( - prevNode.identity, - nextNode.identity + rels[relIndex - 1] = rel = rel.bindTo( + prevNode, + nextNode ) } } else { rel = rels[-relIndex - 1] if (rel instanceof UnboundRelationship) { // See above - rels[-relIndex - 1] = rel = rel.bind( - nextNode.identity, - prevNode.identity + rels[-relIndex - 1] = rel = rel.bindTo( + nextNode, + prevNode ) } } diff --git a/packages/bolt-connection/src/packstream/packstream-v3.js b/packages/bolt-connection/src/packstream/packstream-v3.js index 27f9778b2..b9e8b35f4 100644 --- a/packages/bolt-connection/src/packstream/packstream-v3.js +++ b/packages/bolt-connection/src/packstream/packstream-v3.js @@ -20,11 +20,14 @@ import * as v2 from './packstream-v2'; import { Node, - Relationship + Relationship, + UnboundRelationship } from 'neo4j-driver-core' const NODE_STRUCT_SIZE = 4 const RELATIONSHIP_STRUCT_SIZE = 8 +const UNBOUND_RELATIONSHIP_STRUCT_SIZE = 4 + export class Packer extends v2.Packer { // This implementation is the same } @@ -55,6 +58,19 @@ export class Unpacker extends v2.Unpacker { this.unpack(buffer) // End Node Element Id ) } - - + + _unpackUnboundRelationship (structSize, buffer) { + this._verifyStructSize( + 'UnboundRelationship', + UNBOUND_RELATIONSHIP_STRUCT_SIZE, + structSize + ) + + return new UnboundRelationship( + this.unpack(buffer), // Identity + this.unpack(buffer), // Type + this.unpack(buffer) // Properties, + this.unpack(buffer) // ElementId + ) + } } diff --git a/packages/bolt-connection/test/packstream/packstream-v3.test.js b/packages/bolt-connection/test/packstream/packstream-v3.test.js index 5c6e15096..866e97598 100644 --- a/packages/bolt-connection/test/packstream/packstream-v3.test.js +++ b/packages/bolt-connection/test/packstream/packstream-v3.test.js @@ -21,7 +21,7 @@ import { int, Integer } from 'neo4j-driver-core' import { alloc } from '../../src/channel' import { Packer, Unpacker } from '../../src/packstream/packstream-v3' import { Structure } from '../../src/packstream/packstream-v1' -import { Node, int, Relationship } from 'neo4j-driver-core' +import { Node, int, Relationship, UnboundRelationship } from 'neo4j-driver-core' describe('#unit PackStreamV3', () => { it('should pack integers with small numbers', () => { @@ -212,6 +212,20 @@ describe('#unit PackStreamV3', () => { expect(() => packAndUnpack(struct)).toThrow() }) + it.each( + validUnboundRelationshipsAndConfig() + )('should unpack UnboundRelationships', (struct, expectedRelationship, config) => { + const releationship = packAndUnpack(struct, config) + + expect(releationship).toEqual(expectedRelationship) + }) + + it.each( + invalidUnboundRelationshipsConfig() + )('should thrown error for unpacking invalid UnboundRelationships', (struct) => { + expect(() => packAndUnpack(struct)).toThrow() + }) + function validNodesAndConfig() { function validWithNumber() { const identity = 1 @@ -334,6 +348,57 @@ describe('#unit PackStreamV3', () => { [new Structure(0x52, [1, 2, 3, 'rel', { 'a': 1, 'b': 2 }, 'elementId', 'startNodeId', 'endNodeId', 'myId'])], ] } + + function validUnboundRelationshipsAndConfig() { + function validWithNumber() { + const identity = 1 + const type = 'DOESNT_KNOW' + const properties = { 'a': 1, 'b': 2 } + const elementId = 'element_id_1' + const expectedUnboundRel = new UnboundRelationship(identity, type, properties, elementId) + const struct = new Structure(0x72, [ + identity, type, properties, elementId + ]) + return [struct, expectedUnboundRel, { disableLosslessIntegers: true, useBigInt: false }] + } + + function validWithInt() { + const identity = int(1) + const type = 'DOESNT_KNOW' + const properties = { 'a': int(1), 'b': int(2) } + const elementId = 'element_id_1' + const expectedUnboundRel = new UnboundRelationship(identity, type, properties, elementId) + const struct = new Structure(0x72, [ + identity, type, properties, elementId + ]) + return [struct, expectedUnboundRel, { disableLosslessIntegers: false, useBigInt: false }] + } + + function validWithBigInt() { + const identity = BigInt(1) + const type = 'DOESNT_KNOW' + const properties = { 'a': BigInt(1), 'b': BigInt(2) } + const elementId = 'element_id_1' + const expectedUnboundRel = new UnboundRelationship(identity, type, properties, elementId) + const struct = new Structure(0x72, [ + identity, type, properties, elementId + ]) + return [struct, expectedUnboundRel, { disableLosslessIntegers: false, useBigInt: true }] + } + + return [ + validWithNumber(), + validWithInt(), + validWithBigInt() + ] + } + + function invalidUnboundRelationshipsConfig() { + return [ + [new Structure(0x72, [1, 'DOESNT_KNOW', { 'a': 1, 'b': 2 }])], + [new Structure(0x72, [1, 'DOESNT_KNOW', { 'a': 1, 'b': 2 }, 'elementId', 'myId'])], + ] + } }) function packAndUnpack( From 2f2957637a1be3a6390e4bcc4a6f70d1cd1ac086 Mon Sep 17 00:00:00 2001 From: Antonio Barcelos Date: Tue, 1 Mar 2022 17:23:44 +0100 Subject: [PATCH 10/17] Fix typo --- packages/bolt-connection/src/packstream/packstream-v3.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/bolt-connection/src/packstream/packstream-v3.js b/packages/bolt-connection/src/packstream/packstream-v3.js index b9e8b35f4..08f96b1c7 100644 --- a/packages/bolt-connection/src/packstream/packstream-v3.js +++ b/packages/bolt-connection/src/packstream/packstream-v3.js @@ -69,7 +69,7 @@ export class Unpacker extends v2.Unpacker { return new UnboundRelationship( this.unpack(buffer), // Identity this.unpack(buffer), // Type - this.unpack(buffer) // Properties, + this.unpack(buffer), // Properties this.unpack(buffer) // ElementId ) } From 4433d7d6260cefa9d35e9bd56de94e0bc30ff1e2 Mon Sep 17 00:00:00 2001 From: Antonio Barcelos Date: Tue, 1 Mar 2022 17:37:50 +0100 Subject: [PATCH 11/17] Introduce bolt protocol v5 --- .../src/bolt/bolt-protocol-v5x0.js | 40 ++ .../bolt-connection/src/packstream/index.js | 3 +- .../{packstream-v3.js => packstream-v5.js} | 0 .../test/bolt/bolt-protocol-v5x0.test.js | 369 ++++++++++++++++++ ...tream-v3.test.js => packstream-v5.test.js} | 4 +- packages/core/src/internal/constants.ts | 4 +- 6 files changed, 416 insertions(+), 4 deletions(-) create mode 100644 packages/bolt-connection/src/bolt/bolt-protocol-v5x0.js rename packages/bolt-connection/src/packstream/{packstream-v3.js => packstream-v5.js} (100%) create mode 100644 packages/bolt-connection/test/bolt/bolt-protocol-v5x0.test.js rename packages/bolt-connection/test/packstream/{packstream-v3.test.js => packstream-v5.test.js} (99%) diff --git a/packages/bolt-connection/src/bolt/bolt-protocol-v5x0.js b/packages/bolt-connection/src/bolt/bolt-protocol-v5x0.js new file mode 100644 index 000000000..1e21960bc --- /dev/null +++ b/packages/bolt-connection/src/bolt/bolt-protocol-v5x0.js @@ -0,0 +1,40 @@ +/** + * Copyright (c) "Neo4j" + * Neo4j Sweden AB [http://neo4j.com] + * + * This file is part of Neo4j. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +import BoltProtocolV44 from './bolt-protocol-v4x4' +import { v5 } from '../packstream' + +import { internal } from 'neo4j-driver-core' + +const { + constants: { BOLT_PROTOCOL_V5_0 }, +} = internal + +export default class BoltProtocol extends BoltProtocolV44 { + get version() { + return BOLT_PROTOCOL_V5_0 + } + + _createPacker (chunker) { + return new v5.Packer(chunker) + } + + _createUnpacker (disableLosslessIntegers, useBigInt) { + return new v5.Unpacker(disableLosslessIntegers, useBigInt) + } +} diff --git a/packages/bolt-connection/src/packstream/index.js b/packages/bolt-connection/src/packstream/index.js index b06b71b99..564c32001 100644 --- a/packages/bolt-connection/src/packstream/index.js +++ b/packages/bolt-connection/src/packstream/index.js @@ -19,7 +19,8 @@ import * as v1 from './packstream-v1' import * as v2 from './packstream-v2' +import * as v5 from './packstream-v5' -export { v1, v2 } +export { v1, v2, v5 } export default v2 diff --git a/packages/bolt-connection/src/packstream/packstream-v3.js b/packages/bolt-connection/src/packstream/packstream-v5.js similarity index 100% rename from packages/bolt-connection/src/packstream/packstream-v3.js rename to packages/bolt-connection/src/packstream/packstream-v5.js diff --git a/packages/bolt-connection/test/bolt/bolt-protocol-v5x0.test.js b/packages/bolt-connection/test/bolt/bolt-protocol-v5x0.test.js new file mode 100644 index 000000000..f8b4ed95a --- /dev/null +++ b/packages/bolt-connection/test/bolt/bolt-protocol-v5x0.test.js @@ -0,0 +1,369 @@ +/** + * Copyright (c) "Neo4j" + * Neo4j Sweden AB [http://neo4j.com] + * + * This file is part of Neo4j. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import BoltProtocolV5x0 from '../../src/bolt/bolt-protocol-v5x0' +import RequestMessage from '../../src/bolt/request-message' +import { v5 } from '../../src/packstream' +import utils from '../test-utils' +import { RouteObserver } from '../../src/bolt/stream-observers' +import { internal } from 'neo4j-driver-core' + +const WRITE = 'WRITE' + +const { + txConfig: { TxConfig }, + bookmarks: { Bookmarks } +} = internal + +describe('#unit BoltProtocolV5x0', () => { + beforeEach(() => { + expect.extend(utils.matchers) + }) + + it('should request routing information', () => { + const recorder = new utils.MessageRecordingConnection() + const protocol = new BoltProtocolV5x0(recorder, null, false) + utils.spyProtocolWrite(protocol) + const routingContext = { someContextParam: 'value' } + const databaseName = 'name' + + const observer = protocol.requestRoutingInformation({ + routingContext, + databaseName + }) + + protocol.verifyMessageCount(1) + expect(protocol.messages[0]).toBeMessage( + RequestMessage.routeV4x4(routingContext, [], { databaseName, impersonatedUser: null }) + ) + expect(protocol.observers).toEqual([observer]) + expect(observer).toEqual(expect.any(RouteObserver)) + expect(protocol.flushes).toEqual([true]) + }) + + it('should request routing information sending bookmarks', () => { + const recorder = new utils.MessageRecordingConnection() + const protocol = new BoltProtocolV5x0(recorder, null, false) + utils.spyProtocolWrite(protocol) + const routingContext = { someContextParam: 'value' } + const listOfBookmarks = ['a', 'b', 'c'] + const bookmarks = new Bookmarks(listOfBookmarks) + const databaseName = 'name' + + const observer = protocol.requestRoutingInformation({ + routingContext, + databaseName, + sessionContext: { bookmarks } + }) + + protocol.verifyMessageCount(1) + expect(protocol.messages[0]).toBeMessage( + RequestMessage.routeV4x4(routingContext, listOfBookmarks, { databaseName, impersonatedUser: null}) + ) + expect(protocol.observers).toEqual([observer]) + expect(observer).toEqual(expect.any(RouteObserver)) + expect(protocol.flushes).toEqual([true]) + }) + + it('should run a query', () => { + const database = 'testdb' + const bookmarks = new Bookmarks([ + 'neo4j:bookmark:v1:tx1', + 'neo4j:bookmark:v1:tx2' + ]) + const txConfig = new TxConfig({ + timeout: 5000, + metadata: { x: 1, y: 'something' } + }) + const recorder = new utils.MessageRecordingConnection() + const protocol = new BoltProtocolV5x0(recorder, null, false) + utils.spyProtocolWrite(protocol) + + const query = 'RETURN $x, $y' + const parameters = { x: 'x', y: 'y' } + + const observer = protocol.run(query, parameters, { + bookmarks, + txConfig, + database, + mode: WRITE + }) + + protocol.verifyMessageCount(2) + + expect(protocol.messages[0]).toBeMessage( + RequestMessage.runWithMetadata(query, parameters, { + bookmarks, + txConfig, + database, + mode: WRITE + }) + ) + expect(protocol.messages[1]).toBeMessage(RequestMessage.pull()) + expect(protocol.observers).toEqual([observer, observer]) + expect(protocol.flushes).toEqual([false, true]) + }) + + it('should run a with impersonated user', () => { + const database = 'testdb' + const impersonatedUser = 'the impostor' + const bookmarks = new Bookmarks([ + 'neo4j:bookmark:v1:tx1', + 'neo4j:bookmark:v1:tx2' + ]) + const txConfig = new TxConfig({ + timeout: 5000, + metadata: { x: 1, y: 'something' } + }) + const recorder = new utils.MessageRecordingConnection() + const protocol = new BoltProtocolV5x0(recorder, null, false) + utils.spyProtocolWrite(protocol) + + const query = 'RETURN $x, $y' + const parameters = { x: 'x', y: 'y' } + + const observer = protocol.run(query, parameters, { + bookmarks, + txConfig, + database, + mode: WRITE, + impersonatedUser + }) + + protocol.verifyMessageCount(2) + + expect(protocol.messages[0]).toBeMessage( + RequestMessage.runWithMetadata(query, parameters, { + bookmarks, + txConfig, + database, + mode: WRITE, + impersonatedUser + }) + ) + expect(protocol.messages[1]).toBeMessage(RequestMessage.pull()) + expect(protocol.observers).toEqual([observer, observer]) + expect(protocol.flushes).toEqual([false, true]) + }) + + it('should begin a transaction', () => { + const database = 'testdb' + const bookmarks = new Bookmarks([ + 'neo4j:bookmark:v1:tx1', + 'neo4j:bookmark:v1:tx2' + ]) + const txConfig = new TxConfig({ + timeout: 5000, + metadata: { x: 1, y: 'something' } + }) + const recorder = new utils.MessageRecordingConnection() + const protocol = new BoltProtocolV5x0(recorder, null, false) + utils.spyProtocolWrite(protocol) + + const observer = protocol.beginTransaction({ + bookmarks, + txConfig, + database, + mode: WRITE + }) + + protocol.verifyMessageCount(1) + expect(protocol.messages[0]).toBeMessage( + RequestMessage.begin({ bookmarks, txConfig, database, mode: WRITE }) + ) + expect(protocol.observers).toEqual([observer]) + expect(protocol.flushes).toEqual([true]) + }) + + it('should begin a transaction with impersonated user', () => { + const database = 'testdb' + const impersonatedUser = 'the impostor' + const bookmarks = new Bookmarks([ + 'neo4j:bookmark:v1:tx1', + 'neo4j:bookmark:v1:tx2' + ]) + const txConfig = new TxConfig({ + timeout: 5000, + metadata: { x: 1, y: 'something' } + }) + const recorder = new utils.MessageRecordingConnection() + const protocol = new BoltProtocolV5x0(recorder, null, false) + utils.spyProtocolWrite(protocol) + + const observer = protocol.beginTransaction({ + bookmarks, + txConfig, + database, + mode: WRITE, + impersonatedUser + }) + + protocol.verifyMessageCount(1) + expect(protocol.messages[0]).toBeMessage( + RequestMessage.begin({ bookmarks, txConfig, database, mode: WRITE, impersonatedUser }) + ) + expect(protocol.observers).toEqual([observer]) + expect(protocol.flushes).toEqual([true]) + }) + + it('should return correct bolt version number', () => { + const protocol = new BoltProtocolV5x0(null, null, false) + + expect(protocol.version).toBe(5.0) + }) + + it('should update metadata', () => { + const metadata = { t_first: 1, t_last: 2, db_hits: 3, some_other_key: 4 } + const protocol = new BoltProtocolV5x0(null, null, false) + + const transformedMetadata = protocol.transformMetadata(metadata) + + expect(transformedMetadata).toEqual({ + result_available_after: 1, + result_consumed_after: 2, + db_hits: 3, + some_other_key: 4 + }) + }) + + it('should initialize connection', () => { + const recorder = new utils.MessageRecordingConnection() + const protocol = new BoltProtocolV5x0(recorder, null, false) + utils.spyProtocolWrite(protocol) + + const clientName = 'js-driver/1.2.3' + const authToken = { username: 'neo4j', password: 'secret' } + + const observer = protocol.initialize({ userAgent: clientName, authToken }) + + protocol.verifyMessageCount(1) + expect(protocol.messages[0]).toBeMessage( + RequestMessage.hello(clientName, authToken) + ) + expect(protocol.observers).toEqual([observer]) + expect(protocol.flushes).toEqual([true]) + }) + + it('should begin a transaction', () => { + const bookmarks = new Bookmarks([ + 'neo4j:bookmark:v1:tx1', + 'neo4j:bookmark:v1:tx2' + ]) + const txConfig = new TxConfig({ + timeout: 5000, + metadata: { x: 1, y: 'something' } + }) + const recorder = new utils.MessageRecordingConnection() + const protocol = new BoltProtocolV5x0(recorder, null, false) + utils.spyProtocolWrite(protocol) + + const observer = protocol.beginTransaction({ + bookmarks, + txConfig, + mode: WRITE + }) + + protocol.verifyMessageCount(1) + expect(protocol.messages[0]).toBeMessage( + RequestMessage.begin({ bookmarks, txConfig, mode: WRITE }) + ) + expect(protocol.observers).toEqual([observer]) + expect(protocol.flushes).toEqual([true]) + }) + + it('should commit', () => { + const recorder = new utils.MessageRecordingConnection() + const protocol = new BoltProtocolV5x0(recorder, null, false) + utils.spyProtocolWrite(protocol) + + const observer = protocol.commitTransaction() + + protocol.verifyMessageCount(1) + expect(protocol.messages[0]).toBeMessage(RequestMessage.commit()) + expect(protocol.observers).toEqual([observer]) + expect(protocol.flushes).toEqual([true]) + }) + + it('should rollback', () => { + const recorder = new utils.MessageRecordingConnection() + const protocol = new BoltProtocolV5x0(recorder, null, false) + utils.spyProtocolWrite(protocol) + + const observer = protocol.rollbackTransaction() + + protocol.verifyMessageCount(1) + expect(protocol.messages[0]).toBeMessage(RequestMessage.rollback()) + expect(protocol.observers).toEqual([observer]) + expect(protocol.flushes).toEqual([true]) + }) + + describe('unpacker configuration', () => { + test.each([ + [false, false], + [false, true], + [true, false], + [true, true] + ])( + 'should create unpacker with disableLosslessIntegers=%p and useBigInt=%p', + (disableLosslessIntegers, useBigInt) => { + const protocol = new BoltProtocolV5x0(null, null, { + disableLosslessIntegers, + useBigInt + }) + expect(protocol._unpacker._disableLosslessIntegers).toBe( + disableLosslessIntegers + ) + expect(protocol._unpacker._useBigInt).toBe(useBigInt) + } + ) + }) + + describe('watermarks', () => { + it('.run() should configure watermarks', () => { + const recorder = new utils.MessageRecordingConnection() + const protocol = utils.spyProtocolWrite( + new BoltProtocolV5x0(recorder, null, false) + ) + + const query = 'RETURN $x, $y' + const parameters = { x: 'x', y: 'y' } + const observer = protocol.run(query, parameters, { + bookmarks: Bookmarks.empty(), + txConfig: TxConfig.empty(), + lowRecordWatermark: 100, + highRecordWatermark: 200, + }) + + expect(observer._lowRecordWatermark).toEqual(100) + expect(observer._highRecordWatermark).toEqual(200) + }) + }) + + describe('packstream', () => { + it('should configure v5 packer', () => { + const protocol = new BoltProtocolV5x0(null, null, false) + expect(protocol.packer()).toBeInstanceOf(v5.Packer) + }) + + it('should configure v5 unpacker', () => { + const protocol = new BoltProtocolV5x0(null, null, false) + expect(protocol.unpacker()).toBeInstanceOf(v5.Unpacker) + }) + }) +}) diff --git a/packages/bolt-connection/test/packstream/packstream-v3.test.js b/packages/bolt-connection/test/packstream/packstream-v5.test.js similarity index 99% rename from packages/bolt-connection/test/packstream/packstream-v3.test.js rename to packages/bolt-connection/test/packstream/packstream-v5.test.js index 866e97598..acb014507 100644 --- a/packages/bolt-connection/test/packstream/packstream-v3.test.js +++ b/packages/bolt-connection/test/packstream/packstream-v5.test.js @@ -19,11 +19,11 @@ import { int, Integer } from 'neo4j-driver-core' import { alloc } from '../../src/channel' -import { Packer, Unpacker } from '../../src/packstream/packstream-v3' +import { Packer, Unpacker } from '../../src/packstream/packstream-v5' import { Structure } from '../../src/packstream/packstream-v1' import { Node, int, Relationship, UnboundRelationship } from 'neo4j-driver-core' -describe('#unit PackStreamV3', () => { +describe('#unit PackStreamV5', () => { it('should pack integers with small numbers', () => { let n, i // test small numbers diff --git a/packages/core/src/internal/constants.ts b/packages/core/src/internal/constants.ts index 922ab884f..39e790f2f 100644 --- a/packages/core/src/internal/constants.ts +++ b/packages/core/src/internal/constants.ts @@ -33,6 +33,7 @@ const BOLT_PROTOCOL_V4_1: number = 4.1 const BOLT_PROTOCOL_V4_2: number = 4.2 const BOLT_PROTOCOL_V4_3: number = 4.3 const BOLT_PROTOCOL_V4_4: number = 4.4 +const BOLT_PROTOCOL_V5_0: number = 5.0 export { FETCH_ALL, @@ -48,5 +49,6 @@ export { BOLT_PROTOCOL_V4_1, BOLT_PROTOCOL_V4_2, BOLT_PROTOCOL_V4_3, - BOLT_PROTOCOL_V4_4 + BOLT_PROTOCOL_V4_4, + BOLT_PROTOCOL_V5_0 } From 1322981645cb63832543e25c2b14385d2d63d6bb Mon Sep 17 00:00:00 2001 From: Antonio Barcelos Date: Tue, 1 Mar 2022 17:57:45 +0100 Subject: [PATCH 12/17] Add 5.0 to the handshake --- packages/bolt-connection/src/bolt/create.js | 11 +++++++++++ packages/bolt-connection/src/bolt/handshake.js | 2 +- packages/bolt-connection/test/bolt/index.test.js | 8 +++++--- 3 files changed, 17 insertions(+), 4 deletions(-) diff --git a/packages/bolt-connection/src/bolt/create.js b/packages/bolt-connection/src/bolt/create.js index a8ab0d0d6..3a641fede 100644 --- a/packages/bolt-connection/src/bolt/create.js +++ b/packages/bolt-connection/src/bolt/create.js @@ -26,6 +26,7 @@ import BoltProtocolV4x1 from './bolt-protocol-v4x1' import BoltProtocolV4x2 from './bolt-protocol-v4x2' import BoltProtocolV4x3 from './bolt-protocol-v4x3' import BoltProtocolV4x4 from './bolt-protocol-v4x4' +import BoltProtocolV5x0 from './bolt-protocol-v5x0' import { Chunker, Dechunker } from '../channel' import ResponseHandler from './response-handler' @@ -175,6 +176,16 @@ function createProtocol ( onProtocolError, serversideRouting ) + case 5.0: + return new BoltProtocolV5x0( + server, + chunker, + packingConfig, + createResponseHandler, + log, + onProtocolError, + serversideRouting + ) default: throw newError('Unknown Bolt protocol version: ' + version) } diff --git a/packages/bolt-connection/src/bolt/handshake.js b/packages/bolt-connection/src/bolt/handshake.js index bbed15022..c41d4bd82 100644 --- a/packages/bolt-connection/src/bolt/handshake.js +++ b/packages/bolt-connection/src/bolt/handshake.js @@ -76,9 +76,9 @@ function parseNegotiatedResponse (buffer) { */ function newHandshakeBuffer () { return createHandshakeMessage([ + version(5, 0), [version(4, 4), version(4, 2)], version(4, 1), - version(4, 0), version(3, 0) ]) } diff --git a/packages/bolt-connection/test/bolt/index.test.js b/packages/bolt-connection/test/bolt/index.test.js index 838b46be6..e1bc0fd94 100644 --- a/packages/bolt-connection/test/bolt/index.test.js +++ b/packages/bolt-connection/test/bolt/index.test.js @@ -30,6 +30,7 @@ import BoltProtocolV4x1 from '../../src/bolt/bolt-protocol-v4x1' import BoltProtocolV4x2 from '../../src/bolt/bolt-protocol-v4x2' import BoltProtocolV4x3 from '../../src/bolt/bolt-protocol-v4x3' import BoltProtocolV4x4 from '../../src/bolt/bolt-protocol-v4x4' +import BoltProtocolV5x0 from '../../src/bolt/bolt-protocol-v5x0' const { logger: { Logger } @@ -43,13 +44,13 @@ describe('#unit Bolt', () => { const writtenBuffer = channel.written[0] const boltMagicPreamble = '60 60 b0 17' + const protocolVersion5x0 = '00 00 00 05' const protocolVersion4x4to4x2 = '00 02 04 04' const protocolVersion4x1 = '00 00 01 04' - const protocolVersion4x0 = '00 00 00 04' const protocolVersion3 = '00 00 00 03' expect(writtenBuffer.toHex()).toEqual( - `${boltMagicPreamble} ${protocolVersion4x4to4x2} ${protocolVersion4x1} ${protocolVersion4x0} ${protocolVersion3}` + `${boltMagicPreamble} ${protocolVersion5x0} ${protocolVersion4x4to4x2} ${protocolVersion4x1} ${protocolVersion3}` ) }) @@ -303,7 +304,8 @@ describe('#unit Bolt', () => { v(4.1, BoltProtocolV4x1), v(4.2, BoltProtocolV4x2), v(4.3, BoltProtocolV4x3), - v(4.4, BoltProtocolV4x4) + v(4.4, BoltProtocolV4x4), + v(5.0, BoltProtocolV5x0) ] availableProtocols.forEach(lambda) From 0c74a9c2aaaccb0a494147318bc76b4f2e9b331f Mon Sep 17 00:00:00 2001 From: Antonio Barcelos Date: Wed, 2 Mar 2022 13:08:06 +0100 Subject: [PATCH 13/17] Add support for old identifiers absence in 5.x protocol --- .../src/packstream/packstream-v5.js | 14 +++-- .../test/packstream/packstream-v5.test.js | 52 +++++++++++++++++-- 2 files changed, 58 insertions(+), 8 deletions(-) diff --git a/packages/bolt-connection/src/packstream/packstream-v5.js b/packages/bolt-connection/src/packstream/packstream-v5.js index 08f96b1c7..02685ba69 100644 --- a/packages/bolt-connection/src/packstream/packstream-v5.js +++ b/packages/bolt-connection/src/packstream/packstream-v5.js @@ -37,7 +37,7 @@ export class Unpacker extends v2.Unpacker { this._verifyStructSize('Node', NODE_STRUCT_SIZE, structSize) return new Node( - this.unpack(buffer), // Identity + _valueOrDefault(this.unpack(buffer), -1), // Identity this.unpack(buffer), // Labels this.unpack(buffer), // Properties, this.unpack(buffer) // ElementId @@ -48,9 +48,9 @@ export class Unpacker extends v2.Unpacker { this._verifyStructSize('Relationship', RELATIONSHIP_STRUCT_SIZE, structSize) return new Relationship( - this.unpack(buffer), // Identity - this.unpack(buffer), // Start Node Identity - this.unpack(buffer), // End Node Identity + _valueOrDefault(this.unpack(buffer), -1), // Identity + _valueOrDefault(this.unpack(buffer), -1), // Start Node Identity + _valueOrDefault(this.unpack(buffer), -1), // End Node Identity this.unpack(buffer), // Type this.unpack(buffer), // Properties, this.unpack(buffer), // ElementId @@ -67,10 +67,14 @@ export class Unpacker extends v2.Unpacker { ) return new UnboundRelationship( - this.unpack(buffer), // Identity + _valueOrDefault(this.unpack(buffer), -1), // Identity this.unpack(buffer), // Type this.unpack(buffer), // Properties this.unpack(buffer) // ElementId ) } } + +function _valueOrDefault(value, defaultValue) { + return value === null ? defaultValue : value +} diff --git a/packages/bolt-connection/test/packstream/packstream-v5.test.js b/packages/bolt-connection/test/packstream/packstream-v5.test.js index acb014507..4655a2b7e 100644 --- a/packages/bolt-connection/test/packstream/packstream-v5.test.js +++ b/packages/bolt-connection/test/packstream/packstream-v5.test.js @@ -239,6 +239,18 @@ describe('#unit PackStreamV5', () => { return [nodeStruct, expectedNode, { disableLosslessIntegers: true, useBigInt: false }] } + function validWithoutOldIdentifiers() { + const identity = null + const labels = ['a', 'b'] + const properties = { 'a': 1, 'b': 2 } + const elementId = 'element_id_1' + const expectedNode = new Node(-1, labels, properties, elementId) + const nodeStruct = new Structure(0x4e, [ + identity, labels, properties, elementId + ]) + return [nodeStruct, expectedNode, { disableLosslessIntegers: true, useBigInt: false }] + } + function validWithInt() { const identity = int(1) const labels = ['a', 'b'] @@ -266,7 +278,8 @@ describe('#unit PackStreamV5', () => { return [ validWithNumber(), validWithInt(), - validWithBigInt() + validWithBigInt(), + validWithoutOldIdentifiers() ] } @@ -297,6 +310,25 @@ describe('#unit PackStreamV5', () => { return [relStruct, expectedRel, { disableLosslessIntegers: true, useBigInt: false }] } + function validWithoutOldIdentifiers() { + const identity = null + const start = null + const end = null + const type = 'KNOWS' + const properties = { 'a': 1, 'b': 2 } + const elementId = 'element_id_1' + const startNodeElementId = 'element_id_2' + const endNodeElementId = 'element_id_3' + const expectedRel = new Relationship( + -1, -1, -1, type, properties, + elementId, startNodeElementId, endNodeElementId) + const relStruct = new Structure(0x52, [ + identity, start, end, type, properties, elementId, + startNodeElementId, endNodeElementId + ]) + return [relStruct, expectedRel, { disableLosslessIntegers: true, useBigInt: false }] + } + function validWithInt() { const identity = int(1) const start = int(2) @@ -338,7 +370,8 @@ describe('#unit PackStreamV5', () => { return [ validWithNumber(), validWithInt(), - validWithBigInt() + validWithBigInt(), + validWithoutOldIdentifiers() ] } @@ -362,6 +395,18 @@ describe('#unit PackStreamV5', () => { return [struct, expectedUnboundRel, { disableLosslessIntegers: true, useBigInt: false }] } + function validWithoutOldIdentifiers() { + const identity = null + const type = 'DOESNT_KNOW' + const properties = { 'a': 1, 'b': 2 } + const elementId = 'element_id_1' + const expectedUnboundRel = new UnboundRelationship(-1, type, properties, elementId) + const struct = new Structure(0x72, [ + identity, type, properties, elementId + ]) + return [struct, expectedUnboundRel, { disableLosslessIntegers: true, useBigInt: false }] + } + function validWithInt() { const identity = int(1) const type = 'DOESNT_KNOW' @@ -389,7 +434,8 @@ describe('#unit PackStreamV5', () => { return [ validWithNumber(), validWithInt(), - validWithBigInt() + validWithBigInt(), + validWithoutOldIdentifiers() ] } From 5ad75562bb62339320d5adc33cf139a7485bf8de Mon Sep 17 00:00:00 2001 From: Antonio Barcelos Date: Thu, 3 Mar 2022 13:40:04 +0100 Subject: [PATCH 14/17] Add 5.0 support to testkit --- packages/testkit-backend/src/cypher-native-binders.js | 8 ++++++-- packages/testkit-backend/src/feature/common.js | 1 + 2 files changed, 7 insertions(+), 2 deletions(-) diff --git a/packages/testkit-backend/src/cypher-native-binders.js b/packages/testkit-backend/src/cypher-native-binders.js index f7852e174..c625ab403 100644 --- a/packages/testkit-backend/src/cypher-native-binders.js +++ b/packages/testkit-backend/src/cypher-native-binders.js @@ -60,7 +60,8 @@ export function nativeToCypher (x) { const node = { id: nativeToCypher(x.identity), labels: nativeToCypher(x.labels), - props: nativeToCypher(x.properties) + props: nativeToCypher(x.properties), + elementId: nativeToCypher(x.elementId) } return { name: 'CypherNode', data: node } } @@ -70,7 +71,10 @@ export function nativeToCypher (x) { startNodeId: nativeToCypher(x.start), endNodeId: nativeToCypher(x.end), type: nativeToCypher(x.type), - props: nativeToCypher(x.properties) + props: nativeToCypher(x.properties), + elementId: nativeToCypher(x.elementId), + startNodeElementId: nativeToCypher(x.startNodeElementId), + endNodeElementId: nativeToCypher(x.endNodeElementId) } return { name: 'CypherRelationship', data: relationship } } diff --git a/packages/testkit-backend/src/feature/common.js b/packages/testkit-backend/src/feature/common.js index ea419ef36..e78d029b3 100644 --- a/packages/testkit-backend/src/feature/common.js +++ b/packages/testkit-backend/src/feature/common.js @@ -27,6 +27,7 @@ const features = [ 'Feature:Bolt:4.2', 'Feature:Bolt:4.3', 'Feature:Bolt:4.4', + 'Feature:Bolt:5.0', 'Feature:API:ConnectionAcquisitionTimeout', 'Feature:API:Driver:GetServerInfo', 'Feature:API:Driver.VerifyConnectivity', From b29d94c621942d7c554f7a72f604c66b6a425db6 Mon Sep 17 00:00:00 2001 From: Antonio Barcelos Date: Fri, 4 Mar 2022 14:34:12 +0100 Subject: [PATCH 15/17] Fix deno compatibility --- packages/bolt-connection/src/packstream/packstream-v5.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/bolt-connection/src/packstream/packstream-v5.js b/packages/bolt-connection/src/packstream/packstream-v5.js index 02685ba69..74e005f58 100644 --- a/packages/bolt-connection/src/packstream/packstream-v5.js +++ b/packages/bolt-connection/src/packstream/packstream-v5.js @@ -17,7 +17,7 @@ * limitations under the License. */ -import * as v2 from './packstream-v2'; +import * as v2 from './packstream-v2' import { Node, Relationship, From c9e566718b9da1827ce84a30c34f50d1d1a549c7 Mon Sep 17 00:00:00 2001 From: Antonio Barcelos Date: Mon, 14 Mar 2022 18:40:05 +0100 Subject: [PATCH 16/17] Skip test --- packages/testkit-backend/src/skipped-tests/common.js | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/packages/testkit-backend/src/skipped-tests/common.js b/packages/testkit-backend/src/skipped-tests/common.js index f550b7e03..2887fa17d 100644 --- a/packages/testkit-backend/src/skipped-tests/common.js +++ b/packages/testkit-backend/src/skipped-tests/common.js @@ -1,6 +1,10 @@ import skip, { ifEquals, ifEndsWith, ifStartsWith } from './skip' const skippedTests = [ + skip( + 'Skipped because server doesn\'t support protocol 5.0 yet', + ifEndsWith('neo4j.test_summary.TestSummary.test_protocol_version_information') + ), skip( 'Handle qid omission optmization can cause issues in nested queries', ifEquals('stub.optimizations.test_optimizations.TestOptimizations.test_uses_implicit_default_arguments'), From d92b85c230c3410a3e4797647515cd520496a975 Mon Sep 17 00:00:00 2001 From: Antonio Barcelos Date: Mon, 14 Mar 2022 19:34:02 +0100 Subject: [PATCH 17/17] Fix support for differents number types in the default identity --- .../src/packstream/packstream-v5.js | 34 +++++- .../test/packstream/packstream-v5.test.js | 104 +++++++++++++++++- .../__snapshots__/graph-types.test.ts.snap | 16 ++- packages/core/test/graph-types.test.ts | 8 ++ 4 files changed, 148 insertions(+), 14 deletions(-) diff --git a/packages/bolt-connection/src/packstream/packstream-v5.js b/packages/bolt-connection/src/packstream/packstream-v5.js index 74e005f58..0d8886d20 100644 --- a/packages/bolt-connection/src/packstream/packstream-v5.js +++ b/packages/bolt-connection/src/packstream/packstream-v5.js @@ -21,7 +21,8 @@ import * as v2 from './packstream-v2' import { Node, Relationship, - UnboundRelationship + UnboundRelationship, + int } from 'neo4j-driver-core' const NODE_STRUCT_SIZE = 4 @@ -33,11 +34,32 @@ export class Packer extends v2.Packer { } export class Unpacker extends v2.Unpacker { + /** + * @constructor + * @param {boolean} disableLosslessIntegers if this unpacker should convert all received integers to native JS numbers. + * @param {boolean} useBigInt if this unpacker should convert all received integers to Bigint + */ + constructor (disableLosslessIntegers = false, useBigInt = false) { + this._disableLosslessIntegers = disableLosslessIntegers + this._useBigInt = useBigInt + this._defaultIdentity = this._getDefaultIdentity() + } + + _getDefaultIdentity() { + if (this._useBigInt) { + return BigInt(-1) + } else if (this._disableLosslessIntegers) { + return -1 + } else { + return int(-1) + } + } + _unpackNode (structSize, buffer) { this._verifyStructSize('Node', NODE_STRUCT_SIZE, structSize) return new Node( - _valueOrDefault(this.unpack(buffer), -1), // Identity + _valueOrDefault(this.unpack(buffer), this._defaultIdentity), // Identity this.unpack(buffer), // Labels this.unpack(buffer), // Properties, this.unpack(buffer) // ElementId @@ -48,9 +70,9 @@ export class Unpacker extends v2.Unpacker { this._verifyStructSize('Relationship', RELATIONSHIP_STRUCT_SIZE, structSize) return new Relationship( - _valueOrDefault(this.unpack(buffer), -1), // Identity - _valueOrDefault(this.unpack(buffer), -1), // Start Node Identity - _valueOrDefault(this.unpack(buffer), -1), // End Node Identity + _valueOrDefault(this.unpack(buffer), this._defaultIdentity), // Identity + _valueOrDefault(this.unpack(buffer), this._defaultIdentity), // Start Node Identity + _valueOrDefault(this.unpack(buffer), this._defaultIdentity), // End Node Identity this.unpack(buffer), // Type this.unpack(buffer), // Properties, this.unpack(buffer), // ElementId @@ -67,7 +89,7 @@ export class Unpacker extends v2.Unpacker { ) return new UnboundRelationship( - _valueOrDefault(this.unpack(buffer), -1), // Identity + _valueOrDefault(this.unpack(buffer), this._defaultIdentity), // Identity this.unpack(buffer), // Type this.unpack(buffer), // Properties this.unpack(buffer) // ElementId diff --git a/packages/bolt-connection/test/packstream/packstream-v5.test.js b/packages/bolt-connection/test/packstream/packstream-v5.test.js index 4655a2b7e..cd38f214e 100644 --- a/packages/bolt-connection/test/packstream/packstream-v5.test.js +++ b/packages/bolt-connection/test/packstream/packstream-v5.test.js @@ -239,7 +239,7 @@ describe('#unit PackStreamV5', () => { return [nodeStruct, expectedNode, { disableLosslessIntegers: true, useBigInt: false }] } - function validWithoutOldIdentifiers() { + function validWithoutOldIdentifiersLossy() { const identity = null const labels = ['a', 'b'] const properties = { 'a': 1, 'b': 2 } @@ -251,6 +251,30 @@ describe('#unit PackStreamV5', () => { return [nodeStruct, expectedNode, { disableLosslessIntegers: true, useBigInt: false }] } + function validWithoutOldIdentifiersLosslessInteger() { + const identity = null + const labels = ['a', 'b'] + const properties = { 'a': 1, 'b': 2 } + const elementId = 'element_id_1' + const expectedNode = new Node(int(-1), labels, properties, elementId) + const nodeStruct = new Structure(0x4e, [ + identity, labels, properties, elementId + ]) + return [nodeStruct, expectedNode, { disableLosslessIntegers: false, useBigInt: false }] + } + + function validWithoutOldIdentifiersBigInt() { + const identity = null + const labels = ['a', 'b'] + const properties = { 'a': 1, 'b': 2 } + const elementId = 'element_id_1' + const expectedNode = new Node(BigInt(-1), labels, properties, elementId) + const nodeStruct = new Structure(0x4e, [ + identity, labels, properties, elementId + ]) + return [nodeStruct, expectedNode, { disableLosslessIntegers: false, useBigInt: true }] + } + function validWithInt() { const identity = int(1) const labels = ['a', 'b'] @@ -279,7 +303,9 @@ describe('#unit PackStreamV5', () => { validWithNumber(), validWithInt(), validWithBigInt(), - validWithoutOldIdentifiers() + validWithoutOldIdentifiersLossy(), + validWithoutOldIdentifiersLosslessInteger(), + validWithoutOldIdentifiersBigInt() ] } @@ -310,7 +336,7 @@ describe('#unit PackStreamV5', () => { return [relStruct, expectedRel, { disableLosslessIntegers: true, useBigInt: false }] } - function validWithoutOldIdentifiers() { + function validWithoutOldIdentifiersLossy() { const identity = null const start = null const end = null @@ -329,6 +355,44 @@ describe('#unit PackStreamV5', () => { return [relStruct, expectedRel, { disableLosslessIntegers: true, useBigInt: false }] } + function validWithoutOldIdentifiersLossLess() { + const identity = null + const start = null + const end = null + const type = 'KNOWS' + const properties = { 'a': 1, 'b': 2 } + const elementId = 'element_id_1' + const startNodeElementId = 'element_id_2' + const endNodeElementId = 'element_id_3' + const expectedRel = new Relationship( + int(-1), int(-1), int(-1), type, properties, + elementId, startNodeElementId, endNodeElementId) + const relStruct = new Structure(0x52, [ + identity, start, end, type, properties, elementId, + startNodeElementId, endNodeElementId + ]) + return [relStruct, expectedRel, { disableLosslessIntegers: false, useBigInt: false }] + } + + function validWithoutOldIdentifiersBigInt() { + const identity = null + const start = null + const end = null + const type = 'KNOWS' + const properties = { 'a': 1, 'b': 2 } + const elementId = 'element_id_1' + const startNodeElementId = 'element_id_2' + const endNodeElementId = 'element_id_3' + const expectedRel = new Relationship( + BigInt(-1), BigInt(-1), BigInt(-1), type, properties, + elementId, startNodeElementId, endNodeElementId) + const relStruct = new Structure(0x52, [ + identity, start, end, type, properties, elementId, + startNodeElementId, endNodeElementId + ]) + return [relStruct, expectedRel, { disableLosslessIntegers: true, useBigInt: true }] + } + function validWithInt() { const identity = int(1) const start = int(2) @@ -371,7 +435,9 @@ describe('#unit PackStreamV5', () => { validWithNumber(), validWithInt(), validWithBigInt(), - validWithoutOldIdentifiers() + validWithoutOldIdentifiersLossy(), + validWithoutOldIdentifiersLossLess(), + validWithoutOldIdentifiersBigInt() ] } @@ -395,7 +461,7 @@ describe('#unit PackStreamV5', () => { return [struct, expectedUnboundRel, { disableLosslessIntegers: true, useBigInt: false }] } - function validWithoutOldIdentifiers() { + function validWithoutOldIdentifiersLossy() { const identity = null const type = 'DOESNT_KNOW' const properties = { 'a': 1, 'b': 2 } @@ -407,6 +473,30 @@ describe('#unit PackStreamV5', () => { return [struct, expectedUnboundRel, { disableLosslessIntegers: true, useBigInt: false }] } + function validWithoutOldIdentifiersLossless() { + const identity = null + const type = 'DOESNT_KNOW' + const properties = { 'a': 1, 'b': 2 } + const elementId = 'element_id_1' + const expectedUnboundRel = new UnboundRelationship(int(-1), type, properties, elementId) + const struct = new Structure(0x72, [ + identity, type, properties, elementId + ]) + return [struct, expectedUnboundRel, { disableLosslessIntegers: false, useBigInt: false }] + } + + function validWithoutOldIdentifiersBigInt() { + const identity = null + const type = 'DOESNT_KNOW' + const properties = { 'a': 1, 'b': 2 } + const elementId = 'element_id_1' + const expectedUnboundRel = new UnboundRelationship(BigInt(-1), type, properties, elementId) + const struct = new Structure(0x72, [ + identity, type, properties, elementId + ]) + return [struct, expectedUnboundRel, { disableLosslessIntegers: false, useBigInt: true }] + } + function validWithInt() { const identity = int(1) const type = 'DOESNT_KNOW' @@ -435,7 +525,9 @@ describe('#unit PackStreamV5', () => { validWithNumber(), validWithInt(), validWithBigInt(), - validWithoutOldIdentifiers() + validWithoutOldIdentifiersLossy(), + validWithoutOldIdentifiersLossless(), + validWithoutOldIdentifiersBigInt() ] } diff --git a/packages/core/test/__snapshots__/graph-types.test.ts.snap b/packages/core/test/__snapshots__/graph-types.test.ts.snap index e024c43bb..70d980fc3 100644 --- a/packages/core/test/__snapshots__/graph-types.test.ts.snap +++ b/packages/core/test/__snapshots__/graph-types.test.ts.snap @@ -6,9 +6,13 @@ exports[`Node should be serialized as string 2`] = `"(1:label)"`; exports[`Node should be serialized as string 3`] = `"(1)"`; -exports[`Node should be serialized as string 4`] = `"(1 {property:\\"value\\"})"`; +exports[`Node should be serialized as string 4`] = `"(2:label)"`; -exports[`Node should be serialized as string 5`] = `"(1:label {property:\\"value\\"})"`; +exports[`Node should be serialized as string 5`] = `"(3:label)"`; + +exports[`Node should be serialized as string 6`] = `"(1 {property:\\"value\\"})"`; + +exports[`Node should be serialized as string 7`] = `"(1:label {property:\\"value\\"})"`; exports[`Relationship should be serialized as string 1`] = `"(startNodeElementId)-[:Rel]->(endNodeElementId)"`; @@ -20,8 +24,16 @@ exports[`Relationship should be serialized as string 4`] = `"(2)-[:Rel]->(3)"`; exports[`Relationship should be serialized as string 5`] = `"(2)-[:Rel {property:\\"value\\"}]->(3)"`; +exports[`Relationship should be serialized as string 6`] = `"(5)-[:Rel]->(6)"`; + +exports[`Relationship should be serialized as string 7`] = `"(7)-[:Rel]->(8)"`; + exports[`UnboundRelationship should be serialized as string 1`] = `"-[:Rel]->"`; exports[`UnboundRelationship should be serialized as string 2`] = `"-[:Rel]->"`; exports[`UnboundRelationship should be serialized as string 3`] = `"-[:Rel {property:\\"value\\"}]->"`; + +exports[`UnboundRelationship should be serialized as string 4`] = `"-[:Rel {property:\\"value\\"}]->"`; + +exports[`UnboundRelationship should be serialized as string 5`] = `"-[:Rel {property:\\"value\\"}]->"`; diff --git a/packages/core/test/graph-types.test.ts b/packages/core/test/graph-types.test.ts index 2aecf9913..d991837d0 100644 --- a/packages/core/test/graph-types.test.ts +++ b/packages/core/test/graph-types.test.ts @@ -83,6 +83,8 @@ describe('Node', () => { [new Node(1, ['label'], {}, 'elementId')], [new Node(1, ['label'], {})], [new Node(1, [], {})], + [new Node(BigInt(2), ['label'], {})], + [new Node(int(3), ['label'], {})], [new Node(1, [], { 'property': 'value' })], [new Node(1, ['label'], { 'property': 'value' })], ] @@ -98,6 +100,8 @@ describe('Node', () => { [{ 'property': 'value' }], [{ 'property': 'value', 'labels': ['label'] }], [{ 'property': 'value', 'labels': ['label'], 'identity': 1 }], + [{ identity: BigInt(2), labels: ['label'], properties: { 'property': 'value' } }], + [{ identity: int(3), labels: ['label'], properties: { 'property': 'value' } }], ] } }) @@ -194,6 +198,8 @@ describe('Relationship', () => { [new Relationship(1, 2, 3, 'Rel', {}, 'elementId')], [new Relationship(1, 2, 3, 'Rel', {})], [new Relationship(1, 2, 3, 'Rel', { 'property': 'value' })], + [new Relationship(BigInt(4), BigInt(5), BigInt(6), 'Rel', {})], + [new Relationship(int(6), int(7), int(8), 'Rel', {})], ] } @@ -293,6 +299,8 @@ describe('UnboundRelationship', () => { [new UnboundRelationship(1, 'Rel', {}, 'elementId')], [new UnboundRelationship(1, 'Rel', {})], [new UnboundRelationship(1, 'Rel', { 'property': 'value' })], + [new UnboundRelationship(BigInt(2), 'Rel', { 'property': 'value' })], + [new UnboundRelationship(int(3), 'Rel', { 'property': 'value' })], ] }