Skip to content

Commit 532f027

Browse files
authored
Introduce Driver.getServerInfo method (#878)
This is part of the drivers unification initiative. The `Driver.verifyConnectivity` method was implemented in different ways across the drivers with different returns value. For consistency and clarity about what the method does, the new behaviour of the `Driver.verifyConnectivity` is check the connectivity with all the readers available and then returns a resolve empty promise in case of success (the promise will be rejected, otherwise). The Server Info information was moved to the method `Driver.getSeverInfo` which verifies the connectivity and then returns the `ServerInfo` of one of the read servers. For keeping the backwards compatibility, the `Driver.verifyConnectivity` was deprecated in this version and it should be replace with a version with `Promise<void>` as return value in the next major release. `Driver.getServerInfo` should be used instead if the client code needs the server information.
1 parent 46601b3 commit 532f027

File tree

14 files changed

+574
-146
lines changed

14 files changed

+574
-146
lines changed

packages/bolt-connection/src/connection-provider/connection-provider-direct.js

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -103,4 +103,8 @@ export default class DirectConnectionProvider extends PooledConnectionProvider {
103103
version => version >= BOLT_PROTOCOL_V4_4
104104
)
105105
}
106+
107+
async verifyConnectivityAndGetServerInfo () {
108+
return await this._verifyConnectivityAndGetServerVersion({ address: this._address })
109+
}
106110
}

packages/bolt-connection/src/connection-provider/connection-provider-pooled.js

Lines changed: 18 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,7 @@
1919

2020
import { createChannelConnection, ConnectionErrorHandler } from '../connection'
2121
import Pool, { PoolConfig } from '../pool'
22-
import { error, ConnectionProvider } from 'neo4j-driver-core'
22+
import { error, ConnectionProvider, ServerInfo } from 'neo4j-driver-core'
2323

2424
const { SERVICE_UNAVAILABLE } = error
2525
export default class PooledConnectionProvider extends ConnectionProvider {
@@ -109,6 +109,23 @@ export default class PooledConnectionProvider extends ConnectionProvider {
109109
return conn.close()
110110
}
111111

112+
/**
113+
* Acquire a connection from the pool and return it ServerInfo
114+
* @param {object} param
115+
* @param {string} param.address the server address
116+
* @return {Promise<ServerInfo>} the server info
117+
*/
118+
async _verifyConnectivityAndGetServerVersion ({ address }) {
119+
const connection = await this._connectionPool.acquire(address)
120+
const serverInfo = new ServerInfo(connection.server, connection.protocol().version)
121+
try {
122+
await connection.resetAndFlush()
123+
} finally {
124+
await connection._release()
125+
}
126+
return serverInfo
127+
}
128+
112129
async close () {
113130
// purge all idle connections in the connection pool
114131
await this._connectionPool.close()

packages/bolt-connection/src/connection-provider/connection-provider-routing.js

Lines changed: 18 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -237,7 +237,24 @@ export default class RoutingConnectionProvider extends PooledConnectionProvider
237237
return await this._hasProtocolVersion(
238238
version => version >= BOLT_PROTOCOL_V4_4
239239
)
240-
}
240+
}
241+
242+
async verifyConnectivityAndGetServerInfo ({ database, accessMode }) {
243+
const context = { database: database || DEFAULT_DB_NAME }
244+
245+
const routingTable = await this._freshRoutingTable({
246+
accessMode,
247+
database: context.database,
248+
onDatabaseNameResolved: (databaseName) => {
249+
context.database = context.database || databaseName
250+
}
251+
})
252+
253+
const servers = accessMode === WRITE ? routingTable.writers : routingTable.readers
254+
255+
return Promise.all(servers.map(address => this._verifyConnectivityAndGetServerVersion({ address })))
256+
.then(([serverInfo]) => serverInfo)
257+
}
241258

