Skip to content

Commit 9894a29

Browse files
authored
Create 4.3 Protocol and rediscovery the Routing Table using the Route Message (#646)
Starting the development of the version 4.3 with the creation of the protocol class making it available on the handshake. The replacement of the routing procedure by the route message for routing table discovery on this new version pushes the complexity to the server and makes the protocol cleaner and more efficient. The routing table requisition details was pushed to the protocol implementation classes, the request provides a observer which parses the response and make it available as a RawRoutingTable on completed. The rediscovery class uses the method, constructs the RoutingTable and treats the exceptional cases.
1 parent d0b66c3 commit 9894a29

33 files changed

+1946
-1343
lines changed

src/internal/bolt-protocol-v3.js

+41-1
Original file line numberDiff line numberDiff line change
@@ -22,9 +22,14 @@ import { assertDatabaseIsEmpty } from './bolt-protocol-util'
2222
import {
2323
StreamObserver,
2424
LoginObserver,
25-
ResultStreamObserver
25+
ResultStreamObserver,
26+
ProcedureRouteObserver
2627
} from './stream-observers'
2728
import { BOLT_PROTOCOL_V3 } from './constants'
29+
import Bookmark from './bookmark'
30+
import TxConfig from './tx-config'
31+
const CONTEXT = 'context'
32+
const CALL_GET_ROUTING_TABLE = `CALL dbms.cluster.routing.getRoutingTable($${CONTEXT})`
2833

2934
const noOpObserver = new StreamObserver()
3035

@@ -183,4 +188,39 @@ export default class BoltProtocol extends BoltProtocolV2 {
183188

184189
return observer
185190
}
191+
192+
/**
193+
* Request routing information
194+
*
195+
* @param {Object} param -
196+
* @param {object} param.routingContext The routing context used to define the routing table.
197+
* Multi-datacenter deployments is one of its use cases
198+
* @param {string} param.databaseName The database name
199+
* @param {Bookmark} params.sessionContext.bookmark The bookmark used for request the routing table
200+
* @param {string} params.sessionContext.mode The session mode
201+
* @param {string} params.sessionContext.database The database name used on the session
202+
* @param {function()} params.sessionContext.afterComplete The session param used after the session closed
203+
* @param {function(err: Error)} param.onError
204+
* @param {function(RawRoutingTable)} param.onCompleted
205+
* @returns {RouteObserver} the route observer
206+
*/
207+
requestRoutingInformation ({
208+
routingContext = {},
209+
sessionContext = {},
210+
onError,
211+
onCompleted
212+
}) {
213+
const resultObserver = this.run(
214+
CALL_GET_ROUTING_TABLE,
215+
{ [CONTEXT]: routingContext },
216+
{ ...sessionContext, txConfig: TxConfig.empty() }
217+
)
218+
219+
return new ProcedureRouteObserver({
220+
resultObserver,
221+
connection: this._connection,
222+
onError,
223+
onCompleted
224+
})
225+
}
186226
}

src/internal/bolt-protocol-v4x0.js

+50-1
Original file line numberDiff line numberDiff line change
@@ -18,8 +18,17 @@
1818
*/
1919
import BoltProtocolV3 from './bolt-protocol-v3'
2020
import RequestMessage, { ALL } from './request-message'
21-
import { ResultStreamObserver } from './stream-observers'
21+
import {
22+
ResultStreamObserver,
23+
ProcedureRouteObserver
24+
} from './stream-observers'
2225
import { BOLT_PROTOCOL_V4_0 } from './constants'
26+
import Bookmark from './bookmark'
27+
import TxConfig from './tx-config'
28+
29+
const CONTEXT = 'context'
30+
const DATABASE = 'database'
31+
const CALL_GET_ROUTING_TABLE_MULTI_DB = `CALL dbms.routing.getRoutingTable($${CONTEXT}, $${DATABASE})`
2332

