Skip to content

Decouple Packstream Serialization/De-serialization from Bolt structure hydration #942

New issue

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

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

Already on GitHub? Sign in to your account

Merged
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
36 changes: 31 additions & 5 deletions packages/bolt-connection/src/bolt/bolt-protocol-v1.js
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ import {
} from './bolt-protocol-util'
// eslint-disable-next-line no-unused-vars
import { Chunker } from '../channel'
import { v1 } from '../packstream'
import { structure, v1 } from '../packstream'
import RequestMessage from './request-message'
import {
LoginObserver,
Expand All @@ -33,6 +33,8 @@ import {
StreamObserver
} from './stream-observers'
import { internal } from 'neo4j-driver-core'
import transformersFactories from './bolt-protocol-v1.transformer'
import Transformer from './transformer'

const {
bookmarks: { Bookmarks },
Expand Down Expand Up @@ -80,6 +82,14 @@ export default class BoltProtocol {
this._onProtocolError = onProtocolError
this._fatalError = null
this._lastMessageSignature = null
this._config = { disableLosslessIntegers, useBigInt }
}

get transformer () {
if (this._transformer === undefined) {
this._transformer = new Transformer(Object.values(transformersFactories).map(create => create(this._config)))
}
return this._transformer
}

/**
Expand All @@ -97,6 +107,15 @@ export default class BoltProtocol {
return this._packer
}

/**
* Creates a packable function out of the provided value
* @param x the value to pack
* @returns Function
*/
packable (x) {
return this._packer.packable(x, this.transformer.toStructure)
}

/**
* Get the unpacker.
* @return {Unpacker} the protocol's unpacker.
Expand All @@ -105,6 +124,15 @@ export default class BoltProtocol {
return this._unpacker
}

/**
* Unpack a buffer
* @param {Buffer} buf
* @returns {any|null} The unpacked value
*/
unpack (buf) {
return this._unpacker.unpack(buf, this.transformer.fromStructure)
}

/**
* Transform metadata received in SUCCESS message before it is passed to the handler.
* @param {Object} metadata the received metadata.
Expand Down Expand Up @@ -358,11 +386,9 @@ export default class BoltProtocol {
}

this._lastMessageSignature = message.signature
const messageStruct = new structure.Structure(message.signature, message.fields)

this.packer().packStruct(
message.signature,
message.fields.map(field => this.packer().packable(field))
)
this.packable(messageStruct)()

this._chunker.messageBoundary()

Expand Down
187 changes: 187 additions & 0 deletions packages/bolt-connection/src/bolt/bolt-protocol-v1.transformer.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,187 @@
/**
* 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 {
Node,
newError,
error,
Relationship,
UnboundRelationship,
Path,
toNumber,
PathSegment
} from 'neo4j-driver-core'

import { structure } from '../packstream'
import { TypeTransformer } from './transformer'

const { PROTOCOL_ERROR } = error

const NODE = 0x4e
const NODE_STRUCT_SIZE = 3

const RELATIONSHIP = 0x52
const RELATIONSHIP_STRUCT_SIZE = 5

const UNBOUND_RELATIONSHIP = 0x72
const UNBOUND_RELATIONSHIP_STRUCT_SIZE = 3

const PATH = 0x50
const PATH_STRUCT_SIZE = 3

/**
* Creates the Node Transformer
* @returns {TypeTransformer}
*/
function createNodeTransformer () {
return new TypeTransformer({
signature: NODE,
isTypeInstance: object => object instanceof Node,
toStructure: object => {
throw newError(
`It is not allowed to pass nodes in query parameters, given: ${object}`,
PROTOCOL_ERROR
)
},
fromStructure: struct => {
structure.verifyStructSize('Node', NODE_STRUCT_SIZE, struct.size)

const [identity, labels, properties] = struct.fields

return new Node(identity, labels, properties)
}
})
}

/**
* Creates the Relationship Transformer
* @returns {TypeTransformer}
*/
function createRelationshipTransformer () {
return new TypeTransformer({
signature: RELATIONSHIP,
isTypeInstance: object => object instanceof Relationship,
toStructure: object => {
throw newError(
`It is not allowed to pass relationships in query parameters, given: ${object}`,
PROTOCOL_ERROR
)
},
fromStructure: struct => {
structure.verifyStructSize('Relationship', RELATIONSHIP_STRUCT_SIZE, struct.size)

const [identity, startNodeIdentity, endNodeIdentity, type, properties] = struct.fields

return new Relationship(identity, startNodeIdentity, endNodeIdentity, type, properties)
}
})
}

/**
* Creates the Unbound Relationship Transformer
* @returns {TypeTransformer}
*/
function createUnboundRelationshipTransformer () {
return new TypeTransformer({
signature: UNBOUND_RELATIONSHIP,
isTypeInstance: object => object instanceof UnboundRelationship,
toStructure: object => {
throw newError(
`It is not allowed to pass unbound relationships in query parameters, given: ${object}`,
PROTOCOL_ERROR
)
},
fromStructure: struct => {
structure.verifyStructSize(
'UnboundRelationship',
UNBOUND_RELATIONSHIP_STRUCT_SIZE,
struct.size
)

const [identity, type, properties] = struct.fields

return new UnboundRelationship(identity, type, properties)
}
})
}

/**
* Creates the Path Transformer
* @returns {TypeTransformer}
*/
function createPathTransformer () {
return new TypeTransformer({
signature: PATH,
isTypeInstance: object => object instanceof Path,
toStructure: object => {
throw newError(
`It is not allowed to pass paths in query parameters, given: ${object}`,
PROTOCOL_ERROR
)
},
fromStructure: struct => {
structure.verifyStructSize('Path', PATH_STRUCT_SIZE, struct.size)

const [nodes, rels, sequence] = struct.fields

const segments = []
let prevNode = nodes[0]

for (let i = 0; i < sequence.length; i += 2) {
const nextNode = nodes[sequence[i + 1]]
const relIndex = toNumber(sequence[i])
let rel

if (relIndex > 0) {
rel = rels[relIndex - 1]
if (rel instanceof UnboundRelationship) {
// To avoid duplication, relationships in a path do not contain
// 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.bindTo(
prevNode,
nextNode
)
}
} else {
rel = rels[-relIndex - 1]
if (rel instanceof UnboundRelationship) {
// See above
rels[-relIndex - 1] = rel = rel.bindTo(
nextNode,
prevNode
)
}
}
// Done hydrating one path segment.
segments.push(new PathSegment(prevNode, rel, nextNode))
prevNode = nextNode
}
return new Path(nodes[0], nodes[nodes.length - 1], segments)
}
})
}

export default {
createNodeTransformer,
createRelationshipTransformer,
createUnboundRelationshipTransformer,
createPathTransformer
}
9 changes: 9 additions & 0 deletions packages/bolt-connection/src/bolt/bolt-protocol-v2.js
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,8 @@
import BoltProtocolV1 from './bolt-protocol-v1'
import v2 from '../packstream'
import { internal } from 'neo4j-driver-core'
import transformersFactories from './bolt-protocol-v2.transformer'
import Transformer from './transformer'

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

get transformer () {
if (this._transformer === undefined) {
this._transformer = new Transformer(Object.values(transformersFactories).map(create => create(this._config)))
}
return this._transformer
}

get version () {
return BOLT_PROTOCOL_V2
}
Expand Down
Loading