diff --git a/src/driver.js b/src/driver.js index 592e2522e..16753b30b 100644 --- a/src/driver.js +++ b/src/driver.js @@ -123,6 +123,19 @@ class Driver { return connectionProvider.supportsMultiDb() } + /** + * Returns whether the server supports transaction config capabilities based on the protocol + * version negotiated via handshake. + * + * Note that this function call _always_ causes a round-trip to the server. + * + * @returns {Promise} promise resolved with a boolean or rejected with error. + */ + supportsTransactionConfig () { + const connectionProvider = this._getOrCreateConnectionProvider() + return connectionProvider.supportsTransactionConfig() + } + /** * Acquire a session to communicate with the database. The session will * borrow connections from the underlying connection pool as required and diff --git a/src/internal/connection-provider-direct.js b/src/internal/connection-provider-direct.js index 04d8aa38b..9b2bcf975 100644 --- a/src/internal/connection-provider-direct.js +++ b/src/internal/connection-provider-direct.js @@ -20,7 +20,7 @@ import PooledConnectionProvider from './connection-provider-pooled' import DelegateConnection from './connection-delegate' import ChannelConnection from './connection-channel' -import { BOLT_PROTOCOL_V4 } from './constants' +import { BOLT_PROTOCOL_V4, BOLT_PROTOCOL_V3 } from './constants' export default class DirectConnectionProvider extends PooledConnectionProvider { constructor ({ id, config, log, address, userAgent, authToken }) { @@ -39,7 +39,7 @@ export default class DirectConnectionProvider extends PooledConnectionProvider { .then(connection => new DelegateConnection(connection, null)) } - async supportsMultiDb () { + async _hasProtocolVersion (versionPredicate) { const connection = ChannelConnection.create( this._address, this._config, @@ -52,7 +52,7 @@ export default class DirectConnectionProvider extends PooledConnectionProvider { const protocol = connection.protocol() if (protocol) { - return protocol.version >= BOLT_PROTOCOL_V4 + return versionPredicate(protocol.version) } return false @@ -60,4 +60,16 @@ export default class DirectConnectionProvider extends PooledConnectionProvider { await connection.close() } } + + async supportsMultiDb () { + return await this._hasProtocolVersion( + version => version >= BOLT_PROTOCOL_V4 + ) + } + + async supportsTransactionConfig () { + return await this._hasProtocolVersion( + version => version >= BOLT_PROTOCOL_V3 + ) + } } diff --git a/src/internal/connection-provider-routing.js b/src/internal/connection-provider-routing.js index 3008b451c..4a9bb4515 100644 --- a/src/internal/connection-provider-routing.js +++ b/src/internal/connection-provider-routing.js @@ -33,7 +33,7 @@ import LeastConnectedLoadBalancingStrategy from './least-connected-load-balancin import Bookmark from './bookmark' import ChannelConnection from './connection-channel' import { int } from '../integer' -import { BOLT_PROTOCOL_V4 } from './constants' +import { BOLT_PROTOCOL_V4, BOLT_PROTOCOL_V3 } from './constants' const UNAUTHORIZED_ERROR_CODE = 'Neo.ClientError.Security.Unauthorized' const DATABASE_NOT_FOUND_ERROR_CODE = @@ -152,7 +152,7 @@ export default class RoutingConnectionProvider extends PooledConnectionProvider } } - async supportsMultiDb () { + async _hasProtocolVersion (versionPredicate) { const addresses = await this._resolveSeedRouter(this._seedRouter) let lastError @@ -169,7 +169,7 @@ export default class RoutingConnectionProvider extends PooledConnectionProvider const protocol = connection.protocol() if (protocol) { - return protocol.version >= BOLT_PROTOCOL_V4 + return versionPredicate(protocol.version) } return false @@ -187,6 +187,18 @@ export default class RoutingConnectionProvider extends PooledConnectionProvider return false } + async supportsMultiDb () { + return await this._hasProtocolVersion( + version => version >= BOLT_PROTOCOL_V4 + ) + } + + async supportsTransactionConfig () { + return await this._hasProtocolVersion( + version => version >= BOLT_PROTOCOL_V3 + ) + } + forget (address, database) { if (database || database === '') { this._routingTables[database].forget(address) diff --git a/src/internal/connection-provider.js b/src/internal/connection-provider.js index 466bb1902..afb7282fb 100644 --- a/src/internal/connection-provider.js +++ b/src/internal/connection-provider.js @@ -48,6 +48,16 @@ export default class ConnectionProvider { throw new Error('not implemented') } + /** + * This method checks whether the backend database supports transaction config functionality + * by checking protocol handshake result. + * + * @returns {Promise} + */ + supportsTransactionConfig () { + throw new Error('not implemented') + } + /** * Closes this connection provider along with its internals (connections, pools, etc.) * diff --git a/test/internal/node/direct.driver.boltkit.test.js b/test/internal/node/direct.driver.boltkit.test.js index 4f4ab41d0..7437e5fb0 100644 --- a/test/internal/node/direct.driver.boltkit.test.js +++ b/test/internal/node/direct.driver.boltkit.test.js @@ -418,7 +418,7 @@ describe('#stub-direct direct driver with stub server', () => { } const server = await boltStub.start( - `./test/resources/boltstub/${version}/supports_multi_db.script`, + `./test/resources/boltstub/${version}/supports_protocol_version.script`, 9001 ) @@ -447,6 +447,44 @@ describe('#stub-direct direct driver with stub server', () => { }) }) + describe('should report whether transaction config is supported', () => { + async function verifySupportsTransactionConfig (version, expected) { + if (!boltStub.supported) { + return + } + + const server = await boltStub.start( + `./test/resources/boltstub/${version}/supports_protocol_version.script`, + 9001 + ) + + const driver = boltStub.newDriver('bolt://127.0.0.1:9001') + + await expectAsync(driver.supportsTransactionConfig()).toBeResolvedTo( + expected + ) + + await driver.close() + await server.exit() + } + + it('v1', () => verifySupportsTransactionConfig('v1', false)) + it('v2', () => verifySupportsTransactionConfig('v2', false)) + it('v3', () => verifySupportsTransactionConfig('v3', true)) + it('v4', () => verifySupportsTransactionConfig('v4', true)) + it('on error', async () => { + const driver = boltStub.newDriver('bolt://127.0.0.1:9001') + + await expectAsync(driver.supportsTransactionConfig()).toBeRejectedWith( + jasmine.objectContaining({ + code: SERVICE_UNAVAILABLE + }) + ) + + await driver.close() + }) + }) + describe('should allow to change fetch size', () => { async function verifyFailureOnCommit (version) { if (!boltStub.supported) { diff --git a/test/internal/node/routing.driver.boltkit.test.js b/test/internal/node/routing.driver.boltkit.test.js index 599a25538..aa8cb7d26 100644 --- a/test/internal/node/routing.driver.boltkit.test.js +++ b/test/internal/node/routing.driver.boltkit.test.js @@ -2339,7 +2339,7 @@ describe('#stub-routing routing driver with stub server', () => { } const server = await boltStub.start( - `./test/resources/boltstub/${version}/supports_multi_db.script`, + `./test/resources/boltstub/${version}/supports_protocol_version.script`, 9001 ) @@ -2357,7 +2357,7 @@ describe('#stub-routing routing driver with stub server', () => { } const server = await boltStub.start( - `./test/resources/boltstub/${version}/supports_multi_db.script`, + `./test/resources/boltstub/${version}/supports_protocol_version.script`, 9001 ) @@ -2396,6 +2396,81 @@ describe('#stub-routing routing driver with stub server', () => { }) }) + describe('should report whether transaction config is supported', () => { + async function verifySupportsTransactionConfig (version, expected) { + if (!boltStub.supported) { + return + } + + const server = await boltStub.start( + `./test/resources/boltstub/${version}/supports_protocol_version.script`, + 9001 + ) + + const driver = boltStub.newDriver('neo4j://127.0.0.1:9001') + + await expectAsync(driver.supportsTransactionConfig()).toBeResolvedTo( + expected + ) + + await driver.close() + await server.exit() + } + + async function verifySupportsTransactionConfigWithResolver ( + version, + expected + ) { + if (!boltStub.supported) { + return + } + + const server = await boltStub.start( + `./test/resources/boltstub/${version}/supports_protocol_version.script`, + 9001 + ) + + const driver = boltStub.newDriver('neo4j://127.0.0.1:8000', { + resolver: address => [ + 'neo4j://127.0.0.1:9010', + 'neo4j://127.0.0.1:9005', + 'neo4j://127.0.0.1:9001' + ] + }) + + await expectAsync(driver.supportsTransactionConfig()).toBeResolvedTo( + expected + ) + + await driver.close() + await server.exit() + } + + it('v1', () => verifySupportsTransactionConfig('v1', false)) + it('v2', () => verifySupportsTransactionConfig('v2', false)) + it('v3', () => verifySupportsTransactionConfig('v3', true)) + it('v4', () => verifySupportsTransactionConfig('v4', true)) + it('v1 with resolver', () => + verifySupportsTransactionConfigWithResolver('v1', false)) + it('v2 with resolver', () => + verifySupportsTransactionConfigWithResolver('v2', false)) + it('v3 with resolver', () => + verifySupportsTransactionConfigWithResolver('v3', true)) + it('v4 with resolver', () => + verifySupportsTransactionConfigWithResolver('v4', true)) + it('on error', async () => { + const driver = boltStub.newDriver('neo4j://127.0.0.1:9001') + + await expectAsync(driver.supportsTransactionConfig()).toBeRejectedWith( + jasmine.objectContaining({ + code: SESSION_EXPIRED + }) + ) + + await driver.close() + }) + }) + async function testAddressPurgeOnDatabaseError (script, query, accessMode) { if (!boltStub.supported) { return diff --git a/test/resources/boltstub/v1/supports_multi_db.script b/test/resources/boltstub/v1/supports_protocol_version.script similarity index 100% rename from test/resources/boltstub/v1/supports_multi_db.script rename to test/resources/boltstub/v1/supports_protocol_version.script diff --git a/test/resources/boltstub/v2/supports_multi_db.script b/test/resources/boltstub/v2/supports_protocol_version.script similarity index 100% rename from test/resources/boltstub/v2/supports_multi_db.script rename to test/resources/boltstub/v2/supports_protocol_version.script diff --git a/test/resources/boltstub/v3/supports_multi_db.script b/test/resources/boltstub/v3/supports_protocol_version.script similarity index 100% rename from test/resources/boltstub/v3/supports_multi_db.script rename to test/resources/boltstub/v3/supports_protocol_version.script diff --git a/test/resources/boltstub/v4/supports_multi_db.script b/test/resources/boltstub/v4/supports_protocol_version.script similarity index 100% rename from test/resources/boltstub/v4/supports_multi_db.script rename to test/resources/boltstub/v4/supports_protocol_version.script diff --git a/test/types/driver.test.ts b/test/types/driver.test.ts index 05369b5c0..53abb6037 100644 --- a/test/types/driver.test.ts +++ b/test/types/driver.test.ts @@ -111,6 +111,10 @@ driver.supportsMultiDb().then((supported: boolean) => { console.log(`multi database is supported? => ${supported}`) }) +driver.supportsTransactionConfig().then((supported: boolean) => { + console.log(`transaction config is supported? => ${supported}`) +}) + const rxSession1: RxSession = driver.rxSession() const rxSession2: RxSession = driver.rxSession({ defaultAccessMode: READ }) const rxSession3: RxSession = driver.rxSession({ defaultAccessMode: 'READ' }) diff --git a/types/driver.d.ts b/types/driver.d.ts index 3cb746aec..41ac5553b 100644 --- a/types/driver.d.ts +++ b/types/driver.d.ts @@ -95,6 +95,8 @@ declare interface Driver { verifyConnectivity(): Promise supportsMultiDb(): Promise + + supportsTransactionConfig(): Promise } export {