Skip to content

Commit e221878

Browse files
authored
Decouple Packstream Serialization/De-serialization from Bolt structure hydration (#942)
The current implementation of the packstream packer and unpacker also includes the creation of the bolt structures. This implementation join twos different concepts in the same object and this doesn't allow the flexibility needed for working with different structure for the same object in the same bolt version, for instance. Decoupling this two layer allows the driver supports the new `utc` feature in a more suitable way.
1 parent 7b29704 commit e221878

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

53 files changed

+5061
-1474
lines changed

packages/bolt-connection/src/bolt/bolt-protocol-v1.js

+31-5
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,7 @@ import {
2323
} from './bolt-protocol-util'
2424
// eslint-disable-next-line no-unused-vars
2525
import { Chunker } from '../channel'
26-
import { v1 } from '../packstream'
26+
import { structure, v1 } from '../packstream'
2727
import RequestMessage from './request-message'
2828
import {
2929
LoginObserver,
@@ -33,6 +33,8 @@ import {
3333
StreamObserver
3434
} from './stream-observers'
3535
import { internal } from 'neo4j-driver-core'
36+
import transformersFactories from './bolt-protocol-v1.transformer'
37+
import Transformer from './transformer'
3638

3739
const {
3840
bookmarks: { Bookmarks },
@@ -80,6 +82,14 @@ export default class BoltProtocol {
8082
this._onProtocolError = onProtocolError
8183
this._fatalError = null
8284
this._lastMessageSignature = null
85+
this._config = { disableLosslessIntegers, useBigInt }
86+
}
87+
88+
get transformer () {
89+
if (this._transformer === undefined) {
90+
this._transformer = new Transformer(Object.values(transformersFactories).map(create => create(this._config)))
91+
}
92+
return this._transformer
8393
}
8494

8595
/**
@@ -97,6 +107,15 @@ export default class BoltProtocol {
97107
return this._packer
98108
}
99109

110+
/**
111+
* Creates a packable function out of the provided value
112+
* @param x the value to pack
113+
* @returns Function
114+
*/
115+
packable (x) {
116+
return this._packer.packable(x, this.transformer.toStructure)
117+
}
118+
100119
/**
101120
* Get the unpacker.
102121
* @return {Unpacker} the protocol's unpacker.
@@ -105,6 +124,15 @@ export default class BoltProtocol {
105124
return this._unpacker
106125
}
107126

127+
/**
128+
* Unpack a buffer
129+
* @param {Buffer} buf
130+
* @returns {any|null} The unpacked value
131+
*/
132+
unpack (buf) {
133+
return this._unpacker.unpack(buf, this.transformer.fromStructure)
134+
}
135+
108136
/**
109137
* Transform metadata received in SUCCESS message before it is passed to the handler.
110138
* @param {Object} metadata the received metadata.
@@ -358,11 +386,9 @@ export default class BoltProtocol {
358386
}
359387

360388
this._lastMessageSignature = message.signature
389+
const messageStruct = new structure.Structure(message.signature, message.fields)
361390

362-
this.packer().packStruct(
363-
message.signature,
364-
message.fields.map(field => this.packer().packable(field))
365-
)
391+
this.packable(messageStruct)()
366392

367393
this._chunker.messageBoundary()
368394

Original file line numberDiff line numberDiff line change
@@ -0,0 +1,187 @@
1+
/**
2+
* Copyright (c) "Neo4j"
3+
* Neo4j Sweden AB [http://neo4j.com]
4+
*
5+
* This file is part of Neo4j.
6+
*
7+
* Licensed under the Apache License, Version 2.0 (the "License");
8+
* you may not use this file except in compliance with the License.
9+
* You may obtain a copy of the License at
10+
*
11+
* http://www.apache.org/licenses/LICENSE-2.0
12+
*
13+
* Unless required by applicable law or agreed to in writing, software
14+
* distributed under the License is distributed on an "AS IS" BASIS,
15+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
16+
* See the License for the specific language governing permissions and
17+
* limitations under the License.
18+
*/
19+
20+
import {
21+
Node,
22+
newError,
23+
error,
24+
Relationship,
25+
UnboundRelationship,
26+
Path,
27+
toNumber,
28+
PathSegment
29+
} from 'neo4j-driver-core'
30+
31+
import { structure } from '../packstream'
32+
import { TypeTransformer } from './transformer'
33+
34+
const { PROTOCOL_ERROR } = error
35+
36+
const NODE = 0x4e
37+
const NODE_STRUCT_SIZE = 3
38+
39+
const RELATIONSHIP = 0x52
40+
const RELATIONSHIP_STRUCT_SIZE = 5
41+
42+
const UNBOUND_RELATIONSHIP = 0x72
43+
const UNBOUND_RELATIONSHIP_STRUCT_SIZE = 3
44+
45+
const PATH = 0x50
46+
const PATH_STRUCT_SIZE = 3
47+
48+
/**
49+
* Creates the Node Transformer
50+
* @returns {TypeTransformer}
51+
*/
52+
function createNodeTransformer () {
53+
return new TypeTransformer({
54+
signature: NODE,
55+
isTypeInstance: object => object instanceof Node,
56+
toStructure: object => {
57+
throw newError(
58+
`It is not allowed to pass nodes in query parameters, given: ${object}`,
59+
PROTOCOL_ERROR
60+
)
61+
},
62+
fromStructure: struct => {
63+
structure.verifyStructSize('Node', NODE_STRUCT_SIZE, struct.size)
64+
65+
const [identity, labels, properties] = struct.fields
66+
67+
return new Node(identity, labels, properties)
68+
}
69+
})
70+
}
71+
72+
/**
73+
* Creates the Relationship Transformer
74+
* @returns {TypeTransformer}
75+
*/
76+
function createRelationshipTransformer () {
77+
return new TypeTransformer({
78+
signature: RELATIONSHIP,
79+
isTypeInstance: object => object instanceof Relationship,
80+
toStructure: object => {
81+
throw newError(
82+
`It is not allowed to pass relationships in query parameters, given: ${object}`,
83+
PROTOCOL_ERROR
84+
)
85+
},
86+
fromStructure: struct => {
87+
structure.verifyStructSize('Relationship', RELATIONSHIP_STRUCT_SIZE, struct.size)
88+
89+
const [identity, startNodeIdentity, endNodeIdentity, type, properties] = struct.fields
90+
91+
return new Relationship(identity, startNodeIdentity, endNodeIdentity, type, properties)
92+
}
93+
})
94+
}
95+
96+
/**
97+
* Creates the Unbound Relationship Transformer
98+
* @returns {TypeTransformer}
99+
*/
100+
function createUnboundRelationshipTransformer () {
101+
return new TypeTransformer({
102+
signature: UNBOUND_RELATIONSHIP,
103+
isTypeInstance: object => object instanceof UnboundRelationship,
104+
toStructure: object => {
105+
throw newError(
106+
`It is not allowed to pass unbound relationships in query parameters, given: ${object}`,
107+
PROTOCOL_ERROR
108+
)
109+
},
110+
fromStructure: struct => {
111+
structure.verifyStructSize(
112+
'UnboundRelationship',
113+
UNBOUND_RELATIONSHIP_STRUCT_SIZE,
114+
struct.size
115+
)
116+
117+
const [identity, type, properties] = struct.fields
118+
119+
return new UnboundRelationship(identity, type, properties)
120+
}
121+
})
122+
}
123+
124+
/**
125+
* Creates the Path Transformer
126+
* @returns {TypeTransformer}
127+
*/
128+
function createPathTransformer () {
129+
return new TypeTransformer({
130+
signature: PATH,
131+
isTypeInstance: object => object instanceof Path,
132+
toStructure: object => {
133+
throw newError(
134+
`It is not allowed to pass paths in query parameters, given: ${object}`,
135+
PROTOCOL_ERROR
136+
)
137+
},
138+
fromStructure: struct => {
139+
structure.verifyStructSize('Path', PATH_STRUCT_SIZE, struct.size)
140+
141+
const [nodes, rels, sequence] = struct.fields
142+
143+
const segments = []
144+
let prevNode = nodes[0]
145+
146+
for (let i = 0; i < sequence.length; i += 2) {
147+
const nextNode = nodes[sequence[i + 1]]
148+
const relIndex = toNumber(sequence[i])
149+
let rel
150+
151+
if (relIndex > 0) {
152+
rel = rels[relIndex - 1]
153+
if (rel instanceof UnboundRelationship) {
154+
// To avoid duplication, relationships in a path do not contain
155+
// information about their start and end nodes, that's instead
156+
// inferred from the path sequence. This is us inferring (and,
157+
// for performance reasons remembering) the start/end of a rel.
158+
rels[relIndex - 1] = rel = rel.bindTo(
159+
prevNode,
160+
nextNode
161+
)
162+
}
163+
} else {
164+
rel = rels[-relIndex - 1]
165+
if (rel instanceof UnboundRelationship) {
166+
// See above
167+
rels[-relIndex - 1] = rel = rel.bindTo(
168+
nextNode,
169+
prevNode
170+
)
171+
}
172+
}
173+
// Done hydrating one path segment.
174+
segments.push(new PathSegment(prevNode, rel, nextNode))
175+
prevNode = nextNode
176+
}
177+
return new Path(nodes[0], nodes[nodes.length - 1], segments)
178+
}
179+
})
180+
}
181+
182+
export default {
183+
createNodeTransformer,
184+
createRelationshipTransformer,
185+
createUnboundRelationshipTransformer,
186+
createPathTransformer
187+
}

packages/bolt-connection/src/bolt/bolt-protocol-v2.js

+9
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,8 @@
1919
import BoltProtocolV1 from './bolt-protocol-v1'
2020
import v2 from '../packstream'
2121
import { internal } from 'neo4j-driver-core'
22+
import transformersFactories from './bolt-protocol-v2.transformer'
23+
import Transformer from './transformer'
2224

2325
const {
2426
constants: { BOLT_PROTOCOL_V2 }
@@ -33,6 +35,13 @@ export default class BoltProtocol extends BoltProtocolV1 {
3335
return new v2.Unpacker(disableLosslessIntegers, useBigInt)
3436
}
3537

38+
get transformer () {
39+
if (this._transformer === undefined) {
40+
this._transformer = new Transformer(Object.values(transformersFactories).map(create => create(this._config)))
41+
}
42+
return this._transformer
43+
}
44+
3645
get version () {
3746
return BOLT_PROTOCOL_V2
3847
}

0 commit comments

Comments
 (0)