242259
forget (address, database) {
243260
this._routingTableRegistry.apply(database, {

packages/bolt-connection/src/pool/pool.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -71,7 +71,7 @@ class Pool {
7171
/**
7272
* Acquire and idle resource fom the pool or create a new one.
7373
* @param {ServerAddress} address the address for which we're acquiring.
74-
* @return {Object} resource that is ready to use.
74+
* @return {Promise<Object>} resource that is ready to use.
7575
*/
7676
acquire (address) {
7777
const key = address.asKey()

packages/bolt-connection/test/connection-provider/connection-provider-direct.test.js

Lines changed: 192 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,7 @@
2020
import DirectConnectionProvider from '../../src/connection-provider/connection-provider-direct'
2121
import { Pool } from '../../src/pool'
2222
import { Connection, DelegateConnection } from '../../src/connection'
23-
import { internal, newError } from 'neo4j-driver-core'
23+
import { internal, newError, ServerInfo } from 'neo4j-driver-core'
2424

2525
const {
2626
serverAddress: { ServerAddress },
@@ -139,6 +139,180 @@ it('should not change error when TokenExpired happens', async () => {
139139
expect(error).toBe(expectedError)
140140
})
141141

142+
describe('.verifyConnectivityAndGetServerInfo()', () => {
143+
describe('when connection is available in the pool', () => {
144+
it('should return the server info', async () => {
145+
const { connectionProvider, server, protocolVersion } = setup()
146+
147+
const serverInfo = await connectionProvider.verifyConnectivityAndGetServerInfo()
148+
149+
expect(serverInfo).toEqual(new ServerInfo(server, protocolVersion))
150+
})
151+
152+
it('should reset and flush the connection', async () => {
153+
const { connectionProvider, resetAndFlush } = setup()
154+
155+
await connectionProvider.verifyConnectivityAndGetServerInfo()
156+
157+
expect(resetAndFlush).toBeCalledTimes(1)
158+
})
159+
160+
it('should release the connection', async () => {
161+
const { connectionProvider, seenConnections } = setup()
162+
163+
await connectionProvider.verifyConnectivityAndGetServerInfo()
164+
165+
expect(seenConnections[0]._release).toHaveBeenCalledTimes(1)
166+
})
167+
168+
it('should resetAndFlush and then release the connection', async () => {
169+
const { connectionProvider, seenConnections, resetAndFlush } = setup()
170+
171+
await connectionProvider.verifyConnectivityAndGetServerInfo()
172+
173+
expect(seenConnections[0]._release.mock.invocationCallOrder[0])
174+
.toBeGreaterThan(resetAndFlush.mock.invocationCallOrder[0])
175+
})
176+
177+
describe('when reset and flush fails', () => {
178+
it('should fails with the reset and flush error', async () => {
179+
const error = newError('Error')
180+
const { connectionProvider, resetAndFlush } = setup()
181+
182+
resetAndFlush.mockRejectedValue(error)
183+
184+
try {
185+
await connectionProvider.verifyConnectivityAndGetServerInfo()
186+
expect().toBe('Not reached')
187+
} catch (e) {
188+
expect(e).toBe(error)
189+
}
190+
})
191+
192+
it('should release the connection', async () => {
193+
const error = newError('Error')
194+
const { connectionProvider, resetAndFlush, seenConnections } = setup()
195+
196+
resetAndFlush.mockRejectedValue(error)
197+
198+
try {
199+
await connectionProvider.verifyConnectivityAndGetServerInfo()
200+
} catch (e) {
201+
} finally {
202+
expect(seenConnections[0]._release).toHaveBeenCalledTimes(1)
203+
}
204+
})
205+
206+
describe('and release fails', () => {
207+
it('should fails with the release error', async () => {
208+
const error = newError('Error')
209+
const releaseError = newError('release errror')
210+
211+
const { connectionProvider, resetAndFlush } = setup(
212+
{
213+
releaseMock: () => Promise.reject(releaseError)
214+
})
215+
216+
resetAndFlush.mockRejectedValue(error)
217+
218+
try {
219+
await connectionProvider.verifyConnectivityAndGetServerInfo()
220+
expect().toBe('Not reached')
221+
} catch (e) {
222+
expect(e).toBe(releaseError)
223+
}
224+
})
225+
})
226+
})
227+
228+
describe('when release fails', () => {
229+
it('should fails with the release error', async () => {
230+
const error = newError('Error')
231+
232+
const { connectionProvider } = setup(
233+
{
234+
releaseMock: () => Promise.reject(error)
235+
})
236+
237+
try {
238+
await connectionProvider.verifyConnectivityAndGetServerInfo()
239+
expect().toBe('Not reached')
240+
} catch (e) {
241+
expect(e).toBe(error)
242+
}
243+
})
244+
})
245+
246+
function setup({ releaseMock } = {}) {
247+
const protocolVersion = 4.4
248+
const resetAndFlush = jest.fn(() => Promise.resolve())
249+
const server = { address: 'localhost:123', version: 'neo4j/1234' }
250+
const seenConnections = []
251+
const create = (address, release) => {
252+
const connection = new FakeConnection(address, release, server)
253+
connection.protocol = () => {
254+
return { version: protocolVersion }
255+
}
256+
connection.resetAndFlush = resetAndFlush
257+
if (releaseMock) {
258+
connection._release = releaseMock
259+
}
260+
seenConnections.push(connection)
261+
return connection
262+
}
263+
const address = ServerAddress.fromUrl('localhost:123')
264+
const pool = newPool({ create })
265+
const connectionProvider = newDirectConnectionProvider(address, pool)
266+
return {
267+
connectionProvider,
268+
server,
269+
protocolVersion,
270+
resetAndFlush,
271+
seenConnections
272+
}
273+
}
274+
})
275+
276+
describe('when connection is not available in the pool', () => {
277+
it('should reject with acquisition timeout error', async () => {
278+
const address = ServerAddress.fromUrl('localhost:123')
279+
const pool = newPool({
280+
config: {
281+
acquisitionTimeout: 0,
282+
}
283+
})
284+
285+
const connectionProvider = newDirectConnectionProvider(address, pool)
286+
287+
try {
288+
connectionProvider = await connectionProvider.verifyConnectivityAndGetServerInfo()
289+
expect().toBe('not reached')
290+
} catch (e) {
291+
expect(e).toBeDefined()
292+
}
293+
})
294+
})
295+
296+
describe('when connection it could not create the connection', () => {
297+
it('should reject with connection creation error', async () => {
298+
const error = new Error('Connection creation error')
299+
const address = ServerAddress.fromUrl('localhost:123')
300+
const pool = newPool({
301+
create: () => { throw error }
302+
})
303+
304+
const connectionProvider = newDirectConnectionProvider(address, pool)
305+
306+
try {
307+
connectionProvider = await connectionProvider.verifyConnectivityAndGetServerInfo()
308+
expect().toBe('not reached')
309+
} catch (e) {
310+
expect(e).toBe(error)
311+
}
312+
})
313+
})
314+
})
315+
142316
function newDirectConnectionProvider (address, pool) {
143317
const connectionProvider = new DirectConnectionProvider({
144318
id: 0,
@@ -150,22 +324,34 @@ function newDirectConnectionProvider (address, pool) {
150324
return connectionProvider
151325
}
152326

153-
function newPool () {
327+
function newPool({ create, config } = {}) {
328+
const _create = (address, release) => {
329+
if (create) {
330+
return create(address, release)
331+
}
332+
return new FakeConnection(address, release)
333+
}
154334
return new Pool({
335+
config,
155336
create: (address, release) =>
156-
Promise.resolve(new FakeConnection(address, release))
337+
Promise.resolve(_create(address, release)),
157338
})
158339
}
159340

160341
class FakeConnection extends Connection {
161-
constructor (address, release) {
342+
constructor(address, release, server) {
162343
super(null)
163344

164345
this._address = address
165-
this.release = release
346+
this._release = jest.fn(() => release(address, this))
347+
this._server = server
166348
}
167349

168-
get address () {
350+
get address() {
169351
return this._address
170352
}
353+
354+
get server() {
355+
return this._server
356+
}
171357
}

0 commit comments

Comments
 (0)