2433
export default class BoltProtocol extends BoltProtocolV3 {
2534
get version () {
@@ -119,4 +128,44 @@ export default class BoltProtocol extends BoltProtocolV3 {
119128
}
120129

121130
_noOp () {}
131+
132+
/**
133+
* Request routing information
134+
*
135+
* @param {Object} param -
136+
* @param {object} param.routingContext The routing context used to define the routing table.
137+
* Multi-datacenter deployments is one of its use cases
138+
* @param {string} param.databaseName The database name
139+
* @param {Bookmark} params.sessionContext.bookmark The bookmark used for request the routing table
140+
* @param {string} params.sessionContext.mode The session mode
141+
* @param {string} params.sessionContext.database The database name used on the session
142+
* @param {function()} params.sessionContext.afterComplete The session param used after the session closed
143+
* @param {function(err: Error)} param.onError
144+
* @param {function(RawRoutingTable)} param.onCompleted
145+
* @returns {RouteObserver} the route observer
146+
*/
147+
requestRoutingInformation ({
148+
routingContext = {},
149+
databaseName = null,
150+
sessionContext = {},
151+
initialAddress = null,
152+
onError,
153+
onCompleted
154+
}) {
155+
const resultObserver = this.run(
156+
CALL_GET_ROUTING_TABLE_MULTI_DB,
157+
{
158+
[CONTEXT]: { ...routingContext, address: initialAddress },
159+
[DATABASE]: databaseName
160+
},
161+
{ ...sessionContext, txConfig: TxConfig.empty() }
162+
)
163+
164+
return new ProcedureRouteObserver({
165+
resultObserver,
166+
connection: this._connection,
167+
onError,
168+
onCompleted
169+
})
170+
}
122171
}

src/internal/bolt-protocol-v4x2.js

-11
Original file line numberDiff line numberDiff line change
@@ -20,17 +20,6 @@ import BoltProtocolV41 from './bolt-protocol-v4x1'
2020
import { BOLT_PROTOCOL_V4_2 } from './constants'
2121

2222
export default class BoltProtocol extends BoltProtocolV41 {
23-
/**
24-
* @constructor
25-
* @param {Connection} connection the connection.
26-
* @param {Chunker} chunker the chunker.
27-
* @param {boolean} disableLosslessIntegers if this connection should convert all received integers to native JS numbers.
28-
* @param {Object} serversideRouting
29-
*/
30-
constructor (connection, chunker, disableLosslessIntegers, serversideRouting) {
31-
super(connection, chunker, disableLosslessIntegers, serversideRouting)
32-
}
33-
3423
get version () {
3524
return BOLT_PROTOCOL_V4_2
3625
}

src/internal/bolt-protocol-v4x3.js

+65
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,65 @@
1+
/**
2+
* Copyright (c) 2002-2020 "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+
import BoltProtocolV42 from './bolt-protocol-v4x2'
20+
import { BOLT_PROTOCOL_V4_3 } from './constants'
21+
import RequestMessage from './request-message'
22+
import { RouteObserver } from './stream-observers'
23+
24+
export default class BoltProtocol extends BoltProtocolV42 {
25+
get version () {
26+
return BOLT_PROTOCOL_V4_3
27+
}
28+
29+
/**
30+
* Request routing information
31+
*
32+
* @param {Object} param -
33+
* @param {object} param.routingContext The routing context used to define the routing table.
34+
* Multi-datacenter deployments is one of its use cases
35+
* @param {string} param.databaseName The database name
36+
* @param {function(err: Error)} param.onError
37+
* @param {function(RawRoutingTable)} param.onCompleted
38+
* @returns {RouteObserver} the route observer
39+
*/
40+
41+
requestRoutingInformation ({
42+
routingContext = {},
43+
databaseName = null,
44+
initialAddress = null,
45+
onError,
46+
onCompleted
47+
}) {
48+
const observer = new RouteObserver({
49+
connection: this._connection,
50+
onError,
51+
onCompleted
52+
})
53+
54+
this._connection.write(
55+
RequestMessage.route(
56+
{ ...routingContext, address: initialAddress },
57+
databaseName
58+
),
59+
observer,
60+
true
61+
)
62+
63+
return observer
64+
}
65+
}

src/internal/connection-provider-routing.js

+1-4
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,6 @@ import { READ, WRITE } from '../driver'
2222
import Session from '../session'
2323
import RoutingTable from './routing-table'
2424
import Rediscovery from './rediscovery'
25-
import { RoutingTableGetterFactory } from './routing-table-getter'
2625
import { HostNameResolver } from './node'
2726
import SingleConnectionProvider from './connection-provider-single'
2827
import PooledConnectionProvider from './connection-provider-pooled'
@@ -65,9 +64,7 @@ export default class RoutingConnectionProvider extends PooledConnectionProvider
6564

6665
this._seedRouter = address
6766
this._routingTables = {}
68-
this._rediscovery = new Rediscovery(
69-
new RoutingTableGetterFactory(routingContext, address.toString())
70-
)
67+
this._rediscovery = new Rediscovery(routingContext, address.toString())
7168
this._loadBalancingStrategy = new LeastConnectedLoadBalancingStrategy(
7269
this._connectionPool
7370
)

src/internal/constants.js

+3-1
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@ const BOLT_PROTOCOL_V3 = 3
2626
const BOLT_PROTOCOL_V4_0 = 4.0
2727
const BOLT_PROTOCOL_V4_1 = 4.1
2828
const BOLT_PROTOCOL_V4_2 = 4.2
29+
const BOLT_PROTOCOL_V4_3 = 4.3
2930

3031
export {
3132
ACCESS_MODE_READ,
@@ -35,5 +36,6 @@ export {
3536
BOLT_PROTOCOL_V3,
3637
BOLT_PROTOCOL_V4_0,
3738
BOLT_PROTOCOL_V4_1,
38-
BOLT_PROTOCOL_V4_2
39+
BOLT_PROTOCOL_V4_2,
40+
BOLT_PROTOCOL_V4_3
3941
}

src/internal/protocol-handshaker.js

+9-2
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,7 @@ import BoltProtocolV3 from './bolt-protocol-v3'
2525
import BoltProtocolV4x0 from './bolt-protocol-v4x0'
2626
import BoltProtocolV4x1 from './bolt-protocol-v4x1'
2727
import BoltProtocolV4x2 from './bolt-protocol-v4x2'
28-
28+
import BoltProtocolV4x3 from './bolt-protocol-v4x3'
2929
const BOLT_MAGIC_PREAMBLE = 0x6060b017
3030

3131
export default class ProtocolHandshaker {
@@ -132,6 +132,13 @@ export default class ProtocolHandshaker {
132132
this._disableLosslessIntegers,
133133
this._serversideRouting
134134
)
135+
case 4.3:
136+
return new BoltProtocolV4x3(
137+
this._connection,
138+
this._chunker,
139+
this._disableLosslessIntegers,
140+
this._serversideRouting
141+
)
135142
default:
136143
throw newError('Unknown Bolt protocol version: ' + version)
137144
}
@@ -149,7 +156,7 @@ function newHandshakeBuffer () {
149156
handshakeBuffer.writeInt32(BOLT_MAGIC_PREAMBLE)
150157

151158
// proposed versions
152-
handshakeBuffer.writeInt32((2 << 8) | 4)
159+
handshakeBuffer.writeInt32((3 << 8) | 4)
153160
handshakeBuffer.writeInt32((1 << 8) | 4)
154161
handshakeBuffer.writeInt32(4)
155162
handshakeBuffer.writeInt32(3)

src/internal/rediscovery.js

+57-11
Original file line numberDiff line numberDiff line change
@@ -17,17 +17,23 @@
1717
* limitations under the License.
1818
*/
1919
import RoutingTable from './routing-table'
20+
import RawRoutingTable from './routing-table-raw'
2021
import Session from '../session'
21-
import { RoutingTableGetterFactory } from './routing-table-getter'
2222
import ServerAddress from './server-address'
23+
import { newError, SERVICE_UNAVAILABLE } from '../error'
24+
25+
const PROCEDURE_NOT_FOUND_CODE = 'Neo.ClientError.Procedure.ProcedureNotFound'
26+
const DATABASE_NOT_FOUND_CODE = 'Neo.ClientError.Database.DatabaseNotFound'
2327

2428
export default class Rediscovery {
2529
/**
2630
* @constructor
27-
* @param {RoutingTableGetterFactory} routingTableGetterFactory the util to use.
31+
* @param {object} routingContext
32+
* @param {string} initialAddress
2833
*/
29-
constructor (routingTableGetterFactory) {
30-
this._routingTableGetterFactory = routingTableGetterFactory
34+
constructor (routingContext, initialAddress) {
35+
this._routingContext = routingContext
36+
this._initialAddress = initialAddress
3137
}
3238

3339
/**
@@ -39,15 +45,55 @@ export default class Rediscovery {
3945
*/
4046
lookupRoutingTableOnRouter (session, database, routerAddress) {
4147
return session._acquireConnection(connection => {
42-
const routingTableGetter = this._routingTableGetterFactory.create(
43-
connection
44-
)
45-
return routingTableGetter.get(
48+
return this._requestRawRoutingTable(
4649
connection,
50+
session,
4751
database,
48-
routerAddress,
49-
session
50-
)
52+
routerAddress
53+
).then(rawRoutingTable => {
54+
if (rawRoutingTable.isNull) {
55+
return null
56+
}
57+
return RoutingTable.fromRawRoutingTable(
58+
database,
59+
routerAddress,
60+
rawRoutingTable
61+
)
62+
})
63+
})
64+
}
65+
66+
_requestRawRoutingTable (connection, session, database, routerAddress) {
67+
return new Promise((resolve, reject) => {
68+
connection.protocol().requestRoutingInformation({
69+
routingContext: this._routingContext,
70+
initialAddress: this._initialAddress,
71+
databaseName: database,
72+
sessionContext: {
73+
bookmark: session._lastBookmark,
74+
mode: session._mode,
75+
database: session._database,
76+
afterComplete: session._onComplete
77+
},
78+
onCompleted: resolve,
79+
onError: error => {
80+
if (error.code === DATABASE_NOT_FOUND_CODE) {
81+
reject(error)
82+
} else if (error.code === PROCEDURE_NOT_FOUND_CODE) {
83+
// throw when getServers procedure not found because this is clearly a configuration issue
84+
reject(
85+
newError(
86+
`Server at ${routerAddress.asHostPort()} can't perform routing. Make sure you are connecting to a causal cluster`,
87+
SERVICE_UNAVAILABLE
88+
)
89+
)
90+
} else {
91+
// return nothing when failed to connect because code higher in the callstack is still able to retry with a
92+
// different session towards a different router
93+
resolve(RawRoutingTable.ofNull())
94+
}
95+
}
96+
})
5197
})
5298
}
5399
}

src/internal/request-message.js

+16
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,7 @@ const GOODBYE = 0x02 // 0000 0010 // GOODBYE
3535
const BEGIN = 0x11 // 0001 0001 // BEGIN <metadata>
3636
const COMMIT = 0x12 // 0001 0010 // COMMIT
3737
const ROLLBACK = 0x13 // 0001 0011 // ROLLBACK
38+
const ROUTE = 0x66 // 0110 0110 // ROUTE
3839

3940
const DISCARD = 0x2f // 0010 1111 // DISCARD
4041
const PULL = 0x3f // 0011 1111 // PULL
@@ -209,6 +210,21 @@ export default class RequestMessage {
209210
() => `DISCARD ${JSON.stringify(metadata)}`
210211
)
211212
}
213+
214+
/**
215+
* Generate the ROUTE message, this message is used to fetch the routing table from the server
216+
*
217+
* @param {object} routingContext The routing context used to define the routing table. Multi-datacenter deployments is one of its use cases
218+
* @param {string} databaseName The name of the database to get the routing table for.
219+
* @return {RequestMessage} the ROUTE message.
220+
*/
221+
static route (routingContext = {}, databaseName = null) {
222+
return new RequestMessage(
223+
ROUTE,
224+
[routingContext, databaseName],
225+
() => `ROUTE ${JSON.stringify(routingContext)} ${databaseName}`
226+
)
227+
}
212228
}
213229

214230
/**

0 commit comments

Comments
 (0)