From 0ea50127a9fa5cd2e62bfee92579e591415a4a80 Mon Sep 17 00:00:00 2001 From: MaxAake <61233757+MaxAake@users.noreply.github.com> Date: Wed, 16 Oct 2024 16:18:51 +0200 Subject: [PATCH 01/84] Migrating work from old branch --- .../connection-provider-routing.js | 1 + packages/core/src/driver.ts | 17 ++++++++++++-- .../core/src/internal/connection-holder.ts | 8 +++---- packages/core/src/session.ts | 16 ++++++++++++-- packages/core/test/driver.test.ts | 1 + .../connection-provider-routing.js | 1 + packages/neo4j-driver-deno/lib/core/driver.ts | 17 ++++++++++++-- .../lib/core/internal/connection-holder.ts | 8 +++---- .../neo4j-driver-deno/lib/core/session.ts | 15 +++++++++++-- packages/neo4j-driver/test/driver.test.js | 22 ++++++++++++++++++- 10 files changed, 89 insertions(+), 17 deletions(-) diff --git a/packages/bolt-connection/src/connection-provider/connection-provider-routing.js b/packages/bolt-connection/src/connection-provider/connection-provider-routing.js index 8653b64ca..8bdb881bb 100644 --- a/packages/bolt-connection/src/connection-provider/connection-provider-routing.js +++ b/packages/bolt-connection/src/connection-provider/connection-provider-routing.js @@ -772,6 +772,7 @@ function _isFailFastError (error) { } function _isFailFastSecurityError (error) { + console.error('FFSE: ', error) return error.code.startsWith('Neo.ClientError.Security.') && ![ AUTHORIZATION_EXPIRED_CODE diff --git a/packages/core/src/driver.ts b/packages/core/src/driver.ts index 9a7192425..43b25b865 100644 --- a/packages/core/src/driver.ts +++ b/packages/core/src/driver.ts @@ -97,6 +97,7 @@ type CreateSession = (args: { notificationFilter?: NotificationFilter auth?: AuthToken log: Logger + homeDatabaseCallback?: (user: string, databaseName: string) => void }) => Session type CreateQueryExecutor = (createSession: (config: { database?: string, bookmarkManager?: BookmarkManager }) => Session) => QueryExecutor @@ -470,6 +471,7 @@ class Driver { private readonly _createSession: CreateSession private readonly _defaultExecuteQueryBookmarkManager: BookmarkManager private readonly _queryExecutor: QueryExecutor + homeDatabaseCache: Map /** * You should not be calling this directly, instead use {@link driver}. @@ -509,6 +511,8 @@ class Driver { */ this._connectionProvider = null + this.homeDatabaseCache = new Map() + this._afterConstruction() } @@ -863,6 +867,7 @@ class Driver { }): Session { const sessionMode = Session._validateSessionMode(defaultAccessMode) const connectionProvider = this._getOrCreateConnectionProvider() + const homeDatabase = this.homeDatabaseCache.get(impersonatedUser ?? auth?.principal ?? '') const bookmarks = bookmarkOrBookmarks != null ? new Bookmarks(bookmarkOrBookmarks) : Bookmarks.empty() @@ -872,17 +877,25 @@ class Driver { database: database ?? '', connectionProvider, bookmarks, - config: this._config, + config: { + ...this._config, + homeDatabase + }, reactive, impersonatedUser, fetchSize, bookmarkManager, notificationFilter, auth, - log: this._log + log: this._log, + homeDatabaseCallback: this._homeDatabaseCallback.bind(this) }) } + _homeDatabaseCallback (user: string, databaseName: string): void { + this.homeDatabaseCache.set(user, databaseName) + } + /** * @private */ diff --git a/packages/core/src/internal/connection-holder.ts b/packages/core/src/internal/connection-holder.ts index 2087b875f..41eea3b1f 100644 --- a/packages/core/src/internal/connection-holder.ts +++ b/packages/core/src/internal/connection-holder.ts @@ -161,9 +161,9 @@ class ConnectionHolder implements ConnectionHolderInterface { return this._referenceCount } - initializeConnection (): boolean { + initializeConnection (homeDatabase?: string): boolean { if (this._referenceCount === 0 && (this._connectionProvider != null)) { - this._connectionPromise = this._createConnectionPromise(this._connectionProvider) + this._connectionPromise = this._createConnectionPromise(this._connectionProvider, homeDatabase) } else { this._referenceCount++ return false @@ -172,10 +172,10 @@ class ConnectionHolder implements ConnectionHolderInterface { return true } - private async _createConnectionPromise (connectionProvider: ConnectionProvider): Promise { + private async _createConnectionPromise (connectionProvider: ConnectionProvider, homeDatabase?: string): Promise { return await connectionProvider.acquireConnection({ accessMode: this._mode, - database: this._database, + database: (this._database === '' && homeDatabase !== undefined) ? homeDatabase : this._database, bookmarks: await this._getBookmarks(), impersonatedUser: this._impersonatedUser, onDatabaseNameResolved: this._onDatabaseNameResolved, diff --git a/packages/core/src/session.ts b/packages/core/src/session.ts index f08318569..51b6b3f14 100644 --- a/packages/core/src/session.ts +++ b/packages/core/src/session.ts @@ -74,6 +74,9 @@ class Session { private readonly _bookmarkManager?: BookmarkManager private readonly _notificationFilter?: NotificationFilter private readonly _log: Logger + private readonly _homeDatabaseCallback: Function | undefined + private readonly _auth: AuthToken | undefined + private readonly _homeDatabaseBestGuess /** * @constructor * @protected @@ -101,7 +104,8 @@ class Session { bookmarkManager, notificationFilter, auth, - log + log, + homeDatabaseCallback }: { mode: SessionMode connectionProvider: ConnectionProvider @@ -115,12 +119,16 @@ class Session { notificationFilter?: NotificationFilter auth?: AuthToken log: Logger + homeDatabaseCallback?: (user: string, databaseName: string) => void }) { this._mode = mode this._database = database this._reactive = reactive this._fetchSize = fetchSize this._onDatabaseNameResolved = this._onDatabaseNameResolved.bind(this) + this._homeDatabaseCallback = homeDatabaseCallback + this._homeDatabaseBestGuess = config?.homeDatabase + this._auth = auth this._getConnectionAcquistionBookmarks = this._getConnectionAcquistionBookmarks.bind(this) this._readConnectionHolder = new ConnectionHolder({ mode: ACCESS_MODE_READ, @@ -187,6 +195,7 @@ class Session { const result = this._run(validatedQuery, params, async connection => { const bookmarks = await this._bookmarks() this._assertSessionIsOpen() + console.log('RUNNING TRANSACTION:', validatedQuery, 'AGAINST DATABASE:', this._database, 'AS USER:', this._auth?.principal) return connection.run(validatedQuery, params, { bookmarks, txConfig: autoCommitTxConfig, @@ -254,7 +263,7 @@ class Session { resultPromise = Promise.reject( newError('Cannot run query in a closed session.') ) - } else if (!this._hasTx && connectionHolder.initializeConnection()) { + } else if (!this._hasTx && connectionHolder.initializeConnection(this._homeDatabaseBestGuess)) { resultPromise = connectionHolder .getConnection() // Connection won't be null at this point since the initialize method @@ -511,6 +520,9 @@ class Session { if (!this._databaseNameResolved) { const normalizedDatabase = database ?? '' this._database = normalizedDatabase + if (this._homeDatabaseCallback != null) { + this._homeDatabaseCallback(this._impersonatedUser ?? this._auth?.principal, normalizedDatabase) + } this._readConnectionHolder.setDatabase(normalizedDatabase) this._writeConnectionHolder.setDatabase(normalizedDatabase) this._databaseNameResolved = true diff --git a/packages/core/test/driver.test.ts b/packages/core/test/driver.test.ts index be1c6ce75..66bbac483 100644 --- a/packages/core/test/driver.test.ts +++ b/packages/core/test/driver.test.ts @@ -709,6 +709,7 @@ describe('Driver', () => { impersonatedUser: undefined, // @ts-expect-error log: driver?._log, + homeDatabaseCallback: expect.any(Function), ...extra } } diff --git a/packages/neo4j-driver-deno/lib/bolt-connection/connection-provider/connection-provider-routing.js b/packages/neo4j-driver-deno/lib/bolt-connection/connection-provider/connection-provider-routing.js index fbd1a8b4d..b3373ca66 100644 --- a/packages/neo4j-driver-deno/lib/bolt-connection/connection-provider/connection-provider-routing.js +++ b/packages/neo4j-driver-deno/lib/bolt-connection/connection-provider/connection-provider-routing.js @@ -772,6 +772,7 @@ function _isFailFastError (error) { } function _isFailFastSecurityError (error) { + console.error('FFSE: ', error) return error.code.startsWith('Neo.ClientError.Security.') && ![ AUTHORIZATION_EXPIRED_CODE diff --git a/packages/neo4j-driver-deno/lib/core/driver.ts b/packages/neo4j-driver-deno/lib/core/driver.ts index 0e1f33f42..bc3b3a2f9 100644 --- a/packages/neo4j-driver-deno/lib/core/driver.ts +++ b/packages/neo4j-driver-deno/lib/core/driver.ts @@ -97,6 +97,7 @@ type CreateSession = (args: { notificationFilter?: NotificationFilter auth?: AuthToken log: Logger + homeDatabaseCallback?: (user: string, databaseName: string) => void }) => Session type CreateQueryExecutor = (createSession: (config: { database?: string, bookmarkManager?: BookmarkManager }) => Session) => QueryExecutor @@ -470,6 +471,7 @@ class Driver { private readonly _createSession: CreateSession private readonly _defaultExecuteQueryBookmarkManager: BookmarkManager private readonly _queryExecutor: QueryExecutor + homeDatabaseCache: Map /** * You should not be calling this directly, instead use {@link driver}. @@ -509,6 +511,8 @@ class Driver { */ this._connectionProvider = null + this.homeDatabaseCache = new Map() + this._afterConstruction() } @@ -863,6 +867,7 @@ class Driver { }): Session { const sessionMode = Session._validateSessionMode(defaultAccessMode) const connectionProvider = this._getOrCreateConnectionProvider() + const homeDatabase = this.homeDatabaseCache.get(impersonatedUser ?? auth?.principal ?? '') const bookmarks = bookmarkOrBookmarks != null ? new Bookmarks(bookmarkOrBookmarks) : Bookmarks.empty() @@ -872,17 +877,25 @@ class Driver { database: database ?? '', connectionProvider, bookmarks, - config: this._config, + config: { + ...this._config, + homeDatabase + }, reactive, impersonatedUser, fetchSize, bookmarkManager, notificationFilter, auth, - log: this._log + log: this._log, + homeDatabaseCallback: this._homeDatabaseCallback.bind(this) }) } + _homeDatabaseCallback (user: string, databaseName: string): void { + this.homeDatabaseCache.set(user, databaseName) + } + /** * @private */ diff --git a/packages/neo4j-driver-deno/lib/core/internal/connection-holder.ts b/packages/neo4j-driver-deno/lib/core/internal/connection-holder.ts index 1f304c313..a552a1c6c 100644 --- a/packages/neo4j-driver-deno/lib/core/internal/connection-holder.ts +++ b/packages/neo4j-driver-deno/lib/core/internal/connection-holder.ts @@ -161,9 +161,9 @@ class ConnectionHolder implements ConnectionHolderInterface { return this._referenceCount } - initializeConnection (): boolean { + initializeConnection (homeDatabase?: string): boolean { if (this._referenceCount === 0 && (this._connectionProvider != null)) { - this._connectionPromise = this._createConnectionPromise(this._connectionProvider) + this._connectionPromise = this._createConnectionPromise(this._connectionProvider, homeDatabase) } else { this._referenceCount++ return false @@ -172,10 +172,10 @@ class ConnectionHolder implements ConnectionHolderInterface { return true } - private async _createConnectionPromise (connectionProvider: ConnectionProvider): Promise { + private async _createConnectionPromise (connectionProvider: ConnectionProvider, homeDatabase?: string): Promise { return await connectionProvider.acquireConnection({ accessMode: this._mode, - database: this._database, + database: (this._database === '' && homeDatabase !== undefined) ? homeDatabase : this._database, bookmarks: await this._getBookmarks(), impersonatedUser: this._impersonatedUser, onDatabaseNameResolved: this._onDatabaseNameResolved, diff --git a/packages/neo4j-driver-deno/lib/core/session.ts b/packages/neo4j-driver-deno/lib/core/session.ts index 8dce7b804..2d0efa7bd 100644 --- a/packages/neo4j-driver-deno/lib/core/session.ts +++ b/packages/neo4j-driver-deno/lib/core/session.ts @@ -74,6 +74,9 @@ class Session { private readonly _bookmarkManager?: BookmarkManager private readonly _notificationFilter?: NotificationFilter private readonly _log: Logger + private readonly _homeDatabaseCallback: Function | undefined + private readonly _auth: AuthToken | undefined + private readonly _homeDatabaseBestGuess /** * @constructor * @protected @@ -101,7 +104,8 @@ class Session { bookmarkManager, notificationFilter, auth, - log + log, + homeDatabaseCallback }: { mode: SessionMode connectionProvider: ConnectionProvider @@ -115,12 +119,16 @@ class Session { notificationFilter?: NotificationFilter auth?: AuthToken log: Logger + homeDatabaseCallback?: (user: string, databaseName: string) => void }) { this._mode = mode this._database = database this._reactive = reactive this._fetchSize = fetchSize this._onDatabaseNameResolved = this._onDatabaseNameResolved.bind(this) + this._homeDatabaseCallback = homeDatabaseCallback + this._homeDatabaseBestGuess = config?.homeDatabase + this._auth = auth this._getConnectionAcquistionBookmarks = this._getConnectionAcquistionBookmarks.bind(this) this._readConnectionHolder = new ConnectionHolder({ mode: ACCESS_MODE_READ, @@ -254,7 +262,7 @@ class Session { resultPromise = Promise.reject( newError('Cannot run query in a closed session.') ) - } else if (!this._hasTx && connectionHolder.initializeConnection()) { + } else if (!this._hasTx && connectionHolder.initializeConnection(this._homeDatabaseBestGuess)) { resultPromise = connectionHolder .getConnection() // Connection won't be null at this point since the initialize method @@ -511,6 +519,9 @@ class Session { if (!this._databaseNameResolved) { const normalizedDatabase = database ?? '' this._database = normalizedDatabase + if (this._homeDatabaseCallback != null) { + this._homeDatabaseCallback(this._impersonatedUser ?? this._auth?.principal, normalizedDatabase) + } this._readConnectionHolder.setDatabase(normalizedDatabase) this._writeConnectionHolder.setDatabase(normalizedDatabase) this._databaseNameResolved = true diff --git a/packages/neo4j-driver/test/driver.test.js b/packages/neo4j-driver/test/driver.test.js index 224086a56..b937a19b0 100644 --- a/packages/neo4j-driver/test/driver.test.js +++ b/packages/neo4j-driver/test/driver.test.js @@ -514,7 +514,7 @@ describe('#integration driver', () => { sharedNeo4j.authToken ) - const session1 = driver.session() + const session1 = driver.session({ auth: sharedNeo4j.authToken }) await session1.run('CREATE () RETURN 42') await session1.close() @@ -536,6 +536,26 @@ describe('#integration driver', () => { expect(connections1[0]).not.toEqual(connections2[0]) }) + it('should build home database cache', async () => { + driver = neo4j.driver( + `neo4j://${sharedNeo4j.hostnameWithBoltPort}`, + sharedNeo4j.authToken + ) + + const session1 = driver.session({ auth: sharedNeo4j.authToken }) + await session1.run('CREATE () RETURN 42') + + // one connection should be established + const connections1 = openConnectionFrom(driver) + expect(connections1.length).toEqual(1) + + expect(driver.homeDatabaseCache.get(sharedNeo4j.authToken.principal)).toBe('neo4j') + expect(session1._database).toBe('neo4j') + const session2 = driver.session({ auth: sharedNeo4j.authToken }) + expect(session2._homeDatabaseBestGuess).toBe('neo4j') + await session2.run('CREATE () RETURN 43') + }) + it('should discard old connections', async () => { const maxLifetime = 100000 driver = neo4j.driver( From c62cdaa09b69bfa13cf83372f74600e8ef915a92 Mon Sep 17 00:00:00 2001 From: MaxAake <61233757+MaxAake@users.noreply.github.com> Date: Thu, 17 Oct 2024 16:22:42 +0200 Subject: [PATCH 02/84] function with driver auth tokens as well as session auth --- .../src/connection-provider/connection-provider-routing.js | 3 +-- packages/core/src/session.ts | 5 ++--- .../connection-provider/connection-provider-routing.js | 3 +-- packages/neo4j-driver-deno/lib/core/driver.ts | 2 +- packages/neo4j-driver-deno/lib/core/session.ts | 4 ++-- testkit/testkit.json | 2 +- 6 files changed, 8 insertions(+), 11 deletions(-) diff --git a/packages/bolt-connection/src/connection-provider/connection-provider-routing.js b/packages/bolt-connection/src/connection-provider/connection-provider-routing.js index 8bdb881bb..e7e771ae5 100644 --- a/packages/bolt-connection/src/connection-provider/connection-provider-routing.js +++ b/packages/bolt-connection/src/connection-provider/connection-provider-routing.js @@ -161,7 +161,7 @@ export default class RoutingConnectionProvider extends PooledConnectionProvider onDatabaseNameResolved: (databaseName) => { context.database = context.database || databaseName if (onDatabaseNameResolved) { - onDatabaseNameResolved(databaseName) + onDatabaseNameResolved(databaseName, this._authenticationProvider?._authTokenManager?._authToken?.principal) } } }) @@ -772,7 +772,6 @@ function _isFailFastError (error) { } function _isFailFastSecurityError (error) { - console.error('FFSE: ', error) return error.code.startsWith('Neo.ClientError.Security.') && ![ AUTHORIZATION_EXPIRED_CODE diff --git a/packages/core/src/session.ts b/packages/core/src/session.ts index 51b6b3f14..e6569d611 100644 --- a/packages/core/src/session.ts +++ b/packages/core/src/session.ts @@ -195,7 +195,6 @@ class Session { const result = this._run(validatedQuery, params, async connection => { const bookmarks = await this._bookmarks() this._assertSessionIsOpen() - console.log('RUNNING TRANSACTION:', validatedQuery, 'AGAINST DATABASE:', this._database, 'AS USER:', this._auth?.principal) return connection.run(validatedQuery, params, { bookmarks, txConfig: autoCommitTxConfig, @@ -516,12 +515,12 @@ class Session { * @param {string|undefined} database The resolved database name * @returns {void} */ - _onDatabaseNameResolved (database?: string): void { + _onDatabaseNameResolved (database?: string, user?: string): void { if (!this._databaseNameResolved) { const normalizedDatabase = database ?? '' this._database = normalizedDatabase if (this._homeDatabaseCallback != null) { - this._homeDatabaseCallback(this._impersonatedUser ?? this._auth?.principal, normalizedDatabase) + this._homeDatabaseCallback(this._impersonatedUser ?? this._auth?.principal ?? user, normalizedDatabase) } this._readConnectionHolder.setDatabase(normalizedDatabase) this._writeConnectionHolder.setDatabase(normalizedDatabase) diff --git a/packages/neo4j-driver-deno/lib/bolt-connection/connection-provider/connection-provider-routing.js b/packages/neo4j-driver-deno/lib/bolt-connection/connection-provider/connection-provider-routing.js index b3373ca66..e42d82c4f 100644 --- a/packages/neo4j-driver-deno/lib/bolt-connection/connection-provider/connection-provider-routing.js +++ b/packages/neo4j-driver-deno/lib/bolt-connection/connection-provider/connection-provider-routing.js @@ -161,7 +161,7 @@ export default class RoutingConnectionProvider extends PooledConnectionProvider onDatabaseNameResolved: (databaseName) => { context.database = context.database || databaseName if (onDatabaseNameResolved) { - onDatabaseNameResolved(databaseName) + onDatabaseNameResolved(databaseName, this._authenticationProvider?._authTokenManager?._authToken?.principal) } } }) @@ -772,7 +772,6 @@ function _isFailFastError (error) { } function _isFailFastSecurityError (error) { - console.error('FFSE: ', error) return error.code.startsWith('Neo.ClientError.Security.') && ![ AUTHORIZATION_EXPIRED_CODE diff --git a/packages/neo4j-driver-deno/lib/core/driver.ts b/packages/neo4j-driver-deno/lib/core/driver.ts index bc3b3a2f9..1cb765fd5 100644 --- a/packages/neo4j-driver-deno/lib/core/driver.ts +++ b/packages/neo4j-driver-deno/lib/core/driver.ts @@ -895,7 +895,7 @@ class Driver { _homeDatabaseCallback (user: string, databaseName: string): void { this.homeDatabaseCache.set(user, databaseName) } - + /** * @private */ diff --git a/packages/neo4j-driver-deno/lib/core/session.ts b/packages/neo4j-driver-deno/lib/core/session.ts index 2d0efa7bd..2575bb4b0 100644 --- a/packages/neo4j-driver-deno/lib/core/session.ts +++ b/packages/neo4j-driver-deno/lib/core/session.ts @@ -515,12 +515,12 @@ class Session { * @param {string|undefined} database The resolved database name * @returns {void} */ - _onDatabaseNameResolved (database?: string): void { + _onDatabaseNameResolved (database?: string, user?: string): void { if (!this._databaseNameResolved) { const normalizedDatabase = database ?? '' this._database = normalizedDatabase if (this._homeDatabaseCallback != null) { - this._homeDatabaseCallback(this._impersonatedUser ?? this._auth?.principal, normalizedDatabase) + this._homeDatabaseCallback(this._impersonatedUser ?? this._auth?.principal ?? user, normalizedDatabase) } this._readConnectionHolder.setDatabase(normalizedDatabase) this._writeConnectionHolder.setDatabase(normalizedDatabase) diff --git a/testkit/testkit.json b/testkit/testkit.json index 931900356..83fd2ab1b 100644 --- a/testkit/testkit.json +++ b/testkit/testkit.json @@ -1,6 +1,6 @@ { "testkit": { "uri": "https://github.com/neo4j-drivers/testkit.git", - "ref": "5.0" + "ref": "homedb-cache-spike" } } From 6e9b4955def25f21aa464bef10bb110bb4359468 Mon Sep 17 00:00:00 2001 From: MaxAake <61233757+MaxAake@users.noreply.github.com> Date: Fri, 18 Oct 2024 10:47:17 +0200 Subject: [PATCH 03/84] minor fixes to integration and unit tests --- .../connection-provider-routing.test.js | 4 +-- packages/neo4j-driver/test/driver.test.js | 29 ++++++++++--------- 2 files changed, 17 insertions(+), 16 deletions(-) diff --git a/packages/bolt-connection/test/connection-provider/connection-provider-routing.test.js b/packages/bolt-connection/test/connection-provider/connection-provider-routing.test.js index 297e87d78..45fbe2216 100644 --- a/packages/bolt-connection/test/connection-provider/connection-provider-routing.test.js +++ b/packages/bolt-connection/test/connection-provider/connection-provider-routing.test.js @@ -2772,7 +2772,7 @@ describe.each([ await connectionProvider.acquireConnection({ accessMode: READ, impersonatedUser: user, onDatabaseNameResolved }) - expect(onDatabaseNameResolved).toHaveBeenCalledWith('homedb') + expect(onDatabaseNameResolved).toHaveBeenCalledWith('homedb', undefined) }) it.each(usersDataSet)('should call onDatabaseNameResolved with the resolved db acquiring named db [user=%s]', async (user) => { @@ -2798,7 +2798,7 @@ describe.each([ await connectionProvider.acquireConnection({ accessMode: READ, impersonatedUser: user, onDatabaseNameResolved, database: 'databaseA' }) - expect(onDatabaseNameResolved).toHaveBeenCalledWith('databaseA') + expect(onDatabaseNameResolved).toHaveBeenCalledWith('databaseA', undefined) }) }) diff --git a/packages/neo4j-driver/test/driver.test.js b/packages/neo4j-driver/test/driver.test.js index b937a19b0..e20288904 100644 --- a/packages/neo4j-driver/test/driver.test.js +++ b/packages/neo4j-driver/test/driver.test.js @@ -514,7 +514,7 @@ describe('#integration driver', () => { sharedNeo4j.authToken ) - const session1 = driver.session({ auth: sharedNeo4j.authToken }) + const session1 = driver.session() await session1.run('CREATE () RETURN 42') await session1.close() @@ -541,19 +541,20 @@ describe('#integration driver', () => { `neo4j://${sharedNeo4j.hostnameWithBoltPort}`, sharedNeo4j.authToken ) - - const session1 = driver.session({ auth: sharedNeo4j.authToken }) - await session1.run('CREATE () RETURN 42') - - // one connection should be established - const connections1 = openConnectionFrom(driver) - expect(connections1.length).toEqual(1) - - expect(driver.homeDatabaseCache.get(sharedNeo4j.authToken.principal)).toBe('neo4j') - expect(session1._database).toBe('neo4j') - const session2 = driver.session({ auth: sharedNeo4j.authToken }) - expect(session2._homeDatabaseBestGuess).toBe('neo4j') - await session2.run('CREATE () RETURN 43') + if (driver.supportsSessionAuth) { + const session1 = driver.session({ auth: sharedNeo4j.authToken }) + await session1.run('CREATE () RETURN 42') + + // one connection should be established + const connections1 = openConnectionFrom(driver) + expect(connections1.length).toEqual(1) + + expect(driver.homeDatabaseCache.get(sharedNeo4j.authToken.principal)).toBe('neo4j') + expect(session1._database).toBe('neo4j') + const session2 = driver.session({ auth: sharedNeo4j.authToken }) + expect(session2._homeDatabaseBestGuess).toBe('neo4j') + await session2.run('CREATE () RETURN 43') + } }) it('should discard old connections', async () => { From c307fb8e9a433cff934276b8d9615255f03bce33 Mon Sep 17 00:00:00 2001 From: MaxAake <61233757+MaxAake@users.noreply.github.com> Date: Fri, 18 Oct 2024 12:42:20 +0200 Subject: [PATCH 04/84] Update driver.test.js --- packages/neo4j-driver/test/driver.test.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/neo4j-driver/test/driver.test.js b/packages/neo4j-driver/test/driver.test.js index e20288904..4688eab86 100644 --- a/packages/neo4j-driver/test/driver.test.js +++ b/packages/neo4j-driver/test/driver.test.js @@ -541,7 +541,7 @@ describe('#integration driver', () => { `neo4j://${sharedNeo4j.hostnameWithBoltPort}`, sharedNeo4j.authToken ) - if (driver.supportsSessionAuth) { + if (await sharedNeo4j.cleanupAndGetProtocolVersion() >= 5.1) { const session1 = driver.session({ auth: sharedNeo4j.authToken }) await session1.run('CREATE () RETURN 42') From 24cf859df270bdd8ac1041ff55bcef1116d64cf6 Mon Sep 17 00:00:00 2001 From: MaxAake <61233757+MaxAake@users.noreply.github.com> Date: Fri, 18 Oct 2024 15:08:29 +0200 Subject: [PATCH 05/84] expanded callback usage and fixed tests for old servers --- .../src/connection-provider/connection-provider-routing.js | 2 +- .../connection-provider/connection-provider-routing.js | 2 +- packages/neo4j-driver/test/driver.test.js | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/packages/bolt-connection/src/connection-provider/connection-provider-routing.js b/packages/bolt-connection/src/connection-provider/connection-provider-routing.js index e7e771ae5..365dcb9e9 100644 --- a/packages/bolt-connection/src/connection-provider/connection-provider-routing.js +++ b/packages/bolt-connection/src/connection-provider/connection-provider-routing.js @@ -356,7 +356,7 @@ export default class RoutingConnectionProvider extends PooledConnectionProvider ) return this._refreshRoutingTable(currentRoutingTable, bookmarks, impersonatedUser, auth) .then(newRoutingTable => { - onDatabaseNameResolved(newRoutingTable.database) + onDatabaseNameResolved(newRoutingTable.database, this._authenticationProvider?._authTokenManager?._authToken?.principal) return newRoutingTable }) } diff --git a/packages/neo4j-driver-deno/lib/bolt-connection/connection-provider/connection-provider-routing.js b/packages/neo4j-driver-deno/lib/bolt-connection/connection-provider/connection-provider-routing.js index e42d82c4f..34b942428 100644 --- a/packages/neo4j-driver-deno/lib/bolt-connection/connection-provider/connection-provider-routing.js +++ b/packages/neo4j-driver-deno/lib/bolt-connection/connection-provider/connection-provider-routing.js @@ -356,7 +356,7 @@ export default class RoutingConnectionProvider extends PooledConnectionProvider ) return this._refreshRoutingTable(currentRoutingTable, bookmarks, impersonatedUser, auth) .then(newRoutingTable => { - onDatabaseNameResolved(newRoutingTable.database) + onDatabaseNameResolved(newRoutingTable.database, this._authenticationProvider?._authTokenManager?._authToken?.principal) return newRoutingTable }) } diff --git a/packages/neo4j-driver/test/driver.test.js b/packages/neo4j-driver/test/driver.test.js index 4688eab86..691155cea 100644 --- a/packages/neo4j-driver/test/driver.test.js +++ b/packages/neo4j-driver/test/driver.test.js @@ -541,7 +541,7 @@ describe('#integration driver', () => { `neo4j://${sharedNeo4j.hostnameWithBoltPort}`, sharedNeo4j.authToken ) - if (await sharedNeo4j.cleanupAndGetProtocolVersion() >= 5.1) { + if (protocolVersion >= 5.1) { const session1 = driver.session({ auth: sharedNeo4j.authToken }) await session1.run('CREATE () RETURN 42') From 9066f2429c0af17f2e52c0e71e28b3e6bf4a0a3a Mon Sep 17 00:00:00 2001 From: MaxAake <61233757+MaxAake@users.noreply.github.com> Date: Mon, 21 Oct 2024 15:54:57 +0200 Subject: [PATCH 06/84] change checks for when to update cache --- packages/core/src/session.ts | 10 +++++----- packages/neo4j-driver-deno/lib/core/session.ts | 10 +++++----- 2 files changed, 10 insertions(+), 10 deletions(-) diff --git a/packages/core/src/session.ts b/packages/core/src/session.ts index e6569d611..d992005dd 100644 --- a/packages/core/src/session.ts +++ b/packages/core/src/session.ts @@ -516,12 +516,12 @@ class Session { * @returns {void} */ _onDatabaseNameResolved (database?: string, user?: string): void { + const normalizedDatabase = database ?? '' + this._database = normalizedDatabase + if (this._homeDatabaseCallback != null && normalizedDatabase !== this._homeDatabaseBestGuess) { + this._homeDatabaseCallback(this._impersonatedUser ?? this._auth?.principal ?? user, normalizedDatabase) + } if (!this._databaseNameResolved) { - const normalizedDatabase = database ?? '' - this._database = normalizedDatabase - if (this._homeDatabaseCallback != null) { - this._homeDatabaseCallback(this._impersonatedUser ?? this._auth?.principal ?? user, normalizedDatabase) - } this._readConnectionHolder.setDatabase(normalizedDatabase) this._writeConnectionHolder.setDatabase(normalizedDatabase) this._databaseNameResolved = true diff --git a/packages/neo4j-driver-deno/lib/core/session.ts b/packages/neo4j-driver-deno/lib/core/session.ts index 2575bb4b0..590a4cde1 100644 --- a/packages/neo4j-driver-deno/lib/core/session.ts +++ b/packages/neo4j-driver-deno/lib/core/session.ts @@ -516,12 +516,12 @@ class Session { * @returns {void} */ _onDatabaseNameResolved (database?: string, user?: string): void { + const normalizedDatabase = database ?? '' + this._database = normalizedDatabase + if (this._homeDatabaseCallback != null && normalizedDatabase !== this._homeDatabaseBestGuess) { + this._homeDatabaseCallback(this._impersonatedUser ?? this._auth?.principal ?? user, normalizedDatabase) + } if (!this._databaseNameResolved) { - const normalizedDatabase = database ?? '' - this._database = normalizedDatabase - if (this._homeDatabaseCallback != null) { - this._homeDatabaseCallback(this._impersonatedUser ?? this._auth?.principal ?? user, normalizedDatabase) - } this._readConnectionHolder.setDatabase(normalizedDatabase) this._writeConnectionHolder.setDatabase(normalizedDatabase) this._databaseNameResolved = true From ca501422c20ca3c197c2daac4d745a62fdf4af1d Mon Sep 17 00:00:00 2001 From: MaxAake <61233757+MaxAake@users.noreply.github.com> Date: Tue, 22 Oct 2024 09:23:04 +0200 Subject: [PATCH 07/84] Save round trip when running transactions too --- packages/core/src/session.ts | 2 +- packages/neo4j-driver-deno/lib/core/session.ts | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/core/src/session.ts b/packages/core/src/session.ts index d992005dd..785414c46 100644 --- a/packages/core/src/session.ts +++ b/packages/core/src/session.ts @@ -318,7 +318,7 @@ class Session { const mode = Session._validateSessionMode(accessMode) const connectionHolder = this._connectionHolderWithMode(mode) - connectionHolder.initializeConnection() + connectionHolder.initializeConnection(this._homeDatabaseBestGuess) this._hasTx = true const tx = new TransactionPromise({ diff --git a/packages/neo4j-driver-deno/lib/core/session.ts b/packages/neo4j-driver-deno/lib/core/session.ts index 590a4cde1..7136ad032 100644 --- a/packages/neo4j-driver-deno/lib/core/session.ts +++ b/packages/neo4j-driver-deno/lib/core/session.ts @@ -318,7 +318,7 @@ class Session { const mode = Session._validateSessionMode(accessMode) const connectionHolder = this._connectionHolderWithMode(mode) - connectionHolder.initializeConnection() + connectionHolder.initializeConnection(this._homeDatabaseBestGuess) this._hasTx = true const tx = new TransactionPromise({ From 886b90a3169f2b438c9551904ca426e5a039c4ae Mon Sep 17 00:00:00 2001 From: MaxAake <61233757+MaxAake@users.noreply.github.com> Date: Thu, 24 Oct 2024 08:44:02 +0200 Subject: [PATCH 08/84] update to homedbtable --- .../connection-provider-routing.js | 33 +++++++++++-------- packages/core/src/connection-provider.ts | 1 + .../core/src/internal/connection-holder.ts | 11 ++++--- .../core/src/internal/transaction-executor.ts | 8 +++-- packages/core/src/session.ts | 25 ++++++++++---- packages/core/src/transaction.ts | 10 ++++-- .../connection-provider-routing.js | 33 +++++++++++-------- .../lib/core/connection-provider.ts | 1 + .../lib/core/internal/connection-holder.ts | 11 ++++--- .../lib/core/internal/transaction-executor.ts | 8 +++-- .../neo4j-driver-deno/lib/core/session.ts | 25 ++++++++++---- .../neo4j-driver-deno/lib/core/transaction.ts | 10 ++++-- 12 files changed, 114 insertions(+), 62 deletions(-) diff --git a/packages/bolt-connection/src/connection-provider/connection-provider-routing.js b/packages/bolt-connection/src/connection-provider/connection-provider-routing.js index 365dcb9e9..8ef48dff7 100644 --- a/packages/bolt-connection/src/connection-provider/connection-provider-routing.js +++ b/packages/bolt-connection/src/connection-provider/connection-provider-routing.js @@ -139,9 +139,10 @@ export default class RoutingConnectionProvider extends PooledConnectionProvider * See {@link ConnectionProvider} for more information about this method and * its arguments. */ - async acquireConnection ({ accessMode, database, bookmarks, impersonatedUser, onDatabaseNameResolved, auth } = {}) { + async acquireConnection ({ accessMode, database, bookmarks, impersonatedUser, onDatabaseNameResolved, auth, homeDbTable } = {}) { let name let address + let runResolved = false const context = { database: database || DEFAULT_DB_NAME } const databaseSpecificErrorHandler = new ConnectionErrorHandler( @@ -152,19 +153,25 @@ export default class RoutingConnectionProvider extends PooledConnectionProvider this._handleSecurityError(error, address, conn, context.database) ) - const routingTable = await this._freshRoutingTable({ - accessMode, - database: context.database, - bookmarks, - impersonatedUser, - auth, - onDatabaseNameResolved: (databaseName) => { - context.database = context.database || databaseName - if (onDatabaseNameResolved) { - onDatabaseNameResolved(databaseName, this._authenticationProvider?._authTokenManager?._authToken?.principal) + const routingTable = (homeDbTable && !homeDbTable.isStaleFor(accessMode)) + ? homeDbTable + : await this._freshRoutingTable({ + accessMode, + database: context.database, + bookmarks, + impersonatedUser, + auth, + onDatabaseNameResolved: (databaseName) => { + context.database = context.database || databaseName + if (onDatabaseNameResolved) { + runResolved = true + } } - } - }) + }) + + if (runResolved) { + onDatabaseNameResolved(context.database, this._authenticationProvider?._authTokenManager?._authToken?.principal, routingTable) + } // select a target server based on specified access mode if (accessMode === READ) { diff --git a/packages/core/src/connection-provider.ts b/packages/core/src/connection-provider.ts index 0cfacc1d4..69fa20c17 100644 --- a/packages/core/src/connection-provider.ts +++ b/packages/core/src/connection-provider.ts @@ -66,6 +66,7 @@ class ConnectionProvider { impersonatedUser?: string onDatabaseNameResolved?: (databaseName?: string) => void auth?: AuthToken + homeDbTable?: any }): Promise { throw Error('Not implemented') } diff --git a/packages/core/src/internal/connection-holder.ts b/packages/core/src/internal/connection-holder.ts index 41eea3b1f..3ce7e9af9 100644 --- a/packages/core/src/internal/connection-holder.ts +++ b/packages/core/src/internal/connection-holder.ts @@ -161,9 +161,9 @@ class ConnectionHolder implements ConnectionHolderInterface { return this._referenceCount } - initializeConnection (homeDatabase?: string): boolean { + initializeConnection (homeDatabaseTable?: any): boolean { if (this._referenceCount === 0 && (this._connectionProvider != null)) { - this._connectionPromise = this._createConnectionPromise(this._connectionProvider, homeDatabase) + this._connectionPromise = this._createConnectionPromise(this._connectionProvider, homeDatabaseTable) } else { this._referenceCount++ return false @@ -172,14 +172,15 @@ class ConnectionHolder implements ConnectionHolderInterface { return true } - private async _createConnectionPromise (connectionProvider: ConnectionProvider, homeDatabase?: string): Promise { + private async _createConnectionPromise (connectionProvider: ConnectionProvider, homeDatabaseTable?: any): Promise { return await connectionProvider.acquireConnection({ accessMode: this._mode, - database: (this._database === '' && homeDatabase !== undefined) ? homeDatabase : this._database, + database: this._database ?? '', bookmarks: await this._getBookmarks(), impersonatedUser: this._impersonatedUser, onDatabaseNameResolved: this._onDatabaseNameResolved, - auth: this._auth + auth: this._auth, + homeDbTable: homeDatabaseTable }) } diff --git a/packages/core/src/internal/transaction-executor.ts b/packages/core/src/internal/transaction-executor.ts index 724c4c314..b8f95cb2f 100644 --- a/packages/core/src/internal/transaction-executor.ts +++ b/packages/core/src/internal/transaction-executor.ts @@ -58,6 +58,7 @@ export class TransactionExecutor { private readonly _clearTimeout: ClearTimeout public telemetryApi: NonAutoCommitTelemetryApis public pipelineBegin: boolean + public committedDbCallback: any constructor ( maxRetryTimeMs?: number | null, @@ -67,7 +68,8 @@ export class TransactionExecutor { dependencies: Dependencies = { setTimeout: setTimeoutWrapper, clearTimeout: clearTimeoutWrapper - } + }, + committedDbCallback?: any ) { this._maxRetryTimeMs = _valueOrDefault( maxRetryTimeMs, @@ -88,7 +90,7 @@ export class TransactionExecutor { this._setTimeout = dependencies.setTimeout this._clearTimeout = dependencies.clearTimeout - + this.committedDbCallback = committedDbCallback this._inFlightTimeoutIds = [] this.pipelineBegin = false this.telemetryApi = TELEMETRY_APIS.MANAGED_TRANSACTION @@ -247,7 +249,7 @@ export class TransactionExecutor { if (tx.isOpen()) { // transaction work returned resolved promise and transaction has not been committed/rolled back // try to commit the transaction - tx.commit() + tx.commit(this.committedDbCallback) .then(() => { // transaction was committed, return result to the user resolve(result) diff --git a/packages/core/src/session.ts b/packages/core/src/session.ts index 785414c46..8e0bbea83 100644 --- a/packages/core/src/session.ts +++ b/packages/core/src/session.ts @@ -157,7 +157,7 @@ class Session { this._impersonatedUser = impersonatedUser this._lastBookmarks = bookmarks ?? Bookmarks.empty() this._configuredBookmarks = this._lastBookmarks - this._transactionExecutor = _createTransactionExecutor(config) + this._transactionExecutor = _createTransactionExecutor({ ...config, commitCallback: this.committedDbCallback.bind(this) }) this._databaseNameResolved = this._database !== '' const calculatedWatermaks = this._calculateWatermaks() this._lowRecordWatermark = calculatedWatermaks.low @@ -515,13 +515,23 @@ class Session { * @param {string|undefined} database The resolved database name * @returns {void} */ - _onDatabaseNameResolved (database?: string, user?: string): void { - const normalizedDatabase = database ?? '' - this._database = normalizedDatabase - if (this._homeDatabaseCallback != null && normalizedDatabase !== this._homeDatabaseBestGuess) { - this._homeDatabaseCallback(this._impersonatedUser ?? this._auth?.principal ?? user, normalizedDatabase) + _onDatabaseNameResolved (database?: string, user?: string, table?: any): void { + if (this._homeDatabaseCallback != null) { + this._homeDatabaseCallback(this._impersonatedUser ?? this._auth?.principal ?? user, table) } if (!this._databaseNameResolved) { + const normalizedDatabase = database ?? '' + this._database = normalizedDatabase + this._readConnectionHolder.setDatabase(normalizedDatabase) + this._writeConnectionHolder.setDatabase(normalizedDatabase) + this._databaseNameResolved = true + } + } + + committedDbCallback (database: string): void { + if (!this._databaseNameResolved) { + const normalizedDatabase = database ?? '' + this._database = normalizedDatabase this._readConnectionHolder.setDatabase(normalizedDatabase) this._writeConnectionHolder.setDatabase(normalizedDatabase) this._databaseNameResolved = true @@ -643,9 +653,10 @@ class Session { */ function _createTransactionExecutor (config?: { maxTransactionRetryTime: number | null + commitCallback: any }): TransactionExecutor { const maxRetryTimeMs = config?.maxTransactionRetryTime ?? null - return new TransactionExecutor(maxRetryTimeMs) + return new TransactionExecutor(maxRetryTimeMs, undefined, undefined, undefined, undefined, config?.commitCallback) } export default Session diff --git a/packages/core/src/transaction.ts b/packages/core/src/transaction.ts index 1f3dee59d..4c4e0fbfa 100644 --- a/packages/core/src/transaction.ts +++ b/packages/core/src/transaction.ts @@ -151,7 +151,6 @@ class Transaction { bookmarks: this._bookmarks, txConfig, mode: this._connectionHolder.mode(), - database: this._connectionHolder.database(), impersonatedUser: this._impersonatedUser, notificationFilter: this._notificationFilter, apiTelemetryConfig: this._apiTelemetryConfig, @@ -220,7 +219,7 @@ class Transaction { * * @returns {Promise} An empty promise if committed successfully or error if any error happened during commit. */ - commit (): Promise { + commit (committedDbCallback?: any): Promise { const committed = this._state.commit({ connectionHolder: this._connectionHolder, onError: this._onError, @@ -234,7 +233,12 @@ class Transaction { this._onClose() return new Promise((resolve, reject) => { committed.result.subscribe({ - onCompleted: () => resolve(), + onCompleted: (result: any) => { + if (committedDbCallback !== undefined) { + committedDbCallback(result.database.name) + } + resolve() + }, onError: (error: any) => reject(error) }) }) diff --git a/packages/neo4j-driver-deno/lib/bolt-connection/connection-provider/connection-provider-routing.js b/packages/neo4j-driver-deno/lib/bolt-connection/connection-provider/connection-provider-routing.js index 34b942428..2f3591b75 100644 --- a/packages/neo4j-driver-deno/lib/bolt-connection/connection-provider/connection-provider-routing.js +++ b/packages/neo4j-driver-deno/lib/bolt-connection/connection-provider/connection-provider-routing.js @@ -139,9 +139,10 @@ export default class RoutingConnectionProvider extends PooledConnectionProvider * See {@link ConnectionProvider} for more information about this method and * its arguments. */ - async acquireConnection ({ accessMode, database, bookmarks, impersonatedUser, onDatabaseNameResolved, auth } = {}) { + async acquireConnection ({ accessMode, database, bookmarks, impersonatedUser, onDatabaseNameResolved, auth, homeDbTable } = {}) { let name let address + let runResolved = false const context = { database: database || DEFAULT_DB_NAME } const databaseSpecificErrorHandler = new ConnectionErrorHandler( @@ -152,19 +153,25 @@ export default class RoutingConnectionProvider extends PooledConnectionProvider this._handleSecurityError(error, address, conn, context.database) ) - const routingTable = await this._freshRoutingTable({ - accessMode, - database: context.database, - bookmarks, - impersonatedUser, - auth, - onDatabaseNameResolved: (databaseName) => { - context.database = context.database || databaseName - if (onDatabaseNameResolved) { - onDatabaseNameResolved(databaseName, this._authenticationProvider?._authTokenManager?._authToken?.principal) + const routingTable = (homeDbTable && !homeDbTable.isStaleFor(accessMode)) + ? homeDbTable + : await this._freshRoutingTable({ + accessMode, + database: context.database, + bookmarks, + impersonatedUser, + auth, + onDatabaseNameResolved: (databaseName) => { + context.database = context.database || databaseName + if (onDatabaseNameResolved) { + runResolved = true + } } - } - }) + }) + + if (runResolved) { + onDatabaseNameResolved(context.database, this._authenticationProvider?._authTokenManager?._authToken?.principal, routingTable) + } // select a target server based on specified access mode if (accessMode === READ) { diff --git a/packages/neo4j-driver-deno/lib/core/connection-provider.ts b/packages/neo4j-driver-deno/lib/core/connection-provider.ts index 977aeeada..95df5b4bd 100644 --- a/packages/neo4j-driver-deno/lib/core/connection-provider.ts +++ b/packages/neo4j-driver-deno/lib/core/connection-provider.ts @@ -66,6 +66,7 @@ class ConnectionProvider { impersonatedUser?: string onDatabaseNameResolved?: (databaseName?: string) => void auth?: AuthToken + homeDbTable?: any }): Promise { throw Error('Not implemented') } diff --git a/packages/neo4j-driver-deno/lib/core/internal/connection-holder.ts b/packages/neo4j-driver-deno/lib/core/internal/connection-holder.ts index a552a1c6c..dccca3c33 100644 --- a/packages/neo4j-driver-deno/lib/core/internal/connection-holder.ts +++ b/packages/neo4j-driver-deno/lib/core/internal/connection-holder.ts @@ -161,9 +161,9 @@ class ConnectionHolder implements ConnectionHolderInterface { return this._referenceCount } - initializeConnection (homeDatabase?: string): boolean { + initializeConnection (homeDatabaseTable?: any): boolean { if (this._referenceCount === 0 && (this._connectionProvider != null)) { - this._connectionPromise = this._createConnectionPromise(this._connectionProvider, homeDatabase) + this._connectionPromise = this._createConnectionPromise(this._connectionProvider, homeDatabaseTable) } else { this._referenceCount++ return false @@ -172,14 +172,15 @@ class ConnectionHolder implements ConnectionHolderInterface { return true } - private async _createConnectionPromise (connectionProvider: ConnectionProvider, homeDatabase?: string): Promise { + private async _createConnectionPromise (connectionProvider: ConnectionProvider, homeDatabaseTable?: any): Promise { return await connectionProvider.acquireConnection({ accessMode: this._mode, - database: (this._database === '' && homeDatabase !== undefined) ? homeDatabase : this._database, + database: this._database ?? '', bookmarks: await this._getBookmarks(), impersonatedUser: this._impersonatedUser, onDatabaseNameResolved: this._onDatabaseNameResolved, - auth: this._auth + auth: this._auth, + homeDbTable: homeDatabaseTable }) } diff --git a/packages/neo4j-driver-deno/lib/core/internal/transaction-executor.ts b/packages/neo4j-driver-deno/lib/core/internal/transaction-executor.ts index 5e0e2ea87..bedc5eee5 100644 --- a/packages/neo4j-driver-deno/lib/core/internal/transaction-executor.ts +++ b/packages/neo4j-driver-deno/lib/core/internal/transaction-executor.ts @@ -58,6 +58,7 @@ export class TransactionExecutor { private readonly _clearTimeout: ClearTimeout public telemetryApi: NonAutoCommitTelemetryApis public pipelineBegin: boolean + public committedDbCallback: any constructor ( maxRetryTimeMs?: number | null, @@ -67,7 +68,8 @@ export class TransactionExecutor { dependencies: Dependencies = { setTimeout: setTimeoutWrapper, clearTimeout: clearTimeoutWrapper - } + }, + committedDbCallback?: any ) { this._maxRetryTimeMs = _valueOrDefault( maxRetryTimeMs, @@ -88,7 +90,7 @@ export class TransactionExecutor { this._setTimeout = dependencies.setTimeout this._clearTimeout = dependencies.clearTimeout - + this.committedDbCallback = committedDbCallback this._inFlightTimeoutIds = [] this.pipelineBegin = false this.telemetryApi = TELEMETRY_APIS.MANAGED_TRANSACTION @@ -247,7 +249,7 @@ export class TransactionExecutor { if (tx.isOpen()) { // transaction work returned resolved promise and transaction has not been committed/rolled back // try to commit the transaction - tx.commit() + tx.commit(this.committedDbCallback) .then(() => { // transaction was committed, return result to the user resolve(result) diff --git a/packages/neo4j-driver-deno/lib/core/session.ts b/packages/neo4j-driver-deno/lib/core/session.ts index 7136ad032..3548ef677 100644 --- a/packages/neo4j-driver-deno/lib/core/session.ts +++ b/packages/neo4j-driver-deno/lib/core/session.ts @@ -157,7 +157,7 @@ class Session { this._impersonatedUser = impersonatedUser this._lastBookmarks = bookmarks ?? Bookmarks.empty() this._configuredBookmarks = this._lastBookmarks - this._transactionExecutor = _createTransactionExecutor(config) + this._transactionExecutor = _createTransactionExecutor({...config, commitCallback: this.committedDbCallback.bind(this)}) this._databaseNameResolved = this._database !== '' const calculatedWatermaks = this._calculateWatermaks() this._lowRecordWatermark = calculatedWatermaks.low @@ -515,13 +515,23 @@ class Session { * @param {string|undefined} database The resolved database name * @returns {void} */ - _onDatabaseNameResolved (database?: string, user?: string): void { - const normalizedDatabase = database ?? '' - this._database = normalizedDatabase - if (this._homeDatabaseCallback != null && normalizedDatabase !== this._homeDatabaseBestGuess) { - this._homeDatabaseCallback(this._impersonatedUser ?? this._auth?.principal ?? user, normalizedDatabase) + _onDatabaseNameResolved (database?: string, user?: string, table?: any): void { + if (this._homeDatabaseCallback != null) { + this._homeDatabaseCallback(this._impersonatedUser ?? this._auth?.principal ?? user, table) } if (!this._databaseNameResolved) { + const normalizedDatabase = database ?? '' + this._database = normalizedDatabase + this._readConnectionHolder.setDatabase(normalizedDatabase) + this._writeConnectionHolder.setDatabase(normalizedDatabase) + this._databaseNameResolved = true + } + } + + committedDbCallback(database: string): void { + if (!this._databaseNameResolved) { + const normalizedDatabase = database ?? '' + this._database = normalizedDatabase this._readConnectionHolder.setDatabase(normalizedDatabase) this._writeConnectionHolder.setDatabase(normalizedDatabase) this._databaseNameResolved = true @@ -643,9 +653,10 @@ class Session { */ function _createTransactionExecutor (config?: { maxTransactionRetryTime: number | null + commitCallback: any }): TransactionExecutor { const maxRetryTimeMs = config?.maxTransactionRetryTime ?? null - return new TransactionExecutor(maxRetryTimeMs) + return new TransactionExecutor(maxRetryTimeMs, undefined, undefined, undefined, undefined, config?.commitCallback) } export default Session diff --git a/packages/neo4j-driver-deno/lib/core/transaction.ts b/packages/neo4j-driver-deno/lib/core/transaction.ts index 6291bd961..6443f64d3 100644 --- a/packages/neo4j-driver-deno/lib/core/transaction.ts +++ b/packages/neo4j-driver-deno/lib/core/transaction.ts @@ -151,7 +151,6 @@ class Transaction { bookmarks: this._bookmarks, txConfig, mode: this._connectionHolder.mode(), - database: this._connectionHolder.database(), impersonatedUser: this._impersonatedUser, notificationFilter: this._notificationFilter, apiTelemetryConfig: this._apiTelemetryConfig, @@ -220,7 +219,7 @@ class Transaction { * * @returns {Promise} An empty promise if committed successfully or error if any error happened during commit. */ - commit (): Promise { + commit (committedDbCallback?: any): Promise { const committed = this._state.commit({ connectionHolder: this._connectionHolder, onError: this._onError, @@ -234,7 +233,12 @@ class Transaction { this._onClose() return new Promise((resolve, reject) => { committed.result.subscribe({ - onCompleted: () => resolve(), + onCompleted: (result: any) => { + if(committedDbCallback !== undefined) { + committedDbCallback(result.database.name) + } + resolve() + }, onError: (error: any) => reject(error) }) }) From b4b1a48661d803cf8e1ac7c997f8c794b795fb80 Mon Sep 17 00:00:00 2001 From: MaxAake <61233757+MaxAake@users.noreply.github.com> Date: Thu, 24 Oct 2024 08:44:59 +0200 Subject: [PATCH 09/84] deno --- packages/neo4j-driver-deno/lib/core/session.ts | 4 ++-- packages/neo4j-driver-deno/lib/core/transaction.ts | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/packages/neo4j-driver-deno/lib/core/session.ts b/packages/neo4j-driver-deno/lib/core/session.ts index 3548ef677..8ae4c34fc 100644 --- a/packages/neo4j-driver-deno/lib/core/session.ts +++ b/packages/neo4j-driver-deno/lib/core/session.ts @@ -157,7 +157,7 @@ class Session { this._impersonatedUser = impersonatedUser this._lastBookmarks = bookmarks ?? Bookmarks.empty() this._configuredBookmarks = this._lastBookmarks - this._transactionExecutor = _createTransactionExecutor({...config, commitCallback: this.committedDbCallback.bind(this)}) + this._transactionExecutor = _createTransactionExecutor({ ...config, commitCallback: this.committedDbCallback.bind(this) }) this._databaseNameResolved = this._database !== '' const calculatedWatermaks = this._calculateWatermaks() this._lowRecordWatermark = calculatedWatermaks.low @@ -528,7 +528,7 @@ class Session { } } - committedDbCallback(database: string): void { + committedDbCallback (database: string): void { if (!this._databaseNameResolved) { const normalizedDatabase = database ?? '' this._database = normalizedDatabase diff --git a/packages/neo4j-driver-deno/lib/core/transaction.ts b/packages/neo4j-driver-deno/lib/core/transaction.ts index 6443f64d3..d5f7f6a50 100644 --- a/packages/neo4j-driver-deno/lib/core/transaction.ts +++ b/packages/neo4j-driver-deno/lib/core/transaction.ts @@ -234,7 +234,7 @@ class Transaction { return new Promise((resolve, reject) => { committed.result.subscribe({ onCompleted: (result: any) => { - if(committedDbCallback !== undefined) { + if (committedDbCallback !== undefined) { committedDbCallback(result.database.name) } resolve() From f38a5dfbd71e311b2b01c08c7f79969e58517509 Mon Sep 17 00:00:00 2001 From: MaxAake <61233757+MaxAake@users.noreply.github.com> Date: Fri, 25 Oct 2024 15:23:08 +0200 Subject: [PATCH 10/84] Continued work, hard to clear cache on error :/ --- .../connection-provider-routing.js | 7 ++-- packages/core/src/driver.ts | 24 +++++++---- packages/core/src/session.ts | 41 ++++++++++++------- .../connection-provider-routing.js | 7 ++-- packages/neo4j-driver-deno/lib/core/driver.ts | 24 +++++++---- .../neo4j-driver-deno/lib/core/session.ts | 41 ++++++++++++------- 6 files changed, 92 insertions(+), 52 deletions(-) diff --git a/packages/bolt-connection/src/connection-provider/connection-provider-routing.js b/packages/bolt-connection/src/connection-provider/connection-provider-routing.js index 8ef48dff7..a223b879e 100644 --- a/packages/bolt-connection/src/connection-provider/connection-provider-routing.js +++ b/packages/bolt-connection/src/connection-provider/connection-provider-routing.js @@ -148,12 +148,13 @@ export default class RoutingConnectionProvider extends PooledConnectionProvider const databaseSpecificErrorHandler = new ConnectionErrorHandler( SESSION_EXPIRED, (error, address) => this._handleUnavailability(error, address, context.database), - (error, address) => this._handleWriteFailure(error, address, context.database), + (error, address) => { + this._handleWriteFailure(error, address, homeDbTable?.database ?? context.database) + }, (error, address, conn) => this._handleSecurityError(error, address, conn, context.database) ) - - const routingTable = (homeDbTable && !homeDbTable.isStaleFor(accessMode)) + const routingTable = (homeDbTable && homeDbTable.isStaleFor(accessMode)) ? homeDbTable : await this._freshRoutingTable({ accessMode, diff --git a/packages/core/src/driver.ts b/packages/core/src/driver.ts index 43b25b865..335faeb99 100644 --- a/packages/core/src/driver.ts +++ b/packages/core/src/driver.ts @@ -97,7 +97,8 @@ type CreateSession = (args: { notificationFilter?: NotificationFilter auth?: AuthToken log: Logger - homeDatabaseCallback?: (user: string, databaseName: string) => void + homeDatabaseTableCallback?: (user: string, table: any) => void + committedDbCallback?: (user: string) => void }) => Session type CreateQueryExecutor = (createSession: (config: { database?: string, bookmarkManager?: BookmarkManager }) => Session) => QueryExecutor @@ -471,7 +472,7 @@ class Driver { private readonly _createSession: CreateSession private readonly _defaultExecuteQueryBookmarkManager: BookmarkManager private readonly _queryExecutor: QueryExecutor - homeDatabaseCache: Map + homeDatabaseCache: Map /** * You should not be calling this directly, instead use {@link driver}. @@ -511,7 +512,7 @@ class Driver { */ this._connectionProvider = null - this.homeDatabaseCache = new Map() + this.homeDatabaseCache = new Map() this._afterConstruction() } @@ -841,6 +842,16 @@ class Driver { ) } + _homeDatabaseCallback (user: string, table: any): void { + this.homeDatabaseCache.set(user, table) + } + + _committedDbCallback (database: string, user: string): void { + if (this.homeDatabaseCache.get(user) !== undefined && this.homeDatabaseCache.get(user).database !== database) { + this.homeDatabaseCache.delete(user) + } + } + /** * @private */ @@ -888,14 +899,11 @@ class Driver { notificationFilter, auth, log: this._log, - homeDatabaseCallback: this._homeDatabaseCallback.bind(this) + homeDatabaseTableCallback: this._homeDatabaseCallback.bind(this), + committedDbCallback: this._committedDbCallback.bind(this) }) } - _homeDatabaseCallback (user: string, databaseName: string): void { - this.homeDatabaseCache.set(user, databaseName) - } - /** * @private */ diff --git a/packages/core/src/session.ts b/packages/core/src/session.ts index 8e0bbea83..22eeca750 100644 --- a/packages/core/src/session.ts +++ b/packages/core/src/session.ts @@ -47,6 +47,11 @@ interface TransactionConfig { metadata?: object } +interface DbGuess { + database: string + user: string +} + /** * A Session instance is used for handling the connection and * sending queries through the connection. @@ -74,9 +79,11 @@ class Session { private readonly _bookmarkManager?: BookmarkManager private readonly _notificationFilter?: NotificationFilter private readonly _log: Logger - private readonly _homeDatabaseCallback: Function | undefined + private readonly _homeDatabaseTableCallback: Function | undefined + private readonly _committedDbCallback: Function | undefined private readonly _auth: AuthToken | undefined - private readonly _homeDatabaseBestGuess + private _homeDatabaseBestGuess: string | undefined + private _databaseGuess: DbGuess | undefined /** * @constructor * @protected @@ -105,7 +112,8 @@ class Session { notificationFilter, auth, log, - homeDatabaseCallback + homeDatabaseTableCallback, + committedDbCallback }: { mode: SessionMode connectionProvider: ConnectionProvider @@ -119,14 +127,15 @@ class Session { notificationFilter?: NotificationFilter auth?: AuthToken log: Logger - homeDatabaseCallback?: (user: string, databaseName: string) => void + homeDatabaseTableCallback?: (user: string, table: any) => void + committedDbCallback?: (user: string, database: string) => void }) { this._mode = mode this._database = database this._reactive = reactive this._fetchSize = fetchSize - this._onDatabaseNameResolved = this._onDatabaseNameResolved.bind(this) - this._homeDatabaseCallback = homeDatabaseCallback + this._homeDatabaseTableCallback = homeDatabaseTableCallback + this._committedDbCallback = committedDbCallback this._homeDatabaseBestGuess = config?.homeDatabase this._auth = auth this._getConnectionAcquistionBookmarks = this._getConnectionAcquistionBookmarks.bind(this) @@ -137,7 +146,7 @@ class Session { bookmarks, connectionProvider, impersonatedUser, - onDatabaseNameResolved: this._onDatabaseNameResolved, + onDatabaseNameResolved: this._onDatabaseNameResolved.bind(this), getConnectionAcquistionBookmarks: this._getConnectionAcquistionBookmarks, log }) @@ -516,10 +525,11 @@ class Session { * @returns {void} */ _onDatabaseNameResolved (database?: string, user?: string, table?: any): void { - if (this._homeDatabaseCallback != null) { - this._homeDatabaseCallback(this._impersonatedUser ?? this._auth?.principal ?? user, table) + if (this._homeDatabaseTableCallback != null) { + this._homeDatabaseTableCallback(this._impersonatedUser ?? this._auth?.principal ?? user, table) } if (!this._databaseNameResolved) { + this._databaseGuess = { user: this._impersonatedUser ?? this._auth?.principal ?? user ?? '', database: database ?? '' } const normalizedDatabase = database ?? '' this._database = normalizedDatabase this._readConnectionHolder.setDatabase(normalizedDatabase) @@ -529,12 +539,13 @@ class Session { } committedDbCallback (database: string): void { - if (!this._databaseNameResolved) { - const normalizedDatabase = database ?? '' - this._database = normalizedDatabase - this._readConnectionHolder.setDatabase(normalizedDatabase) - this._writeConnectionHolder.setDatabase(normalizedDatabase) - this._databaseNameResolved = true + if (this._committedDbCallback !== undefined && (this._databaseGuess != null)) { + this._committedDbCallback(database, this._databaseGuess.user) + } + if ((this._databaseGuess != null) && database !== this._databaseGuess.database) { + this._homeDatabaseBestGuess = undefined + this._readConnectionHolder.setDatabase(undefined) + this._writeConnectionHolder.setDatabase(undefined) } } diff --git a/packages/neo4j-driver-deno/lib/bolt-connection/connection-provider/connection-provider-routing.js b/packages/neo4j-driver-deno/lib/bolt-connection/connection-provider/connection-provider-routing.js index 2f3591b75..e64d1b4a5 100644 --- a/packages/neo4j-driver-deno/lib/bolt-connection/connection-provider/connection-provider-routing.js +++ b/packages/neo4j-driver-deno/lib/bolt-connection/connection-provider/connection-provider-routing.js @@ -148,12 +148,13 @@ export default class RoutingConnectionProvider extends PooledConnectionProvider const databaseSpecificErrorHandler = new ConnectionErrorHandler( SESSION_EXPIRED, (error, address) => this._handleUnavailability(error, address, context.database), - (error, address) => this._handleWriteFailure(error, address, context.database), + (error, address) => { + this._handleWriteFailure(error, address, homeDbTable?.database ?? context.database) + }, (error, address, conn) => this._handleSecurityError(error, address, conn, context.database) ) - - const routingTable = (homeDbTable && !homeDbTable.isStaleFor(accessMode)) + const routingTable = (homeDbTable && homeDbTable.isStaleFor(accessMode)) ? homeDbTable : await this._freshRoutingTable({ accessMode, diff --git a/packages/neo4j-driver-deno/lib/core/driver.ts b/packages/neo4j-driver-deno/lib/core/driver.ts index 1cb765fd5..0159364cb 100644 --- a/packages/neo4j-driver-deno/lib/core/driver.ts +++ b/packages/neo4j-driver-deno/lib/core/driver.ts @@ -97,7 +97,8 @@ type CreateSession = (args: { notificationFilter?: NotificationFilter auth?: AuthToken log: Logger - homeDatabaseCallback?: (user: string, databaseName: string) => void + homeDatabaseTableCallback?: (user: string, table: any) => void + committedDbCallback?: (user: string) => void }) => Session type CreateQueryExecutor = (createSession: (config: { database?: string, bookmarkManager?: BookmarkManager }) => Session) => QueryExecutor @@ -471,7 +472,7 @@ class Driver { private readonly _createSession: CreateSession private readonly _defaultExecuteQueryBookmarkManager: BookmarkManager private readonly _queryExecutor: QueryExecutor - homeDatabaseCache: Map + homeDatabaseCache: Map /** * You should not be calling this directly, instead use {@link driver}. @@ -511,7 +512,7 @@ class Driver { */ this._connectionProvider = null - this.homeDatabaseCache = new Map() + this.homeDatabaseCache = new Map() this._afterConstruction() } @@ -841,6 +842,16 @@ class Driver { ) } + _homeDatabaseCallback (user: string, table: any): void { + this.homeDatabaseCache.set(user, table) + } + + _committedDbCallback (database: string, user: string): void { + if(this.homeDatabaseCache.get(user) !== undefined && this.homeDatabaseCache.get(user).database !== database){ + this.homeDatabaseCache.delete(user) + } + } + /** * @private */ @@ -888,14 +899,11 @@ class Driver { notificationFilter, auth, log: this._log, - homeDatabaseCallback: this._homeDatabaseCallback.bind(this) + homeDatabaseTableCallback: this._homeDatabaseCallback.bind(this), + committedDbCallback: this._committedDbCallback.bind(this) }) } - _homeDatabaseCallback (user: string, databaseName: string): void { - this.homeDatabaseCache.set(user, databaseName) - } - /** * @private */ diff --git a/packages/neo4j-driver-deno/lib/core/session.ts b/packages/neo4j-driver-deno/lib/core/session.ts index 8ae4c34fc..76fb7ff3a 100644 --- a/packages/neo4j-driver-deno/lib/core/session.ts +++ b/packages/neo4j-driver-deno/lib/core/session.ts @@ -47,6 +47,11 @@ interface TransactionConfig { metadata?: object } +interface DbGuess { + database: string + user: string +} + /** * A Session instance is used for handling the connection and * sending queries through the connection. @@ -74,9 +79,11 @@ class Session { private readonly _bookmarkManager?: BookmarkManager private readonly _notificationFilter?: NotificationFilter private readonly _log: Logger - private readonly _homeDatabaseCallback: Function | undefined + private readonly _homeDatabaseTableCallback: Function | undefined + private readonly _committedDbCallback: Function | undefined private readonly _auth: AuthToken | undefined - private readonly _homeDatabaseBestGuess + private _homeDatabaseBestGuess: string | undefined + private _databaseGuess: DbGuess | undefined /** * @constructor * @protected @@ -105,7 +112,8 @@ class Session { notificationFilter, auth, log, - homeDatabaseCallback + homeDatabaseTableCallback, + committedDbCallback }: { mode: SessionMode connectionProvider: ConnectionProvider @@ -119,14 +127,15 @@ class Session { notificationFilter?: NotificationFilter auth?: AuthToken log: Logger - homeDatabaseCallback?: (user: string, databaseName: string) => void + homeDatabaseTableCallback?: (user: string, table: any) => void + committedDbCallback?: (user: string, database: string) => void }) { this._mode = mode this._database = database this._reactive = reactive this._fetchSize = fetchSize - this._onDatabaseNameResolved = this._onDatabaseNameResolved.bind(this) - this._homeDatabaseCallback = homeDatabaseCallback + this._homeDatabaseTableCallback = homeDatabaseTableCallback + this._committedDbCallback = committedDbCallback this._homeDatabaseBestGuess = config?.homeDatabase this._auth = auth this._getConnectionAcquistionBookmarks = this._getConnectionAcquistionBookmarks.bind(this) @@ -137,7 +146,7 @@ class Session { bookmarks, connectionProvider, impersonatedUser, - onDatabaseNameResolved: this._onDatabaseNameResolved, + onDatabaseNameResolved: this._onDatabaseNameResolved.bind(this), getConnectionAcquistionBookmarks: this._getConnectionAcquistionBookmarks, log }) @@ -516,10 +525,11 @@ class Session { * @returns {void} */ _onDatabaseNameResolved (database?: string, user?: string, table?: any): void { - if (this._homeDatabaseCallback != null) { - this._homeDatabaseCallback(this._impersonatedUser ?? this._auth?.principal ?? user, table) + if(this._homeDatabaseTableCallback) { + this._homeDatabaseTableCallback(this._impersonatedUser ?? this._auth?.principal ?? user, table) } if (!this._databaseNameResolved) { + this._databaseGuess = {user: this._impersonatedUser ?? this._auth?.principal ?? user ?? "", database: database ?? ""} const normalizedDatabase = database ?? '' this._database = normalizedDatabase this._readConnectionHolder.setDatabase(normalizedDatabase) @@ -529,12 +539,13 @@ class Session { } committedDbCallback (database: string): void { - if (!this._databaseNameResolved) { - const normalizedDatabase = database ?? '' - this._database = normalizedDatabase - this._readConnectionHolder.setDatabase(normalizedDatabase) - this._writeConnectionHolder.setDatabase(normalizedDatabase) - this._databaseNameResolved = true + if(this._committedDbCallback !== undefined && this._databaseGuess) { + this._committedDbCallback(database, this._databaseGuess.user) + } + if(this._databaseGuess && database !== this._databaseGuess.database) { + this._homeDatabaseBestGuess = undefined + this._readConnectionHolder.setDatabase(undefined) + this._writeConnectionHolder.setDatabase(undefined) } } From 195f05f8631fe0527387c9f3e7d5029866954be6 Mon Sep 17 00:00:00 2001 From: MaxAake <61233757+MaxAake@users.noreply.github.com> Date: Tue, 29 Oct 2024 11:34:42 +0100 Subject: [PATCH 11/84] continued work, requires server side fix to commit success message --- .../connection-provider-routing.js | 7 +++--- packages/core/src/connection-provider.ts | 4 +++- packages/core/src/driver.ts | 19 ++++++++++++---- .../core/src/internal/connection-holder.ts | 6 +++++ packages/core/src/session.ts | 18 ++++++++------- .../connection-provider-routing.js | 7 +++--- .../lib/core/connection-provider.ts | 4 +++- packages/neo4j-driver-deno/lib/core/driver.ts | 21 +++++++++++++----- .../lib/core/internal/connection-holder.ts | 6 +++++ .../neo4j-driver-deno/lib/core/session.ts | 22 ++++++++++--------- packages/neo4j-driver/src/index.js | 4 +++- 11 files changed, 82 insertions(+), 36 deletions(-) diff --git a/packages/bolt-connection/src/connection-provider/connection-provider-routing.js b/packages/bolt-connection/src/connection-provider/connection-provider-routing.js index a223b879e..08d1c7192 100644 --- a/packages/bolt-connection/src/connection-provider/connection-provider-routing.js +++ b/packages/bolt-connection/src/connection-provider/connection-provider-routing.js @@ -139,7 +139,7 @@ export default class RoutingConnectionProvider extends PooledConnectionProvider * See {@link ConnectionProvider} for more information about this method and * its arguments. */ - async acquireConnection ({ accessMode, database, bookmarks, impersonatedUser, onDatabaseNameResolved, auth, homeDbTable } = {}) { + async acquireConnection ({ accessMode, database, bookmarks, impersonatedUser, onDatabaseNameResolved, removeFailureFromCache, auth, homeDbTable } = {}) { let name let address let runResolved = false @@ -149,12 +149,13 @@ export default class RoutingConnectionProvider extends PooledConnectionProvider SESSION_EXPIRED, (error, address) => this._handleUnavailability(error, address, context.database), (error, address) => { - this._handleWriteFailure(error, address, homeDbTable?.database ?? context.database) + removeFailureFromCache(homeDbTable?.database ?? context.database) + return this._handleWriteFailure(error, address, homeDbTable?.database ?? context.database) }, (error, address, conn) => this._handleSecurityError(error, address, conn, context.database) ) - const routingTable = (homeDbTable && homeDbTable.isStaleFor(accessMode)) + const routingTable = (homeDbTable && !homeDbTable.isStaleFor(accessMode)) ? homeDbTable : await this._freshRoutingTable({ accessMode, diff --git a/packages/core/src/connection-provider.ts b/packages/core/src/connection-provider.ts index 69fa20c17..b9ff70180 100644 --- a/packages/core/src/connection-provider.ts +++ b/packages/core/src/connection-provider.ts @@ -57,6 +57,7 @@ class ConnectionProvider { * @property {Bookmarks} param.bookmarks - the bookmarks to send to routing discovery * @property {string} param.impersonatedUser - the impersonated user * @property {function (databaseName:string?)} param.onDatabaseNameResolved - Callback called when the database name get resolved + * @property {function (databaseName:string?)} param.removeFailureFromCache - Callback for deleting lost db from cache * @returns {Promise} */ acquireConnection (param?: { @@ -64,7 +65,8 @@ class ConnectionProvider { database?: string bookmarks: bookmarks.Bookmarks impersonatedUser?: string - onDatabaseNameResolved?: (databaseName?: string) => void + onDatabaseNameResolved?: (database?: string) => void + removeFailureFromCache?: (database?: string) => void auth?: AuthToken homeDbTable?: any }): Promise { diff --git a/packages/core/src/driver.ts b/packages/core/src/driver.ts index 335faeb99..aa1384efb 100644 --- a/packages/core/src/driver.ts +++ b/packages/core/src/driver.ts @@ -99,6 +99,7 @@ type CreateSession = (args: { log: Logger homeDatabaseTableCallback?: (user: string, table: any) => void committedDbCallback?: (user: string) => void + removeFailureFromCache?: (database: string) => void }) => Session type CreateQueryExecutor = (createSession: (config: { database?: string, bookmarkManager?: BookmarkManager }) => Session) => QueryExecutor @@ -110,6 +111,7 @@ interface DriverConfig { logging?: LoggingConfig notificationFilter?: NotificationFilter connectionLivenessCheckTimeout?: number + user?: string | undefined } /** @@ -852,6 +854,14 @@ class Driver { } } + _removeFailureFromCache (database: string): void { + this.homeDatabaseCache.forEach((_, key) => { + if (this.homeDatabaseCache.get(key).database === database) { + this.homeDatabaseCache.delete(key) + } + }) + } + /** * @private */ @@ -878,7 +888,7 @@ class Driver { }): Session { const sessionMode = Session._validateSessionMode(defaultAccessMode) const connectionProvider = this._getOrCreateConnectionProvider() - const homeDatabase = this.homeDatabaseCache.get(impersonatedUser ?? auth?.principal ?? '') + const homeDatabase = this.homeDatabaseCache.get(impersonatedUser ?? auth?.principal ?? this._config.user ?? '') const bookmarks = bookmarkOrBookmarks != null ? new Bookmarks(bookmarkOrBookmarks) : Bookmarks.empty() @@ -890,7 +900,8 @@ class Driver { bookmarks, config: { ...this._config, - homeDatabase + homeDatabase, + userGuess: impersonatedUser ?? auth?.principal ?? '' }, reactive, impersonatedUser, @@ -900,7 +911,8 @@ class Driver { auth, log: this._log, homeDatabaseTableCallback: this._homeDatabaseCallback.bind(this), - committedDbCallback: this._committedDbCallback.bind(this) + committedDbCallback: this._committedDbCallback.bind(this), + removeFailureFromCache: this._removeFailureFromCache.bind(this) }) } @@ -916,7 +928,6 @@ class Driver { createHostNameResolver(this._config) ) } - return this._connectionProvider } } diff --git a/packages/core/src/internal/connection-holder.ts b/packages/core/src/internal/connection-holder.ts index 3ce7e9af9..501d921a8 100644 --- a/packages/core/src/internal/connection-holder.ts +++ b/packages/core/src/internal/connection-holder.ts @@ -85,6 +85,7 @@ class ConnectionHolder implements ConnectionHolderInterface { private readonly _impersonatedUser?: string private readonly _getConnectionAcquistionBookmarks: () => Promise private readonly _onDatabaseNameResolved?: (databaseName?: string) => void + private readonly removeFailureFromCache?: (databaseName?: string) => void private readonly _auth?: AuthToken private readonly _log: Logger private _closed: boolean @@ -98,6 +99,7 @@ class ConnectionHolder implements ConnectionHolderInterface { * @property {ConnectionProvider} params.connectionProvider - the connection provider to acquire connections from. * @property {string?} params.impersonatedUser - the user which will be impersonated * @property {function(databaseName:string)} params.onDatabaseNameResolved - callback called when the database name is resolved + * @property {function (databaseName:string?)} param.removeFailureFromCache - Callback for deleting lost db from cache * @property {function():Promise} params.getConnectionAcquistionBookmarks - called for getting Bookmarks for acquiring connections * @property {AuthToken} params.auth - the target auth for the to-be-acquired connection */ @@ -108,6 +110,7 @@ class ConnectionHolder implements ConnectionHolderInterface { connectionProvider, impersonatedUser, onDatabaseNameResolved, + removeFailureFromCache, getConnectionAcquistionBookmarks, auth, log @@ -118,6 +121,7 @@ class ConnectionHolder implements ConnectionHolderInterface { connectionProvider?: ConnectionProvider impersonatedUser?: string onDatabaseNameResolved?: (databaseName?: string) => void + removeFailureFromCache?: (databaseName?: string) => void getConnectionAcquistionBookmarks?: () => Promise auth?: AuthToken log: Logger @@ -131,6 +135,7 @@ class ConnectionHolder implements ConnectionHolderInterface { this._referenceCount = 0 this._connectionPromise = Promise.resolve(null) this._onDatabaseNameResolved = onDatabaseNameResolved + this.removeFailureFromCache = removeFailureFromCache this._auth = auth this._log = log this._logError = this._logError.bind(this) @@ -179,6 +184,7 @@ class ConnectionHolder implements ConnectionHolderInterface { bookmarks: await this._getBookmarks(), impersonatedUser: this._impersonatedUser, onDatabaseNameResolved: this._onDatabaseNameResolved, + removeFailureFromCache: this.removeFailureFromCache, auth: this._auth, homeDbTable: homeDatabaseTable }) diff --git a/packages/core/src/session.ts b/packages/core/src/session.ts index 22eeca750..33539c261 100644 --- a/packages/core/src/session.ts +++ b/packages/core/src/session.ts @@ -48,7 +48,7 @@ interface TransactionConfig { } interface DbGuess { - database: string + database: any user: string } @@ -82,7 +82,6 @@ class Session { private readonly _homeDatabaseTableCallback: Function | undefined private readonly _committedDbCallback: Function | undefined private readonly _auth: AuthToken | undefined - private _homeDatabaseBestGuess: string | undefined private _databaseGuess: DbGuess | undefined /** * @constructor @@ -113,6 +112,7 @@ class Session { auth, log, homeDatabaseTableCallback, + removeFailureFromCache, committedDbCallback }: { mode: SessionMode @@ -128,6 +128,7 @@ class Session { auth?: AuthToken log: Logger homeDatabaseTableCallback?: (user: string, table: any) => void + removeFailureFromCache?: (database: string) => void committedDbCallback?: (user: string, database: string) => void }) { this._mode = mode @@ -136,7 +137,7 @@ class Session { this._fetchSize = fetchSize this._homeDatabaseTableCallback = homeDatabaseTableCallback this._committedDbCallback = committedDbCallback - this._homeDatabaseBestGuess = config?.homeDatabase + this._databaseGuess = { user: config?.userGuess, database: config?.homeDatabase } this._auth = auth this._getConnectionAcquistionBookmarks = this._getConnectionAcquistionBookmarks.bind(this) this._readConnectionHolder = new ConnectionHolder({ @@ -147,6 +148,7 @@ class Session { connectionProvider, impersonatedUser, onDatabaseNameResolved: this._onDatabaseNameResolved.bind(this), + removeFailureFromCache, getConnectionAcquistionBookmarks: this._getConnectionAcquistionBookmarks, log }) @@ -271,7 +273,7 @@ class Session { resultPromise = Promise.reject( newError('Cannot run query in a closed session.') ) - } else if (!this._hasTx && connectionHolder.initializeConnection(this._homeDatabaseBestGuess)) { + } else if (!this._hasTx && connectionHolder.initializeConnection(this._databaseGuess?.database)) { resultPromise = connectionHolder .getConnection() // Connection won't be null at this point since the initialize method @@ -327,7 +329,7 @@ class Session { const mode = Session._validateSessionMode(accessMode) const connectionHolder = this._connectionHolderWithMode(mode) - connectionHolder.initializeConnection(this._homeDatabaseBestGuess) + connectionHolder.initializeConnection(this._databaseGuess?.database) this._hasTx = true const tx = new TransactionPromise({ @@ -525,11 +527,11 @@ class Session { * @returns {void} */ _onDatabaseNameResolved (database?: string, user?: string, table?: any): void { + this._databaseGuess = { user: this._impersonatedUser ?? this._auth?.principal ?? user ?? '', database: table } if (this._homeDatabaseTableCallback != null) { this._homeDatabaseTableCallback(this._impersonatedUser ?? this._auth?.principal ?? user, table) } if (!this._databaseNameResolved) { - this._databaseGuess = { user: this._impersonatedUser ?? this._auth?.principal ?? user ?? '', database: database ?? '' } const normalizedDatabase = database ?? '' this._database = normalizedDatabase this._readConnectionHolder.setDatabase(normalizedDatabase) @@ -539,11 +541,11 @@ class Session { } committedDbCallback (database: string): void { - if (this._committedDbCallback !== undefined && (this._databaseGuess != null)) { + if (this._committedDbCallback !== undefined && this._databaseGuess !== undefined) { this._committedDbCallback(database, this._databaseGuess.user) } if ((this._databaseGuess != null) && database !== this._databaseGuess.database) { - this._homeDatabaseBestGuess = undefined + this._databaseGuess = undefined this._readConnectionHolder.setDatabase(undefined) this._writeConnectionHolder.setDatabase(undefined) } diff --git a/packages/neo4j-driver-deno/lib/bolt-connection/connection-provider/connection-provider-routing.js b/packages/neo4j-driver-deno/lib/bolt-connection/connection-provider/connection-provider-routing.js index e64d1b4a5..17dcaba19 100644 --- a/packages/neo4j-driver-deno/lib/bolt-connection/connection-provider/connection-provider-routing.js +++ b/packages/neo4j-driver-deno/lib/bolt-connection/connection-provider/connection-provider-routing.js @@ -139,7 +139,7 @@ export default class RoutingConnectionProvider extends PooledConnectionProvider * See {@link ConnectionProvider} for more information about this method and * its arguments. */ - async acquireConnection ({ accessMode, database, bookmarks, impersonatedUser, onDatabaseNameResolved, auth, homeDbTable } = {}) { + async acquireConnection ({ accessMode, database, bookmarks, impersonatedUser, onDatabaseNameResolved, removeFailureFromCache, auth, homeDbTable } = {}) { let name let address let runResolved = false @@ -149,12 +149,13 @@ export default class RoutingConnectionProvider extends PooledConnectionProvider SESSION_EXPIRED, (error, address) => this._handleUnavailability(error, address, context.database), (error, address) => { - this._handleWriteFailure(error, address, homeDbTable?.database ?? context.database) + removeFailureFromCache(homeDbTable?.database ?? context.database) + return this._handleWriteFailure(error, address, homeDbTable?.database ?? context.database) }, (error, address, conn) => this._handleSecurityError(error, address, conn, context.database) ) - const routingTable = (homeDbTable && homeDbTable.isStaleFor(accessMode)) + const routingTable = (homeDbTable && !homeDbTable.isStaleFor(accessMode)) ? homeDbTable : await this._freshRoutingTable({ accessMode, diff --git a/packages/neo4j-driver-deno/lib/core/connection-provider.ts b/packages/neo4j-driver-deno/lib/core/connection-provider.ts index 95df5b4bd..496ee5571 100644 --- a/packages/neo4j-driver-deno/lib/core/connection-provider.ts +++ b/packages/neo4j-driver-deno/lib/core/connection-provider.ts @@ -57,6 +57,7 @@ class ConnectionProvider { * @property {Bookmarks} param.bookmarks - the bookmarks to send to routing discovery * @property {string} param.impersonatedUser - the impersonated user * @property {function (databaseName:string?)} param.onDatabaseNameResolved - Callback called when the database name get resolved + * @property {function (databaseName:string?)} param.removeFailureFromCache - Callback for deleting lost db from cache * @returns {Promise} */ acquireConnection (param?: { @@ -64,7 +65,8 @@ class ConnectionProvider { database?: string bookmarks: bookmarks.Bookmarks impersonatedUser?: string - onDatabaseNameResolved?: (databaseName?: string) => void + onDatabaseNameResolved?: (database?: string) => void + removeFailureFromCache?: (database?: string) => void auth?: AuthToken homeDbTable?: any }): Promise { diff --git a/packages/neo4j-driver-deno/lib/core/driver.ts b/packages/neo4j-driver-deno/lib/core/driver.ts index 0159364cb..2030a1f3f 100644 --- a/packages/neo4j-driver-deno/lib/core/driver.ts +++ b/packages/neo4j-driver-deno/lib/core/driver.ts @@ -99,6 +99,7 @@ type CreateSession = (args: { log: Logger homeDatabaseTableCallback?: (user: string, table: any) => void committedDbCallback?: (user: string) => void + removeFailureFromCache?: (database: string) => void }) => Session type CreateQueryExecutor = (createSession: (config: { database?: string, bookmarkManager?: BookmarkManager }) => Session) => QueryExecutor @@ -110,6 +111,7 @@ interface DriverConfig { logging?: LoggingConfig notificationFilter?: NotificationFilter connectionLivenessCheckTimeout?: number + user?: string | undefined } /** @@ -847,11 +849,19 @@ class Driver { } _committedDbCallback (database: string, user: string): void { - if(this.homeDatabaseCache.get(user) !== undefined && this.homeDatabaseCache.get(user).database !== database){ + if (this.homeDatabaseCache.get(user) !== undefined && this.homeDatabaseCache.get(user).database !== database) { this.homeDatabaseCache.delete(user) } } + _removeFailureFromCache (database: string): void { + this.homeDatabaseCache.forEach((_, key) => { + if(this.homeDatabaseCache.get(key).database === database) { + this.homeDatabaseCache.delete(key) + } + }) + } + /** * @private */ @@ -878,7 +888,7 @@ class Driver { }): Session { const sessionMode = Session._validateSessionMode(defaultAccessMode) const connectionProvider = this._getOrCreateConnectionProvider() - const homeDatabase = this.homeDatabaseCache.get(impersonatedUser ?? auth?.principal ?? '') + const homeDatabase = this.homeDatabaseCache.get(impersonatedUser ?? auth?.principal ?? this._config.user ?? '') const bookmarks = bookmarkOrBookmarks != null ? new Bookmarks(bookmarkOrBookmarks) : Bookmarks.empty() @@ -890,7 +900,8 @@ class Driver { bookmarks, config: { ...this._config, - homeDatabase + homeDatabase, + userGuess: impersonatedUser ?? auth?.principal ?? '' }, reactive, impersonatedUser, @@ -900,7 +911,8 @@ class Driver { auth, log: this._log, homeDatabaseTableCallback: this._homeDatabaseCallback.bind(this), - committedDbCallback: this._committedDbCallback.bind(this) + committedDbCallback: this._committedDbCallback.bind(this), + removeFailureFromCache: this._removeFailureFromCache.bind(this) }) } @@ -916,7 +928,6 @@ class Driver { createHostNameResolver(this._config) ) } - return this._connectionProvider } } diff --git a/packages/neo4j-driver-deno/lib/core/internal/connection-holder.ts b/packages/neo4j-driver-deno/lib/core/internal/connection-holder.ts index dccca3c33..735cfb163 100644 --- a/packages/neo4j-driver-deno/lib/core/internal/connection-holder.ts +++ b/packages/neo4j-driver-deno/lib/core/internal/connection-holder.ts @@ -85,6 +85,7 @@ class ConnectionHolder implements ConnectionHolderInterface { private readonly _impersonatedUser?: string private readonly _getConnectionAcquistionBookmarks: () => Promise private readonly _onDatabaseNameResolved?: (databaseName?: string) => void + private readonly removeFailureFromCache?: (databaseName?: string) => void private readonly _auth?: AuthToken private readonly _log: Logger private _closed: boolean @@ -98,6 +99,7 @@ class ConnectionHolder implements ConnectionHolderInterface { * @property {ConnectionProvider} params.connectionProvider - the connection provider to acquire connections from. * @property {string?} params.impersonatedUser - the user which will be impersonated * @property {function(databaseName:string)} params.onDatabaseNameResolved - callback called when the database name is resolved + * @property {function (databaseName:string?)} param.removeFailureFromCache - Callback for deleting lost db from cache * @property {function():Promise} params.getConnectionAcquistionBookmarks - called for getting Bookmarks for acquiring connections * @property {AuthToken} params.auth - the target auth for the to-be-acquired connection */ @@ -108,6 +110,7 @@ class ConnectionHolder implements ConnectionHolderInterface { connectionProvider, impersonatedUser, onDatabaseNameResolved, + removeFailureFromCache, getConnectionAcquistionBookmarks, auth, log @@ -118,6 +121,7 @@ class ConnectionHolder implements ConnectionHolderInterface { connectionProvider?: ConnectionProvider impersonatedUser?: string onDatabaseNameResolved?: (databaseName?: string) => void + removeFailureFromCache?: (databaseName?: string) => void getConnectionAcquistionBookmarks?: () => Promise auth?: AuthToken log: Logger @@ -131,6 +135,7 @@ class ConnectionHolder implements ConnectionHolderInterface { this._referenceCount = 0 this._connectionPromise = Promise.resolve(null) this._onDatabaseNameResolved = onDatabaseNameResolved + this.removeFailureFromCache = removeFailureFromCache this._auth = auth this._log = log this._logError = this._logError.bind(this) @@ -179,6 +184,7 @@ class ConnectionHolder implements ConnectionHolderInterface { bookmarks: await this._getBookmarks(), impersonatedUser: this._impersonatedUser, onDatabaseNameResolved: this._onDatabaseNameResolved, + removeFailureFromCache: this.removeFailureFromCache, auth: this._auth, homeDbTable: homeDatabaseTable }) diff --git a/packages/neo4j-driver-deno/lib/core/session.ts b/packages/neo4j-driver-deno/lib/core/session.ts index 76fb7ff3a..b2d561962 100644 --- a/packages/neo4j-driver-deno/lib/core/session.ts +++ b/packages/neo4j-driver-deno/lib/core/session.ts @@ -48,7 +48,7 @@ interface TransactionConfig { } interface DbGuess { - database: string + database: any user: string } @@ -82,7 +82,6 @@ class Session { private readonly _homeDatabaseTableCallback: Function | undefined private readonly _committedDbCallback: Function | undefined private readonly _auth: AuthToken | undefined - private _homeDatabaseBestGuess: string | undefined private _databaseGuess: DbGuess | undefined /** * @constructor @@ -113,6 +112,7 @@ class Session { auth, log, homeDatabaseTableCallback, + removeFailureFromCache, committedDbCallback }: { mode: SessionMode @@ -128,6 +128,7 @@ class Session { auth?: AuthToken log: Logger homeDatabaseTableCallback?: (user: string, table: any) => void + removeFailureFromCache?: (database: string) => void committedDbCallback?: (user: string, database: string) => void }) { this._mode = mode @@ -136,7 +137,7 @@ class Session { this._fetchSize = fetchSize this._homeDatabaseTableCallback = homeDatabaseTableCallback this._committedDbCallback = committedDbCallback - this._homeDatabaseBestGuess = config?.homeDatabase + this._databaseGuess = {user: config?.userGuess, database: config?.homeDatabase} this._auth = auth this._getConnectionAcquistionBookmarks = this._getConnectionAcquistionBookmarks.bind(this) this._readConnectionHolder = new ConnectionHolder({ @@ -147,6 +148,7 @@ class Session { connectionProvider, impersonatedUser, onDatabaseNameResolved: this._onDatabaseNameResolved.bind(this), + removeFailureFromCache: removeFailureFromCache, getConnectionAcquistionBookmarks: this._getConnectionAcquistionBookmarks, log }) @@ -271,7 +273,7 @@ class Session { resultPromise = Promise.reject( newError('Cannot run query in a closed session.') ) - } else if (!this._hasTx && connectionHolder.initializeConnection(this._homeDatabaseBestGuess)) { + } else if (!this._hasTx && connectionHolder.initializeConnection(this._databaseGuess?.database)) { resultPromise = connectionHolder .getConnection() // Connection won't be null at this point since the initialize method @@ -327,7 +329,7 @@ class Session { const mode = Session._validateSessionMode(accessMode) const connectionHolder = this._connectionHolderWithMode(mode) - connectionHolder.initializeConnection(this._homeDatabaseBestGuess) + connectionHolder.initializeConnection(this._databaseGuess?.database) this._hasTx = true const tx = new TransactionPromise({ @@ -525,11 +527,11 @@ class Session { * @returns {void} */ _onDatabaseNameResolved (database?: string, user?: string, table?: any): void { - if(this._homeDatabaseTableCallback) { + this._databaseGuess = { user: this._impersonatedUser ?? this._auth?.principal ?? user ?? '', database: table } + if (this._homeDatabaseTableCallback != null) { this._homeDatabaseTableCallback(this._impersonatedUser ?? this._auth?.principal ?? user, table) } if (!this._databaseNameResolved) { - this._databaseGuess = {user: this._impersonatedUser ?? this._auth?.principal ?? user ?? "", database: database ?? ""} const normalizedDatabase = database ?? '' this._database = normalizedDatabase this._readConnectionHolder.setDatabase(normalizedDatabase) @@ -539,11 +541,11 @@ class Session { } committedDbCallback (database: string): void { - if(this._committedDbCallback !== undefined && this._databaseGuess) { + if (this._committedDbCallback !== undefined && this._databaseGuess !== undefined) { this._committedDbCallback(database, this._databaseGuess.user) } - if(this._databaseGuess && database !== this._databaseGuess.database) { - this._homeDatabaseBestGuess = undefined + if ((this._databaseGuess != null) && database !== this._databaseGuess.database) { + this._databaseGuess = undefined this._readConnectionHolder.setDatabase(undefined) this._writeConnectionHolder.setDatabase(undefined) } diff --git a/packages/neo4j-driver/src/index.js b/packages/neo4j-driver/src/index.js index 911ad9fcd..2b31be6a3 100644 --- a/packages/neo4j-driver/src/index.js +++ b/packages/neo4j-driver/src/index.js @@ -189,7 +189,9 @@ function driver (url, authToken, config = {}) { typename: routing ? 'Routing' : 'Direct', routing } - + if (authToken.scheme === 'basic') { + config.user = authToken.principal + } return new Driver(meta, config, createConnectionProviderFunction()) function createConnectionProviderFunction () { From 317e2820fd52d40d8f578e3f792dcef8199f8b53 Mon Sep 17 00:00:00 2001 From: MaxAake <61233757+MaxAake@users.noreply.github.com> Date: Tue, 29 Oct 2024 11:35:16 +0100 Subject: [PATCH 12/84] deno --- packages/neo4j-driver-deno/lib/core/driver.ts | 2 +- packages/neo4j-driver-deno/lib/core/session.ts | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/packages/neo4j-driver-deno/lib/core/driver.ts b/packages/neo4j-driver-deno/lib/core/driver.ts index 2030a1f3f..f983bbad3 100644 --- a/packages/neo4j-driver-deno/lib/core/driver.ts +++ b/packages/neo4j-driver-deno/lib/core/driver.ts @@ -856,7 +856,7 @@ class Driver { _removeFailureFromCache (database: string): void { this.homeDatabaseCache.forEach((_, key) => { - if(this.homeDatabaseCache.get(key).database === database) { + if (this.homeDatabaseCache.get(key).database === database) { this.homeDatabaseCache.delete(key) } }) diff --git a/packages/neo4j-driver-deno/lib/core/session.ts b/packages/neo4j-driver-deno/lib/core/session.ts index b2d561962..40ed8b54c 100644 --- a/packages/neo4j-driver-deno/lib/core/session.ts +++ b/packages/neo4j-driver-deno/lib/core/session.ts @@ -137,7 +137,7 @@ class Session { this._fetchSize = fetchSize this._homeDatabaseTableCallback = homeDatabaseTableCallback this._committedDbCallback = committedDbCallback - this._databaseGuess = {user: config?.userGuess, database: config?.homeDatabase} + this._databaseGuess = { user: config?.userGuess, database: config?.homeDatabase } this._auth = auth this._getConnectionAcquistionBookmarks = this._getConnectionAcquistionBookmarks.bind(this) this._readConnectionHolder = new ConnectionHolder({ @@ -148,7 +148,7 @@ class Session { connectionProvider, impersonatedUser, onDatabaseNameResolved: this._onDatabaseNameResolved.bind(this), - removeFailureFromCache: removeFailureFromCache, + removeFailureFromCache, getConnectionAcquistionBookmarks: this._getConnectionAcquistionBookmarks, log }) From d0753282f0ce2441bc10d2118c560799266eb496 Mon Sep 17 00:00:00 2001 From: MaxAake <61233757+MaxAake@users.noreply.github.com> Date: Wed, 30 Oct 2024 16:28:29 +0100 Subject: [PATCH 13/84] logic to turn off and on caching using connection hints --- .../connection-provider-routing.js | 21 ++++++++++++-- .../src/connection/connection-channel.js | 21 ++++++++++++-- packages/core/src/driver.ts | 21 ++++++++------ packages/core/src/session.ts | 29 +++++++++---------- .../connection-provider-routing.js | 21 ++++++++++++-- .../connection/connection-channel.js | 21 ++++++++++++-- packages/neo4j-driver-deno/lib/core/driver.ts | 19 +++++++----- .../neo4j-driver-deno/lib/core/session.ts | 29 +++++++++---------- 8 files changed, 123 insertions(+), 59 deletions(-) diff --git a/packages/bolt-connection/src/connection-provider/connection-provider-routing.js b/packages/bolt-connection/src/connection-provider/connection-provider-routing.js index 08d1c7192..27979390d 100644 --- a/packages/bolt-connection/src/connection-provider/connection-provider-routing.js +++ b/packages/bolt-connection/src/connection-provider/connection-provider-routing.js @@ -78,7 +78,9 @@ export default class RoutingConnectionProvider extends PooledConnectionProvider this._createConnectionErrorHandler(), this._log, await this._clientCertificateHolder.getClientCertificate(), - this._routingContext + this._routingContext, + undefined, + this._channelSsrCallback.bind(this) ) }) @@ -99,6 +101,8 @@ export default class RoutingConnectionProvider extends PooledConnectionProvider ) this._refreshRoutingTable = functional.reuseOngoingRequest(this._refreshRoutingTable, this) + this._withSSR = 0 + this._withoutSSR = 0 } _createConnectionErrorHandler () { @@ -155,7 +159,7 @@ export default class RoutingConnectionProvider extends PooledConnectionProvider (error, address, conn) => this._handleSecurityError(error, address, conn, context.database) ) - const routingTable = (homeDbTable && !homeDbTable.isStaleFor(accessMode)) + const routingTable = (homeDbTable && !homeDbTable.isStaleFor(accessMode) && this._SSREnabled) ? homeDbTable : await this._freshRoutingTable({ accessMode, @@ -672,6 +676,18 @@ export default class RoutingConnectionProvider extends PooledConnectionProvider routingTable.forgetRouter(address) } } + + _channelSsrCallback (isEnabled, opened) { + if (isEnabled) { + this._withSSR = this._withSSR + (opened ? 1 : -1) + } else { + this._withoutSSR = this._withoutSSR + (opened ? 1 : -1) + } + } + + _SSREnabled () { + return this._withSSR > 0 && this._withoutSSR === 0 + } } /** @@ -781,6 +797,7 @@ function _isFailFastError (error) { } function _isFailFastSecurityError (error) { + console.error(error) return error.code.startsWith('Neo.ClientError.Security.') && ![ AUTHORIZATION_EXPIRED_CODE diff --git a/packages/bolt-connection/src/connection/connection-channel.js b/packages/bolt-connection/src/connection/connection-channel.js index bf4e65ca7..18aefc4e8 100644 --- a/packages/bolt-connection/src/connection/connection-channel.js +++ b/packages/bolt-connection/src/connection/connection-channel.js @@ -43,7 +43,8 @@ export function createChannelConnection ( log, clientCertificate, serversideRouting = null, - createChannel = channelConfig => new Channel(channelConfig) + createChannel = channelConfig => new Channel(channelConfig), + ssrCallback ) { const channelConfig = new ChannelConfig( address, @@ -89,7 +90,8 @@ export function createChannelConnection ( chunker, config.notificationFilter, createProtocol, - config.telemetryDisabled + config.telemetryDisabled, + ssrCallback ) // forward all pending bytes to the dechunker @@ -124,7 +126,8 @@ export default class ChannelConnection extends Connection { chunker, // to be removed, notificationFilter, protocolSupplier, - telemetryDisabled + telemetryDisabled, + ssrCallback ) { super(errorHandler) this._authToken = null @@ -143,6 +146,7 @@ export default class ChannelConnection extends Connection { this._notificationFilter = notificationFilter this._telemetryDisabledDriverConfig = telemetryDisabled === true this._telemetryDisabledConnection = true + this._ssrCallback = ssrCallback // connection from the database, returned in response for HELLO message and might not be available this._dbConnectionId = null @@ -331,6 +335,16 @@ export default class ChannelConnection extends Connection { if (telemetryEnabledHint === true) { this._telemetryDisabledConnection = false } + + const SSREnabledHint = metadata.hints['ssr.enabled'] + if (this.serversideRouting !== SSREnabledHint) { + this._ssrCallback(SSREnabledHint, true) + } + if (SSREnabledHint === true) { + this.serversideRouting = true + } else if (SSREnabledHint === false) { + this.serversideRouting = false + } } } resolve(self) @@ -538,6 +552,7 @@ export default class ChannelConnection extends Connection { * @returns {Promise} - A promise that will be resolved when the underlying channel is closed. */ async close () { + this._ssrCallback(this.serversideRouting, false) if (this._log.isDebugEnabled()) { this._log.debug('closing') } diff --git a/packages/core/src/driver.ts b/packages/core/src/driver.ts index aa1384efb..1a9ce7968 100644 --- a/packages/core/src/driver.ts +++ b/packages/core/src/driver.ts @@ -844,7 +844,7 @@ class Driver { ) } - _homeDatabaseCallback (user: string, table: any): void { + _homeDatabaseTableCallback (user: string, table: any): void { this.homeDatabaseCache.set(user, table) } @@ -888,20 +888,23 @@ class Driver { }): Session { const sessionMode = Session._validateSessionMode(defaultAccessMode) const connectionProvider = this._getOrCreateConnectionProvider() - const homeDatabase = this.homeDatabaseCache.get(impersonatedUser ?? auth?.principal ?? this._config.user ?? '') + const cachedUser = impersonatedUser ?? auth?.principal ?? this._config.user ?? '' + const cachedHomeDatabaseRoutingTable = this.homeDatabaseCache.get(cachedUser ?? '') + const homeDatabaseTableCallback = this._homeDatabaseTableCallback.bind(this) + const committedDbCallback = this._committedDbCallback.bind(this) + const removeFailureFromCache = this._removeFailureFromCache.bind(this) const bookmarks = bookmarkOrBookmarks != null ? new Bookmarks(bookmarkOrBookmarks) : Bookmarks.empty() - return this._createSession({ mode: sessionMode, database: database ?? '', connectionProvider, bookmarks, config: { - ...this._config, - homeDatabase, - userGuess: impersonatedUser ?? auth?.principal ?? '' + cachedHomeDatabaseRoutingTable, + cachedUser, + ...this._config }, reactive, impersonatedUser, @@ -910,9 +913,9 @@ class Driver { notificationFilter, auth, log: this._log, - homeDatabaseTableCallback: this._homeDatabaseCallback.bind(this), - committedDbCallback: this._committedDbCallback.bind(this), - removeFailureFromCache: this._removeFailureFromCache.bind(this) + homeDatabaseTableCallback, + committedDbCallback, + removeFailureFromCache }) } diff --git a/packages/core/src/session.ts b/packages/core/src/session.ts index 33539c261..16c9a95f7 100644 --- a/packages/core/src/session.ts +++ b/packages/core/src/session.ts @@ -48,7 +48,7 @@ interface TransactionConfig { } interface DbGuess { - database: any + routingTable: any user: string } @@ -80,7 +80,7 @@ class Session { private readonly _notificationFilter?: NotificationFilter private readonly _log: Logger private readonly _homeDatabaseTableCallback: Function | undefined - private readonly _committedDbCallback: Function | undefined + private readonly _driverCommittedDbCallback: Function | undefined private readonly _auth: AuthToken | undefined private _databaseGuess: DbGuess | undefined /** @@ -136,8 +136,10 @@ class Session { this._reactive = reactive this._fetchSize = fetchSize this._homeDatabaseTableCallback = homeDatabaseTableCallback - this._committedDbCallback = committedDbCallback - this._databaseGuess = { user: config?.userGuess, database: config?.homeDatabase } + this._driverCommittedDbCallback = committedDbCallback + if (config?.cachedUser !== undefined && config?.cachedHomeDatabaseRoutingTable !== undefined) { + this._databaseGuess = { user: config?.cachedUser, routingTable: config?.cachedHomeDatabaseRoutingTable } + } this._auth = auth this._getConnectionAcquistionBookmarks = this._getConnectionAcquistionBookmarks.bind(this) this._readConnectionHolder = new ConnectionHolder({ @@ -168,7 +170,7 @@ class Session { this._impersonatedUser = impersonatedUser this._lastBookmarks = bookmarks ?? Bookmarks.empty() this._configuredBookmarks = this._lastBookmarks - this._transactionExecutor = _createTransactionExecutor({ ...config, commitCallback: this.committedDbCallback.bind(this) }) + this._transactionExecutor = _createTransactionExecutor({ ...config, commitCallback: this._committedDbCallback.bind(this) }) this._databaseNameResolved = this._database !== '' const calculatedWatermaks = this._calculateWatermaks() this._lowRecordWatermark = calculatedWatermaks.low @@ -273,7 +275,7 @@ class Session { resultPromise = Promise.reject( newError('Cannot run query in a closed session.') ) - } else if (!this._hasTx && connectionHolder.initializeConnection(this._databaseGuess?.database)) { + } else if (!this._hasTx && connectionHolder.initializeConnection(this._databaseGuess?.routingTable)) { resultPromise = connectionHolder .getConnection() // Connection won't be null at this point since the initialize method @@ -329,7 +331,7 @@ class Session { const mode = Session._validateSessionMode(accessMode) const connectionHolder = this._connectionHolderWithMode(mode) - connectionHolder.initializeConnection(this._databaseGuess?.database) + connectionHolder.initializeConnection(this._databaseGuess?.routingTable) this._hasTx = true const tx = new TransactionPromise({ @@ -527,7 +529,7 @@ class Session { * @returns {void} */ _onDatabaseNameResolved (database?: string, user?: string, table?: any): void { - this._databaseGuess = { user: this._impersonatedUser ?? this._auth?.principal ?? user ?? '', database: table } + this._databaseGuess = { user: this._impersonatedUser ?? this._auth?.principal ?? user ?? '', routingTable: table } if (this._homeDatabaseTableCallback != null) { this._homeDatabaseTableCallback(this._impersonatedUser ?? this._auth?.principal ?? user, table) } @@ -540,14 +542,9 @@ class Session { } } - committedDbCallback (database: string): void { - if (this._committedDbCallback !== undefined && this._databaseGuess !== undefined) { - this._committedDbCallback(database, this._databaseGuess.user) - } - if ((this._databaseGuess != null) && database !== this._databaseGuess.database) { - this._databaseGuess = undefined - this._readConnectionHolder.setDatabase(undefined) - this._writeConnectionHolder.setDatabase(undefined) + _committedDbCallback (database: string): void { + if (this._driverCommittedDbCallback !== undefined && this._databaseGuess !== undefined) { + this._driverCommittedDbCallback(database, this._databaseGuess.user) } } diff --git a/packages/neo4j-driver-deno/lib/bolt-connection/connection-provider/connection-provider-routing.js b/packages/neo4j-driver-deno/lib/bolt-connection/connection-provider/connection-provider-routing.js index 17dcaba19..c25d8431a 100644 --- a/packages/neo4j-driver-deno/lib/bolt-connection/connection-provider/connection-provider-routing.js +++ b/packages/neo4j-driver-deno/lib/bolt-connection/connection-provider/connection-provider-routing.js @@ -78,7 +78,9 @@ export default class RoutingConnectionProvider extends PooledConnectionProvider this._createConnectionErrorHandler(), this._log, await this._clientCertificateHolder.getClientCertificate(), - this._routingContext + this._routingContext, + undefined, + this._channelSsrCallback.bind(this) ) }) @@ -99,6 +101,8 @@ export default class RoutingConnectionProvider extends PooledConnectionProvider ) this._refreshRoutingTable = functional.reuseOngoingRequest(this._refreshRoutingTable, this) + this._withSSR = 0 + this._withoutSSR = 0 } _createConnectionErrorHandler () { @@ -155,7 +159,7 @@ export default class RoutingConnectionProvider extends PooledConnectionProvider (error, address, conn) => this._handleSecurityError(error, address, conn, context.database) ) - const routingTable = (homeDbTable && !homeDbTable.isStaleFor(accessMode)) + const routingTable = (homeDbTable && !homeDbTable.isStaleFor(accessMode) && this._SSREnabled) ? homeDbTable : await this._freshRoutingTable({ accessMode, @@ -672,6 +676,18 @@ export default class RoutingConnectionProvider extends PooledConnectionProvider routingTable.forgetRouter(address) } } + + _channelSsrCallback (isEnabled, opened) { + if (isEnabled) { + this._withSSR = this._withSSR + (opened ? 1 : -1) + } else { + this._withoutSSR = this._withoutSSR + (opened ? 1 : -1) + } + } + + _SSREnabled () { + return this._withSSR > 0 && this._withoutSSR === 0 + } } /** @@ -781,6 +797,7 @@ function _isFailFastError (error) { } function _isFailFastSecurityError (error) { + console.error(error) return error.code.startsWith('Neo.ClientError.Security.') && ![ AUTHORIZATION_EXPIRED_CODE diff --git a/packages/neo4j-driver-deno/lib/bolt-connection/connection/connection-channel.js b/packages/neo4j-driver-deno/lib/bolt-connection/connection/connection-channel.js index 0da4592db..278e9cb70 100644 --- a/packages/neo4j-driver-deno/lib/bolt-connection/connection/connection-channel.js +++ b/packages/neo4j-driver-deno/lib/bolt-connection/connection/connection-channel.js @@ -43,7 +43,8 @@ export function createChannelConnection ( log, clientCertificate, serversideRouting = null, - createChannel = channelConfig => new Channel(channelConfig) + createChannel = channelConfig => new Channel(channelConfig), + ssrCallback ) { const channelConfig = new ChannelConfig( address, @@ -89,7 +90,8 @@ export function createChannelConnection ( chunker, config.notificationFilter, createProtocol, - config.telemetryDisabled + config.telemetryDisabled, + ssrCallback ) // forward all pending bytes to the dechunker @@ -124,7 +126,8 @@ export default class ChannelConnection extends Connection { chunker, // to be removed, notificationFilter, protocolSupplier, - telemetryDisabled + telemetryDisabled, + ssrCallback ) { super(errorHandler) this._authToken = null @@ -143,6 +146,7 @@ export default class ChannelConnection extends Connection { this._notificationFilter = notificationFilter this._telemetryDisabledDriverConfig = telemetryDisabled === true this._telemetryDisabledConnection = true + this._ssrCallback = ssrCallback // connection from the database, returned in response for HELLO message and might not be available this._dbConnectionId = null @@ -331,6 +335,16 @@ export default class ChannelConnection extends Connection { if (telemetryEnabledHint === true) { this._telemetryDisabledConnection = false } + + const SSREnabledHint = metadata.hints['ssr.enabled'] + if (this.serversideRouting !== SSREnabledHint) { + this._ssrCallback(SSREnabledHint, true) + } + if (SSREnabledHint === true) { + this.serversideRouting = true + } else if (SSREnabledHint === false) { + this.serversideRouting = false + } } } resolve(self) @@ -538,6 +552,7 @@ export default class ChannelConnection extends Connection { * @returns {Promise} - A promise that will be resolved when the underlying channel is closed. */ async close () { + this._ssrCallback(this.serversideRouting, false) if (this._log.isDebugEnabled()) { this._log.debug('closing') } diff --git a/packages/neo4j-driver-deno/lib/core/driver.ts b/packages/neo4j-driver-deno/lib/core/driver.ts index f983bbad3..084739f6e 100644 --- a/packages/neo4j-driver-deno/lib/core/driver.ts +++ b/packages/neo4j-driver-deno/lib/core/driver.ts @@ -844,7 +844,7 @@ class Driver { ) } - _homeDatabaseCallback (user: string, table: any): void { + _homeDatabaseTableCallback (user: string, table: any): void { this.homeDatabaseCache.set(user, table) } @@ -888,20 +888,23 @@ class Driver { }): Session { const sessionMode = Session._validateSessionMode(defaultAccessMode) const connectionProvider = this._getOrCreateConnectionProvider() - const homeDatabase = this.homeDatabaseCache.get(impersonatedUser ?? auth?.principal ?? this._config.user ?? '') + const cachedUser = impersonatedUser ?? auth?.principal ?? this._config.user ?? '' + const cachedHomeDatabaseRoutingTable = this.homeDatabaseCache.get(cachedUser ?? '') + const homeDatabaseTableCallback = this._homeDatabaseTableCallback.bind(this) + const committedDbCallback = this._committedDbCallback.bind(this) + const removeFailureFromCache = this._removeFailureFromCache.bind(this) const bookmarks = bookmarkOrBookmarks != null ? new Bookmarks(bookmarkOrBookmarks) : Bookmarks.empty() - return this._createSession({ mode: sessionMode, database: database ?? '', connectionProvider, bookmarks, config: { + cachedHomeDatabaseRoutingTable, + cachedUser, ...this._config, - homeDatabase, - userGuess: impersonatedUser ?? auth?.principal ?? '' }, reactive, impersonatedUser, @@ -910,9 +913,9 @@ class Driver { notificationFilter, auth, log: this._log, - homeDatabaseTableCallback: this._homeDatabaseCallback.bind(this), - committedDbCallback: this._committedDbCallback.bind(this), - removeFailureFromCache: this._removeFailureFromCache.bind(this) + homeDatabaseTableCallback, + committedDbCallback, + removeFailureFromCache }) } diff --git a/packages/neo4j-driver-deno/lib/core/session.ts b/packages/neo4j-driver-deno/lib/core/session.ts index 40ed8b54c..2d9214555 100644 --- a/packages/neo4j-driver-deno/lib/core/session.ts +++ b/packages/neo4j-driver-deno/lib/core/session.ts @@ -48,7 +48,7 @@ interface TransactionConfig { } interface DbGuess { - database: any + routingTable: any user: string } @@ -80,7 +80,7 @@ class Session { private readonly _notificationFilter?: NotificationFilter private readonly _log: Logger private readonly _homeDatabaseTableCallback: Function | undefined - private readonly _committedDbCallback: Function | undefined + private readonly _driverCommittedDbCallback: Function | undefined private readonly _auth: AuthToken | undefined private _databaseGuess: DbGuess | undefined /** @@ -136,8 +136,10 @@ class Session { this._reactive = reactive this._fetchSize = fetchSize this._homeDatabaseTableCallback = homeDatabaseTableCallback - this._committedDbCallback = committedDbCallback - this._databaseGuess = { user: config?.userGuess, database: config?.homeDatabase } + this._driverCommittedDbCallback = committedDbCallback + if(config?.cachedUser !== undefined && config?.cachedHomeDatabaseRoutingTable !== undefined) { + this._databaseGuess = { user: config?.cachedUser, routingTable: config?.cachedHomeDatabaseRoutingTable } + } this._auth = auth this._getConnectionAcquistionBookmarks = this._getConnectionAcquistionBookmarks.bind(this) this._readConnectionHolder = new ConnectionHolder({ @@ -168,7 +170,7 @@ class Session { this._impersonatedUser = impersonatedUser this._lastBookmarks = bookmarks ?? Bookmarks.empty() this._configuredBookmarks = this._lastBookmarks - this._transactionExecutor = _createTransactionExecutor({ ...config, commitCallback: this.committedDbCallback.bind(this) }) + this._transactionExecutor = _createTransactionExecutor({ ...config, commitCallback: this._committedDbCallback.bind(this) }) this._databaseNameResolved = this._database !== '' const calculatedWatermaks = this._calculateWatermaks() this._lowRecordWatermark = calculatedWatermaks.low @@ -273,7 +275,7 @@ class Session { resultPromise = Promise.reject( newError('Cannot run query in a closed session.') ) - } else if (!this._hasTx && connectionHolder.initializeConnection(this._databaseGuess?.database)) { + } else if (!this._hasTx && connectionHolder.initializeConnection(this._databaseGuess?.routingTable)) { resultPromise = connectionHolder .getConnection() // Connection won't be null at this point since the initialize method @@ -329,7 +331,7 @@ class Session { const mode = Session._validateSessionMode(accessMode) const connectionHolder = this._connectionHolderWithMode(mode) - connectionHolder.initializeConnection(this._databaseGuess?.database) + connectionHolder.initializeConnection(this._databaseGuess?.routingTable) this._hasTx = true const tx = new TransactionPromise({ @@ -527,7 +529,7 @@ class Session { * @returns {void} */ _onDatabaseNameResolved (database?: string, user?: string, table?: any): void { - this._databaseGuess = { user: this._impersonatedUser ?? this._auth?.principal ?? user ?? '', database: table } + this._databaseGuess = { user: this._impersonatedUser ?? this._auth?.principal ?? user ?? '', routingTable: table } if (this._homeDatabaseTableCallback != null) { this._homeDatabaseTableCallback(this._impersonatedUser ?? this._auth?.principal ?? user, table) } @@ -540,14 +542,9 @@ class Session { } } - committedDbCallback (database: string): void { - if (this._committedDbCallback !== undefined && this._databaseGuess !== undefined) { - this._committedDbCallback(database, this._databaseGuess.user) - } - if ((this._databaseGuess != null) && database !== this._databaseGuess.database) { - this._databaseGuess = undefined - this._readConnectionHolder.setDatabase(undefined) - this._writeConnectionHolder.setDatabase(undefined) + _committedDbCallback (database: string): void { + if (this._driverCommittedDbCallback !== undefined && this._databaseGuess !== undefined) { + this._driverCommittedDbCallback(database, this._databaseGuess.user) } } From 81a4924c6a7582e42e46073d375aa505fd61c51b Mon Sep 17 00:00:00 2001 From: MaxAake <61233757+MaxAake@users.noreply.github.com> Date: Wed, 30 Oct 2024 16:28:57 +0100 Subject: [PATCH 14/84] deno --- packages/neo4j-driver-deno/lib/core/driver.ts | 4 ++-- packages/neo4j-driver-deno/lib/core/session.ts | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/packages/neo4j-driver-deno/lib/core/driver.ts b/packages/neo4j-driver-deno/lib/core/driver.ts index 084739f6e..ab10d6a2a 100644 --- a/packages/neo4j-driver-deno/lib/core/driver.ts +++ b/packages/neo4j-driver-deno/lib/core/driver.ts @@ -888,7 +888,7 @@ class Driver { }): Session { const sessionMode = Session._validateSessionMode(defaultAccessMode) const connectionProvider = this._getOrCreateConnectionProvider() - const cachedUser = impersonatedUser ?? auth?.principal ?? this._config.user ?? '' + const cachedUser = impersonatedUser ?? auth?.principal ?? this._config.user ?? '' const cachedHomeDatabaseRoutingTable = this.homeDatabaseCache.get(cachedUser ?? '') const homeDatabaseTableCallback = this._homeDatabaseTableCallback.bind(this) const committedDbCallback = this._committedDbCallback.bind(this) @@ -904,7 +904,7 @@ class Driver { config: { cachedHomeDatabaseRoutingTable, cachedUser, - ...this._config, + ...this._config }, reactive, impersonatedUser, diff --git a/packages/neo4j-driver-deno/lib/core/session.ts b/packages/neo4j-driver-deno/lib/core/session.ts index 2d9214555..06e3c7108 100644 --- a/packages/neo4j-driver-deno/lib/core/session.ts +++ b/packages/neo4j-driver-deno/lib/core/session.ts @@ -137,7 +137,7 @@ class Session { this._fetchSize = fetchSize this._homeDatabaseTableCallback = homeDatabaseTableCallback this._driverCommittedDbCallback = committedDbCallback - if(config?.cachedUser !== undefined && config?.cachedHomeDatabaseRoutingTable !== undefined) { + if (config?.cachedUser !== undefined && config?.cachedHomeDatabaseRoutingTable !== undefined) { this._databaseGuess = { user: config?.cachedUser, routingTable: config?.cachedHomeDatabaseRoutingTable } } this._auth = auth From ac118049f99056318660df138fa87fe73c473c49 Mon Sep 17 00:00:00 2001 From: MaxAake <61233757+MaxAake@users.noreply.github.com> Date: Thu, 31 Oct 2024 12:00:07 +0100 Subject: [PATCH 15/84] change back to caching database name --- .../connection-provider-routing.js | 25 +++++++++--------- packages/core/src/connection-provider.ts | 2 +- packages/core/src/driver.ts | 16 ++++++------ .../core/src/internal/connection-holder.ts | 4 +-- packages/core/src/session.ts | 26 +++++++++---------- .../connection-provider-routing.js | 25 +++++++++--------- .../lib/core/connection-provider.ts | 2 +- packages/neo4j-driver-deno/lib/core/driver.ts | 16 ++++++------ .../lib/core/internal/connection-holder.ts | 4 +-- .../neo4j-driver-deno/lib/core/session.ts | 26 +++++++++---------- 10 files changed, 74 insertions(+), 72 deletions(-) diff --git a/packages/bolt-connection/src/connection-provider/connection-provider-routing.js b/packages/bolt-connection/src/connection-provider/connection-provider-routing.js index 27979390d..b59ebbd35 100644 --- a/packages/bolt-connection/src/connection-provider/connection-provider-routing.js +++ b/packages/bolt-connection/src/connection-provider/connection-provider-routing.js @@ -143,24 +143,30 @@ export default class RoutingConnectionProvider extends PooledConnectionProvider * See {@link ConnectionProvider} for more information about this method and * its arguments. */ - async acquireConnection ({ accessMode, database, bookmarks, impersonatedUser, onDatabaseNameResolved, removeFailureFromCache, auth, homeDbTable } = {}) { + async acquireConnection ({ accessMode, database, bookmarks, impersonatedUser, onDatabaseNameResolved, removeFailureFromCache, auth, homeDb } = {}) { let name let address - let runResolved = false const context = { database: database || DEFAULT_DB_NAME } const databaseSpecificErrorHandler = new ConnectionErrorHandler( SESSION_EXPIRED, (error, address) => this._handleUnavailability(error, address, context.database), (error, address) => { - removeFailureFromCache(homeDbTable?.database ?? context.database) - return this._handleWriteFailure(error, address, homeDbTable?.database ?? context.database) + removeFailureFromCache(homeDb ?? context.database) + return this._handleWriteFailure(error, address, homeDb ?? context.database) }, (error, address, conn) => this._handleSecurityError(error, address, conn, context.database) ) - const routingTable = (homeDbTable && !homeDbTable.isStaleFor(accessMode) && this._SSREnabled) - ? homeDbTable + let currentRoutingTable + if (this._SSREnabled() && homeDb !== undefined) { + currentRoutingTable = this._routingTableRegistry.get( + homeDb, + () => new RoutingTable({ database: homeDb }) + ) + } + const routingTable = (currentRoutingTable && !currentRoutingTable.isStaleFor(accessMode)) + ? currentRoutingTable : await this._freshRoutingTable({ accessMode, database: context.database, @@ -170,15 +176,11 @@ export default class RoutingConnectionProvider extends PooledConnectionProvider onDatabaseNameResolved: (databaseName) => { context.database = context.database || databaseName if (onDatabaseNameResolved) { - runResolved = true + onDatabaseNameResolved(context.database, this._authenticationProvider?._authTokenManager?._authToken?.principal) } } }) - if (runResolved) { - onDatabaseNameResolved(context.database, this._authenticationProvider?._authTokenManager?._authToken?.principal, routingTable) - } - // select a target server based on specified access mode if (accessMode === READ) { address = this._loadBalancingStrategy.selectReader(routingTable.readers) @@ -360,7 +362,6 @@ export default class RoutingConnectionProvider extends PooledConnectionProvider database, () => new RoutingTable({ database }) ) - if (!currentRoutingTable.isStaleFor(accessMode)) { return currentRoutingTable } diff --git a/packages/core/src/connection-provider.ts b/packages/core/src/connection-provider.ts index b9ff70180..6b44e09f8 100644 --- a/packages/core/src/connection-provider.ts +++ b/packages/core/src/connection-provider.ts @@ -68,7 +68,7 @@ class ConnectionProvider { onDatabaseNameResolved?: (database?: string) => void removeFailureFromCache?: (database?: string) => void auth?: AuthToken - homeDbTable?: any + homeDb?: any }): Promise { throw Error('Not implemented') } diff --git a/packages/core/src/driver.ts b/packages/core/src/driver.ts index 1a9ce7968..9657417cc 100644 --- a/packages/core/src/driver.ts +++ b/packages/core/src/driver.ts @@ -97,7 +97,7 @@ type CreateSession = (args: { notificationFilter?: NotificationFilter auth?: AuthToken log: Logger - homeDatabaseTableCallback?: (user: string, table: any) => void + homeDatabaseCallback?: (user: string, database: any) => void committedDbCallback?: (user: string) => void removeFailureFromCache?: (database: string) => void }) => Session @@ -844,19 +844,19 @@ class Driver { ) } - _homeDatabaseTableCallback (user: string, table: any): void { + _homeDatabaseCallback (user: string, table: any): void { this.homeDatabaseCache.set(user, table) } _committedDbCallback (database: string, user: string): void { - if (this.homeDatabaseCache.get(user) !== undefined && this.homeDatabaseCache.get(user).database !== database) { + if (this.homeDatabaseCache.get(user) !== undefined && this.homeDatabaseCache.get(user) !== database) { this.homeDatabaseCache.delete(user) } } _removeFailureFromCache (database: string): void { this.homeDatabaseCache.forEach((_, key) => { - if (this.homeDatabaseCache.get(key).database === database) { + if (this.homeDatabaseCache.get(key) === database) { this.homeDatabaseCache.delete(key) } }) @@ -889,8 +889,8 @@ class Driver { const sessionMode = Session._validateSessionMode(defaultAccessMode) const connectionProvider = this._getOrCreateConnectionProvider() const cachedUser = impersonatedUser ?? auth?.principal ?? this._config.user ?? '' - const cachedHomeDatabaseRoutingTable = this.homeDatabaseCache.get(cachedUser ?? '') - const homeDatabaseTableCallback = this._homeDatabaseTableCallback.bind(this) + const cachedHomeDatabase = this.homeDatabaseCache.get(cachedUser) + const homeDatabaseCallback = this._homeDatabaseCallback.bind(this) const committedDbCallback = this._committedDbCallback.bind(this) const removeFailureFromCache = this._removeFailureFromCache.bind(this) const bookmarks = bookmarkOrBookmarks != null @@ -902,7 +902,7 @@ class Driver { connectionProvider, bookmarks, config: { - cachedHomeDatabaseRoutingTable, + cachedHomeDatabase, cachedUser, ...this._config }, @@ -913,7 +913,7 @@ class Driver { notificationFilter, auth, log: this._log, - homeDatabaseTableCallback, + homeDatabaseCallback, committedDbCallback, removeFailureFromCache }) diff --git a/packages/core/src/internal/connection-holder.ts b/packages/core/src/internal/connection-holder.ts index 501d921a8..46f59a09e 100644 --- a/packages/core/src/internal/connection-holder.ts +++ b/packages/core/src/internal/connection-holder.ts @@ -177,7 +177,7 @@ class ConnectionHolder implements ConnectionHolderInterface { return true } - private async _createConnectionPromise (connectionProvider: ConnectionProvider, homeDatabaseTable?: any): Promise { + private async _createConnectionPromise (connectionProvider: ConnectionProvider, homeDatabase?: string): Promise { return await connectionProvider.acquireConnection({ accessMode: this._mode, database: this._database ?? '', @@ -186,7 +186,7 @@ class ConnectionHolder implements ConnectionHolderInterface { onDatabaseNameResolved: this._onDatabaseNameResolved, removeFailureFromCache: this.removeFailureFromCache, auth: this._auth, - homeDbTable: homeDatabaseTable + homeDb: homeDatabase }) } diff --git a/packages/core/src/session.ts b/packages/core/src/session.ts index 16c9a95f7..8aae17e33 100644 --- a/packages/core/src/session.ts +++ b/packages/core/src/session.ts @@ -48,7 +48,7 @@ interface TransactionConfig { } interface DbGuess { - routingTable: any + database: any user: string } @@ -79,7 +79,7 @@ class Session { private readonly _bookmarkManager?: BookmarkManager private readonly _notificationFilter?: NotificationFilter private readonly _log: Logger - private readonly _homeDatabaseTableCallback: Function | undefined + private readonly _homeDatabaseCallback: Function | undefined private readonly _driverCommittedDbCallback: Function | undefined private readonly _auth: AuthToken | undefined private _databaseGuess: DbGuess | undefined @@ -111,7 +111,7 @@ class Session { notificationFilter, auth, log, - homeDatabaseTableCallback, + homeDatabaseCallback, removeFailureFromCache, committedDbCallback }: { @@ -127,7 +127,7 @@ class Session { notificationFilter?: NotificationFilter auth?: AuthToken log: Logger - homeDatabaseTableCallback?: (user: string, table: any) => void + homeDatabaseCallback?: (user: string, database: any) => void removeFailureFromCache?: (database: string) => void committedDbCallback?: (user: string, database: string) => void }) { @@ -135,10 +135,10 @@ class Session { this._database = database this._reactive = reactive this._fetchSize = fetchSize - this._homeDatabaseTableCallback = homeDatabaseTableCallback + this._homeDatabaseCallback = homeDatabaseCallback this._driverCommittedDbCallback = committedDbCallback - if (config?.cachedUser !== undefined && config?.cachedHomeDatabaseRoutingTable !== undefined) { - this._databaseGuess = { user: config?.cachedUser, routingTable: config?.cachedHomeDatabaseRoutingTable } + if (config?.cachedUser !== undefined && config?.cachedHomeDatabase !== undefined) { + this._databaseGuess = { user: config?.cachedUser, database: config?.cachedHomeDatabase } } this._auth = auth this._getConnectionAcquistionBookmarks = this._getConnectionAcquistionBookmarks.bind(this) @@ -275,7 +275,7 @@ class Session { resultPromise = Promise.reject( newError('Cannot run query in a closed session.') ) - } else if (!this._hasTx && connectionHolder.initializeConnection(this._databaseGuess?.routingTable)) { + } else if (!this._hasTx && connectionHolder.initializeConnection(this._databaseGuess?.database)) { resultPromise = connectionHolder .getConnection() // Connection won't be null at this point since the initialize method @@ -331,7 +331,7 @@ class Session { const mode = Session._validateSessionMode(accessMode) const connectionHolder = this._connectionHolderWithMode(mode) - connectionHolder.initializeConnection(this._databaseGuess?.routingTable) + connectionHolder.initializeConnection(this._databaseGuess?.database) this._hasTx = true const tx = new TransactionPromise({ @@ -528,10 +528,10 @@ class Session { * @param {string|undefined} database The resolved database name * @returns {void} */ - _onDatabaseNameResolved (database?: string, user?: string, table?: any): void { - this._databaseGuess = { user: this._impersonatedUser ?? this._auth?.principal ?? user ?? '', routingTable: table } - if (this._homeDatabaseTableCallback != null) { - this._homeDatabaseTableCallback(this._impersonatedUser ?? this._auth?.principal ?? user, table) + _onDatabaseNameResolved (database?: string, user?: string): void { + this._databaseGuess = { user: this._impersonatedUser ?? this._auth?.principal ?? user ?? '', database } + if (this._homeDatabaseCallback != null) { + this._homeDatabaseCallback(this._impersonatedUser ?? this._auth?.principal ?? user, database) } if (!this._databaseNameResolved) { const normalizedDatabase = database ?? '' diff --git a/packages/neo4j-driver-deno/lib/bolt-connection/connection-provider/connection-provider-routing.js b/packages/neo4j-driver-deno/lib/bolt-connection/connection-provider/connection-provider-routing.js index c25d8431a..b381c4258 100644 --- a/packages/neo4j-driver-deno/lib/bolt-connection/connection-provider/connection-provider-routing.js +++ b/packages/neo4j-driver-deno/lib/bolt-connection/connection-provider/connection-provider-routing.js @@ -143,24 +143,30 @@ export default class RoutingConnectionProvider extends PooledConnectionProvider * See {@link ConnectionProvider} for more information about this method and * its arguments. */ - async acquireConnection ({ accessMode, database, bookmarks, impersonatedUser, onDatabaseNameResolved, removeFailureFromCache, auth, homeDbTable } = {}) { + async acquireConnection ({ accessMode, database, bookmarks, impersonatedUser, onDatabaseNameResolved, removeFailureFromCache, auth, homeDb } = {}) { let name let address - let runResolved = false const context = { database: database || DEFAULT_DB_NAME } const databaseSpecificErrorHandler = new ConnectionErrorHandler( SESSION_EXPIRED, (error, address) => this._handleUnavailability(error, address, context.database), (error, address) => { - removeFailureFromCache(homeDbTable?.database ?? context.database) - return this._handleWriteFailure(error, address, homeDbTable?.database ?? context.database) + removeFailureFromCache(homeDb ?? context.database) + return this._handleWriteFailure(error, address, homeDb ?? context.database) }, (error, address, conn) => this._handleSecurityError(error, address, conn, context.database) ) - const routingTable = (homeDbTable && !homeDbTable.isStaleFor(accessMode) && this._SSREnabled) - ? homeDbTable + let currentRoutingTable + if (this._SSREnabled() && homeDb !== undefined) { + currentRoutingTable = this._routingTableRegistry.get( + homeDb, + () => new RoutingTable({ database: homeDb }) + ) + } + const routingTable = (currentRoutingTable && !currentRoutingTable.isStaleFor(accessMode)) + ? currentRoutingTable : await this._freshRoutingTable({ accessMode, database: context.database, @@ -170,15 +176,11 @@ export default class RoutingConnectionProvider extends PooledConnectionProvider onDatabaseNameResolved: (databaseName) => { context.database = context.database || databaseName if (onDatabaseNameResolved) { - runResolved = true + onDatabaseNameResolved(context.database, this._authenticationProvider?._authTokenManager?._authToken?.principal) } } }) - if (runResolved) { - onDatabaseNameResolved(context.database, this._authenticationProvider?._authTokenManager?._authToken?.principal, routingTable) - } - // select a target server based on specified access mode if (accessMode === READ) { address = this._loadBalancingStrategy.selectReader(routingTable.readers) @@ -360,7 +362,6 @@ export default class RoutingConnectionProvider extends PooledConnectionProvider database, () => new RoutingTable({ database }) ) - if (!currentRoutingTable.isStaleFor(accessMode)) { return currentRoutingTable } diff --git a/packages/neo4j-driver-deno/lib/core/connection-provider.ts b/packages/neo4j-driver-deno/lib/core/connection-provider.ts index 496ee5571..de95b9264 100644 --- a/packages/neo4j-driver-deno/lib/core/connection-provider.ts +++ b/packages/neo4j-driver-deno/lib/core/connection-provider.ts @@ -68,7 +68,7 @@ class ConnectionProvider { onDatabaseNameResolved?: (database?: string) => void removeFailureFromCache?: (database?: string) => void auth?: AuthToken - homeDbTable?: any + homeDb?: any }): Promise { throw Error('Not implemented') } diff --git a/packages/neo4j-driver-deno/lib/core/driver.ts b/packages/neo4j-driver-deno/lib/core/driver.ts index ab10d6a2a..1159f8d5b 100644 --- a/packages/neo4j-driver-deno/lib/core/driver.ts +++ b/packages/neo4j-driver-deno/lib/core/driver.ts @@ -97,7 +97,7 @@ type CreateSession = (args: { notificationFilter?: NotificationFilter auth?: AuthToken log: Logger - homeDatabaseTableCallback?: (user: string, table: any) => void + homeDatabaseCallback?: (user: string, database: any) => void committedDbCallback?: (user: string) => void removeFailureFromCache?: (database: string) => void }) => Session @@ -844,19 +844,19 @@ class Driver { ) } - _homeDatabaseTableCallback (user: string, table: any): void { + _homeDatabaseCallback (user: string, table: any): void { this.homeDatabaseCache.set(user, table) } _committedDbCallback (database: string, user: string): void { - if (this.homeDatabaseCache.get(user) !== undefined && this.homeDatabaseCache.get(user).database !== database) { + if (this.homeDatabaseCache.get(user) !== undefined && this.homeDatabaseCache.get(user) !== database) { this.homeDatabaseCache.delete(user) } } _removeFailureFromCache (database: string): void { this.homeDatabaseCache.forEach((_, key) => { - if (this.homeDatabaseCache.get(key).database === database) { + if (this.homeDatabaseCache.get(key) === database) { this.homeDatabaseCache.delete(key) } }) @@ -889,8 +889,8 @@ class Driver { const sessionMode = Session._validateSessionMode(defaultAccessMode) const connectionProvider = this._getOrCreateConnectionProvider() const cachedUser = impersonatedUser ?? auth?.principal ?? this._config.user ?? '' - const cachedHomeDatabaseRoutingTable = this.homeDatabaseCache.get(cachedUser ?? '') - const homeDatabaseTableCallback = this._homeDatabaseTableCallback.bind(this) + const cachedHomeDatabase = this.homeDatabaseCache.get(cachedUser) + const homeDatabaseCallback = this._homeDatabaseCallback.bind(this) const committedDbCallback = this._committedDbCallback.bind(this) const removeFailureFromCache = this._removeFailureFromCache.bind(this) const bookmarks = bookmarkOrBookmarks != null @@ -902,7 +902,7 @@ class Driver { connectionProvider, bookmarks, config: { - cachedHomeDatabaseRoutingTable, + cachedHomeDatabase, cachedUser, ...this._config }, @@ -913,7 +913,7 @@ class Driver { notificationFilter, auth, log: this._log, - homeDatabaseTableCallback, + homeDatabaseCallback, committedDbCallback, removeFailureFromCache }) diff --git a/packages/neo4j-driver-deno/lib/core/internal/connection-holder.ts b/packages/neo4j-driver-deno/lib/core/internal/connection-holder.ts index 735cfb163..476e11c3f 100644 --- a/packages/neo4j-driver-deno/lib/core/internal/connection-holder.ts +++ b/packages/neo4j-driver-deno/lib/core/internal/connection-holder.ts @@ -177,7 +177,7 @@ class ConnectionHolder implements ConnectionHolderInterface { return true } - private async _createConnectionPromise (connectionProvider: ConnectionProvider, homeDatabaseTable?: any): Promise { + private async _createConnectionPromise (connectionProvider: ConnectionProvider, homeDatabase?: string): Promise { return await connectionProvider.acquireConnection({ accessMode: this._mode, database: this._database ?? '', @@ -186,7 +186,7 @@ class ConnectionHolder implements ConnectionHolderInterface { onDatabaseNameResolved: this._onDatabaseNameResolved, removeFailureFromCache: this.removeFailureFromCache, auth: this._auth, - homeDbTable: homeDatabaseTable + homeDb: homeDatabase }) } diff --git a/packages/neo4j-driver-deno/lib/core/session.ts b/packages/neo4j-driver-deno/lib/core/session.ts index 06e3c7108..d7476be9b 100644 --- a/packages/neo4j-driver-deno/lib/core/session.ts +++ b/packages/neo4j-driver-deno/lib/core/session.ts @@ -48,7 +48,7 @@ interface TransactionConfig { } interface DbGuess { - routingTable: any + database: any user: string } @@ -79,7 +79,7 @@ class Session { private readonly _bookmarkManager?: BookmarkManager private readonly _notificationFilter?: NotificationFilter private readonly _log: Logger - private readonly _homeDatabaseTableCallback: Function | undefined + private readonly _homeDatabaseCallback: Function | undefined private readonly _driverCommittedDbCallback: Function | undefined private readonly _auth: AuthToken | undefined private _databaseGuess: DbGuess | undefined @@ -111,7 +111,7 @@ class Session { notificationFilter, auth, log, - homeDatabaseTableCallback, + homeDatabaseCallback, removeFailureFromCache, committedDbCallback }: { @@ -127,7 +127,7 @@ class Session { notificationFilter?: NotificationFilter auth?: AuthToken log: Logger - homeDatabaseTableCallback?: (user: string, table: any) => void + homeDatabaseCallback?: (user: string, database: any) => void removeFailureFromCache?: (database: string) => void committedDbCallback?: (user: string, database: string) => void }) { @@ -135,10 +135,10 @@ class Session { this._database = database this._reactive = reactive this._fetchSize = fetchSize - this._homeDatabaseTableCallback = homeDatabaseTableCallback + this._homeDatabaseCallback = homeDatabaseCallback this._driverCommittedDbCallback = committedDbCallback - if (config?.cachedUser !== undefined && config?.cachedHomeDatabaseRoutingTable !== undefined) { - this._databaseGuess = { user: config?.cachedUser, routingTable: config?.cachedHomeDatabaseRoutingTable } + if (config?.cachedUser !== undefined && config?.cachedHomeDatabase !== undefined) { + this._databaseGuess = { user: config?.cachedUser, database: config?.cachedHomeDatabase } } this._auth = auth this._getConnectionAcquistionBookmarks = this._getConnectionAcquistionBookmarks.bind(this) @@ -275,7 +275,7 @@ class Session { resultPromise = Promise.reject( newError('Cannot run query in a closed session.') ) - } else if (!this._hasTx && connectionHolder.initializeConnection(this._databaseGuess?.routingTable)) { + } else if (!this._hasTx && connectionHolder.initializeConnection(this._databaseGuess?.database)) { resultPromise = connectionHolder .getConnection() // Connection won't be null at this point since the initialize method @@ -331,7 +331,7 @@ class Session { const mode = Session._validateSessionMode(accessMode) const connectionHolder = this._connectionHolderWithMode(mode) - connectionHolder.initializeConnection(this._databaseGuess?.routingTable) + connectionHolder.initializeConnection(this._databaseGuess?.database) this._hasTx = true const tx = new TransactionPromise({ @@ -528,10 +528,10 @@ class Session { * @param {string|undefined} database The resolved database name * @returns {void} */ - _onDatabaseNameResolved (database?: string, user?: string, table?: any): void { - this._databaseGuess = { user: this._impersonatedUser ?? this._auth?.principal ?? user ?? '', routingTable: table } - if (this._homeDatabaseTableCallback != null) { - this._homeDatabaseTableCallback(this._impersonatedUser ?? this._auth?.principal ?? user, table) + _onDatabaseNameResolved (database?: string, user?: string): void { + this._databaseGuess = { user: this._impersonatedUser ?? this._auth?.principal ?? user ?? '', database } + if (this._homeDatabaseCallback != null) { + this._homeDatabaseCallback(this._impersonatedUser ?? this._auth?.principal ?? user, database) } if (!this._databaseNameResolved) { const normalizedDatabase = database ?? '' From 458a493de921204f17b5391aba2f8cf15daad0c7 Mon Sep 17 00:00:00 2001 From: MaxAake <61233757+MaxAake@users.noreply.github.com> Date: Fri, 1 Nov 2024 15:16:10 +0100 Subject: [PATCH 16/84] bolt 5.8 plus reworks --- .../src/bolt/bolt-protocol-v5x8.js | 39 +++++++++++++++++++ .../bolt/bolt-protocol-v5x8.transformer.js | 22 +++++++++++ packages/bolt-connection/src/bolt/create.js | 9 +++++ .../bolt-connection/src/bolt/handshake.js | 2 +- .../connection-provider-routing.js | 6 +-- .../src/connection/connection-channel.js | 10 ++--- packages/core/src/driver.ts | 4 +- packages/core/src/internal/constants.ts | 2 + .../core/src/internal/transaction-executor.ts | 7 +--- packages/core/src/session.ts | 15 +++---- packages/core/src/transaction-promise.ts | 9 ++++- packages/core/src/transaction.ts | 14 +++---- .../bolt/bolt-protocol-v5x8.js | 39 +++++++++++++++++++ .../bolt/bolt-protocol-v5x8.transformer.js | 22 +++++++++++ .../lib/bolt-connection/bolt/create.js | 9 +++++ .../lib/bolt-connection/bolt/handshake.js | 2 +- .../connection-provider-routing.js | 6 +-- .../connection/connection-channel.js | 10 ++--- packages/neo4j-driver-deno/lib/core/driver.ts | 4 +- .../lib/core/internal/constants.ts | 2 + .../lib/core/internal/transaction-executor.ts | 7 +--- .../neo4j-driver-deno/lib/core/session.ts | 15 +++---- .../lib/core/transaction-promise.ts | 9 ++++- .../neo4j-driver-deno/lib/core/transaction.ts | 14 +++---- 24 files changed, 212 insertions(+), 66 deletions(-) create mode 100644 packages/bolt-connection/src/bolt/bolt-protocol-v5x8.js create mode 100644 packages/bolt-connection/src/bolt/bolt-protocol-v5x8.transformer.js create mode 100644 packages/neo4j-driver-deno/lib/bolt-connection/bolt/bolt-protocol-v5x8.js create mode 100644 packages/neo4j-driver-deno/lib/bolt-connection/bolt/bolt-protocol-v5x8.transformer.js diff --git a/packages/bolt-connection/src/bolt/bolt-protocol-v5x8.js b/packages/bolt-connection/src/bolt/bolt-protocol-v5x8.js new file mode 100644 index 000000000..5c17e6952 --- /dev/null +++ b/packages/bolt-connection/src/bolt/bolt-protocol-v5x8.js @@ -0,0 +1,39 @@ +/** + * Copyright (c) "Neo4j" + * Neo4j Sweden AB [https://neo4j.com] + * + * 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 BoltProtocolV5x7 from './bolt-protocol-v5x7' + +import transformersFactories from './bolt-protocol-v5x5.transformer' +import Transformer from './transformer' + +import { internal } from 'neo4j-driver-core' + +const { + constants: { BOLT_PROTOCOL_V5_8 } +} = internal + +export default class BoltProtocol extends BoltProtocolV5x7 { + get version () { + return BOLT_PROTOCOL_V5_8 + } + + get transformer () { + if (this._transformer === undefined) { + this._transformer = new Transformer(Object.values(transformersFactories).map(create => create(this._config, this._log))) + } + return this._transformer + } +} diff --git a/packages/bolt-connection/src/bolt/bolt-protocol-v5x8.transformer.js b/packages/bolt-connection/src/bolt/bolt-protocol-v5x8.transformer.js new file mode 100644 index 000000000..a4e4d0957 --- /dev/null +++ b/packages/bolt-connection/src/bolt/bolt-protocol-v5x8.transformer.js @@ -0,0 +1,22 @@ +/** + * Copyright (c) "Neo4j" + * Neo4j Sweden AB [https://neo4j.com] + * + * 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 v5x7 from './bolt-protocol-v5x6.transformer' + +export default { + ...v5x7 +} diff --git a/packages/bolt-connection/src/bolt/create.js b/packages/bolt-connection/src/bolt/create.js index fe204b612..1b8f792a5 100644 --- a/packages/bolt-connection/src/bolt/create.js +++ b/packages/bolt-connection/src/bolt/create.js @@ -32,6 +32,7 @@ import BoltProtocolV5x4 from './bolt-protocol-v5x4' import BoltProtocolV5x5 from './bolt-protocol-v5x5' import BoltProtocolV5x6 from './bolt-protocol-v5x6' import BoltProtocolV5x7 from './bolt-protocol-v5x7' +import BoltProtocolV5x8 from './bolt-protocol-v5x8' // eslint-disable-next-line no-unused-vars import { Chunker, Dechunker } from '../channel' import ResponseHandler from './response-handler' @@ -257,6 +258,14 @@ function createProtocol ( log, onProtocolError, serversideRouting) + case 5.8: + return new BoltProtocolV5x8(server, + chunker, + packingConfig, + createResponseHandler, + log, + onProtocolError, + serversideRouting) default: throw newError('Unknown Bolt protocol version: ' + version) } diff --git a/packages/bolt-connection/src/bolt/handshake.js b/packages/bolt-connection/src/bolt/handshake.js index abf318025..58250e2af 100644 --- a/packages/bolt-connection/src/bolt/handshake.js +++ b/packages/bolt-connection/src/bolt/handshake.js @@ -76,7 +76,7 @@ function parseNegotiatedResponse (buffer, log) { */ function newHandshakeBuffer () { return createHandshakeMessage([ - [version(5, 7), version(5, 0)], + [version(5, 8), version(5, 0)], [version(4, 4), version(4, 2)], version(4, 1), version(3, 0) diff --git a/packages/bolt-connection/src/connection-provider/connection-provider-routing.js b/packages/bolt-connection/src/connection-provider/connection-provider-routing.js index b59ebbd35..43c06d195 100644 --- a/packages/bolt-connection/src/connection-provider/connection-provider-routing.js +++ b/packages/bolt-connection/src/connection-provider/connection-provider-routing.js @@ -176,7 +176,7 @@ export default class RoutingConnectionProvider extends PooledConnectionProvider onDatabaseNameResolved: (databaseName) => { context.database = context.database || databaseName if (onDatabaseNameResolved) { - onDatabaseNameResolved(context.database, this._authenticationProvider?._authTokenManager?._authToken?.principal) + onDatabaseNameResolved(databaseName, this._authenticationProvider?._authTokenManager?._authToken?.principal) } } }) @@ -679,9 +679,9 @@ export default class RoutingConnectionProvider extends PooledConnectionProvider } _channelSsrCallback (isEnabled, opened) { - if (isEnabled) { + if (isEnabled === true) { this._withSSR = this._withSSR + (opened ? 1 : -1) - } else { + } else if (isEnabled === false) { this._withoutSSR = this._withoutSSR + (opened ? 1 : -1) } } diff --git a/packages/bolt-connection/src/connection/connection-channel.js b/packages/bolt-connection/src/connection/connection-channel.js index 18aefc4e8..de9c38095 100644 --- a/packages/bolt-connection/src/connection/connection-channel.js +++ b/packages/bolt-connection/src/connection/connection-channel.js @@ -336,15 +336,13 @@ export default class ChannelConnection extends Connection { this._telemetryDisabledConnection = false } - const SSREnabledHint = metadata.hints['ssr.enabled'] - if (this.serversideRouting !== SSREnabledHint) { - this._ssrCallback(SSREnabledHint, true) - } - if (SSREnabledHint === true) { + const SSREnabledHint = true// metadata.hints['ssr.enabled'] + if (SSREnabledHint) { this.serversideRouting = true - } else if (SSREnabledHint === false) { + } else { this.serversideRouting = false } + this._ssrCallback(this.serversideRouting, true) } } resolve(self) diff --git a/packages/core/src/driver.ts b/packages/core/src/driver.ts index 9657417cc..690792aea 100644 --- a/packages/core/src/driver.ts +++ b/packages/core/src/driver.ts @@ -844,8 +844,8 @@ class Driver { ) } - _homeDatabaseCallback (user: string, table: any): void { - this.homeDatabaseCache.set(user, table) + _homeDatabaseCallback (user: string, database: any): void { + this.homeDatabaseCache.set(user, database) } _committedDbCallback (database: string, user: string): void { diff --git a/packages/core/src/internal/constants.ts b/packages/core/src/internal/constants.ts index ed32dcef5..b45034da9 100644 --- a/packages/core/src/internal/constants.ts +++ b/packages/core/src/internal/constants.ts @@ -39,6 +39,7 @@ const BOLT_PROTOCOL_V5_4: number = 5.4 const BOLT_PROTOCOL_V5_5: number = 5.5 const BOLT_PROTOCOL_V5_6: number = 5.6 const BOLT_PROTOCOL_V5_7: number = 5.7 +const BOLT_PROTOCOL_V5_8: number = 5.8 const TELEMETRY_APIS = { MANAGED_TRANSACTION: 0, @@ -74,5 +75,6 @@ export { BOLT_PROTOCOL_V5_5, BOLT_PROTOCOL_V5_6, BOLT_PROTOCOL_V5_7, + BOLT_PROTOCOL_V5_8, TELEMETRY_APIS } diff --git a/packages/core/src/internal/transaction-executor.ts b/packages/core/src/internal/transaction-executor.ts index b8f95cb2f..11a055e78 100644 --- a/packages/core/src/internal/transaction-executor.ts +++ b/packages/core/src/internal/transaction-executor.ts @@ -58,7 +58,6 @@ export class TransactionExecutor { private readonly _clearTimeout: ClearTimeout public telemetryApi: NonAutoCommitTelemetryApis public pipelineBegin: boolean - public committedDbCallback: any constructor ( maxRetryTimeMs?: number | null, @@ -68,8 +67,7 @@ export class TransactionExecutor { dependencies: Dependencies = { setTimeout: setTimeoutWrapper, clearTimeout: clearTimeoutWrapper - }, - committedDbCallback?: any + } ) { this._maxRetryTimeMs = _valueOrDefault( maxRetryTimeMs, @@ -90,7 +88,6 @@ export class TransactionExecutor { this._setTimeout = dependencies.setTimeout this._clearTimeout = dependencies.clearTimeout - this.committedDbCallback = committedDbCallback this._inFlightTimeoutIds = [] this.pipelineBegin = false this.telemetryApi = TELEMETRY_APIS.MANAGED_TRANSACTION @@ -249,7 +246,7 @@ export class TransactionExecutor { if (tx.isOpen()) { // transaction work returned resolved promise and transaction has not been committed/rolled back // try to commit the transaction - tx.commit(this.committedDbCallback) + tx.commit() .then(() => { // transaction was committed, return result to the user resolve(result) diff --git a/packages/core/src/session.ts b/packages/core/src/session.ts index 8aae17e33..04aca3811 100644 --- a/packages/core/src/session.ts +++ b/packages/core/src/session.ts @@ -137,9 +137,6 @@ class Session { this._fetchSize = fetchSize this._homeDatabaseCallback = homeDatabaseCallback this._driverCommittedDbCallback = committedDbCallback - if (config?.cachedUser !== undefined && config?.cachedHomeDatabase !== undefined) { - this._databaseGuess = { user: config?.cachedUser, database: config?.cachedHomeDatabase } - } this._auth = auth this._getConnectionAcquistionBookmarks = this._getConnectionAcquistionBookmarks.bind(this) this._readConnectionHolder = new ConnectionHolder({ @@ -161,7 +158,7 @@ class Session { bookmarks, connectionProvider, impersonatedUser, - onDatabaseNameResolved: this._onDatabaseNameResolved, + onDatabaseNameResolved: this._onDatabaseNameResolved.bind(this), getConnectionAcquistionBookmarks: this._getConnectionAcquistionBookmarks, log }) @@ -170,7 +167,7 @@ class Session { this._impersonatedUser = impersonatedUser this._lastBookmarks = bookmarks ?? Bookmarks.empty() this._configuredBookmarks = this._lastBookmarks - this._transactionExecutor = _createTransactionExecutor({ ...config, commitCallback: this._committedDbCallback.bind(this) }) + this._transactionExecutor = _createTransactionExecutor(config) this._databaseNameResolved = this._database !== '' const calculatedWatermaks = this._calculateWatermaks() this._lowRecordWatermark = calculatedWatermaks.low @@ -179,6 +176,9 @@ class Session { this._bookmarkManager = bookmarkManager this._notificationFilter = notificationFilter this._log = log + if (config?.cachedUser !== undefined && config?.cachedHomeDatabase !== undefined) { + this._databaseGuess = { user: config?.cachedUser, database: config?.cachedHomeDatabase } + } } /** @@ -345,7 +345,8 @@ class Session { lowRecordWatermark: this._lowRecordWatermark, highRecordWatermark: this._highRecordWatermark, notificationFilter: this._notificationFilter, - apiTelemetryConfig + apiTelemetryConfig, + onDbCallback: this._committedDbCallback.bind(this) }) tx._begin(() => this._bookmarks(), txConfig) return tx @@ -666,7 +667,7 @@ function _createTransactionExecutor (config?: { commitCallback: any }): TransactionExecutor { const maxRetryTimeMs = config?.maxTransactionRetryTime ?? null - return new TransactionExecutor(maxRetryTimeMs, undefined, undefined, undefined, undefined, config?.commitCallback) + return new TransactionExecutor(maxRetryTimeMs) } export default Session diff --git a/packages/core/src/transaction-promise.ts b/packages/core/src/transaction-promise.ts index af184fa70..6583163fc 100644 --- a/packages/core/src/transaction-promise.ts +++ b/packages/core/src/transaction-promise.ts @@ -43,6 +43,7 @@ class TransactionPromise extends Transaction implements Promise { private _beginPromise?: Promise private _reject?: (error: Error) => void private _resolve?: (value: Transaction | PromiseLike) => void + private readonly _onDbCallback: (database: string) => void /** * @constructor @@ -69,7 +70,8 @@ class TransactionPromise extends Transaction implements Promise { highRecordWatermark, lowRecordWatermark, notificationFilter, - apiTelemetryConfig + apiTelemetryConfig, + onDbCallback }: { connectionHolder: ConnectionHolder onClose: () => void @@ -82,6 +84,7 @@ class TransactionPromise extends Transaction implements Promise { lowRecordWatermark: number notificationFilter?: NotificationFilter apiTelemetryConfig?: NonAutoCommitApiTelemetryConfig + onDbCallback: (database: string) => void }) { super({ connectionHolder, @@ -96,6 +99,7 @@ class TransactionPromise extends Transaction implements Promise { notificationFilter, apiTelemetryConfig }) + this._onDbCallback = onDbCallback } /** @@ -174,7 +178,8 @@ class TransactionPromise extends Transaction implements Promise { _begin (bookmarks: () => Promise, txConfig: TxConfig): void { return super._begin(bookmarks, txConfig, { onError: this._onBeginError.bind(this), - onComplete: this._onBeginMetadata.bind(this) + onComplete: this._onBeginMetadata.bind(this), + onDB: this._onDbCallback }) } diff --git a/packages/core/src/transaction.ts b/packages/core/src/transaction.ts index 4c4e0fbfa..85f1331d9 100644 --- a/packages/core/src/transaction.ts +++ b/packages/core/src/transaction.ts @@ -140,6 +140,7 @@ class Transaction { _begin (getBookmarks: () => Promise, txConfig: TxConfig, events?: { onError: (error: Error) => void onComplete: (metadata: any) => void + onDB: (database: string) => void }): void { this._connectionHolder .getConnection() @@ -151,6 +152,7 @@ class Transaction { bookmarks: this._bookmarks, txConfig, mode: this._connectionHolder.mode(), + database: this._connectionHolder.database(), impersonatedUser: this._impersonatedUser, notificationFilter: this._notificationFilter, apiTelemetryConfig: this._apiTelemetryConfig, @@ -164,6 +166,9 @@ class Transaction { if (events != null) { events.onComplete(metadata) } + if (metadata.db !== undefined && ((events?.onDB) != null)) { + events.onDB(metadata.db) + } this._onComplete(metadata) } }) @@ -219,7 +224,7 @@ class Transaction { * * @returns {Promise} An empty promise if committed successfully or error if any error happened during commit. */ - commit (committedDbCallback?: any): Promise { + commit (): Promise { const committed = this._state.commit({ connectionHolder: this._connectionHolder, onError: this._onError, @@ -233,12 +238,7 @@ class Transaction { this._onClose() return new Promise((resolve, reject) => { committed.result.subscribe({ - onCompleted: (result: any) => { - if (committedDbCallback !== undefined) { - committedDbCallback(result.database.name) - } - resolve() - }, + onCompleted: () => resolve(), onError: (error: any) => reject(error) }) }) diff --git a/packages/neo4j-driver-deno/lib/bolt-connection/bolt/bolt-protocol-v5x8.js b/packages/neo4j-driver-deno/lib/bolt-connection/bolt/bolt-protocol-v5x8.js new file mode 100644 index 000000000..2ae1186a0 --- /dev/null +++ b/packages/neo4j-driver-deno/lib/bolt-connection/bolt/bolt-protocol-v5x8.js @@ -0,0 +1,39 @@ +/** + * Copyright (c) "Neo4j" + * Neo4j Sweden AB [https://neo4j.com] + * + * 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 BoltProtocolV5x7 from './bolt-protocol-v5x7.js' + +import transformersFactories from './bolt-protocol-v5x5.transformer.js' +import Transformer from './transformer.js' + +import { internal } from '../../core/index.ts' + +const { + constants: { BOLT_PROTOCOL_V5_8 } +} = internal + +export default class BoltProtocol extends BoltProtocolV5x7 { + get version () { + return BOLT_PROTOCOL_V5_8 + } + + get transformer () { + if (this._transformer === undefined) { + this._transformer = new Transformer(Object.values(transformersFactories).map(create => create(this._config, this._log))) + } + return this._transformer + } +} diff --git a/packages/neo4j-driver-deno/lib/bolt-connection/bolt/bolt-protocol-v5x8.transformer.js b/packages/neo4j-driver-deno/lib/bolt-connection/bolt/bolt-protocol-v5x8.transformer.js new file mode 100644 index 000000000..e151c7523 --- /dev/null +++ b/packages/neo4j-driver-deno/lib/bolt-connection/bolt/bolt-protocol-v5x8.transformer.js @@ -0,0 +1,22 @@ +/** + * Copyright (c) "Neo4j" + * Neo4j Sweden AB [https://neo4j.com] + * + * 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 v5x7 from './bolt-protocol-v5x6.transformer.js' + +export default { + ...v5x7 +} diff --git a/packages/neo4j-driver-deno/lib/bolt-connection/bolt/create.js b/packages/neo4j-driver-deno/lib/bolt-connection/bolt/create.js index 7a0fe2165..188e8c6bd 100644 --- a/packages/neo4j-driver-deno/lib/bolt-connection/bolt/create.js +++ b/packages/neo4j-driver-deno/lib/bolt-connection/bolt/create.js @@ -32,6 +32,7 @@ import BoltProtocolV5x4 from './bolt-protocol-v5x4.js' import BoltProtocolV5x5 from './bolt-protocol-v5x5.js' import BoltProtocolV5x6 from './bolt-protocol-v5x6.js' import BoltProtocolV5x7 from './bolt-protocol-v5x7.js' +import BoltProtocolV5x8 from './bolt-protocol-v5x8.js' // eslint-disable-next-line no-unused-vars import { Chunker, Dechunker } from '../channel/index.js' import ResponseHandler from './response-handler.js' @@ -257,6 +258,14 @@ function createProtocol ( log, onProtocolError, serversideRouting) + case 5.8: + return new BoltProtocolV5x8(server, + chunker, + packingConfig, + createResponseHandler, + log, + onProtocolError, + serversideRouting) default: throw newError('Unknown Bolt protocol version: ' + version) } diff --git a/packages/neo4j-driver-deno/lib/bolt-connection/bolt/handshake.js b/packages/neo4j-driver-deno/lib/bolt-connection/bolt/handshake.js index c91e0e18f..caff6d596 100644 --- a/packages/neo4j-driver-deno/lib/bolt-connection/bolt/handshake.js +++ b/packages/neo4j-driver-deno/lib/bolt-connection/bolt/handshake.js @@ -76,7 +76,7 @@ function parseNegotiatedResponse (buffer, log) { */ function newHandshakeBuffer () { return createHandshakeMessage([ - [version(5, 7), version(5, 0)], + [version(5, 8), version(5, 0)], [version(4, 4), version(4, 2)], version(4, 1), version(3, 0) diff --git a/packages/neo4j-driver-deno/lib/bolt-connection/connection-provider/connection-provider-routing.js b/packages/neo4j-driver-deno/lib/bolt-connection/connection-provider/connection-provider-routing.js index b381c4258..2f266e1b4 100644 --- a/packages/neo4j-driver-deno/lib/bolt-connection/connection-provider/connection-provider-routing.js +++ b/packages/neo4j-driver-deno/lib/bolt-connection/connection-provider/connection-provider-routing.js @@ -176,7 +176,7 @@ export default class RoutingConnectionProvider extends PooledConnectionProvider onDatabaseNameResolved: (databaseName) => { context.database = context.database || databaseName if (onDatabaseNameResolved) { - onDatabaseNameResolved(context.database, this._authenticationProvider?._authTokenManager?._authToken?.principal) + onDatabaseNameResolved(databaseName, this._authenticationProvider?._authTokenManager?._authToken?.principal) } } }) @@ -679,9 +679,9 @@ export default class RoutingConnectionProvider extends PooledConnectionProvider } _channelSsrCallback (isEnabled, opened) { - if (isEnabled) { + if (isEnabled === true) { this._withSSR = this._withSSR + (opened ? 1 : -1) - } else { + } else if (isEnabled === false) { this._withoutSSR = this._withoutSSR + (opened ? 1 : -1) } } diff --git a/packages/neo4j-driver-deno/lib/bolt-connection/connection/connection-channel.js b/packages/neo4j-driver-deno/lib/bolt-connection/connection/connection-channel.js index 278e9cb70..af7bcdb23 100644 --- a/packages/neo4j-driver-deno/lib/bolt-connection/connection/connection-channel.js +++ b/packages/neo4j-driver-deno/lib/bolt-connection/connection/connection-channel.js @@ -336,15 +336,13 @@ export default class ChannelConnection extends Connection { this._telemetryDisabledConnection = false } - const SSREnabledHint = metadata.hints['ssr.enabled'] - if (this.serversideRouting !== SSREnabledHint) { - this._ssrCallback(SSREnabledHint, true) - } - if (SSREnabledHint === true) { + const SSREnabledHint = true// metadata.hints['ssr.enabled'] + if (SSREnabledHint) { this.serversideRouting = true - } else if (SSREnabledHint === false) { + } else { this.serversideRouting = false } + this._ssrCallback(this.serversideRouting, true) } } resolve(self) diff --git a/packages/neo4j-driver-deno/lib/core/driver.ts b/packages/neo4j-driver-deno/lib/core/driver.ts index 1159f8d5b..cb3863b7b 100644 --- a/packages/neo4j-driver-deno/lib/core/driver.ts +++ b/packages/neo4j-driver-deno/lib/core/driver.ts @@ -844,8 +844,8 @@ class Driver { ) } - _homeDatabaseCallback (user: string, table: any): void { - this.homeDatabaseCache.set(user, table) + _homeDatabaseCallback (user: string, database: any): void { + this.homeDatabaseCache.set(user, database) } _committedDbCallback (database: string, user: string): void { diff --git a/packages/neo4j-driver-deno/lib/core/internal/constants.ts b/packages/neo4j-driver-deno/lib/core/internal/constants.ts index ed32dcef5..b45034da9 100644 --- a/packages/neo4j-driver-deno/lib/core/internal/constants.ts +++ b/packages/neo4j-driver-deno/lib/core/internal/constants.ts @@ -39,6 +39,7 @@ const BOLT_PROTOCOL_V5_4: number = 5.4 const BOLT_PROTOCOL_V5_5: number = 5.5 const BOLT_PROTOCOL_V5_6: number = 5.6 const BOLT_PROTOCOL_V5_7: number = 5.7 +const BOLT_PROTOCOL_V5_8: number = 5.8 const TELEMETRY_APIS = { MANAGED_TRANSACTION: 0, @@ -74,5 +75,6 @@ export { BOLT_PROTOCOL_V5_5, BOLT_PROTOCOL_V5_6, BOLT_PROTOCOL_V5_7, + BOLT_PROTOCOL_V5_8, TELEMETRY_APIS } diff --git a/packages/neo4j-driver-deno/lib/core/internal/transaction-executor.ts b/packages/neo4j-driver-deno/lib/core/internal/transaction-executor.ts index bedc5eee5..e334f314e 100644 --- a/packages/neo4j-driver-deno/lib/core/internal/transaction-executor.ts +++ b/packages/neo4j-driver-deno/lib/core/internal/transaction-executor.ts @@ -58,7 +58,6 @@ export class TransactionExecutor { private readonly _clearTimeout: ClearTimeout public telemetryApi: NonAutoCommitTelemetryApis public pipelineBegin: boolean - public committedDbCallback: any constructor ( maxRetryTimeMs?: number | null, @@ -68,8 +67,7 @@ export class TransactionExecutor { dependencies: Dependencies = { setTimeout: setTimeoutWrapper, clearTimeout: clearTimeoutWrapper - }, - committedDbCallback?: any + } ) { this._maxRetryTimeMs = _valueOrDefault( maxRetryTimeMs, @@ -90,7 +88,6 @@ export class TransactionExecutor { this._setTimeout = dependencies.setTimeout this._clearTimeout = dependencies.clearTimeout - this.committedDbCallback = committedDbCallback this._inFlightTimeoutIds = [] this.pipelineBegin = false this.telemetryApi = TELEMETRY_APIS.MANAGED_TRANSACTION @@ -249,7 +246,7 @@ export class TransactionExecutor { if (tx.isOpen()) { // transaction work returned resolved promise and transaction has not been committed/rolled back // try to commit the transaction - tx.commit(this.committedDbCallback) + tx.commit() .then(() => { // transaction was committed, return result to the user resolve(result) diff --git a/packages/neo4j-driver-deno/lib/core/session.ts b/packages/neo4j-driver-deno/lib/core/session.ts index d7476be9b..7055cc403 100644 --- a/packages/neo4j-driver-deno/lib/core/session.ts +++ b/packages/neo4j-driver-deno/lib/core/session.ts @@ -137,9 +137,6 @@ class Session { this._fetchSize = fetchSize this._homeDatabaseCallback = homeDatabaseCallback this._driverCommittedDbCallback = committedDbCallback - if (config?.cachedUser !== undefined && config?.cachedHomeDatabase !== undefined) { - this._databaseGuess = { user: config?.cachedUser, database: config?.cachedHomeDatabase } - } this._auth = auth this._getConnectionAcquistionBookmarks = this._getConnectionAcquistionBookmarks.bind(this) this._readConnectionHolder = new ConnectionHolder({ @@ -161,7 +158,7 @@ class Session { bookmarks, connectionProvider, impersonatedUser, - onDatabaseNameResolved: this._onDatabaseNameResolved, + onDatabaseNameResolved: this._onDatabaseNameResolved.bind(this), getConnectionAcquistionBookmarks: this._getConnectionAcquistionBookmarks, log }) @@ -170,7 +167,7 @@ class Session { this._impersonatedUser = impersonatedUser this._lastBookmarks = bookmarks ?? Bookmarks.empty() this._configuredBookmarks = this._lastBookmarks - this._transactionExecutor = _createTransactionExecutor({ ...config, commitCallback: this._committedDbCallback.bind(this) }) + this._transactionExecutor = _createTransactionExecutor(config) this._databaseNameResolved = this._database !== '' const calculatedWatermaks = this._calculateWatermaks() this._lowRecordWatermark = calculatedWatermaks.low @@ -179,6 +176,9 @@ class Session { this._bookmarkManager = bookmarkManager this._notificationFilter = notificationFilter this._log = log + if (config?.cachedUser !== undefined && config?.cachedHomeDatabase !== undefined) { + this._databaseGuess = { user: config?.cachedUser, database: config?.cachedHomeDatabase } + } } /** @@ -345,7 +345,8 @@ class Session { lowRecordWatermark: this._lowRecordWatermark, highRecordWatermark: this._highRecordWatermark, notificationFilter: this._notificationFilter, - apiTelemetryConfig + apiTelemetryConfig, + onDbCallback: this._committedDbCallback.bind(this) }) tx._begin(() => this._bookmarks(), txConfig) return tx @@ -666,7 +667,7 @@ function _createTransactionExecutor (config?: { commitCallback: any }): TransactionExecutor { const maxRetryTimeMs = config?.maxTransactionRetryTime ?? null - return new TransactionExecutor(maxRetryTimeMs, undefined, undefined, undefined, undefined, config?.commitCallback) + return new TransactionExecutor(maxRetryTimeMs) } export default Session diff --git a/packages/neo4j-driver-deno/lib/core/transaction-promise.ts b/packages/neo4j-driver-deno/lib/core/transaction-promise.ts index 66eb5b79c..2940b4606 100644 --- a/packages/neo4j-driver-deno/lib/core/transaction-promise.ts +++ b/packages/neo4j-driver-deno/lib/core/transaction-promise.ts @@ -43,6 +43,7 @@ class TransactionPromise extends Transaction implements Promise { private _beginPromise?: Promise private _reject?: (error: Error) => void private _resolve?: (value: Transaction | PromiseLike) => void + private _onDbCallback: (database: string) => void /** * @constructor @@ -69,7 +70,8 @@ class TransactionPromise extends Transaction implements Promise { highRecordWatermark, lowRecordWatermark, notificationFilter, - apiTelemetryConfig + apiTelemetryConfig, + onDbCallback, }: { connectionHolder: ConnectionHolder onClose: () => void @@ -82,6 +84,7 @@ class TransactionPromise extends Transaction implements Promise { lowRecordWatermark: number notificationFilter?: NotificationFilter apiTelemetryConfig?: NonAutoCommitApiTelemetryConfig + onDbCallback: (database: string) => void }) { super({ connectionHolder, @@ -96,6 +99,7 @@ class TransactionPromise extends Transaction implements Promise { notificationFilter, apiTelemetryConfig }) + this._onDbCallback = onDbCallback } /** @@ -174,7 +178,8 @@ class TransactionPromise extends Transaction implements Promise { _begin (bookmarks: () => Promise, txConfig: TxConfig): void { return super._begin(bookmarks, txConfig, { onError: this._onBeginError.bind(this), - onComplete: this._onBeginMetadata.bind(this) + onComplete: this._onBeginMetadata.bind(this), + onDB: this._onDbCallback }) } diff --git a/packages/neo4j-driver-deno/lib/core/transaction.ts b/packages/neo4j-driver-deno/lib/core/transaction.ts index d5f7f6a50..423204b53 100644 --- a/packages/neo4j-driver-deno/lib/core/transaction.ts +++ b/packages/neo4j-driver-deno/lib/core/transaction.ts @@ -140,6 +140,7 @@ class Transaction { _begin (getBookmarks: () => Promise, txConfig: TxConfig, events?: { onError: (error: Error) => void onComplete: (metadata: any) => void + onDB: (database: string) => void }): void { this._connectionHolder .getConnection() @@ -151,6 +152,7 @@ class Transaction { bookmarks: this._bookmarks, txConfig, mode: this._connectionHolder.mode(), + database: this._connectionHolder.database(), impersonatedUser: this._impersonatedUser, notificationFilter: this._notificationFilter, apiTelemetryConfig: this._apiTelemetryConfig, @@ -164,6 +166,9 @@ class Transaction { if (events != null) { events.onComplete(metadata) } + if(metadata.db !== undefined && events?.onDB) { + events.onDB(metadata.db) + } this._onComplete(metadata) } }) @@ -219,7 +224,7 @@ class Transaction { * * @returns {Promise} An empty promise if committed successfully or error if any error happened during commit. */ - commit (committedDbCallback?: any): Promise { + commit (): Promise { const committed = this._state.commit({ connectionHolder: this._connectionHolder, onError: this._onError, @@ -233,12 +238,7 @@ class Transaction { this._onClose() return new Promise((resolve, reject) => { committed.result.subscribe({ - onCompleted: (result: any) => { - if (committedDbCallback !== undefined) { - committedDbCallback(result.database.name) - } - resolve() - }, + onCompleted: () => resolve(), onError: (error: any) => reject(error) }) }) From 6b238fab3025bceef9bd11467c73a7a5cdf6a492 Mon Sep 17 00:00:00 2001 From: MaxAake <61233757+MaxAake@users.noreply.github.com> Date: Fri, 1 Nov 2024 15:27:44 +0100 Subject: [PATCH 17/84] deno and minor fix --- packages/bolt-connection/src/connection/connection-channel.js | 2 +- .../lib/bolt-connection/connection/connection-channel.js | 2 +- packages/neo4j-driver-deno/lib/core/transaction-promise.ts | 4 ++-- packages/neo4j-driver-deno/lib/core/transaction.ts | 4 ++-- 4 files changed, 6 insertions(+), 6 deletions(-) diff --git a/packages/bolt-connection/src/connection/connection-channel.js b/packages/bolt-connection/src/connection/connection-channel.js index de9c38095..264a670c5 100644 --- a/packages/bolt-connection/src/connection/connection-channel.js +++ b/packages/bolt-connection/src/connection/connection-channel.js @@ -336,7 +336,7 @@ export default class ChannelConnection extends Connection { this._telemetryDisabledConnection = false } - const SSREnabledHint = true// metadata.hints['ssr.enabled'] + const SSREnabledHint = metadata.hints['ssr.enabled'] if (SSREnabledHint) { this.serversideRouting = true } else { diff --git a/packages/neo4j-driver-deno/lib/bolt-connection/connection/connection-channel.js b/packages/neo4j-driver-deno/lib/bolt-connection/connection/connection-channel.js index af7bcdb23..e81313ceb 100644 --- a/packages/neo4j-driver-deno/lib/bolt-connection/connection/connection-channel.js +++ b/packages/neo4j-driver-deno/lib/bolt-connection/connection/connection-channel.js @@ -336,7 +336,7 @@ export default class ChannelConnection extends Connection { this._telemetryDisabledConnection = false } - const SSREnabledHint = true// metadata.hints['ssr.enabled'] + const SSREnabledHint = metadata.hints['ssr.enabled'] if (SSREnabledHint) { this.serversideRouting = true } else { diff --git a/packages/neo4j-driver-deno/lib/core/transaction-promise.ts b/packages/neo4j-driver-deno/lib/core/transaction-promise.ts index 2940b4606..b12f21c58 100644 --- a/packages/neo4j-driver-deno/lib/core/transaction-promise.ts +++ b/packages/neo4j-driver-deno/lib/core/transaction-promise.ts @@ -43,7 +43,7 @@ class TransactionPromise extends Transaction implements Promise { private _beginPromise?: Promise private _reject?: (error: Error) => void private _resolve?: (value: Transaction | PromiseLike) => void - private _onDbCallback: (database: string) => void + private readonly _onDbCallback: (database: string) => void /** * @constructor @@ -71,7 +71,7 @@ class TransactionPromise extends Transaction implements Promise { lowRecordWatermark, notificationFilter, apiTelemetryConfig, - onDbCallback, + onDbCallback }: { connectionHolder: ConnectionHolder onClose: () => void diff --git a/packages/neo4j-driver-deno/lib/core/transaction.ts b/packages/neo4j-driver-deno/lib/core/transaction.ts index 423204b53..aec0062e8 100644 --- a/packages/neo4j-driver-deno/lib/core/transaction.ts +++ b/packages/neo4j-driver-deno/lib/core/transaction.ts @@ -166,7 +166,7 @@ class Transaction { if (events != null) { events.onComplete(metadata) } - if(metadata.db !== undefined && events?.onDB) { + if (metadata.db !== undefined && ((events?.onDB) != null)) { events.onDB(metadata.db) } this._onComplete(metadata) @@ -238,7 +238,7 @@ class Transaction { this._onClose() return new Promise((resolve, reject) => { committed.result.subscribe({ - onCompleted: () => resolve(), + onCompleted: () => resolve(), onError: (error: any) => reject(error) }) }) From 936233fbf9eac673ec32da5981b64c5db9af81d0 Mon Sep 17 00:00:00 2001 From: MaxAake <61233757+MaxAake@users.noreply.github.com> Date: Mon, 4 Nov 2024 14:47:00 +0100 Subject: [PATCH 18/84] remove debug log and fix minor issue --- .../connection-provider/connection-provider-routing.js | 1 - .../bolt-connection/src/connection/connection-channel.js | 8 ++++++-- .../connection-provider/connection-provider-routing.js | 1 - .../lib/bolt-connection/connection/connection-channel.js | 8 ++++++-- 4 files changed, 12 insertions(+), 6 deletions(-) diff --git a/packages/bolt-connection/src/connection-provider/connection-provider-routing.js b/packages/bolt-connection/src/connection-provider/connection-provider-routing.js index 43c06d195..a8dff5935 100644 --- a/packages/bolt-connection/src/connection-provider/connection-provider-routing.js +++ b/packages/bolt-connection/src/connection-provider/connection-provider-routing.js @@ -798,7 +798,6 @@ function _isFailFastError (error) { } function _isFailFastSecurityError (error) { - console.error(error) return error.code.startsWith('Neo.ClientError.Security.') && ![ AUTHORIZATION_EXPIRED_CODE diff --git a/packages/bolt-connection/src/connection/connection-channel.js b/packages/bolt-connection/src/connection/connection-channel.js index 264a670c5..40211faad 100644 --- a/packages/bolt-connection/src/connection/connection-channel.js +++ b/packages/bolt-connection/src/connection/connection-channel.js @@ -342,7 +342,9 @@ export default class ChannelConnection extends Connection { } else { this.serversideRouting = false } - this._ssrCallback(this.serversideRouting, true) + if (this._ssrCallback !== undefined) { + this._ssrCallback(this.serversideRouting, true) + } } } resolve(self) @@ -550,7 +552,9 @@ export default class ChannelConnection extends Connection { * @returns {Promise} - A promise that will be resolved when the underlying channel is closed. */ async close () { - this._ssrCallback(this.serversideRouting, false) + if (this._ssrCallback !== undefined) { + this._ssrCallback(this.serversideRouting, false) + } if (this._log.isDebugEnabled()) { this._log.debug('closing') } diff --git a/packages/neo4j-driver-deno/lib/bolt-connection/connection-provider/connection-provider-routing.js b/packages/neo4j-driver-deno/lib/bolt-connection/connection-provider/connection-provider-routing.js index 2f266e1b4..60c65dc29 100644 --- a/packages/neo4j-driver-deno/lib/bolt-connection/connection-provider/connection-provider-routing.js +++ b/packages/neo4j-driver-deno/lib/bolt-connection/connection-provider/connection-provider-routing.js @@ -798,7 +798,6 @@ function _isFailFastError (error) { } function _isFailFastSecurityError (error) { - console.error(error) return error.code.startsWith('Neo.ClientError.Security.') && ![ AUTHORIZATION_EXPIRED_CODE diff --git a/packages/neo4j-driver-deno/lib/bolt-connection/connection/connection-channel.js b/packages/neo4j-driver-deno/lib/bolt-connection/connection/connection-channel.js index e81313ceb..ca9737637 100644 --- a/packages/neo4j-driver-deno/lib/bolt-connection/connection/connection-channel.js +++ b/packages/neo4j-driver-deno/lib/bolt-connection/connection/connection-channel.js @@ -342,7 +342,9 @@ export default class ChannelConnection extends Connection { } else { this.serversideRouting = false } - this._ssrCallback(this.serversideRouting, true) + if (this._ssrCallback !== undefined) { + this._ssrCallback(this.serversideRouting, true) + } } } resolve(self) @@ -550,7 +552,9 @@ export default class ChannelConnection extends Connection { * @returns {Promise} - A promise that will be resolved when the underlying channel is closed. */ async close () { - this._ssrCallback(this.serversideRouting, false) + if (this._ssrCallback !== undefined) { + this._ssrCallback(this.serversideRouting, false) + } if (this._log.isDebugEnabled()) { this._log.debug('closing') } From a84543eaea370ee3a8d7dccfbcdc23621e1d1d5c Mon Sep 17 00:00:00 2001 From: MaxAake <61233757+MaxAake@users.noreply.github.com> Date: Thu, 7 Nov 2024 09:31:22 +0100 Subject: [PATCH 19/84] many small rewrites, test updates --- .../bolt-connection/test/bolt/index.test.js | 2 +- packages/core/src/auth.ts | 18 ++++++++++++--- packages/core/src/connection-provider.ts | 4 ++-- packages/core/src/driver.ts | 12 +++++----- .../core/src/internal/connection-holder.ts | 8 +++---- packages/core/src/session.ts | 22 +++++++++---------- packages/core/src/types.ts | 1 + packages/core/test/auth.test.ts | 22 ++++++++++++++----- packages/core/test/driver.test.ts | 12 ++++++---- packages/core/test/transaction.test.ts | 3 ++- packages/neo4j-driver-deno/lib/core/auth.ts | 19 +++++++++++++--- .../lib/core/connection-provider.ts | 4 ++-- packages/neo4j-driver-deno/lib/core/driver.ts | 12 +++++----- .../lib/core/internal/connection-holder.ts | 8 +++---- .../neo4j-driver-deno/lib/core/session.ts | 22 +++++++++---------- packages/neo4j-driver-deno/lib/core/types.ts | 1 + packages/neo4j-driver/src/index.js | 2 +- packages/neo4j-driver/test/driver.test.js | 2 +- 18 files changed, 109 insertions(+), 65 deletions(-) diff --git a/packages/bolt-connection/test/bolt/index.test.js b/packages/bolt-connection/test/bolt/index.test.js index 1a9117556..7e3c1303b 100644 --- a/packages/bolt-connection/test/bolt/index.test.js +++ b/packages/bolt-connection/test/bolt/index.test.js @@ -48,7 +48,7 @@ describe('#unit Bolt', () => { const writtenBuffer = channel.written[0] const boltMagicPreamble = '60 60 b0 17' - const protocolVersion5x7to5x0 = '00 07 07 05' + const protocolVersion5x7to5x0 = '00 08 08 05' const protocolVersion4x4to4x2 = '00 02 04 04' const protocolVersion4x1 = '00 00 01 04' const protocolVersion3 = '00 00 00 03' diff --git a/packages/core/src/auth.ts b/packages/core/src/auth.ts index 6237a1c0e..212c2143c 100644 --- a/packages/core/src/auth.ts +++ b/packages/core/src/auth.ts @@ -32,23 +32,26 @@ const auth = { scheme: 'basic', principal: username, credentials: password, + cacheKey: username, realm } } else { - return { scheme: 'basic', principal: username, credentials: password } + return { scheme: 'basic', principal: username, credentials: password, cacheKey: username } } }, kerberos: (base64EncodedTicket: string) => { return { scheme: 'kerberos', principal: '', // This empty string is required for backwards compatibility. - credentials: base64EncodedTicket + credentials: base64EncodedTicket, + cacheKey: hash(base64EncodedTicket) } }, bearer: (base64EncodedToken: string) => { return { scheme: 'bearer', - credentials: base64EncodedToken + credentials: base64EncodedToken, + cacheKey: hash(base64EncodedToken) } }, none: () => { @@ -76,6 +79,7 @@ const auth = { if (isNotEmpty(parameters)) { output.parameters = parameters } + output.cacheKey = hash(principal + (isNotEmpty(credentials) ? credentials : '') + (isNotEmpty(realm) ? realm : '') + scheme) return output } } @@ -89,4 +93,12 @@ function isNotEmpty (value: T | null | undefined): bo ) } +function hash (string: string): string { + let hash = 0 + for (let i = 0; i < string.length; ++i) { + hash = Math.imul(31, hash) + string.charCodeAt(i) + } + return hash.toString() +} + export default auth diff --git a/packages/core/src/connection-provider.ts b/packages/core/src/connection-provider.ts index 6b44e09f8..aca74f375 100644 --- a/packages/core/src/connection-provider.ts +++ b/packages/core/src/connection-provider.ts @@ -65,8 +65,8 @@ class ConnectionProvider { database?: string bookmarks: bookmarks.Bookmarks impersonatedUser?: string - onDatabaseNameResolved?: (database?: string) => void - removeFailureFromCache?: (database?: string) => void + onDatabaseNameResolved?: (database: string) => void + removeFailureFromCache?: (database: string) => void auth?: AuthToken homeDb?: any }): Promise { diff --git a/packages/core/src/driver.ts b/packages/core/src/driver.ts index 690792aea..f92b079c5 100644 --- a/packages/core/src/driver.ts +++ b/packages/core/src/driver.ts @@ -98,8 +98,8 @@ type CreateSession = (args: { auth?: AuthToken log: Logger homeDatabaseCallback?: (user: string, database: any) => void - committedDbCallback?: (user: string) => void removeFailureFromCache?: (database: string) => void + beginDbCallback?: (user: string, database: string) => void }) => Session type CreateQueryExecutor = (createSession: (config: { database?: string, bookmarkManager?: BookmarkManager }) => Session) => QueryExecutor @@ -848,7 +848,7 @@ class Driver { this.homeDatabaseCache.set(user, database) } - _committedDbCallback (database: string, user: string): void { + _beginDbCallback (database: string, user: string): void { if (this.homeDatabaseCache.get(user) !== undefined && this.homeDatabaseCache.get(user) !== database) { this.homeDatabaseCache.delete(user) } @@ -888,10 +888,10 @@ class Driver { }): Session { const sessionMode = Session._validateSessionMode(defaultAccessMode) const connectionProvider = this._getOrCreateConnectionProvider() - const cachedUser = impersonatedUser ?? auth?.principal ?? this._config.user ?? '' - const cachedHomeDatabase = this.homeDatabaseCache.get(cachedUser) + const cachedUser = impersonatedUser ?? auth?.cacheKey ?? this._config.user + const cachedHomeDatabase = cachedUser !== undefined ? this.homeDatabaseCache.get(cachedUser) : undefined const homeDatabaseCallback = this._homeDatabaseCallback.bind(this) - const committedDbCallback = this._committedDbCallback.bind(this) + const beginDbCallback = this._beginDbCallback.bind(this) const removeFailureFromCache = this._removeFailureFromCache.bind(this) const bookmarks = bookmarkOrBookmarks != null ? new Bookmarks(bookmarkOrBookmarks) @@ -914,7 +914,7 @@ class Driver { auth, log: this._log, homeDatabaseCallback, - committedDbCallback, + beginDbCallback, removeFailureFromCache }) } diff --git a/packages/core/src/internal/connection-holder.ts b/packages/core/src/internal/connection-holder.ts index 46f59a09e..fb56b10b0 100644 --- a/packages/core/src/internal/connection-holder.ts +++ b/packages/core/src/internal/connection-holder.ts @@ -84,8 +84,8 @@ class ConnectionHolder implements ConnectionHolderInterface { private _connectionPromise: Promise private readonly _impersonatedUser?: string private readonly _getConnectionAcquistionBookmarks: () => Promise - private readonly _onDatabaseNameResolved?: (databaseName?: string) => void - private readonly removeFailureFromCache?: (databaseName?: string) => void + private readonly _onDatabaseNameResolved?: (databaseName: string) => void + private readonly removeFailureFromCache?: (databaseName: string) => void private readonly _auth?: AuthToken private readonly _log: Logger private _closed: boolean @@ -120,8 +120,8 @@ class ConnectionHolder implements ConnectionHolderInterface { bookmarks?: Bookmarks connectionProvider?: ConnectionProvider impersonatedUser?: string - onDatabaseNameResolved?: (databaseName?: string) => void - removeFailureFromCache?: (databaseName?: string) => void + onDatabaseNameResolved?: (database: string) => void + removeFailureFromCache?: (database: string) => void getConnectionAcquistionBookmarks?: () => Promise auth?: AuthToken log: Logger diff --git a/packages/core/src/session.ts b/packages/core/src/session.ts index 04aca3811..83fa85083 100644 --- a/packages/core/src/session.ts +++ b/packages/core/src/session.ts @@ -80,7 +80,7 @@ class Session { private readonly _notificationFilter?: NotificationFilter private readonly _log: Logger private readonly _homeDatabaseCallback: Function | undefined - private readonly _driverCommittedDbCallback: Function | undefined + private readonly _driverBeginDbCallback: Function | undefined private readonly _auth: AuthToken | undefined private _databaseGuess: DbGuess | undefined /** @@ -113,7 +113,7 @@ class Session { log, homeDatabaseCallback, removeFailureFromCache, - committedDbCallback + beginDbCallback }: { mode: SessionMode connectionProvider: ConnectionProvider @@ -127,16 +127,16 @@ class Session { notificationFilter?: NotificationFilter auth?: AuthToken log: Logger - homeDatabaseCallback?: (user: string, database: any) => void + homeDatabaseCallback?: (user: string, database: string) => void removeFailureFromCache?: (database: string) => void - committedDbCallback?: (user: string, database: string) => void + beginDbCallback?: (user: string, database: string) => void }) { this._mode = mode this._database = database this._reactive = reactive this._fetchSize = fetchSize this._homeDatabaseCallback = homeDatabaseCallback - this._driverCommittedDbCallback = committedDbCallback + this._driverBeginDbCallback = beginDbCallback this._auth = auth this._getConnectionAcquistionBookmarks = this._getConnectionAcquistionBookmarks.bind(this) this._readConnectionHolder = new ConnectionHolder({ @@ -346,7 +346,7 @@ class Session { highRecordWatermark: this._highRecordWatermark, notificationFilter: this._notificationFilter, apiTelemetryConfig, - onDbCallback: this._committedDbCallback.bind(this) + onDbCallback: this._beginDbCallback.bind(this) }) tx._begin(() => this._bookmarks(), txConfig) return tx @@ -530,9 +530,9 @@ class Session { * @returns {void} */ _onDatabaseNameResolved (database?: string, user?: string): void { - this._databaseGuess = { user: this._impersonatedUser ?? this._auth?.principal ?? user ?? '', database } + this._databaseGuess = { user: this._impersonatedUser ?? this._auth?.cacheKey ?? user ?? '', database } if (this._homeDatabaseCallback != null) { - this._homeDatabaseCallback(this._impersonatedUser ?? this._auth?.principal ?? user, database) + this._homeDatabaseCallback(this._impersonatedUser ?? this._auth?.cacheKey ?? user, database) } if (!this._databaseNameResolved) { const normalizedDatabase = database ?? '' @@ -543,9 +543,9 @@ class Session { } } - _committedDbCallback (database: string): void { - if (this._driverCommittedDbCallback !== undefined && this._databaseGuess !== undefined) { - this._driverCommittedDbCallback(database, this._databaseGuess.user) + _beginDbCallback (database: string): void { + if (this._driverBeginDbCallback !== undefined && this._databaseGuess !== undefined) { + this._driverBeginDbCallback(database, this._databaseGuess.user) } } diff --git a/packages/core/src/types.ts b/packages/core/src/types.ts index 5e63b9ff1..36c9e19fb 100644 --- a/packages/core/src/types.ts +++ b/packages/core/src/types.ts @@ -48,6 +48,7 @@ export interface AuthToken { credentials: string realm?: string parameters?: Parameters + cacheKey?: string } export interface BoltAgent { diff --git a/packages/core/test/auth.test.ts b/packages/core/test/auth.test.ts index 710935709..6cd4c3d94 100644 --- a/packages/core/test/auth.test.ts +++ b/packages/core/test/auth.test.ts @@ -16,9 +16,17 @@ */ import auth from '../src/auth' +function hash (string: string): string { + let hash = 0 + for (let i = 0; i < string.length; ++i) { + hash = Math.imul(31, hash) + string.charCodeAt(i) + } + return hash.toString() +} + describe('auth', () => { test('.bearer()', () => { - expect(auth.bearer('==Qyahiadakkda')).toEqual({ scheme: 'bearer', credentials: '==Qyahiadakkda' }) + expect(auth.bearer('==Qyahiadakkda')).toEqual({ scheme: 'bearer', credentials: '==Qyahiadakkda', cacheKey: hash('==Qyahiadakkda') }) }) test.each([ @@ -29,28 +37,32 @@ describe('auth', () => { principal: 'user', credentials: 'pass', realm: 'realm', - parameters: { param: 'param' } + parameters: { param: 'param' }, + cacheKey: hash('user' + 'pass' + 'realm' + 'scheme') } ], [ ['user', '', '', 'scheme', {}], { scheme: 'scheme', - principal: 'user' + principal: 'user', + cacheKey: hash('user' + 'scheme') } ], [ ['user', undefined, undefined, 'scheme', undefined], { scheme: 'scheme', - principal: 'user' + principal: 'user', + cacheKey: hash('user' + 'scheme') } ], [ ['user', null, null, 'scheme', null], { scheme: 'scheme', - principal: 'user' + principal: 'user', + cacheKey: hash('user' + 'scheme') } ] ])('.custom()', (args, output) => { diff --git a/packages/core/test/driver.test.ts b/packages/core/test/driver.test.ts index 66bbac483..370dbaff4 100644 --- a/packages/core/test/driver.test.ts +++ b/packages/core/test/driver.test.ts @@ -70,7 +70,7 @@ describe('Driver', () => { const session = driver?.session({ impersonatedUser }) expect(session).not.toBeUndefined() - expect(createSession).toHaveBeenCalledWith(expectedSessionParams({ impersonatedUser })) + expect(createSession).toHaveBeenCalledWith(expectedSessionParams({ impersonatedUser }, impersonatedUser)) }) it('should create the session without impersonated user', () => { @@ -84,13 +84,14 @@ describe('Driver', () => { const auth = { scheme: 'basic', principal: 'the imposter', - credentials: 'super safe password' + credentials: 'super safe password', + cacheKey: 'the imposter' } const session = driver?.session({ auth }) expect(session).not.toBeUndefined() - expect(createSession).toHaveBeenCalledWith(expectedSessionParams({ auth })) + expect(createSession).toHaveBeenCalledWith(expectedSessionParams({ auth }, auth.principal)) }) it('should create the session without auth', () => { @@ -691,10 +692,11 @@ describe('Driver', () => { ) => connectionProvider } - function expectedSessionParams (extra: any = {}): any { + function expectedSessionParams (extra: any = {}, cachedUser: string | undefined = undefined): any { return { bookmarks: Bookmarks.empty(), config: { + cachedUser, connectionAcquisitionTimeout: 60000, fetchSize: 1000, maxConnectionLifetime: 3600000, @@ -710,6 +712,8 @@ describe('Driver', () => { // @ts-expect-error log: driver?._log, homeDatabaseCallback: expect.any(Function), + beginDbCallback: expect.any(Function), + removeFailureFromCache: expect.any(Function), ...extra } } diff --git a/packages/core/test/transaction.test.ts b/packages/core/test/transaction.test.ts index 0558e4105..e95617594 100644 --- a/packages/core/test/transaction.test.ts +++ b/packages/core/test/transaction.test.ts @@ -566,7 +566,8 @@ function newTransactionPromise ({ impersonatedUser: '', highRecordWatermark, lowRecordWatermark, - notificationFilter + notificationFilter, + onDbCallback: (_: string) => { } }) return transaction diff --git a/packages/neo4j-driver-deno/lib/core/auth.ts b/packages/neo4j-driver-deno/lib/core/auth.ts index 6237a1c0e..34504be41 100644 --- a/packages/neo4j-driver-deno/lib/core/auth.ts +++ b/packages/neo4j-driver-deno/lib/core/auth.ts @@ -32,23 +32,26 @@ const auth = { scheme: 'basic', principal: username, credentials: password, + cacheKey: username, realm } } else { - return { scheme: 'basic', principal: username, credentials: password } + return { scheme: 'basic', principal: username, credentials: password, cacheKey: username } } }, kerberos: (base64EncodedTicket: string) => { return { scheme: 'kerberos', principal: '', // This empty string is required for backwards compatibility. - credentials: base64EncodedTicket + credentials: base64EncodedTicket, + cacheKey: hash(base64EncodedTicket) } }, bearer: (base64EncodedToken: string) => { return { scheme: 'bearer', - credentials: base64EncodedToken + credentials: base64EncodedToken, + cacheKey: hash(base64EncodedToken) } }, none: () => { @@ -76,6 +79,7 @@ const auth = { if (isNotEmpty(parameters)) { output.parameters = parameters } + output.cacheKey = hash(principal + (isNotEmpty(credentials) ? credentials : "") + (isNotEmpty(realm) ? realm : "") + scheme) return output } } @@ -89,4 +93,13 @@ function isNotEmpty (value: T | null | undefined): bo ) } + +function hash(string: string): string { + let hash = 0 + for (let i = 0; i < string.length; ++i) { + hash = Math.imul(31, hash) + string.charCodeAt(i) + } + return hash.toString() +} + export default auth diff --git a/packages/neo4j-driver-deno/lib/core/connection-provider.ts b/packages/neo4j-driver-deno/lib/core/connection-provider.ts index de95b9264..7be2c59e1 100644 --- a/packages/neo4j-driver-deno/lib/core/connection-provider.ts +++ b/packages/neo4j-driver-deno/lib/core/connection-provider.ts @@ -65,8 +65,8 @@ class ConnectionProvider { database?: string bookmarks: bookmarks.Bookmarks impersonatedUser?: string - onDatabaseNameResolved?: (database?: string) => void - removeFailureFromCache?: (database?: string) => void + onDatabaseNameResolved?: (database: string) => void + removeFailureFromCache?: (database: string) => void auth?: AuthToken homeDb?: any }): Promise { diff --git a/packages/neo4j-driver-deno/lib/core/driver.ts b/packages/neo4j-driver-deno/lib/core/driver.ts index cb3863b7b..5ed793c61 100644 --- a/packages/neo4j-driver-deno/lib/core/driver.ts +++ b/packages/neo4j-driver-deno/lib/core/driver.ts @@ -98,8 +98,8 @@ type CreateSession = (args: { auth?: AuthToken log: Logger homeDatabaseCallback?: (user: string, database: any) => void - committedDbCallback?: (user: string) => void removeFailureFromCache?: (database: string) => void + beginDbCallback?: (user: string, database: string) => void }) => Session type CreateQueryExecutor = (createSession: (config: { database?: string, bookmarkManager?: BookmarkManager }) => Session) => QueryExecutor @@ -848,7 +848,7 @@ class Driver { this.homeDatabaseCache.set(user, database) } - _committedDbCallback (database: string, user: string): void { + _beginDbCallback (database: string, user: string): void { if (this.homeDatabaseCache.get(user) !== undefined && this.homeDatabaseCache.get(user) !== database) { this.homeDatabaseCache.delete(user) } @@ -888,10 +888,10 @@ class Driver { }): Session { const sessionMode = Session._validateSessionMode(defaultAccessMode) const connectionProvider = this._getOrCreateConnectionProvider() - const cachedUser = impersonatedUser ?? auth?.principal ?? this._config.user ?? '' - const cachedHomeDatabase = this.homeDatabaseCache.get(cachedUser) + const cachedUser = impersonatedUser ?? auth?.cacheKey ?? this._config.user + const cachedHomeDatabase = cachedUser !== undefined ? this.homeDatabaseCache.get(cachedUser) : undefined const homeDatabaseCallback = this._homeDatabaseCallback.bind(this) - const committedDbCallback = this._committedDbCallback.bind(this) + const beginDbCallback = this._beginDbCallback.bind(this) const removeFailureFromCache = this._removeFailureFromCache.bind(this) const bookmarks = bookmarkOrBookmarks != null ? new Bookmarks(bookmarkOrBookmarks) @@ -914,7 +914,7 @@ class Driver { auth, log: this._log, homeDatabaseCallback, - committedDbCallback, + beginDbCallback, removeFailureFromCache }) } diff --git a/packages/neo4j-driver-deno/lib/core/internal/connection-holder.ts b/packages/neo4j-driver-deno/lib/core/internal/connection-holder.ts index 476e11c3f..f8a7660ed 100644 --- a/packages/neo4j-driver-deno/lib/core/internal/connection-holder.ts +++ b/packages/neo4j-driver-deno/lib/core/internal/connection-holder.ts @@ -84,8 +84,8 @@ class ConnectionHolder implements ConnectionHolderInterface { private _connectionPromise: Promise private readonly _impersonatedUser?: string private readonly _getConnectionAcquistionBookmarks: () => Promise - private readonly _onDatabaseNameResolved?: (databaseName?: string) => void - private readonly removeFailureFromCache?: (databaseName?: string) => void + private readonly _onDatabaseNameResolved?: (databaseName: string) => void + private readonly removeFailureFromCache?: (databaseName: string) => void private readonly _auth?: AuthToken private readonly _log: Logger private _closed: boolean @@ -120,8 +120,8 @@ class ConnectionHolder implements ConnectionHolderInterface { bookmarks?: Bookmarks connectionProvider?: ConnectionProvider impersonatedUser?: string - onDatabaseNameResolved?: (databaseName?: string) => void - removeFailureFromCache?: (databaseName?: string) => void + onDatabaseNameResolved?: (database: string) => void + removeFailureFromCache?: (database: string) => void getConnectionAcquistionBookmarks?: () => Promise auth?: AuthToken log: Logger diff --git a/packages/neo4j-driver-deno/lib/core/session.ts b/packages/neo4j-driver-deno/lib/core/session.ts index 7055cc403..9ac2433d8 100644 --- a/packages/neo4j-driver-deno/lib/core/session.ts +++ b/packages/neo4j-driver-deno/lib/core/session.ts @@ -80,7 +80,7 @@ class Session { private readonly _notificationFilter?: NotificationFilter private readonly _log: Logger private readonly _homeDatabaseCallback: Function | undefined - private readonly _driverCommittedDbCallback: Function | undefined + private readonly _driverBeginDbCallback: Function | undefined private readonly _auth: AuthToken | undefined private _databaseGuess: DbGuess | undefined /** @@ -113,7 +113,7 @@ class Session { log, homeDatabaseCallback, removeFailureFromCache, - committedDbCallback + beginDbCallback }: { mode: SessionMode connectionProvider: ConnectionProvider @@ -127,16 +127,16 @@ class Session { notificationFilter?: NotificationFilter auth?: AuthToken log: Logger - homeDatabaseCallback?: (user: string, database: any) => void + homeDatabaseCallback?: (user: string, database: string) => void removeFailureFromCache?: (database: string) => void - committedDbCallback?: (user: string, database: string) => void + beginDbCallback?: (user: string, database: string) => void }) { this._mode = mode this._database = database this._reactive = reactive this._fetchSize = fetchSize this._homeDatabaseCallback = homeDatabaseCallback - this._driverCommittedDbCallback = committedDbCallback + this._driverBeginDbCallback = beginDbCallback this._auth = auth this._getConnectionAcquistionBookmarks = this._getConnectionAcquistionBookmarks.bind(this) this._readConnectionHolder = new ConnectionHolder({ @@ -346,7 +346,7 @@ class Session { highRecordWatermark: this._highRecordWatermark, notificationFilter: this._notificationFilter, apiTelemetryConfig, - onDbCallback: this._committedDbCallback.bind(this) + onDbCallback: this._beginDbCallback.bind(this) }) tx._begin(() => this._bookmarks(), txConfig) return tx @@ -530,9 +530,9 @@ class Session { * @returns {void} */ _onDatabaseNameResolved (database?: string, user?: string): void { - this._databaseGuess = { user: this._impersonatedUser ?? this._auth?.principal ?? user ?? '', database } + this._databaseGuess = { user: this._impersonatedUser ?? this._auth?.cacheKey ?? user ?? '', database } if (this._homeDatabaseCallback != null) { - this._homeDatabaseCallback(this._impersonatedUser ?? this._auth?.principal ?? user, database) + this._homeDatabaseCallback(this._impersonatedUser ?? this._auth?.cacheKey ?? user, database) } if (!this._databaseNameResolved) { const normalizedDatabase = database ?? '' @@ -543,9 +543,9 @@ class Session { } } - _committedDbCallback (database: string): void { - if (this._driverCommittedDbCallback !== undefined && this._databaseGuess !== undefined) { - this._driverCommittedDbCallback(database, this._databaseGuess.user) + _beginDbCallback (database: string): void { + if (this._driverBeginDbCallback !== undefined && this._databaseGuess !== undefined) { + this._driverBeginDbCallback(database, this._databaseGuess.user) } } diff --git a/packages/neo4j-driver-deno/lib/core/types.ts b/packages/neo4j-driver-deno/lib/core/types.ts index d493c400d..eb13dc254 100644 --- a/packages/neo4j-driver-deno/lib/core/types.ts +++ b/packages/neo4j-driver-deno/lib/core/types.ts @@ -48,6 +48,7 @@ export interface AuthToken { credentials: string realm?: string parameters?: Parameters + cacheKey?: string } export interface BoltAgent { diff --git a/packages/neo4j-driver/src/index.js b/packages/neo4j-driver/src/index.js index 2b31be6a3..c497387ca 100644 --- a/packages/neo4j-driver/src/index.js +++ b/packages/neo4j-driver/src/index.js @@ -189,7 +189,7 @@ function driver (url, authToken, config = {}) { typename: routing ? 'Routing' : 'Direct', routing } - if (authToken.scheme === 'basic') { + if (authToken?.scheme === 'basic') { config.user = authToken.principal } return new Driver(meta, config, createConnectionProviderFunction()) diff --git a/packages/neo4j-driver/test/driver.test.js b/packages/neo4j-driver/test/driver.test.js index 691155cea..3fcf27136 100644 --- a/packages/neo4j-driver/test/driver.test.js +++ b/packages/neo4j-driver/test/driver.test.js @@ -552,7 +552,7 @@ describe('#integration driver', () => { expect(driver.homeDatabaseCache.get(sharedNeo4j.authToken.principal)).toBe('neo4j') expect(session1._database).toBe('neo4j') const session2 = driver.session({ auth: sharedNeo4j.authToken }) - expect(session2._homeDatabaseBestGuess).toBe('neo4j') + expect(session2._databaseGuess.database).toBe('neo4j') await session2.run('CREATE () RETURN 43') } }) From ced7c4644af009b47046ec8a8e160a841fad25c3 Mon Sep 17 00:00:00 2001 From: MaxAake <61233757+MaxAake@users.noreply.github.com> Date: Thu, 7 Nov 2024 10:09:41 +0100 Subject: [PATCH 20/84] deno and removal off cachekey in hello --- packages/bolt-connection/src/bolt/request-message.js | 1 + .../lib/bolt-connection/bolt/request-message.js | 1 + packages/neo4j-driver-deno/lib/core/auth.ts | 5 ++--- 3 files changed, 4 insertions(+), 3 deletions(-) diff --git a/packages/bolt-connection/src/bolt/request-message.js b/packages/bolt-connection/src/bolt/request-message.js index 1a08d100b..271f3915d 100644 --- a/packages/bolt-connection/src/bolt/request-message.js +++ b/packages/bolt-connection/src/bolt/request-message.js @@ -130,6 +130,7 @@ export default class RequestMessage { */ static hello (userAgent, authToken, routing = null, patchs = null) { const metadata = Object.assign({ user_agent: userAgent }, authToken) + metadata.cacheKey = undefined if (routing) { metadata.routing = routing } diff --git a/packages/neo4j-driver-deno/lib/bolt-connection/bolt/request-message.js b/packages/neo4j-driver-deno/lib/bolt-connection/bolt/request-message.js index 43812e795..53aabdaec 100644 --- a/packages/neo4j-driver-deno/lib/bolt-connection/bolt/request-message.js +++ b/packages/neo4j-driver-deno/lib/bolt-connection/bolt/request-message.js @@ -130,6 +130,7 @@ export default class RequestMessage { */ static hello (userAgent, authToken, routing = null, patchs = null) { const metadata = Object.assign({ user_agent: userAgent }, authToken) + metadata.cacheKey = undefined if (routing) { metadata.routing = routing } diff --git a/packages/neo4j-driver-deno/lib/core/auth.ts b/packages/neo4j-driver-deno/lib/core/auth.ts index 34504be41..212c2143c 100644 --- a/packages/neo4j-driver-deno/lib/core/auth.ts +++ b/packages/neo4j-driver-deno/lib/core/auth.ts @@ -79,7 +79,7 @@ const auth = { if (isNotEmpty(parameters)) { output.parameters = parameters } - output.cacheKey = hash(principal + (isNotEmpty(credentials) ? credentials : "") + (isNotEmpty(realm) ? realm : "") + scheme) + output.cacheKey = hash(principal + (isNotEmpty(credentials) ? credentials : '') + (isNotEmpty(realm) ? realm : '') + scheme) return output } } @@ -93,8 +93,7 @@ function isNotEmpty (value: T | null | undefined): bo ) } - -function hash(string: string): string { +function hash (string: string): string { let hash = 0 for (let i = 0; i < string.length; ++i) { hash = Math.imul(31, hash) + string.charCodeAt(i) From fafde750a8a2956f16b37a685b39266b6cc7515c Mon Sep 17 00:00:00 2001 From: MaxAake <61233757+MaxAake@users.noreply.github.com> Date: Thu, 7 Nov 2024 10:17:26 +0100 Subject: [PATCH 21/84] remove cachekey from logon and init --- packages/bolt-connection/src/bolt/request-message.js | 8 ++++++-- .../lib/bolt-connection/bolt/request-message.js | 8 ++++++-- 2 files changed, 12 insertions(+), 4 deletions(-) diff --git a/packages/bolt-connection/src/bolt/request-message.js b/packages/bolt-connection/src/bolt/request-message.js index 271f3915d..2f1af6817 100644 --- a/packages/bolt-connection/src/bolt/request-message.js +++ b/packages/bolt-connection/src/bolt/request-message.js @@ -84,9 +84,11 @@ export default class RequestMessage { * @return {RequestMessage} new INIT message. */ static init (clientName, authToken) { + const metadata = Object.assign({}, authToken) + metadata.cacheKey = undefined return new RequestMessage( INIT, - [clientName, authToken], + [clientName, metadata], () => `INIT ${clientName} {...}` ) } @@ -267,9 +269,11 @@ export default class RequestMessage { * @returns {RequestMessage} new LOGON message */ static logon (authToken) { + const metadata = Object.assign({}, authToken) + metadata.cacheKey = undefined return new RequestMessage( LOGON, - [authToken], + [metadata], () => 'LOGON { ... }' ) } diff --git a/packages/neo4j-driver-deno/lib/bolt-connection/bolt/request-message.js b/packages/neo4j-driver-deno/lib/bolt-connection/bolt/request-message.js index 53aabdaec..07591b263 100644 --- a/packages/neo4j-driver-deno/lib/bolt-connection/bolt/request-message.js +++ b/packages/neo4j-driver-deno/lib/bolt-connection/bolt/request-message.js @@ -84,9 +84,11 @@ export default class RequestMessage { * @return {RequestMessage} new INIT message. */ static init (clientName, authToken) { + const metadata = Object.assign({}, authToken) + metadata.cacheKey = undefined return new RequestMessage( INIT, - [clientName, authToken], + [clientName, metadata], () => `INIT ${clientName} {...}` ) } @@ -267,9 +269,11 @@ export default class RequestMessage { * @returns {RequestMessage} new LOGON message */ static logon (authToken) { + const metadata = Object.assign({}, authToken) + metadata.cacheKey = undefined return new RequestMessage( LOGON, - [authToken], + [metadata], () => 'LOGON { ... }' ) } From b8633aac237e576f84333ae4ad3faf59037a61c9 Mon Sep 17 00:00:00 2001 From: MaxAake <61233757+MaxAake@users.noreply.github.com> Date: Thu, 7 Nov 2024 12:45:53 +0100 Subject: [PATCH 22/84] protect from undefined callback --- .../src/connection-provider/connection-provider-routing.js | 4 +++- .../connection-provider/connection-provider-routing.js | 4 +++- 2 files changed, 6 insertions(+), 2 deletions(-) diff --git a/packages/bolt-connection/src/connection-provider/connection-provider-routing.js b/packages/bolt-connection/src/connection-provider/connection-provider-routing.js index a8dff5935..8937cc53b 100644 --- a/packages/bolt-connection/src/connection-provider/connection-provider-routing.js +++ b/packages/bolt-connection/src/connection-provider/connection-provider-routing.js @@ -152,7 +152,9 @@ export default class RoutingConnectionProvider extends PooledConnectionProvider SESSION_EXPIRED, (error, address) => this._handleUnavailability(error, address, context.database), (error, address) => { - removeFailureFromCache(homeDb ?? context.database) + if (removeFailureFromCache !== undefined) { + removeFailureFromCache(homeDb ?? context.database) + } return this._handleWriteFailure(error, address, homeDb ?? context.database) }, (error, address, conn) => diff --git a/packages/neo4j-driver-deno/lib/bolt-connection/connection-provider/connection-provider-routing.js b/packages/neo4j-driver-deno/lib/bolt-connection/connection-provider/connection-provider-routing.js index 60c65dc29..c9b819be5 100644 --- a/packages/neo4j-driver-deno/lib/bolt-connection/connection-provider/connection-provider-routing.js +++ b/packages/neo4j-driver-deno/lib/bolt-connection/connection-provider/connection-provider-routing.js @@ -152,7 +152,9 @@ export default class RoutingConnectionProvider extends PooledConnectionProvider SESSION_EXPIRED, (error, address) => this._handleUnavailability(error, address, context.database), (error, address) => { - removeFailureFromCache(homeDb ?? context.database) + if (removeFailureFromCache !== undefined) { + removeFailureFromCache(homeDb ?? context.database) + } return this._handleWriteFailure(error, address, homeDb ?? context.database) }, (error, address, conn) => From 2cc2e8962804ce7e88253973035521e23232fdd4 Mon Sep 17 00:00:00 2001 From: MaxAake <61233757+MaxAake@users.noreply.github.com> Date: Thu, 7 Nov 2024 15:20:13 +0100 Subject: [PATCH 23/84] Update index.js --- packages/neo4j-driver/src/index.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/neo4j-driver/src/index.js b/packages/neo4j-driver/src/index.js index c497387ca..ae4be06cc 100644 --- a/packages/neo4j-driver/src/index.js +++ b/packages/neo4j-driver/src/index.js @@ -189,7 +189,7 @@ function driver (url, authToken, config = {}) { typename: routing ? 'Routing' : 'Direct', routing } - if (authToken?.scheme === 'basic') { + if (authToken !== undefined && authToken.scheme === 'basic') { config.user = authToken.principal } return new Driver(meta, config, createConnectionProviderFunction()) From 050efaa0417fa5ce077bd4bb1b92c758912eb21e Mon Sep 17 00:00:00 2001 From: MaxAake <61233757+MaxAake@users.noreply.github.com> Date: Fri, 8 Nov 2024 10:41:29 +0100 Subject: [PATCH 24/84] Update auth.test.js --- packages/neo4j-driver/test/auth.test.js | 23 ++++++++++++++++++----- 1 file changed, 18 insertions(+), 5 deletions(-) diff --git a/packages/neo4j-driver/test/auth.test.js b/packages/neo4j-driver/test/auth.test.js index 4f333bbc6..90f3caba2 100644 --- a/packages/neo4j-driver/test/auth.test.js +++ b/packages/neo4j-driver/test/auth.test.js @@ -17,13 +17,22 @@ import neo4j from '../src' +function hash (string) { + let hash = 0 + for (let i = 0; i < string.length; ++i) { + hash = Math.imul(31, hash) + string.charCodeAt(i) + } + return hash.toString() +} + describe('#unit auth', () => { it('should use correct username and password in basic auth', () => { const token = neo4j.auth.basic('cat', 'dog') expect(token).toEqual({ scheme: 'basic', principal: 'cat', - credentials: 'dog' + credentials: 'dog', + cacheKey: 'cat' }) }) @@ -33,7 +42,8 @@ describe('#unit auth', () => { scheme: 'basic', principal: 'cat', credentials: 'dog', - realm: 'apartment' + realm: 'apartment', + cacheKey: 'cat' }) }) @@ -42,7 +52,8 @@ describe('#unit auth', () => { expect(token).toEqual({ scheme: 'kerberos', principal: '', - credentials: 'my-ticket' + credentials: 'my-ticket', + cacheKey: hash('my-ticket') }) }) @@ -52,7 +63,8 @@ describe('#unit auth', () => { scheme: 'pets', principal: 'cat', credentials: 'dog', - realm: 'apartment' + realm: 'apartment', + cacheKey: hash('catdogapartmentpets') }) }) @@ -66,7 +78,8 @@ describe('#unit auth', () => { principal: 'cat', credentials: 'dog', realm: 'apartment', - parameters: { key1: 'value1', key2: 42 } + parameters: { key1: 'value1', key2: 42 }, + cacheKey: hash('catdogapartmentpets') }) }) }) From 19e4f360d12d8d3d50ebc7fa467961aa7a8c8d0d Mon Sep 17 00:00:00 2001 From: MaxAake <61233757+MaxAake@users.noreply.github.com> Date: Mon, 11 Nov 2024 09:07:05 +0100 Subject: [PATCH 25/84] move hash function to allow imports --- packages/core/src/auth-util.ts | 24 ++++++++++++++++++ packages/core/src/auth.ts | 10 ++------ packages/core/test/auth.test.ts | 9 +------ .../neo4j-driver-deno/lib/core/auth-util.ts | 25 +++++++++++++++++++ packages/neo4j-driver-deno/lib/core/auth.ts | 9 ++----- 5 files changed, 54 insertions(+), 23 deletions(-) create mode 100644 packages/core/src/auth-util.ts create mode 100644 packages/neo4j-driver-deno/lib/core/auth-util.ts diff --git a/packages/core/src/auth-util.ts b/packages/core/src/auth-util.ts new file mode 100644 index 000000000..42a2ea25b --- /dev/null +++ b/packages/core/src/auth-util.ts @@ -0,0 +1,24 @@ +/** + * Copyright (c) "Neo4j" + * Neo4j Sweden AB [https://neo4j.com] + * + * 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. + */ + +export function hash (string: string): string { // PLACEHOLDER HASH FUNCTION + let hash = 0 + for (let i = 0; i < string.length; ++i) { + hash = Math.imul(31, hash) + string.charCodeAt(i) + } + return hash.toString() +} diff --git a/packages/core/src/auth.ts b/packages/core/src/auth.ts index 212c2143c..0049d25c4 100644 --- a/packages/core/src/auth.ts +++ b/packages/core/src/auth.ts @@ -15,6 +15,8 @@ * limitations under the License. */ +import { hash } from './auth-util' + /** * @property {function(username: string, password: string, realm: ?string)} basic the function to create a * basic authentication token. @@ -93,12 +95,4 @@ function isNotEmpty (value: T | null | undefined): bo ) } -function hash (string: string): string { - let hash = 0 - for (let i = 0; i < string.length; ++i) { - hash = Math.imul(31, hash) + string.charCodeAt(i) - } - return hash.toString() -} - export default auth diff --git a/packages/core/test/auth.test.ts b/packages/core/test/auth.test.ts index 6cd4c3d94..e978aa52a 100644 --- a/packages/core/test/auth.test.ts +++ b/packages/core/test/auth.test.ts @@ -15,14 +15,7 @@ * limitations under the License. */ import auth from '../src/auth' - -function hash (string: string): string { - let hash = 0 - for (let i = 0; i < string.length; ++i) { - hash = Math.imul(31, hash) + string.charCodeAt(i) - } - return hash.toString() -} +import { hash } from '../src/auth-util' describe('auth', () => { test('.bearer()', () => { diff --git a/packages/neo4j-driver-deno/lib/core/auth-util.ts b/packages/neo4j-driver-deno/lib/core/auth-util.ts new file mode 100644 index 000000000..45b882795 --- /dev/null +++ b/packages/neo4j-driver-deno/lib/core/auth-util.ts @@ -0,0 +1,25 @@ +/** + * Copyright (c) "Neo4j" + * Neo4j Sweden AB [https://neo4j.com] + * + * 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. + */ + + +export function hash (string: string): string { //PLACEHOLDER HASH FUNCTION + let hash = 0 + for (let i = 0; i < string.length; ++i) { + hash = Math.imul(31, hash) + string.charCodeAt(i) + } + return hash.toString() +} \ No newline at end of file diff --git a/packages/neo4j-driver-deno/lib/core/auth.ts b/packages/neo4j-driver-deno/lib/core/auth.ts index 212c2143c..37403a391 100644 --- a/packages/neo4j-driver-deno/lib/core/auth.ts +++ b/packages/neo4j-driver-deno/lib/core/auth.ts @@ -15,6 +15,8 @@ * limitations under the License. */ +import { hash } from './auth-util.ts' + /** * @property {function(username: string, password: string, realm: ?string)} basic the function to create a * basic authentication token. @@ -93,12 +95,5 @@ function isNotEmpty (value: T | null | undefined): bo ) } -function hash (string: string): string { - let hash = 0 - for (let i = 0; i < string.length; ++i) { - hash = Math.imul(31, hash) + string.charCodeAt(i) - } - return hash.toString() -} export default auth From dfbdeb4d33fd95bae0d606a4b777a4b792f0b5db Mon Sep 17 00:00:00 2001 From: MaxAake <61233757+MaxAake@users.noreply.github.com> Date: Mon, 11 Nov 2024 09:08:01 +0100 Subject: [PATCH 26/84] deno --- packages/neo4j-driver-deno/lib/core/auth-util.ts | 15 +++++++-------- packages/neo4j-driver-deno/lib/core/auth.ts | 1 - 2 files changed, 7 insertions(+), 9 deletions(-) diff --git a/packages/neo4j-driver-deno/lib/core/auth-util.ts b/packages/neo4j-driver-deno/lib/core/auth-util.ts index 45b882795..42a2ea25b 100644 --- a/packages/neo4j-driver-deno/lib/core/auth-util.ts +++ b/packages/neo4j-driver-deno/lib/core/auth-util.ts @@ -15,11 +15,10 @@ * limitations under the License. */ - -export function hash (string: string): string { //PLACEHOLDER HASH FUNCTION - let hash = 0 - for (let i = 0; i < string.length; ++i) { - hash = Math.imul(31, hash) + string.charCodeAt(i) - } - return hash.toString() -} \ No newline at end of file +export function hash (string: string): string { // PLACEHOLDER HASH FUNCTION + let hash = 0 + for (let i = 0; i < string.length; ++i) { + hash = Math.imul(31, hash) + string.charCodeAt(i) + } + return hash.toString() +} diff --git a/packages/neo4j-driver-deno/lib/core/auth.ts b/packages/neo4j-driver-deno/lib/core/auth.ts index 37403a391..7630c28c2 100644 --- a/packages/neo4j-driver-deno/lib/core/auth.ts +++ b/packages/neo4j-driver-deno/lib/core/auth.ts @@ -95,5 +95,4 @@ function isNotEmpty (value: T | null | undefined): bo ) } - export default auth From a8a675f3a22173e5a7b961cbb695b2ab7d4a905c Mon Sep 17 00:00:00 2001 From: MaxAake <61233757+MaxAake@users.noreply.github.com> Date: Mon, 11 Nov 2024 11:46:18 +0100 Subject: [PATCH 27/84] small rework --- .../src/bolt/request-message.js | 12 +- .../connection-provider-routing.js | 3 +- .../src/connection/connection-channel.js | 4 +- .../test/bolt/bolt-protocol-v5x8.test.js | 1579 +++++++++++++++++ packages/core/src/session.ts | 4 +- .../bolt-connection/bolt/request-message.js | 12 +- .../connection-provider-routing.js | 3 +- .../connection/connection-channel.js | 4 +- .../neo4j-driver-deno/lib/core/session.ts | 4 +- 9 files changed, 1601 insertions(+), 24 deletions(-) create mode 100644 packages/bolt-connection/test/bolt/bolt-protocol-v5x8.test.js diff --git a/packages/bolt-connection/src/bolt/request-message.js b/packages/bolt-connection/src/bolt/request-message.js index 2f1af6817..a5d86ad56 100644 --- a/packages/bolt-connection/src/bolt/request-message.js +++ b/packages/bolt-connection/src/bolt/request-message.js @@ -84,11 +84,11 @@ export default class RequestMessage { * @return {RequestMessage} new INIT message. */ static init (clientName, authToken) { - const metadata = Object.assign({}, authToken) - metadata.cacheKey = undefined + const auth = Object.assign({}, authToken) + auth.cacheKey = undefined return new RequestMessage( INIT, - [clientName, metadata], + [clientName, auth], () => `INIT ${clientName} {...}` ) } @@ -269,11 +269,11 @@ export default class RequestMessage { * @returns {RequestMessage} new LOGON message */ static logon (authToken) { - const metadata = Object.assign({}, authToken) - metadata.cacheKey = undefined + const auth = Object.assign({}, authToken) + auth.cacheKey = undefined return new RequestMessage( LOGON, - [metadata], + [auth], () => 'LOGON { ... }' ) } diff --git a/packages/bolt-connection/src/connection-provider/connection-provider-routing.js b/packages/bolt-connection/src/connection-provider/connection-provider-routing.js index 8937cc53b..f59d17499 100644 --- a/packages/bolt-connection/src/connection-provider/connection-provider-routing.js +++ b/packages/bolt-connection/src/connection-provider/connection-provider-routing.js @@ -79,7 +79,6 @@ export default class RoutingConnectionProvider extends PooledConnectionProvider this._log, await this._clientCertificateHolder.getClientCertificate(), this._routingContext, - undefined, this._channelSsrCallback.bind(this) ) }) @@ -178,7 +177,7 @@ export default class RoutingConnectionProvider extends PooledConnectionProvider onDatabaseNameResolved: (databaseName) => { context.database = context.database || databaseName if (onDatabaseNameResolved) { - onDatabaseNameResolved(databaseName, this._authenticationProvider?._authTokenManager?._authToken?.principal) + onDatabaseNameResolved(databaseName, this.auth?.cacheKey ?? this._authenticationProvider?._authTokenManager?._authToken?.cacheKey) } } }) diff --git a/packages/bolt-connection/src/connection/connection-channel.js b/packages/bolt-connection/src/connection/connection-channel.js index 40211faad..aa5449c13 100644 --- a/packages/bolt-connection/src/connection/connection-channel.js +++ b/packages/bolt-connection/src/connection/connection-channel.js @@ -43,8 +43,8 @@ export function createChannelConnection ( log, clientCertificate, serversideRouting = null, - createChannel = channelConfig => new Channel(channelConfig), - ssrCallback + ssrCallback = (_) => {}, + createChannel = channelConfig => new Channel(channelConfig) ) { const channelConfig = new ChannelConfig( address, diff --git a/packages/bolt-connection/test/bolt/bolt-protocol-v5x8.test.js b/packages/bolt-connection/test/bolt/bolt-protocol-v5x8.test.js new file mode 100644 index 000000000..49eb7cf8f --- /dev/null +++ b/packages/bolt-connection/test/bolt/bolt-protocol-v5x8.test.js @@ -0,0 +1,1579 @@ +/** + * Copyright (c) "Neo4j" + * Neo4j Sweden AB [https://neo4j.com] + * + * 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 BoltProtocolV5x8 from '../../src/bolt/bolt-protocol-v5x8' +import RequestMessage from '../../src/bolt/request-message' +import { v2, structure } from '../../src/packstream' +import utils from '../test-utils' +import { LoginObserver, RouteObserver } from '../../src/bolt/stream-observers' +import fc from 'fast-check' +import { + Date, + DateTime, + Duration, + LocalDateTime, + LocalTime, + Path, + PathSegment, + Point, + Relationship, + Time, + UnboundRelationship, + Node, + internal +} from 'neo4j-driver-core' + +import { alloc } from '../../src/channel' +import { notificationFilterBehaviour, telemetryBehaviour } from './behaviour' + +const WRITE = 'WRITE' + +const { + txConfig: { TxConfig }, + bookmarks: { Bookmarks }, + logger: { Logger }, + temporalUtil +} = internal + +describe('#unit BoltProtocolV5x8', () => { + beforeEach(() => { + expect.extend(utils.matchers) + }) + + telemetryBehaviour.protocolSupportsTelemetry(newProtocol) + + it('should enrich error metadata', () => { + const protocol = new BoltProtocolV5x8() + const enrichedData = protocol.enrichErrorMetadata({ neo4j_code: 'hello', diagnostic_record: {} }) + expect(enrichedData.code).toBe('hello') + expect(enrichedData.diagnostic_record.OPERATION).toBe('') + expect(enrichedData.diagnostic_record.OPERATION_CODE).toBe('0') + expect(enrichedData.diagnostic_record.CURRENT_SCHEMA).toBe('/') + }) + + it('should request routing information', () => { + const recorder = new utils.MessageRecordingConnection() + const protocol = new BoltProtocolV5x8(recorder, null, false) + utils.spyProtocolWrite(protocol) + const routingContext = { someContextParam: 'value' } + const databaseName = 'name' + + const observer = protocol.requestRoutingInformation({ + routingContext, + databaseName + }) + + protocol.verifyMessageCount(1) + expect(protocol.messages[0]).toBeMessage( + RequestMessage.routeV4x4(routingContext, [], { databaseName, impersonatedUser: null }) + ) + expect(protocol.observers).toEqual([observer]) + expect(observer).toEqual(expect.any(RouteObserver)) + expect(protocol.flushes).toEqual([true]) + }) + + it('should request routing information sending bookmarks', () => { + const recorder = new utils.MessageRecordingConnection() + const protocol = new BoltProtocolV5x8(recorder, null, false) + utils.spyProtocolWrite(protocol) + const routingContext = { someContextParam: 'value' } + const listOfBookmarks = ['a', 'b', 'c'] + const bookmarks = new Bookmarks(listOfBookmarks) + const databaseName = 'name' + + const observer = protocol.requestRoutingInformation({ + routingContext, + databaseName, + sessionContext: { bookmarks } + }) + + protocol.verifyMessageCount(1) + expect(protocol.messages[0]).toBeMessage( + RequestMessage.routeV4x4(routingContext, listOfBookmarks, { databaseName, impersonatedUser: null }) + ) + expect(protocol.observers).toEqual([observer]) + expect(observer).toEqual(expect.any(RouteObserver)) + expect(protocol.flushes).toEqual([true]) + }) + + it('should run a query', () => { + const database = 'testdb' + const bookmarks = new Bookmarks([ + 'neo4j:bookmark:v1:tx1', + 'neo4j:bookmark:v1:tx2' + ]) + const txConfig = new TxConfig({ + timeout: 5000, + metadata: { x: 1, y: 'something' } + }) + const recorder = new utils.MessageRecordingConnection() + const protocol = new BoltProtocolV5x8(recorder, null, false) + utils.spyProtocolWrite(protocol) + + const query = 'RETURN $x, $y' + const parameters = { x: 'x', y: 'y' } + + const observer = protocol.run(query, parameters, { + bookmarks, + txConfig, + database, + mode: WRITE + }) + + protocol.verifyMessageCount(2) + + expect(protocol.messages[0]).toBeMessage( + RequestMessage.runWithMetadata(query, parameters, { + bookmarks, + txConfig, + database, + mode: WRITE + }) + ) + expect(protocol.messages[1]).toBeMessage(RequestMessage.pull()) + expect(protocol.observers).toEqual([observer, observer]) + expect(protocol.flushes).toEqual([false, true]) + }) + + it('should run a with impersonated user', () => { + const database = 'testdb' + const impersonatedUser = 'the impostor' + const bookmarks = new Bookmarks([ + 'neo4j:bookmark:v1:tx1', + 'neo4j:bookmark:v1:tx2' + ]) + const txConfig = new TxConfig({ + timeout: 5000, + metadata: { x: 1, y: 'something' } + }) + const recorder = new utils.MessageRecordingConnection() + const protocol = new BoltProtocolV5x8(recorder, null, false) + utils.spyProtocolWrite(protocol) + + const query = 'RETURN $x, $y' + const parameters = { x: 'x', y: 'y' } + + const observer = protocol.run(query, parameters, { + bookmarks, + txConfig, + database, + mode: WRITE, + impersonatedUser + }) + + protocol.verifyMessageCount(2) + + expect(protocol.messages[0]).toBeMessage( + RequestMessage.runWithMetadata(query, parameters, { + bookmarks, + txConfig, + database, + mode: WRITE, + impersonatedUser + }) + ) + expect(protocol.messages[1]).toBeMessage(RequestMessage.pull()) + expect(protocol.observers).toEqual([observer, observer]) + expect(protocol.flushes).toEqual([false, true]) + }) + + it('should begin a transaction', () => { + const database = 'testdb' + const bookmarks = new Bookmarks([ + 'neo4j:bookmark:v1:tx1', + 'neo4j:bookmark:v1:tx2' + ]) + const txConfig = new TxConfig({ + timeout: 5000, + metadata: { x: 1, y: 'something' } + }) + const recorder = new utils.MessageRecordingConnection() + const protocol = new BoltProtocolV5x8(recorder, null, false) + utils.spyProtocolWrite(protocol) + + const observer = protocol.beginTransaction({ + bookmarks, + txConfig, + database, + mode: WRITE + }) + + protocol.verifyMessageCount(1) + expect(protocol.messages[0]).toBeMessage( + RequestMessage.begin({ bookmarks, txConfig, database, mode: WRITE }) + ) + expect(protocol.observers).toEqual([observer]) + expect(protocol.flushes).toEqual([true]) + }) + + it('should begin a transaction with impersonated user', () => { + const database = 'testdb' + const impersonatedUser = 'the impostor' + const bookmarks = new Bookmarks([ + 'neo4j:bookmark:v1:tx1', + 'neo4j:bookmark:v1:tx2' + ]) + const txConfig = new TxConfig({ + timeout: 5000, + metadata: { x: 1, y: 'something' } + }) + const recorder = new utils.MessageRecordingConnection() + const protocol = new BoltProtocolV5x8(recorder, null, false) + utils.spyProtocolWrite(protocol) + + const observer = protocol.beginTransaction({ + bookmarks, + txConfig, + database, + mode: WRITE, + impersonatedUser + }) + + protocol.verifyMessageCount(1) + expect(protocol.messages[0]).toBeMessage( + RequestMessage.begin({ bookmarks, txConfig, database, mode: WRITE, impersonatedUser }) + ) + expect(protocol.observers).toEqual([observer]) + expect(protocol.flushes).toEqual([true]) + }) + + it('should return correct bolt version number', () => { + const protocol = new BoltProtocolV5x8(null, null, false) + + expect(protocol.version).toBe(5.8) + }) + + it('should update metadata', () => { + const metadata = { t_first: 1, t_last: 2, db_hits: 3, some_other_key: 4 } + const protocol = new BoltProtocolV5x8(null, null, false) + + const transformedMetadata = protocol.transformMetadata(metadata) + + expect(transformedMetadata).toEqual({ + result_available_after: 1, + result_consumed_after: 2, + db_hits: 3, + some_other_key: 4 + }) + }) + + it('should initialize connection', () => { + const recorder = new utils.MessageRecordingConnection() + const protocol = new BoltProtocolV5x8(recorder, null, false) + utils.spyProtocolWrite(protocol) + + const clientName = 'js-driver/1.2.3' + const boltAgent = { + product: 'neo4j-javascript/5.8', + platform: 'netbsd 1.1.1; Some arch', + languageDetails: 'Node/16.0.1 (v8 1.7.0)' + } + const authToken = { username: 'neo4j', password: 'secret' } + + const observer = protocol.initialize({ userAgent: clientName, boltAgent, authToken }) + + protocol.verifyMessageCount(2) + expect(protocol.messages[0]).toBeMessage( + RequestMessage.hello5x3(clientName, boltAgent) + ) + expect(protocol.messages[1]).toBeMessage( + RequestMessage.logon(authToken) + ) + + expect(protocol.observers.length).toBe(2) + + // hello observer + const helloObserver = protocol.observers[0] + expect(helloObserver).toBeInstanceOf(LoginObserver) + expect(helloObserver).not.toBe(observer) + + // login observer + const loginObserver = protocol.observers[1] + expect(loginObserver).toBeInstanceOf(LoginObserver) + expect(loginObserver).toBe(observer) + + expect(protocol.flushes).toEqual([false, true]) + }) + + it.each([ + 'javascript-driver/5.8.0', + '', + undefined, + null + ])('should always use the user agent set by the user', (userAgent) => { + const recorder = new utils.MessageRecordingConnection() + const protocol = new BoltProtocolV5x8(recorder, null, false) + utils.spyProtocolWrite(protocol) + + const boltAgent = { + product: 'neo4j-javascript/5.8', + platform: 'netbsd 1.1.1; Some arch', + languageDetails: 'Node/16.0.1 (v8 1.7.0)' + } + const authToken = { username: 'neo4j', password: 'secret' } + + const observer = protocol.initialize({ userAgent, boltAgent, authToken }) + + protocol.verifyMessageCount(2) + expect(protocol.messages[0]).toBeMessage( + RequestMessage.hello5x3(userAgent, boltAgent) + ) + expect(protocol.messages[1]).toBeMessage( + RequestMessage.logon(authToken) + ) + + expect(protocol.observers.length).toBe(2) + + // hello observer + const helloObserver = protocol.observers[0] + expect(helloObserver).toBeInstanceOf(LoginObserver) + expect(helloObserver).not.toBe(observer) + + // login observer + const loginObserver = protocol.observers[1] + expect(loginObserver).toBeInstanceOf(LoginObserver) + expect(loginObserver).toBe(observer) + + expect(protocol.flushes).toEqual([false, true]) + }) + + it.each( + [true, false] + )('should logon to the server [flush=%s]', (flush) => { + const recorder = new utils.MessageRecordingConnection() + const protocol = new BoltProtocolV5x8(recorder, null, false) + utils.spyProtocolWrite(protocol) + + const authToken = { username: 'neo4j', password: 'secret' } + + const observer = protocol.logon({ authToken, flush }) + + protocol.verifyMessageCount(1) + + expect(protocol.messages[0]).toBeMessage( + RequestMessage.logon(authToken) + ) + + expect(protocol.observers).toEqual([observer]) + expect(protocol.flushes).toEqual([flush]) + }) + + it.each( + [true, false] + )('should logoff from the server [flush=%s]', (flush) => { + const recorder = new utils.MessageRecordingConnection() + const protocol = new BoltProtocolV5x8(recorder, null, false) + utils.spyProtocolWrite(protocol) + + const observer = protocol.logoff({ flush }) + + protocol.verifyMessageCount(1) + + expect(protocol.messages[0]).toBeMessage( + RequestMessage.logoff() + ) + + expect(protocol.observers).toEqual([observer]) + expect(protocol.flushes).toEqual([flush]) + }) + + it('should begin a transaction', () => { + const bookmarks = new Bookmarks([ + 'neo4j:bookmark:v1:tx1', + 'neo4j:bookmark:v1:tx2' + ]) + const txConfig = new TxConfig({ + timeout: 5000, + metadata: { x: 1, y: 'something' } + }) + const recorder = new utils.MessageRecordingConnection() + const protocol = new BoltProtocolV5x8(recorder, null, false) + utils.spyProtocolWrite(protocol) + + const observer = protocol.beginTransaction({ + bookmarks, + txConfig, + mode: WRITE + }) + + protocol.verifyMessageCount(1) + expect(protocol.messages[0]).toBeMessage( + RequestMessage.begin({ bookmarks, txConfig, mode: WRITE }) + ) + expect(protocol.observers).toEqual([observer]) + expect(protocol.flushes).toEqual([true]) + }) + + it('should commit', () => { + const recorder = new utils.MessageRecordingConnection() + const protocol = new BoltProtocolV5x8(recorder, null, false) + utils.spyProtocolWrite(protocol) + + const observer = protocol.commitTransaction() + + protocol.verifyMessageCount(1) + expect(protocol.messages[0]).toBeMessage(RequestMessage.commit()) + expect(protocol.observers).toEqual([observer]) + expect(protocol.flushes).toEqual([true]) + }) + + it('should rollback', () => { + const recorder = new utils.MessageRecordingConnection() + const protocol = new BoltProtocolV5x8(recorder, null, false) + utils.spyProtocolWrite(protocol) + + const observer = protocol.rollbackTransaction() + + protocol.verifyMessageCount(1) + expect(protocol.messages[0]).toBeMessage(RequestMessage.rollback()) + expect(protocol.observers).toEqual([observer]) + expect(protocol.flushes).toEqual([true]) + }) + + it('should support logoff', () => { + const recorder = new utils.MessageRecordingConnection() + const protocol = new BoltProtocolV5x8(recorder, null, false) + + expect(protocol.supportsReAuth).toBe(true) + }) + + describe('unpacker configuration', () => { + test.each([ + [false, false], + [false, true], + [true, false], + [true, true] + ])( + 'should create unpacker with disableLosslessIntegers=%p and useBigInt=%p', + (disableLosslessIntegers, useBigInt) => { + const protocol = new BoltProtocolV5x8(null, null, { + disableLosslessIntegers, + useBigInt + }) + expect(protocol._unpacker._disableLosslessIntegers).toBe( + disableLosslessIntegers + ) + expect(protocol._unpacker._useBigInt).toBe(useBigInt) + } + ) + }) + + describe('notificationFilter', () => { + notificationFilterBehaviour.shouldSupportGqlNotificationFilterOnInitialize(newProtocol) + notificationFilterBehaviour.shouldSupportGqlNotificationFilterOnBeginTransaction(newProtocol) + notificationFilterBehaviour.shouldSupportGqlNotificationFilterOnRun(newProtocol) + }) + + describe('watermarks', () => { + it('.run() should configure watermarks', () => { + const recorder = new utils.MessageRecordingConnection() + const protocol = utils.spyProtocolWrite( + new BoltProtocolV5x8(recorder, null, false) + ) + + const query = 'RETURN $x, $y' + const parameters = { x: 'x', y: 'y' } + const observer = protocol.run(query, parameters, { + bookmarks: Bookmarks.empty(), + txConfig: TxConfig.empty(), + lowRecordWatermark: 100, + highRecordWatermark: 200 + }) + + expect(observer._lowRecordWatermark).toEqual(100) + expect(observer._highRecordWatermark).toEqual(200) + }) + }) + + describe('packstream', () => { + it('should configure v2 packer', () => { + const protocol = new BoltProtocolV5x8(null, null, false) + expect(protocol.packer()).toBeInstanceOf(v2.Packer) + }) + + it('should configure v2 unpacker', () => { + const protocol = new BoltProtocolV5x8(null, null, false) + expect(protocol.unpacker()).toBeInstanceOf(v2.Unpacker) + }) + }) + + describe('.packable()', () => { + it.each([ + ['Node', new Node(1, ['a'], { a: 'b' }, 'c')], + ['Relationship', new Relationship(1, 2, 3, 'a', { b: 'c' }, 'd', 'e', 'f')], + ['UnboundRelationship', new UnboundRelationship(1, 'a', { b: 'c' }, '1')], + ['Path', new Path(new Node(1, [], {}), new Node(2, [], {}), [])] + ])('should resultant function not pack graph types (%s)', (_, graphType) => { + const protocol = new BoltProtocolV5x8( + new utils.MessageRecordingConnection(), + null, + false + ) + + const packable = protocol.packable(graphType) + + expect(packable).toThrowErrorMatchingSnapshot() + }) + + it.each([ + ['Duration', new Duration(1, 1, 1, 1)], + ['LocalTime', new LocalTime(1, 1, 1, 1)], + ['Time', new Time(1, 1, 1, 1, 1)], + ['Date', new Date(1, 1, 1)], + ['LocalDateTime', new LocalDateTime(1, 1, 1, 1, 1, 1, 1)], + [ + 'DateTimeWithZoneOffset', + new DateTime(2022, 6, 14, 15, 21, 18, 183_000_000, 120 * 60) + ], + [ + 'DateTimeWithZoneOffset / 1978', + new DateTime(1978, 12, 16, 10, 5, 59, 128000987, -150 * 60) + ], + [ + 'DateTimeWithZoneId / Berlin 2:30 CET', + new DateTime(2022, 10, 30, 2, 30, 0, 183_000_000, 2 * 60 * 60, 'Europe/Berlin') + ], + [ + 'DateTimeWithZoneId / Berlin 2:30 CEST', + new DateTime(2022, 10, 30, 2, 30, 0, 183_000_000, 1 * 60 * 60, 'Europe/Berlin') + ], + ['Point2D', new Point(1, 1, 1)], + ['Point3D', new Point(1, 1, 1, 1)] + ])('should pack spatial types and temporal types (%s)', (_, object) => { + const buffer = alloc(256) + const protocol = new BoltProtocolV5x8( + new utils.MessageRecordingConnection(), + buffer, + { + disableLosslessIntegers: true + } + ) + + const packable = protocol.packable(object) + + expect(packable).not.toThrow() + + buffer.reset() + + const unpacked = protocol.unpack(buffer) + + expect(unpacked).toEqual(object) + }) + + it.each([ + [ + 'DateTimeWithZoneId / Australia', + new DateTime(2022, 6, 15, 15, 21, 18, 183_000_000, undefined, 'Australia/Eucla') + ], + [ + 'DateTimeWithZoneId', + new DateTime(2022, 6, 22, 15, 21, 18, 183_000_000, undefined, 'Europe/Berlin') + ], + [ + 'DateTimeWithZoneId / Europe just before turn CEST', + new DateTime(2022, 3, 27, 1, 59, 59, 183_000_000, undefined, 'Europe/Berlin') + ], + [ + 'DateTimeWithZoneId / Europe just 1 before turn CEST', + new DateTime(2022, 3, 27, 0, 59, 59, 183_000_000, undefined, 'Europe/Berlin') + ], + [ + 'DateTimeWithZoneId / Europe just after turn CEST', + new DateTime(2022, 3, 27, 3, 0, 0, 183_000_000, undefined, 'Europe/Berlin') + ], + [ + 'DateTimeWithZoneId / Europe just 1 after turn CEST', + new DateTime(2022, 3, 27, 4, 0, 0, 183_000_000, undefined, 'Europe/Berlin') + ], + [ + 'DateTimeWithZoneId / Europe just before turn CET', + new DateTime(2022, 10, 30, 2, 59, 59, 183_000_000, undefined, 'Europe/Berlin') + ], + [ + 'DateTimeWithZoneId / Europe just 1 before turn CET', + new DateTime(2022, 10, 30, 1, 59, 59, 183_000_000, undefined, 'Europe/Berlin') + ], + [ + 'DateTimeWithZoneId / Europe just after turn CET', + new DateTime(2022, 10, 30, 3, 0, 0, 183_000_000, undefined, 'Europe/Berlin') + ], + [ + 'DateTimeWithZoneId / Europe just 1 after turn CET', + new DateTime(2022, 10, 30, 4, 0, 0, 183_000_000, undefined, 'Europe/Berlin') + ], + [ + 'DateTimeWithZoneId / Sao Paulo just before turn summer time', + new DateTime(2018, 11, 4, 11, 59, 59, 183_000_000, undefined, 'America/Sao_Paulo') + ], + [ + 'DateTimeWithZoneId / Sao Paulo just 1 before turn summer time', + new DateTime(2018, 11, 4, 10, 59, 59, 183_000_000, undefined, 'America/Sao_Paulo') + ], + [ + 'DateTimeWithZoneId / Sao Paulo just after turn summer time', + new DateTime(2018, 11, 5, 1, 0, 0, 183_000_000, undefined, 'America/Sao_Paulo') + ], + [ + 'DateTimeWithZoneId / Sao Paulo just 1 after turn summer time', + new DateTime(2018, 11, 5, 2, 0, 0, 183_000_000, undefined, 'America/Sao_Paulo') + ], + [ + 'DateTimeWithZoneId / Sao Paulo just before turn winter time', + new DateTime(2019, 2, 17, 11, 59, 59, 183_000_000, undefined, 'America/Sao_Paulo') + ], + [ + 'DateTimeWithZoneId / Sao Paulo just 1 before turn winter time', + new DateTime(2019, 2, 17, 10, 59, 59, 183_000_000, undefined, 'America/Sao_Paulo') + ], + [ + 'DateTimeWithZoneId / Sao Paulo just after turn winter time', + new DateTime(2019, 2, 18, 0, 0, 0, 183_000_000, undefined, 'America/Sao_Paulo') + ], + [ + 'DateTimeWithZoneId / Sao Paulo just 1 after turn winter time', + new DateTime(2019, 2, 18, 1, 0, 0, 183_000_000, undefined, 'America/Sao_Paulo') + ], + [ + 'DateTimeWithZoneId / Istanbul', + new DateTime(1978, 12, 16, 12, 35, 59, 128000987, undefined, 'Europe/Istanbul') + ], + [ + 'DateTimeWithZoneId / Istanbul', + new DateTime(2020, 6, 15, 4, 30, 0, 183_000_000, undefined, 'Pacific/Honolulu') + ], + [ + 'DateWithWithZoneId / Berlin before common era', + new DateTime(-2020, 6, 15, 4, 30, 0, 183_000_000, undefined, 'Europe/Berlin') + ], + [ + 'DateWithWithZoneId / Max Date', + new DateTime(99_999, 12, 31, 23, 59, 59, 999_999_999, undefined, 'Pacific/Kiritimati') + ], + [ + 'DateWithWithZoneId / Min Date', + new DateTime(-99_999, 12, 31, 23, 59, 59, 999_999_999, undefined, 'Pacific/Samoa') + ], + [ + 'DateWithWithZoneId / Ambiguous date between 00 and 99', + new DateTime(50, 12, 31, 23, 59, 59, 999_999_999, undefined, 'Pacific/Samoa') + ] + ])('should pack and unpack DateTimeWithZoneId and without offset (%s)', (_, object) => { + const buffer = alloc(256) + const loggerFunction = jest.fn() + const protocol = new BoltProtocolV5x8( + new utils.MessageRecordingConnection(), + buffer, + { + disableLosslessIntegers: true + }, + undefined, + new Logger('debug', loggerFunction) + ) + + const packable = protocol.packable(object) + + expect(packable).not.toThrow() + expect(loggerFunction) + .toBeCalledWith('warn', + 'DateTime objects without "timeZoneOffsetSeconds" property ' + + 'are prune to bugs related to ambiguous times. For instance, ' + + '2022-10-30T2:30:00[Europe/Berlin] could be GMT+1 or GMT+2.') + + buffer.reset() + + const unpacked = protocol.unpack(buffer) + + expect(unpacked.timeZoneOffsetSeconds).toBeDefined() + + const unpackedDateTimeWithoutOffset = new DateTime( + unpacked.year, + unpacked.month, + unpacked.day, + unpacked.hour, + unpacked.minute, + unpacked.second, + unpacked.nanosecond, + undefined, + unpacked.timeZoneId + ) + + expect(unpackedDateTimeWithoutOffset).toEqual(object) + }) + + it('should pack and unpack DateTimeWithOffset', () => { + fc.assert( + fc.property( + fc.date({ + min: temporalUtil.newDate(utils.MIN_UTC_IN_MS + utils.ONE_DAY_IN_MS), + max: temporalUtil.newDate(utils.MAX_UTC_IN_MS - utils.ONE_DAY_IN_MS) + }), + fc.integer({ min: 0, max: 999_999 }), + utils.arbitraryTimeZoneId(), + (date, nanoseconds, timeZoneId) => { + const object = new DateTime( + date.getUTCFullYear(), + date.getUTCMonth() + 1, + date.getUTCDate(), + date.getUTCHours(), + date.getUTCMinutes(), + date.getUTCSeconds(), + date.getUTCMilliseconds() * 1_000_000 + nanoseconds, + undefined, + timeZoneId + ) + const buffer = alloc(256) + const loggerFunction = jest.fn() + const protocol = new BoltProtocolV5x8( + new utils.MessageRecordingConnection(), + buffer, + { + disableLosslessIntegers: true + }, + undefined, + new Logger('debug', loggerFunction) + ) + + const packable = protocol.packable(object) + + expect(packable).not.toThrow() + expect(loggerFunction) + .toBeCalledWith('warn', + 'DateTime objects without "timeZoneOffsetSeconds" property ' + + 'are prune to bugs related to ambiguous times. For instance, ' + + '2022-10-30T2:30:00[Europe/Berlin] could be GMT+1 or GMT+2.') + + buffer.reset() + + const unpacked = protocol.unpack(buffer) + + expect(unpacked.timeZoneOffsetSeconds).toBeDefined() + + const unpackedDateTimeWithoutOffset = new DateTime( + unpacked.year, + unpacked.month, + unpacked.day, + unpacked.hour, + unpacked.minute, + unpacked.second, + unpacked.nanosecond, + undefined, + unpacked.timeZoneId + ) + + expect(unpackedDateTimeWithoutOffset).toEqual(object) + }) + ) + }) + + it('should pack and unpack DateTimeWithZoneIdAndNoOffset', () => { + fc.assert( + fc.property(fc.date(), date => { + const object = DateTime.fromStandardDate(date) + const buffer = alloc(256) + const loggerFunction = jest.fn() + const protocol = new BoltProtocolV5x8( + new utils.MessageRecordingConnection(), + buffer, + { + disableLosslessIntegers: true + }, + undefined, + new Logger('debug', loggerFunction) + ) + + const packable = protocol.packable(object) + + expect(packable).not.toThrow() + + buffer.reset() + + const unpacked = protocol.unpack(buffer) + + expect(unpacked.timeZoneOffsetSeconds).toBeDefined() + + expect(unpacked).toEqual(object) + }) + ) + }) + }) + + describe('.unpack()', () => { + it.each([ + [ + 'Node', + new structure.Structure(0x4e, [1, ['a'], { c: 'd' }, 'elementId']), + new Node(1, ['a'], { c: 'd' }, 'elementId') + ], + [ + 'Relationship', + new structure.Structure(0x52, [1, 2, 3, '4', { 5: 6 }, 'elementId', 'node1', 'node2']), + new Relationship(1, 2, 3, '4', { 5: 6 }, 'elementId', 'node1', 'node2') + ], + [ + 'UnboundRelationship', + new structure.Structure(0x72, [1, '2', { 3: 4 }, 'elementId']), + new UnboundRelationship(1, '2', { 3: 4 }, 'elementId') + ], + [ + 'Path', + new structure.Structure( + 0x50, + [ + [ + new structure.Structure(0x4e, [1, ['2'], { 3: '4' }, 'node1']), + new structure.Structure(0x4e, [4, ['5'], { 6: 7 }, 'node2']), + new structure.Structure(0x4e, [2, ['3'], { 4: '5' }, 'node3']) + ], + [ + new structure.Structure(0x52, [3, 1, 4, 'reltype1', { 4: '5' }, 'rel1', 'node1', 'node2']), + new structure.Structure(0x52, [5, 4, 2, 'reltype2', { 6: 7 }, 'rel2', 'node2', 'node3']) + ], + [1, 1, 2, 2] + ] + ), + new Path( + new Node(1, ['2'], { 3: '4' }, 'node1'), + new Node(2, ['3'], { 4: '5' }, 'node3'), + [ + new PathSegment( + new Node(1, ['2'], { 3: '4' }, 'node1'), + new Relationship(3, 1, 4, 'reltype1', { 4: '5' }, 'rel1', 'node1', 'node2'), + new Node(4, ['5'], { 6: 7 }, 'node2') + ), + new PathSegment( + new Node(4, ['5'], { 6: 7 }, 'node2'), + new Relationship(5, 4, 2, 'reltype2', { 6: 7 }, 'rel2', 'node2', 'node3'), + new Node(2, ['3'], { 4: '5' }, 'node3') + ) + ] + ) + ] + ])('should unpack graph types (%s)', (_, struct, graphObject) => { + const buffer = alloc(256) + const protocol = new BoltProtocolV5x8( + new utils.MessageRecordingConnection(), + buffer, + false + ) + + const packable = protocol.packable(struct) + + expect(packable).not.toThrow() + + buffer.reset() + + const unpacked = protocol.unpack(buffer) + expect(unpacked).toEqual(graphObject) + }) + + it.each([ + [ + 'Node with less fields', + new structure.Structure(0x4e, [1, ['a'], { c: 'd' }]) + ], + [ + 'Node with more fields', + new structure.Structure(0x4e, [1, ['a'], { c: 'd' }, '1', 'b']) + ], + [ + 'Relationship with less fields', + new structure.Structure(0x52, [1, 2, 3, '4', { 5: 6 }]) + ], + [ + 'Relationship with more fields', + new structure.Structure(0x52, [1, 2, 3, '4', { 5: 6 }, '1', '2', '3', '4']) + ], + [ + 'UnboundRelationship with less fields', + new structure.Structure(0x72, [1, '2', { 3: 4 }]) + ], + [ + 'UnboundRelationship with more fields', + new structure.Structure(0x72, [1, '2', { 3: 4 }, '1', '2']) + ], + [ + 'Path with less fields', + new structure.Structure( + 0x50, + [ + [ + new structure.Structure(0x4e, [1, ['2'], { 3: '4' }]), + new structure.Structure(0x4e, [4, ['5'], { 6: 7 }]), + new structure.Structure(0x4e, [2, ['3'], { 4: '5' }]) + ], + [ + new structure.Structure(0x52, [3, 1, 4, 'rel1', { 4: '5' }]), + new structure.Structure(0x52, [5, 4, 2, 'rel2', { 6: 7 }]) + ] + ] + ) + ], + [ + 'Path with more fields', + new structure.Structure( + 0x50, + [ + [ + new structure.Structure(0x4e, [1, ['2'], { 3: '4' }]), + new structure.Structure(0x4e, [4, ['5'], { 6: 7 }]), + new structure.Structure(0x4e, [2, ['3'], { 4: '5' }]) + ], + [ + new structure.Structure(0x52, [3, 1, 4, 'rel1', { 4: '5' }]), + new structure.Structure(0x52, [5, 4, 2, 'rel2', { 6: 7 }]) + ], + [1, 1, 2, 2], + 'a' + ] + ) + ], + [ + 'Point with less fields', + new structure.Structure(0x58, [1, 2]) + ], + [ + 'Point with more fields', + new structure.Structure(0x58, [1, 2, 3, 4]) + ], + [ + 'Point3D with less fields', + new structure.Structure(0x59, [1, 2, 3]) + ], + + [ + 'Point3D with more fields', + new structure.Structure(0x59, [1, 2, 3, 4, 6]) + ], + [ + 'Duration with less fields', + new structure.Structure(0x45, [1, 2, 3]) + ], + [ + 'Duration with more fields', + new structure.Structure(0x45, [1, 2, 3, 4, 5]) + ], + [ + 'LocalTime with less fields', + new structure.Structure(0x74, []) + ], + [ + 'LocalTime with more fields', + new structure.Structure(0x74, [1, 2]) + ], + [ + 'Time with less fields', + new structure.Structure(0x54, [1]) + ], + [ + 'Time with more fileds', + new structure.Structure(0x54, [1, 2, 3]) + ], + [ + 'Date with less fields', + new structure.Structure(0x44, []) + ], + [ + 'Date with more fields', + new structure.Structure(0x44, [1, 2]) + ], + [ + 'LocalDateTime with less fields', + new structure.Structure(0x64, [1]) + ], + [ + 'LocalDateTime with more fields', + new structure.Structure(0x64, [1, 2, 3]) + ], + [ + 'DateTimeWithZoneOffset with less fields', + new structure.Structure(0x49, [1, 2]) + ], + [ + 'DateTimeWithZoneOffset with more fields', + new structure.Structure(0x49, [1, 2, 3, 4]) + ], + [ + 'DateTimeWithZoneId with less fields', + new structure.Structure(0x69, [1, 2]) + ], + [ + 'DateTimeWithZoneId with more fields', + new structure.Structure(0x69, [1, 2, 'America/Sao Paulo', 'Brasil']) + ] + ])('should not unpack with wrong size (%s)', (_, struct) => { + const buffer = alloc(256) + const protocol = new BoltProtocolV5x8( + new utils.MessageRecordingConnection(), + buffer, + false + ) + + const packable = protocol.packable(struct) + + expect(packable).not.toThrow() + + buffer.reset() + + const unpacked = protocol.unpack(buffer) + expect(() => unpacked instanceof structure.Structure).toThrowErrorMatchingSnapshot() + }) + + it.each([ + [ + 'Point', + new structure.Structure(0x58, [1, 2, 3]), + new Point(1, 2, 3) + ], + [ + 'Point3D', + new structure.Structure(0x59, [1, 2, 3, 4]), + new Point(1, 2, 3, 4) + ], + [ + 'Duration', + new structure.Structure(0x45, [1, 2, 3, 4]), + new Duration(1, 2, 3, 4) + ], + [ + 'LocalTime', + new structure.Structure(0x74, [1]), + new LocalTime(0, 0, 0, 1) + ], + [ + 'Time', + new structure.Structure(0x54, [1, 2]), + new Time(0, 0, 0, 1, 2) + ], + [ + 'Date', + new structure.Structure(0x44, [1]), + new Date(1970, 1, 2) + ], + [ + 'LocalDateTime', + new structure.Structure(0x64, [1, 2]), + new LocalDateTime(1970, 1, 1, 0, 0, 1, 2) + ], + [ + 'DateTimeWithZoneOffset', + new structure.Structure(0x49, [ + 1655212878, 183_000_000, 120 * 60 + ]), + new DateTime(2022, 6, 14, 15, 21, 18, 183_000_000, 120 * 60) + ], + [ + 'DateTimeWithZoneOffset / 1978', + new structure.Structure(0x49, [ + 282659759, 128000987, -150 * 60 + ]), + new DateTime(1978, 12, 16, 10, 5, 59, 128000987, -150 * 60) + ], + [ + 'DateTimeWithZoneId', + new structure.Structure(0x69, [ + 1655212878, 183_000_000, 'Europe/Berlin' + ]), + new DateTime(2022, 6, 14, 15, 21, 18, 183_000_000, 2 * 60 * 60, 'Europe/Berlin') + ], + [ + 'DateTimeWithZoneId / Australia', + new structure.Structure(0x69, [ + 1655212878, 183_000_000, 'Australia/Eucla' + ]), + new DateTime(2022, 6, 14, 22, 6, 18, 183_000_000, 8 * 60 * 60 + 45 * 60, 'Australia/Eucla') + ], + [ + 'DateTimeWithZoneId / Honolulu', + new structure.Structure(0x69, [ + 1592231400, 183_000_000, 'Pacific/Honolulu' + ]), + new DateTime(2020, 6, 15, 4, 30, 0, 183_000_000, -10 * 60 * 60, 'Pacific/Honolulu') + ], + [ + 'DateTimeWithZoneId / Midnight', + new structure.Structure(0x69, [ + 1685397950, 183_000_000, 'Europe/Berlin' + ]), + new DateTime(2023, 5, 30, 0, 5, 50, 183_000_000, 2 * 60 * 60, 'Europe/Berlin') + ] + ])('should unpack spatial types and temporal types (%s)', (_, struct, object) => { + const buffer = alloc(256) + const protocol = new BoltProtocolV5x8( + new utils.MessageRecordingConnection(), + buffer, + { + disableLosslessIntegers: true + } + ) + + const packable = protocol.packable(struct) + + expect(packable).not.toThrow() + + buffer.reset() + + const unpacked = protocol.unpack(buffer) + expect(unpacked).toEqual(object) + }) + + it.each([ + [ + 'DateTimeWithZoneOffset/0x46', + new structure.Structure(0x46, [1, 2, 3]) + ], + [ + 'DateTimeWithZoneId/0x66', + new structure.Structure(0x66, [1, 2, 'America/Sao_Paulo']) + ] + ])('should unpack deprecated temporal types as unknown structs (%s)', (_, struct) => { + const buffer = alloc(256) + const protocol = new BoltProtocolV5x8( + new utils.MessageRecordingConnection(), + buffer, + { + disableLosslessIntegers: true + } + ) + + const packable = protocol.packable(struct) + + expect(packable).not.toThrow() + + buffer.reset() + + const unpacked = protocol.unpack(buffer) + expect(unpacked).toEqual(struct) + }) + }) + + describe('result metadata enrichment', () => { + it('run should configure BoltProtocolV5x8._enrichMetadata as enrichMetadata', () => { + const database = 'testdb' + const bookmarks = new Bookmarks([ + 'neo4j:bookmark:v1:tx1', + 'neo4j:bookmark:v1:tx2' + ]) + const txConfig = new TxConfig({ + timeout: 5000, + metadata: { x: 1, y: 'something' } + }) + const recorder = new utils.MessageRecordingConnection() + const protocol = new BoltProtocolV5x8(recorder, null, false) + utils.spyProtocolWrite(protocol) + + const query = 'RETURN $x, $y' + const parameters = { x: 'x', y: 'y' } + + const observer = protocol.run(query, parameters, { + bookmarks, + txConfig, + database, + mode: WRITE + }) + + expect(observer._enrichMetadata).toBe(protocol._enrichMetadata) + }) + + describe('BoltProtocolV5x8._enrichMetadata', () => { + const protocol = newProtocol() + + it('should handle empty metadata', () => { + const metadata = protocol._enrichMetadata({}) + + expect(metadata).toEqual({}) + }) + + it('should handle metadata with random objects', () => { + const metadata = protocol._enrichMetadata({ + a: 1133, + b: 345 + }) + + expect(metadata).toEqual({ + a: 1133, + b: 345 + }) + }) + + it('should handle metadata not change notifications ', () => { + const metadata = protocol._enrichMetadata({ + a: 1133, + b: 345, + notifications: [ + { + severity: 'WARNING', + category: 'HINT' + } + ] + }) + + expect(metadata).toEqual({ + a: 1133, + b: 345, + notifications: [ + { + severity: 'WARNING', + category: 'HINT' + } + ] + }) + }) + + it.each([ + [null, null], + [undefined, undefined], + [[], []], + [statusesWithDiagnosticRecord(null, null), statusesWithDiagnosticRecord(null, null)], + [statusesWithDiagnosticRecord(undefined, undefined), statusesWithDiagnosticRecord({ + OPERATION: '', + OPERATION_CODE: '0', + CURRENT_SCHEMA: '/' + }, + { + OPERATION: '', + OPERATION_CODE: '0', + CURRENT_SCHEMA: '/' + })], + [ + statusesWithDiagnosticRecord({ + OPERATION: 'A' + }), + statusesWithDiagnosticRecord({ + OPERATION: 'A', + OPERATION_CODE: '0', + CURRENT_SCHEMA: '/' + }) + ], + [ + statusesWithDiagnosticRecord({ + OPERATION: 'A', + OPERATION_CODE: 'B' + }), + statusesWithDiagnosticRecord({ + OPERATION: 'A', + OPERATION_CODE: 'B', + CURRENT_SCHEMA: '/' + }) + ], + [ + statusesWithDiagnosticRecord({ + OPERATION: 'A', + OPERATION_CODE: 'B', + CURRENT_SCHEMA: '/C' + }), + statusesWithDiagnosticRecord({ + OPERATION: 'A', + OPERATION_CODE: 'B', + CURRENT_SCHEMA: '/C' + }) + ], + [ + statusesWithDiagnosticRecord({ + OPERATION: 'A', + OPERATION_CODE: 'B', + CURRENT_SCHEMA: '/C', + _status_parameters: { d: 'E' } + }), + statusesWithDiagnosticRecord({ + OPERATION: 'A', + OPERATION_CODE: 'B', + CURRENT_SCHEMA: '/C', + _status_parameters: { d: 'E' } + }) + ], + [ + statusesWithDiagnosticRecord({ + OPERATION: 'A', + OPERATION_CODE: 'B', + CURRENT_SCHEMA: '/C', + _status_parameters: { d: 'E' }, + _severity: 'F' + }), + statusesWithDiagnosticRecord({ + OPERATION: 'A', + OPERATION_CODE: 'B', + CURRENT_SCHEMA: '/C', + _status_parameters: { d: 'E' }, + _severity: 'F' + }) + ], + [ + statusesWithDiagnosticRecord({ + OPERATION: 'A', + OPERATION_CODE: 'B', + CURRENT_SCHEMA: '/C', + _status_parameters: { d: 'E' }, + _severity: 'F', + _classification: 'G' + }), + statusesWithDiagnosticRecord({ + OPERATION: 'A', + OPERATION_CODE: 'B', + CURRENT_SCHEMA: '/C', + _status_parameters: { d: 'E' }, + _severity: 'F', + _classification: 'G' + }) + ], + [ + statusesWithDiagnosticRecord({ + OPERATION: 'A', + OPERATION_CODE: 'B', + CURRENT_SCHEMA: '/C', + _status_parameters: { d: 'E' }, + _severity: 'F', + _classification: 'G', + _position: { + offset: 1, + line: 2, + column: 3 + } + }), + statusesWithDiagnosticRecord({ + OPERATION: 'A', + OPERATION_CODE: 'B', + CURRENT_SCHEMA: '/C', + _status_parameters: { d: 'E' }, + _severity: 'F', + _classification: 'G', + _position: { + offset: 1, + line: 2, + column: 3 + } + }) + ], + [ + statusesWithDiagnosticRecord({ + OPERATION: null + }), + statusesWithDiagnosticRecord({ + OPERATION: null, + OPERATION_CODE: '0', + CURRENT_SCHEMA: '/' + }) + ], + [ + statusesWithDiagnosticRecord({ + OPERATION: null, + OPERATION_CODE: null + }), + statusesWithDiagnosticRecord({ + OPERATION: null, + OPERATION_CODE: null, + CURRENT_SCHEMA: '/' + }) + ], + [ + statusesWithDiagnosticRecord({ + OPERATION: null, + OPERATION_CODE: null, + CURRENT_SCHEMA: null + }), + statusesWithDiagnosticRecord({ + OPERATION: null, + OPERATION_CODE: null, + CURRENT_SCHEMA: null + }) + ], + [ + statusesWithDiagnosticRecord({ + OPERATION: null, + OPERATION_CODE: null, + CURRENT_SCHEMA: null, + _status_parameters: null + }), + statusesWithDiagnosticRecord({ + OPERATION: null, + OPERATION_CODE: null, + CURRENT_SCHEMA: null, + _status_parameters: null + }) + ], + [ + statusesWithDiagnosticRecord({ + OPERATION: null, + OPERATION_CODE: null, + CURRENT_SCHEMA: null, + _status_parameters: null, + _severity: null + }), + statusesWithDiagnosticRecord({ + OPERATION: null, + OPERATION_CODE: null, + CURRENT_SCHEMA: null, + _status_parameters: null, + _severity: null + }) + ], + [ + statusesWithDiagnosticRecord({ + OPERATION: null, + OPERATION_CODE: null, + CURRENT_SCHEMA: null, + _status_parameters: null, + _severity: null, + _classification: null + }), + statusesWithDiagnosticRecord({ + OPERATION: null, + OPERATION_CODE: null, + CURRENT_SCHEMA: null, + _status_parameters: null, + _severity: null, + _classification: null + }) + ], + [ + statusesWithDiagnosticRecord({ + OPERATION: null, + OPERATION_CODE: null, + CURRENT_SCHEMA: null, + _status_parameters: null, + _severity: null, + _classification: null, + _position: null + }), + statusesWithDiagnosticRecord({ + OPERATION: null, + OPERATION_CODE: null, + CURRENT_SCHEMA: null, + _status_parameters: null, + _severity: null, + _classification: null, + _position: null + }) + ], + [ + statusesWithDiagnosticRecord({ + OPERATION: undefined, + OPERATION_CODE: undefined, + CURRENT_SCHEMA: undefined, + _status_parameters: undefined, + _severity: undefined, + _classification: undefined, + _position: undefined + }), + statusesWithDiagnosticRecord({ + OPERATION: undefined, + OPERATION_CODE: undefined, + CURRENT_SCHEMA: undefined, + _status_parameters: undefined, + _severity: undefined, + _classification: undefined, + _position: undefined + }) + ], + [ + [{ + gql_status: '03N33', + status_description: 'info: description', + neo4j_code: 'Neo.Info.My.Code', + title: 'Mitt title', + diagnostic_record: { + _classification: 'SOME', + _severity: 'INFORMATION' + } + }], + [{ + gql_status: '03N33', + status_description: 'info: description', + neo4j_code: 'Neo.Info.My.Code', + title: 'Mitt title', + diagnostic_record: { + OPERATION: '', + OPERATION_CODE: '0', + CURRENT_SCHEMA: '/', + _classification: 'SOME', + _severity: 'INFORMATION' + } + }] + ], + [ + [{ + gql_status: '03N33', + status_description: 'info: description', + neo4j_code: 'Neo.Info.My.Code', + description: 'description', + title: 'Mitt title', + diagnostic_record: { + _classification: 'SOME', + _severity: 'INFORMATION' + } + }], + [{ + gql_status: '03N33', + status_description: 'info: description', + neo4j_code: 'Neo.Info.My.Code', + title: 'Mitt title', + description: 'description', + diagnostic_record: { + OPERATION: '', + OPERATION_CODE: '0', + CURRENT_SCHEMA: '/', + _classification: 'SOME', + _severity: 'INFORMATION' + } + }] + ], + [ + [{ + gql_status: '03N33', + status_description: 'info: description', + description: 'description' + }], + [{ + gql_status: '03N33', + status_description: 'info: description', + description: 'description', + diagnostic_record: { + OPERATION: '', + OPERATION_CODE: '0', + CURRENT_SCHEMA: '/' + } + }] + ] + ])('should handle statuses (%o) ', (statuses, expectedStatuses) => { + const metadata = protocol._enrichMetadata({ + a: 1133, + b: 345, + statuses + }) + + expect(metadata).toEqual({ + a: 1133, + b: 345, + statuses: expectedStatuses + }) + }) + }) + + function statusesWithDiagnosticRecord (...diagnosticRecords) { + return diagnosticRecords.map(diagnosticRecord => { + return { + gql_status: '00000', + status_description: 'note: successful completion', + diagnostic_record: diagnosticRecord + } + }) + } + }) + + function newProtocol (recorder) { + return new BoltProtocolV5x8(recorder, null, false, undefined, undefined, () => {}) + } +}) diff --git a/packages/core/src/session.ts b/packages/core/src/session.ts index 83fa85083..2202d3633 100644 --- a/packages/core/src/session.ts +++ b/packages/core/src/session.ts @@ -530,9 +530,9 @@ class Session { * @returns {void} */ _onDatabaseNameResolved (database?: string, user?: string): void { - this._databaseGuess = { user: this._impersonatedUser ?? this._auth?.cacheKey ?? user ?? '', database } + this._databaseGuess = { user: this._impersonatedUser ?? user ?? '', database } if (this._homeDatabaseCallback != null) { - this._homeDatabaseCallback(this._impersonatedUser ?? this._auth?.cacheKey ?? user, database) + this._homeDatabaseCallback(this._impersonatedUser ?? user, database) } if (!this._databaseNameResolved) { const normalizedDatabase = database ?? '' diff --git a/packages/neo4j-driver-deno/lib/bolt-connection/bolt/request-message.js b/packages/neo4j-driver-deno/lib/bolt-connection/bolt/request-message.js index 07591b263..90232335b 100644 --- a/packages/neo4j-driver-deno/lib/bolt-connection/bolt/request-message.js +++ b/packages/neo4j-driver-deno/lib/bolt-connection/bolt/request-message.js @@ -84,11 +84,11 @@ export default class RequestMessage { * @return {RequestMessage} new INIT message. */ static init (clientName, authToken) { - const metadata = Object.assign({}, authToken) - metadata.cacheKey = undefined + const auth = Object.assign({}, authToken) + auth.cacheKey = undefined return new RequestMessage( INIT, - [clientName, metadata], + [clientName, auth], () => `INIT ${clientName} {...}` ) } @@ -269,11 +269,11 @@ export default class RequestMessage { * @returns {RequestMessage} new LOGON message */ static logon (authToken) { - const metadata = Object.assign({}, authToken) - metadata.cacheKey = undefined + const auth = Object.assign({}, authToken) + auth.cacheKey = undefined return new RequestMessage( LOGON, - [metadata], + [auth], () => 'LOGON { ... }' ) } diff --git a/packages/neo4j-driver-deno/lib/bolt-connection/connection-provider/connection-provider-routing.js b/packages/neo4j-driver-deno/lib/bolt-connection/connection-provider/connection-provider-routing.js index c9b819be5..1455549f6 100644 --- a/packages/neo4j-driver-deno/lib/bolt-connection/connection-provider/connection-provider-routing.js +++ b/packages/neo4j-driver-deno/lib/bolt-connection/connection-provider/connection-provider-routing.js @@ -79,7 +79,6 @@ export default class RoutingConnectionProvider extends PooledConnectionProvider this._log, await this._clientCertificateHolder.getClientCertificate(), this._routingContext, - undefined, this._channelSsrCallback.bind(this) ) }) @@ -178,7 +177,7 @@ export default class RoutingConnectionProvider extends PooledConnectionProvider onDatabaseNameResolved: (databaseName) => { context.database = context.database || databaseName if (onDatabaseNameResolved) { - onDatabaseNameResolved(databaseName, this._authenticationProvider?._authTokenManager?._authToken?.principal) + onDatabaseNameResolved(databaseName, this.auth?.cacheKey ?? this._authenticationProvider?._authTokenManager?._authToken?.cacheKey) } } }) diff --git a/packages/neo4j-driver-deno/lib/bolt-connection/connection/connection-channel.js b/packages/neo4j-driver-deno/lib/bolt-connection/connection/connection-channel.js index ca9737637..42e716461 100644 --- a/packages/neo4j-driver-deno/lib/bolt-connection/connection/connection-channel.js +++ b/packages/neo4j-driver-deno/lib/bolt-connection/connection/connection-channel.js @@ -43,8 +43,8 @@ export function createChannelConnection ( log, clientCertificate, serversideRouting = null, - createChannel = channelConfig => new Channel(channelConfig), - ssrCallback + ssrCallback = (_) => {}, + createChannel = channelConfig => new Channel(channelConfig) ) { const channelConfig = new ChannelConfig( address, diff --git a/packages/neo4j-driver-deno/lib/core/session.ts b/packages/neo4j-driver-deno/lib/core/session.ts index 9ac2433d8..286f6c8cd 100644 --- a/packages/neo4j-driver-deno/lib/core/session.ts +++ b/packages/neo4j-driver-deno/lib/core/session.ts @@ -530,9 +530,9 @@ class Session { * @returns {void} */ _onDatabaseNameResolved (database?: string, user?: string): void { - this._databaseGuess = { user: this._impersonatedUser ?? this._auth?.cacheKey ?? user ?? '', database } + this._databaseGuess = { user: this._impersonatedUser ?? user ?? '', database } if (this._homeDatabaseCallback != null) { - this._homeDatabaseCallback(this._impersonatedUser ?? this._auth?.cacheKey ?? user, database) + this._homeDatabaseCallback(this._impersonatedUser ?? user, database) } if (!this._databaseNameResolved) { const normalizedDatabase = database ?? '' From ae829240c71cb41f3f088fd5cf9685e7461a9aa4 Mon Sep 17 00:00:00 2001 From: MaxAake <61233757+MaxAake@users.noreply.github.com> Date: Mon, 11 Nov 2024 16:40:29 +0100 Subject: [PATCH 28/84] add bolt 5x8 feature --- .../bolt-protocol-v5x8.test.js.snap | 61 +++++++++++++++++++ .../testkit-backend/src/feature/common.js | 1 + 2 files changed, 62 insertions(+) create mode 100644 packages/bolt-connection/test/bolt/__snapshots__/bolt-protocol-v5x8.test.js.snap diff --git a/packages/bolt-connection/test/bolt/__snapshots__/bolt-protocol-v5x8.test.js.snap b/packages/bolt-connection/test/bolt/__snapshots__/bolt-protocol-v5x8.test.js.snap new file mode 100644 index 000000000..38356e707 --- /dev/null +++ b/packages/bolt-connection/test/bolt/__snapshots__/bolt-protocol-v5x8.test.js.snap @@ -0,0 +1,61 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`#unit BoltProtocolV5x8 .packable() should resultant function not pack graph types (Node) 1`] = `"It is not allowed to pass nodes in query parameters, given: (c:a {a:"b"})"`; + +exports[`#unit BoltProtocolV5x8 .packable() should resultant function not pack graph types (Path) 1`] = `"It is not allowed to pass paths in query parameters, given: [object Object]"`; + +exports[`#unit BoltProtocolV5x8 .packable() should resultant function not pack graph types (Relationship) 1`] = `"It is not allowed to pass relationships in query parameters, given: (e)-[:a {b:"c"}]->(f)"`; + +exports[`#unit BoltProtocolV5x8 .packable() should resultant function not pack graph types (UnboundRelationship) 1`] = `"It is not allowed to pass unbound relationships in query parameters, given: -[:a {b:"c"}]->"`; + +exports[`#unit BoltProtocolV5x8 .unpack() should not unpack with wrong size (Date with less fields) 1`] = `"Wrong struct size for Date, expected 1 but was 0"`; + +exports[`#unit BoltProtocolV5x8 .unpack() should not unpack with wrong size (Date with more fields) 1`] = `"Wrong struct size for Date, expected 1 but was 2"`; + +exports[`#unit BoltProtocolV5x8 .unpack() should not unpack with wrong size (DateTimeWithZoneId with less fields) 1`] = `"Wrong struct size for DateTimeWithZoneId, expected 3 but was 2"`; + +exports[`#unit BoltProtocolV5x8 .unpack() should not unpack with wrong size (DateTimeWithZoneId with more fields) 1`] = `"Wrong struct size for DateTimeWithZoneId, expected 3 but was 4"`; + +exports[`#unit BoltProtocolV5x8 .unpack() should not unpack with wrong size (DateTimeWithZoneOffset with less fields) 1`] = `"Wrong struct size for DateTimeWithZoneOffset, expected 3 but was 2"`; + +exports[`#unit BoltProtocolV5x8 .unpack() should not unpack with wrong size (DateTimeWithZoneOffset with more fields) 1`] = `"Wrong struct size for DateTimeWithZoneOffset, expected 3 but was 4"`; + +exports[`#unit BoltProtocolV5x8 .unpack() should not unpack with wrong size (Duration with less fields) 1`] = `"Wrong struct size for Duration, expected 4 but was 3"`; + +exports[`#unit BoltProtocolV5x8 .unpack() should not unpack with wrong size (Duration with more fields) 1`] = `"Wrong struct size for Duration, expected 4 but was 5"`; + +exports[`#unit BoltProtocolV5x8 .unpack() should not unpack with wrong size (LocalDateTime with less fields) 1`] = `"Wrong struct size for LocalDateTime, expected 2 but was 1"`; + +exports[`#unit BoltProtocolV5x8 .unpack() should not unpack with wrong size (LocalDateTime with more fields) 1`] = `"Wrong struct size for LocalDateTime, expected 2 but was 3"`; + +exports[`#unit BoltProtocolV5x8 .unpack() should not unpack with wrong size (LocalTime with less fields) 1`] = `"Wrong struct size for LocalTime, expected 1 but was 0"`; + +exports[`#unit BoltProtocolV5x8 .unpack() should not unpack with wrong size (LocalTime with more fields) 1`] = `"Wrong struct size for LocalTime, expected 1 but was 2"`; + +exports[`#unit BoltProtocolV5x8 .unpack() should not unpack with wrong size (Node with less fields) 1`] = `"Wrong struct size for Node, expected 4 but was 3"`; + +exports[`#unit BoltProtocolV5x8 .unpack() should not unpack with wrong size (Node with more fields) 1`] = `"Wrong struct size for Node, expected 4 but was 5"`; + +exports[`#unit BoltProtocolV5x8 .unpack() should not unpack with wrong size (Path with less fields) 1`] = `"Wrong struct size for Path, expected 3 but was 2"`; + +exports[`#unit BoltProtocolV5x8 .unpack() should not unpack with wrong size (Path with more fields) 1`] = `"Wrong struct size for Path, expected 3 but was 4"`; + +exports[`#unit BoltProtocolV5x8 .unpack() should not unpack with wrong size (Point with less fields) 1`] = `"Wrong struct size for Point2D, expected 3 but was 2"`; + +exports[`#unit BoltProtocolV5x8 .unpack() should not unpack with wrong size (Point with more fields) 1`] = `"Wrong struct size for Point2D, expected 3 but was 4"`; + +exports[`#unit BoltProtocolV5x8 .unpack() should not unpack with wrong size (Point3D with less fields) 1`] = `"Wrong struct size for Point3D, expected 4 but was 3"`; + +exports[`#unit BoltProtocolV5x8 .unpack() should not unpack with wrong size (Point3D with more fields) 1`] = `"Wrong struct size for Point3D, expected 4 but was 5"`; + +exports[`#unit BoltProtocolV5x8 .unpack() should not unpack with wrong size (Relationship with less fields) 1`] = `"Wrong struct size for Relationship, expected 8 but was 5"`; + +exports[`#unit BoltProtocolV5x8 .unpack() should not unpack with wrong size (Relationship with more fields) 1`] = `"Wrong struct size for Relationship, expected 8 but was 9"`; + +exports[`#unit BoltProtocolV5x8 .unpack() should not unpack with wrong size (Time with less fields) 1`] = `"Wrong struct size for Time, expected 2 but was 1"`; + +exports[`#unit BoltProtocolV5x8 .unpack() should not unpack with wrong size (Time with more fileds) 1`] = `"Wrong struct size for Time, expected 2 but was 3"`; + +exports[`#unit BoltProtocolV5x8 .unpack() should not unpack with wrong size (UnboundRelationship with less fields) 1`] = `"Wrong struct size for UnboundRelationship, expected 4 but was 3"`; + +exports[`#unit BoltProtocolV5x8 .unpack() should not unpack with wrong size (UnboundRelationship with more fields) 1`] = `"Wrong struct size for UnboundRelationship, expected 4 but was 5"`; diff --git a/packages/testkit-backend/src/feature/common.js b/packages/testkit-backend/src/feature/common.js index 5dec96283..eeb0aad3d 100644 --- a/packages/testkit-backend/src/feature/common.js +++ b/packages/testkit-backend/src/feature/common.js @@ -27,6 +27,7 @@ const features = [ 'Feature:Bolt:5.5', 'Feature:Bolt:5.6', 'Feature:Bolt:5.7', + 'Feature:Bolt:5.8', 'Feature:Bolt:Patch:UTC', 'Feature:API:ConnectionAcquisitionTimeout', 'Feature:API:Driver.ExecuteQuery', From fde547f4deea7342598ef1a63404adc8eee836a3 Mon Sep 17 00:00:00 2001 From: MaxAake <61233757+MaxAake@users.noreply.github.com> Date: Thu, 14 Nov 2024 13:07:42 +0100 Subject: [PATCH 29/84] cleanup and fixing for testkit tests --- .../bolt/bolt-protocol-v5x8.transformer.js | 2 +- .../connection-provider-routing.js | 9 ++-- packages/core/src/auth-util.ts | 24 ---------- packages/core/src/auth.ts | 8 ++-- packages/core/src/connection-provider.ts | 4 ++ packages/core/src/driver.ts | 16 ++----- packages/core/src/session.ts | 48 ++++++++++--------- packages/core/test/auth.test.ts | 11 ++--- packages/core/test/driver.test.ts | 8 ++-- .../bolt/bolt-protocol-v5x8.transformer.js | 2 +- .../connection-provider-routing.js | 9 ++-- .../neo4j-driver-deno/lib/core/auth-util.ts | 24 ---------- packages/neo4j-driver-deno/lib/core/auth.ts | 7 ++- .../lib/core/connection-provider.ts | 4 ++ packages/neo4j-driver-deno/lib/core/driver.ts | 16 ++----- .../neo4j-driver-deno/lib/core/session.ts | 48 ++++++++++--------- packages/neo4j-driver/src/index.js | 3 -- packages/neo4j-driver/test/auth.test.js | 14 ++---- packages/neo4j-driver/test/driver.test.js | 4 +- .../test/internal/connection-channel.test.js | 1 + 20 files changed, 96 insertions(+), 166 deletions(-) delete mode 100644 packages/core/src/auth-util.ts delete mode 100644 packages/neo4j-driver-deno/lib/core/auth-util.ts diff --git a/packages/bolt-connection/src/bolt/bolt-protocol-v5x8.transformer.js b/packages/bolt-connection/src/bolt/bolt-protocol-v5x8.transformer.js index a4e4d0957..e0f0c13bd 100644 --- a/packages/bolt-connection/src/bolt/bolt-protocol-v5x8.transformer.js +++ b/packages/bolt-connection/src/bolt/bolt-protocol-v5x8.transformer.js @@ -15,7 +15,7 @@ * limitations under the License. */ -import v5x7 from './bolt-protocol-v5x6.transformer' +import v5x7 from './bolt-protocol-v5x7.transformer' export default { ...v5x7 diff --git a/packages/bolt-connection/src/connection-provider/connection-provider-routing.js b/packages/bolt-connection/src/connection-provider/connection-provider-routing.js index f59d17499..8594c3039 100644 --- a/packages/bolt-connection/src/connection-provider/connection-provider-routing.js +++ b/packages/bolt-connection/src/connection-provider/connection-provider-routing.js @@ -160,7 +160,7 @@ export default class RoutingConnectionProvider extends PooledConnectionProvider this._handleSecurityError(error, address, conn, context.database) ) let currentRoutingTable - if (this._SSREnabled() && homeDb !== undefined) { + if (this.SSREnabled() && homeDb !== undefined) { currentRoutingTable = this._routingTableRegistry.get( homeDb, () => new RoutingTable({ database: homeDb }) @@ -177,7 +177,7 @@ export default class RoutingConnectionProvider extends PooledConnectionProvider onDatabaseNameResolved: (databaseName) => { context.database = context.database || databaseName if (onDatabaseNameResolved) { - onDatabaseNameResolved(databaseName, this.auth?.cacheKey ?? this._authenticationProvider?._authTokenManager?._authToken?.cacheKey) + onDatabaseNameResolved(databaseName) } } }) @@ -203,7 +203,6 @@ export default class RoutingConnectionProvider extends PooledConnectionProvider try { const connection = await this._connectionPool.acquire({ auth }, address) - if (auth) { await this._verifyStickyConnection({ auth, @@ -371,7 +370,7 @@ export default class RoutingConnectionProvider extends PooledConnectionProvider ) return this._refreshRoutingTable(currentRoutingTable, bookmarks, impersonatedUser, auth) .then(newRoutingTable => { - onDatabaseNameResolved(newRoutingTable.database, this._authenticationProvider?._authTokenManager?._authToken?.principal) + onDatabaseNameResolved(newRoutingTable.database) return newRoutingTable }) } @@ -687,7 +686,7 @@ export default class RoutingConnectionProvider extends PooledConnectionProvider } } - _SSREnabled () { + SSREnabled () { return this._withSSR > 0 && this._withoutSSR === 0 } } diff --git a/packages/core/src/auth-util.ts b/packages/core/src/auth-util.ts deleted file mode 100644 index 42a2ea25b..000000000 --- a/packages/core/src/auth-util.ts +++ /dev/null @@ -1,24 +0,0 @@ -/** - * Copyright (c) "Neo4j" - * Neo4j Sweden AB [https://neo4j.com] - * - * 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. - */ - -export function hash (string: string): string { // PLACEHOLDER HASH FUNCTION - let hash = 0 - for (let i = 0; i < string.length; ++i) { - hash = Math.imul(31, hash) + string.charCodeAt(i) - } - return hash.toString() -} diff --git a/packages/core/src/auth.ts b/packages/core/src/auth.ts index 0049d25c4..023988a5c 100644 --- a/packages/core/src/auth.ts +++ b/packages/core/src/auth.ts @@ -15,8 +15,6 @@ * limitations under the License. */ -import { hash } from './auth-util' - /** * @property {function(username: string, password: string, realm: ?string)} basic the function to create a * basic authentication token. @@ -46,14 +44,14 @@ const auth = { scheme: 'kerberos', principal: '', // This empty string is required for backwards compatibility. credentials: base64EncodedTicket, - cacheKey: hash(base64EncodedTicket) + cacheKey: base64EncodedTicket } }, bearer: (base64EncodedToken: string) => { return { scheme: 'bearer', credentials: base64EncodedToken, - cacheKey: hash(base64EncodedToken) + cacheKey: base64EncodedToken } }, none: () => { @@ -81,7 +79,7 @@ const auth = { if (isNotEmpty(parameters)) { output.parameters = parameters } - output.cacheKey = hash(principal + (isNotEmpty(credentials) ? credentials : '') + (isNotEmpty(realm) ? realm : '') + scheme) + output.cacheKey = principal + (isNotEmpty(credentials) ? credentials : '') + (isNotEmpty(realm) ? realm : '') + scheme return output } } diff --git a/packages/core/src/connection-provider.ts b/packages/core/src/connection-provider.ts index aca74f375..f5dcc0a54 100644 --- a/packages/core/src/connection-provider.ts +++ b/packages/core/src/connection-provider.ts @@ -113,6 +113,10 @@ class ConnectionProvider { throw Error('Not implemented') } + SSREnabled (): boolean { + return false + } + /** * This method verifies the connectivity of the database by trying to acquire a connection * for each server available in the cluster. diff --git a/packages/core/src/driver.ts b/packages/core/src/driver.ts index f92b079c5..4672ca009 100644 --- a/packages/core/src/driver.ts +++ b/packages/core/src/driver.ts @@ -99,7 +99,6 @@ type CreateSession = (args: { log: Logger homeDatabaseCallback?: (user: string, database: any) => void removeFailureFromCache?: (database: string) => void - beginDbCallback?: (user: string, database: string) => void }) => Session type CreateQueryExecutor = (createSession: (config: { database?: string, bookmarkManager?: BookmarkManager }) => Session) => QueryExecutor @@ -848,12 +847,6 @@ class Driver { this.homeDatabaseCache.set(user, database) } - _beginDbCallback (database: string, user: string): void { - if (this.homeDatabaseCache.get(user) !== undefined && this.homeDatabaseCache.get(user) !== database) { - this.homeDatabaseCache.delete(user) - } - } - _removeFailureFromCache (database: string): void { this.homeDatabaseCache.forEach((_, key) => { if (this.homeDatabaseCache.get(key) === database) { @@ -888,10 +881,11 @@ class Driver { }): Session { const sessionMode = Session._validateSessionMode(defaultAccessMode) const connectionProvider = this._getOrCreateConnectionProvider() - const cachedUser = impersonatedUser ?? auth?.cacheKey ?? this._config.user - const cachedHomeDatabase = cachedUser !== undefined ? this.homeDatabaseCache.get(cachedUser) : undefined + let cachedHomeDatabase + if (database !== undefined) { + cachedHomeDatabase = this.homeDatabaseCache.get(impersonatedUser ?? auth?.cacheKey ?? 'DEFAULT') + } const homeDatabaseCallback = this._homeDatabaseCallback.bind(this) - const beginDbCallback = this._beginDbCallback.bind(this) const removeFailureFromCache = this._removeFailureFromCache.bind(this) const bookmarks = bookmarkOrBookmarks != null ? new Bookmarks(bookmarkOrBookmarks) @@ -903,7 +897,6 @@ class Driver { bookmarks, config: { cachedHomeDatabase, - cachedUser, ...this._config }, reactive, @@ -914,7 +907,6 @@ class Driver { auth, log: this._log, homeDatabaseCallback, - beginDbCallback, removeFailureFromCache }) } diff --git a/packages/core/src/session.ts b/packages/core/src/session.ts index 2202d3633..56fc0ccda 100644 --- a/packages/core/src/session.ts +++ b/packages/core/src/session.ts @@ -47,11 +47,6 @@ interface TransactionConfig { metadata?: object } -interface DbGuess { - database: any - user: string -} - /** * A Session instance is used for handling the connection and * sending queries through the connection. @@ -80,9 +75,8 @@ class Session { private readonly _notificationFilter?: NotificationFilter private readonly _log: Logger private readonly _homeDatabaseCallback: Function | undefined - private readonly _driverBeginDbCallback: Function | undefined private readonly _auth: AuthToken | undefined - private _databaseGuess: DbGuess | undefined + private _databaseGuess: string | undefined /** * @constructor * @protected @@ -112,8 +106,7 @@ class Session { auth, log, homeDatabaseCallback, - removeFailureFromCache, - beginDbCallback + removeFailureFromCache }: { mode: SessionMode connectionProvider: ConnectionProvider @@ -129,14 +122,12 @@ class Session { log: Logger homeDatabaseCallback?: (user: string, database: string) => void removeFailureFromCache?: (database: string) => void - beginDbCallback?: (user: string, database: string) => void }) { this._mode = mode this._database = database this._reactive = reactive this._fetchSize = fetchSize this._homeDatabaseCallback = homeDatabaseCallback - this._driverBeginDbCallback = beginDbCallback this._auth = auth this._getConnectionAcquistionBookmarks = this._getConnectionAcquistionBookmarks.bind(this) this._readConnectionHolder = new ConnectionHolder({ @@ -176,9 +167,7 @@ class Session { this._bookmarkManager = bookmarkManager this._notificationFilter = notificationFilter this._log = log - if (config?.cachedUser !== undefined && config?.cachedHomeDatabase !== undefined) { - this._databaseGuess = { user: config?.cachedUser, database: config?.cachedHomeDatabase } - } + this._databaseGuess = config?.cachedHomeDatabase } /** @@ -275,7 +264,7 @@ class Session { resultPromise = Promise.reject( newError('Cannot run query in a closed session.') ) - } else if (!this._hasTx && connectionHolder.initializeConnection(this._databaseGuess?.database)) { + } else if (!this._hasTx && connectionHolder.initializeConnection(this._databaseGuess)) { resultPromise = connectionHolder .getConnection() // Connection won't be null at this point since the initialize method @@ -331,7 +320,7 @@ class Session { const mode = Session._validateSessionMode(accessMode) const connectionHolder = this._connectionHolderWithMode(mode) - connectionHolder.initializeConnection(this._databaseGuess?.database) + connectionHolder.initializeConnection(this._databaseGuess) this._hasTx = true const tx = new TransactionPromise({ @@ -529,12 +518,12 @@ class Session { * @param {string|undefined} database The resolved database name * @returns {void} */ - _onDatabaseNameResolved (database?: string, user?: string): void { - this._databaseGuess = { user: this._impersonatedUser ?? user ?? '', database } - if (this._homeDatabaseCallback != null) { - this._homeDatabaseCallback(this._impersonatedUser ?? user, database) - } + _onDatabaseNameResolved (database?: string): void { + this._databaseGuess = database if (!this._databaseNameResolved) { + if (this._homeDatabaseCallback != null) { + this._homeDatabaseCallback(this._impersonatedUser ?? this._auth?.cacheKey ?? 'DEFAULT', database) + } const normalizedDatabase = database ?? '' this._database = normalizedDatabase this._readConnectionHolder.setDatabase(normalizedDatabase) @@ -544,8 +533,18 @@ class Session { } _beginDbCallback (database: string): void { - if (this._driverBeginDbCallback !== undefined && this._databaseGuess !== undefined) { - this._driverBeginDbCallback(database, this._databaseGuess.user) + if (this._connectionHolderWithMode(this._mode).connectionProvider()?.SSREnabled() !== null && this._connectionHolderWithMode(this._mode).connectionProvider()?.SSREnabled() !== true) { + this._databaseGuess = database + if (!this._databaseNameResolved) { + if (this._homeDatabaseCallback != null) { + this._homeDatabaseCallback(this._impersonatedUser ?? this._auth?.cacheKey ?? 'DEFAULT', database) + } + const normalizedDatabase = database ?? '' + this._database = normalizedDatabase + this._readConnectionHolder.setDatabase(normalizedDatabase) + this._writeConnectionHolder.setDatabase(normalizedDatabase) + this._databaseNameResolved = true + } } } @@ -613,6 +612,9 @@ class Session { * @returns {void} */ _onCompleteCallback (meta: { bookmark: string | string[], db?: string }, previousBookmarks?: Bookmarks): void { + if (meta.db !== undefined) { + this._beginDbCallback(meta.db) + } this._updateBookmarks(new Bookmarks(meta.bookmark), previousBookmarks, meta.db) } diff --git a/packages/core/test/auth.test.ts b/packages/core/test/auth.test.ts index e978aa52a..302448a06 100644 --- a/packages/core/test/auth.test.ts +++ b/packages/core/test/auth.test.ts @@ -15,11 +15,10 @@ * limitations under the License. */ import auth from '../src/auth' -import { hash } from '../src/auth-util' describe('auth', () => { test('.bearer()', () => { - expect(auth.bearer('==Qyahiadakkda')).toEqual({ scheme: 'bearer', credentials: '==Qyahiadakkda', cacheKey: hash('==Qyahiadakkda') }) + expect(auth.bearer('==Qyahiadakkda')).toEqual({ scheme: 'bearer', credentials: '==Qyahiadakkda', cacheKey: '==Qyahiadakkda' }) }) test.each([ @@ -31,7 +30,7 @@ describe('auth', () => { credentials: 'pass', realm: 'realm', parameters: { param: 'param' }, - cacheKey: hash('user' + 'pass' + 'realm' + 'scheme') + cacheKey: 'user' + 'pass' + 'realm' + 'scheme' } ], [ @@ -39,7 +38,7 @@ describe('auth', () => { { scheme: 'scheme', principal: 'user', - cacheKey: hash('user' + 'scheme') + cacheKey: 'user' + 'scheme' } ], [ @@ -47,7 +46,7 @@ describe('auth', () => { { scheme: 'scheme', principal: 'user', - cacheKey: hash('user' + 'scheme') + cacheKey: 'user' + 'scheme' } ], [ @@ -55,7 +54,7 @@ describe('auth', () => { { scheme: 'scheme', principal: 'user', - cacheKey: hash('user' + 'scheme') + cacheKey: 'user' + 'scheme' } ] ])('.custom()', (args, output) => { diff --git a/packages/core/test/driver.test.ts b/packages/core/test/driver.test.ts index 370dbaff4..c321b409f 100644 --- a/packages/core/test/driver.test.ts +++ b/packages/core/test/driver.test.ts @@ -70,7 +70,7 @@ describe('Driver', () => { const session = driver?.session({ impersonatedUser }) expect(session).not.toBeUndefined() - expect(createSession).toHaveBeenCalledWith(expectedSessionParams({ impersonatedUser }, impersonatedUser)) + expect(createSession).toHaveBeenCalledWith(expectedSessionParams({ impersonatedUser })) }) it('should create the session without impersonated user', () => { @@ -91,7 +91,7 @@ describe('Driver', () => { const session = driver?.session({ auth }) expect(session).not.toBeUndefined() - expect(createSession).toHaveBeenCalledWith(expectedSessionParams({ auth }, auth.principal)) + expect(createSession).toHaveBeenCalledWith(expectedSessionParams({ auth })) }) it('should create the session without auth', () => { @@ -692,11 +692,10 @@ describe('Driver', () => { ) => connectionProvider } - function expectedSessionParams (extra: any = {}, cachedUser: string | undefined = undefined): any { + function expectedSessionParams (extra: any = {}): any { return { bookmarks: Bookmarks.empty(), config: { - cachedUser, connectionAcquisitionTimeout: 60000, fetchSize: 1000, maxConnectionLifetime: 3600000, @@ -712,7 +711,6 @@ describe('Driver', () => { // @ts-expect-error log: driver?._log, homeDatabaseCallback: expect.any(Function), - beginDbCallback: expect.any(Function), removeFailureFromCache: expect.any(Function), ...extra } diff --git a/packages/neo4j-driver-deno/lib/bolt-connection/bolt/bolt-protocol-v5x8.transformer.js b/packages/neo4j-driver-deno/lib/bolt-connection/bolt/bolt-protocol-v5x8.transformer.js index e151c7523..89304f7a9 100644 --- a/packages/neo4j-driver-deno/lib/bolt-connection/bolt/bolt-protocol-v5x8.transformer.js +++ b/packages/neo4j-driver-deno/lib/bolt-connection/bolt/bolt-protocol-v5x8.transformer.js @@ -15,7 +15,7 @@ * limitations under the License. */ -import v5x7 from './bolt-protocol-v5x6.transformer.js' +import v5x7 from './bolt-protocol-v5x7.transformer.js' export default { ...v5x7 diff --git a/packages/neo4j-driver-deno/lib/bolt-connection/connection-provider/connection-provider-routing.js b/packages/neo4j-driver-deno/lib/bolt-connection/connection-provider/connection-provider-routing.js index 1455549f6..7d829e52b 100644 --- a/packages/neo4j-driver-deno/lib/bolt-connection/connection-provider/connection-provider-routing.js +++ b/packages/neo4j-driver-deno/lib/bolt-connection/connection-provider/connection-provider-routing.js @@ -160,7 +160,7 @@ export default class RoutingConnectionProvider extends PooledConnectionProvider this._handleSecurityError(error, address, conn, context.database) ) let currentRoutingTable - if (this._SSREnabled() && homeDb !== undefined) { + if (this.SSREnabled() && homeDb !== undefined) { currentRoutingTable = this._routingTableRegistry.get( homeDb, () => new RoutingTable({ database: homeDb }) @@ -177,7 +177,7 @@ export default class RoutingConnectionProvider extends PooledConnectionProvider onDatabaseNameResolved: (databaseName) => { context.database = context.database || databaseName if (onDatabaseNameResolved) { - onDatabaseNameResolved(databaseName, this.auth?.cacheKey ?? this._authenticationProvider?._authTokenManager?._authToken?.cacheKey) + onDatabaseNameResolved(databaseName) } } }) @@ -203,7 +203,6 @@ export default class RoutingConnectionProvider extends PooledConnectionProvider try { const connection = await this._connectionPool.acquire({ auth }, address) - if (auth) { await this._verifyStickyConnection({ auth, @@ -371,7 +370,7 @@ export default class RoutingConnectionProvider extends PooledConnectionProvider ) return this._refreshRoutingTable(currentRoutingTable, bookmarks, impersonatedUser, auth) .then(newRoutingTable => { - onDatabaseNameResolved(newRoutingTable.database, this._authenticationProvider?._authTokenManager?._authToken?.principal) + onDatabaseNameResolved(newRoutingTable.database) return newRoutingTable }) } @@ -687,7 +686,7 @@ export default class RoutingConnectionProvider extends PooledConnectionProvider } } - _SSREnabled () { + SSREnabled () { return this._withSSR > 0 && this._withoutSSR === 0 } } diff --git a/packages/neo4j-driver-deno/lib/core/auth-util.ts b/packages/neo4j-driver-deno/lib/core/auth-util.ts deleted file mode 100644 index 42a2ea25b..000000000 --- a/packages/neo4j-driver-deno/lib/core/auth-util.ts +++ /dev/null @@ -1,24 +0,0 @@ -/** - * Copyright (c) "Neo4j" - * Neo4j Sweden AB [https://neo4j.com] - * - * 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. - */ - -export function hash (string: string): string { // PLACEHOLDER HASH FUNCTION - let hash = 0 - for (let i = 0; i < string.length; ++i) { - hash = Math.imul(31, hash) + string.charCodeAt(i) - } - return hash.toString() -} diff --git a/packages/neo4j-driver-deno/lib/core/auth.ts b/packages/neo4j-driver-deno/lib/core/auth.ts index 7630c28c2..b6e944827 100644 --- a/packages/neo4j-driver-deno/lib/core/auth.ts +++ b/packages/neo4j-driver-deno/lib/core/auth.ts @@ -15,7 +15,6 @@ * limitations under the License. */ -import { hash } from './auth-util.ts' /** * @property {function(username: string, password: string, realm: ?string)} basic the function to create a @@ -46,14 +45,14 @@ const auth = { scheme: 'kerberos', principal: '', // This empty string is required for backwards compatibility. credentials: base64EncodedTicket, - cacheKey: hash(base64EncodedTicket) + cacheKey: base64EncodedTicket } }, bearer: (base64EncodedToken: string) => { return { scheme: 'bearer', credentials: base64EncodedToken, - cacheKey: hash(base64EncodedToken) + cacheKey: base64EncodedToken } }, none: () => { @@ -81,7 +80,7 @@ const auth = { if (isNotEmpty(parameters)) { output.parameters = parameters } - output.cacheKey = hash(principal + (isNotEmpty(credentials) ? credentials : '') + (isNotEmpty(realm) ? realm : '') + scheme) + output.cacheKey = principal + (isNotEmpty(credentials) ? credentials : '') + (isNotEmpty(realm) ? realm : '') + scheme return output } } diff --git a/packages/neo4j-driver-deno/lib/core/connection-provider.ts b/packages/neo4j-driver-deno/lib/core/connection-provider.ts index 7be2c59e1..252b7f2a1 100644 --- a/packages/neo4j-driver-deno/lib/core/connection-provider.ts +++ b/packages/neo4j-driver-deno/lib/core/connection-provider.ts @@ -113,6 +113,10 @@ class ConnectionProvider { throw Error('Not implemented') } + SSREnabled (): boolean { + return false + } + /** * This method verifies the connectivity of the database by trying to acquire a connection * for each server available in the cluster. diff --git a/packages/neo4j-driver-deno/lib/core/driver.ts b/packages/neo4j-driver-deno/lib/core/driver.ts index 5ed793c61..abd9412ff 100644 --- a/packages/neo4j-driver-deno/lib/core/driver.ts +++ b/packages/neo4j-driver-deno/lib/core/driver.ts @@ -99,7 +99,6 @@ type CreateSession = (args: { log: Logger homeDatabaseCallback?: (user: string, database: any) => void removeFailureFromCache?: (database: string) => void - beginDbCallback?: (user: string, database: string) => void }) => Session type CreateQueryExecutor = (createSession: (config: { database?: string, bookmarkManager?: BookmarkManager }) => Session) => QueryExecutor @@ -848,12 +847,6 @@ class Driver { this.homeDatabaseCache.set(user, database) } - _beginDbCallback (database: string, user: string): void { - if (this.homeDatabaseCache.get(user) !== undefined && this.homeDatabaseCache.get(user) !== database) { - this.homeDatabaseCache.delete(user) - } - } - _removeFailureFromCache (database: string): void { this.homeDatabaseCache.forEach((_, key) => { if (this.homeDatabaseCache.get(key) === database) { @@ -888,10 +881,11 @@ class Driver { }): Session { const sessionMode = Session._validateSessionMode(defaultAccessMode) const connectionProvider = this._getOrCreateConnectionProvider() - const cachedUser = impersonatedUser ?? auth?.cacheKey ?? this._config.user - const cachedHomeDatabase = cachedUser !== undefined ? this.homeDatabaseCache.get(cachedUser) : undefined + let cachedHomeDatabase + if(database !== undefined) { + cachedHomeDatabase = this.homeDatabaseCache.get(impersonatedUser ?? auth?.cacheKey ?? "DEFAULT") + } const homeDatabaseCallback = this._homeDatabaseCallback.bind(this) - const beginDbCallback = this._beginDbCallback.bind(this) const removeFailureFromCache = this._removeFailureFromCache.bind(this) const bookmarks = bookmarkOrBookmarks != null ? new Bookmarks(bookmarkOrBookmarks) @@ -903,7 +897,6 @@ class Driver { bookmarks, config: { cachedHomeDatabase, - cachedUser, ...this._config }, reactive, @@ -914,7 +907,6 @@ class Driver { auth, log: this._log, homeDatabaseCallback, - beginDbCallback, removeFailureFromCache }) } diff --git a/packages/neo4j-driver-deno/lib/core/session.ts b/packages/neo4j-driver-deno/lib/core/session.ts index 286f6c8cd..691f067df 100644 --- a/packages/neo4j-driver-deno/lib/core/session.ts +++ b/packages/neo4j-driver-deno/lib/core/session.ts @@ -47,11 +47,6 @@ interface TransactionConfig { metadata?: object } -interface DbGuess { - database: any - user: string -} - /** * A Session instance is used for handling the connection and * sending queries through the connection. @@ -80,9 +75,8 @@ class Session { private readonly _notificationFilter?: NotificationFilter private readonly _log: Logger private readonly _homeDatabaseCallback: Function | undefined - private readonly _driverBeginDbCallback: Function | undefined private readonly _auth: AuthToken | undefined - private _databaseGuess: DbGuess | undefined + private _databaseGuess: string | undefined /** * @constructor * @protected @@ -112,8 +106,7 @@ class Session { auth, log, homeDatabaseCallback, - removeFailureFromCache, - beginDbCallback + removeFailureFromCache }: { mode: SessionMode connectionProvider: ConnectionProvider @@ -129,14 +122,12 @@ class Session { log: Logger homeDatabaseCallback?: (user: string, database: string) => void removeFailureFromCache?: (database: string) => void - beginDbCallback?: (user: string, database: string) => void }) { this._mode = mode this._database = database this._reactive = reactive this._fetchSize = fetchSize this._homeDatabaseCallback = homeDatabaseCallback - this._driverBeginDbCallback = beginDbCallback this._auth = auth this._getConnectionAcquistionBookmarks = this._getConnectionAcquistionBookmarks.bind(this) this._readConnectionHolder = new ConnectionHolder({ @@ -176,9 +167,7 @@ class Session { this._bookmarkManager = bookmarkManager this._notificationFilter = notificationFilter this._log = log - if (config?.cachedUser !== undefined && config?.cachedHomeDatabase !== undefined) { - this._databaseGuess = { user: config?.cachedUser, database: config?.cachedHomeDatabase } - } + this._databaseGuess = config?.cachedHomeDatabase } /** @@ -275,7 +264,7 @@ class Session { resultPromise = Promise.reject( newError('Cannot run query in a closed session.') ) - } else if (!this._hasTx && connectionHolder.initializeConnection(this._databaseGuess?.database)) { + } else if (!this._hasTx && connectionHolder.initializeConnection(this._databaseGuess)) { resultPromise = connectionHolder .getConnection() // Connection won't be null at this point since the initialize method @@ -331,7 +320,7 @@ class Session { const mode = Session._validateSessionMode(accessMode) const connectionHolder = this._connectionHolderWithMode(mode) - connectionHolder.initializeConnection(this._databaseGuess?.database) + connectionHolder.initializeConnection(this._databaseGuess) this._hasTx = true const tx = new TransactionPromise({ @@ -529,12 +518,12 @@ class Session { * @param {string|undefined} database The resolved database name * @returns {void} */ - _onDatabaseNameResolved (database?: string, user?: string): void { - this._databaseGuess = { user: this._impersonatedUser ?? user ?? '', database } - if (this._homeDatabaseCallback != null) { - this._homeDatabaseCallback(this._impersonatedUser ?? user, database) - } + _onDatabaseNameResolved (database?: string): void { + this._databaseGuess = database if (!this._databaseNameResolved) { + if (this._homeDatabaseCallback != null) { + this._homeDatabaseCallback(this._impersonatedUser ?? this._auth?.cacheKey ?? "DEFAULT", database) + } const normalizedDatabase = database ?? '' this._database = normalizedDatabase this._readConnectionHolder.setDatabase(normalizedDatabase) @@ -544,8 +533,18 @@ class Session { } _beginDbCallback (database: string): void { - if (this._driverBeginDbCallback !== undefined && this._databaseGuess !== undefined) { - this._driverBeginDbCallback(database, this._databaseGuess.user) + if(this._connectionHolderWithMode(this._mode).connectionProvider()?.SSREnabled() !== null && this._connectionHolderWithMode(this._mode).connectionProvider()?.SSREnabled() !== true){ + this._databaseGuess = database + if (!this._databaseNameResolved) { + if (this._homeDatabaseCallback != null) { + this._homeDatabaseCallback(this._impersonatedUser ?? this._auth?.cacheKey ?? "DEFAULT", database) + } + const normalizedDatabase = database ?? '' + this._database = normalizedDatabase + this._readConnectionHolder.setDatabase(normalizedDatabase) + this._writeConnectionHolder.setDatabase(normalizedDatabase) + this._databaseNameResolved = true + } } } @@ -613,6 +612,9 @@ class Session { * @returns {void} */ _onCompleteCallback (meta: { bookmark: string | string[], db?: string }, previousBookmarks?: Bookmarks): void { + if(meta.db !== undefined) { + this._beginDbCallback(meta.db) + } this._updateBookmarks(new Bookmarks(meta.bookmark), previousBookmarks, meta.db) } diff --git a/packages/neo4j-driver/src/index.js b/packages/neo4j-driver/src/index.js index ae4be06cc..16ede9447 100644 --- a/packages/neo4j-driver/src/index.js +++ b/packages/neo4j-driver/src/index.js @@ -189,9 +189,6 @@ function driver (url, authToken, config = {}) { typename: routing ? 'Routing' : 'Direct', routing } - if (authToken !== undefined && authToken.scheme === 'basic') { - config.user = authToken.principal - } return new Driver(meta, config, createConnectionProviderFunction()) function createConnectionProviderFunction () { diff --git a/packages/neo4j-driver/test/auth.test.js b/packages/neo4j-driver/test/auth.test.js index 90f3caba2..216972c50 100644 --- a/packages/neo4j-driver/test/auth.test.js +++ b/packages/neo4j-driver/test/auth.test.js @@ -17,14 +17,6 @@ import neo4j from '../src' -function hash (string) { - let hash = 0 - for (let i = 0; i < string.length; ++i) { - hash = Math.imul(31, hash) + string.charCodeAt(i) - } - return hash.toString() -} - describe('#unit auth', () => { it('should use correct username and password in basic auth', () => { const token = neo4j.auth.basic('cat', 'dog') @@ -53,7 +45,7 @@ describe('#unit auth', () => { scheme: 'kerberos', principal: '', credentials: 'my-ticket', - cacheKey: hash('my-ticket') + cacheKey: 'my-ticket' }) }) @@ -64,7 +56,7 @@ describe('#unit auth', () => { principal: 'cat', credentials: 'dog', realm: 'apartment', - cacheKey: hash('catdogapartmentpets') + cacheKey: 'catdogapartmentpets' }) }) @@ -79,7 +71,7 @@ describe('#unit auth', () => { credentials: 'dog', realm: 'apartment', parameters: { key1: 'value1', key2: 42 }, - cacheKey: hash('catdogapartmentpets') + cacheKey: 'catdogapartmentpets' }) }) }) diff --git a/packages/neo4j-driver/test/driver.test.js b/packages/neo4j-driver/test/driver.test.js index 3fcf27136..a0dbdf8f9 100644 --- a/packages/neo4j-driver/test/driver.test.js +++ b/packages/neo4j-driver/test/driver.test.js @@ -549,10 +549,10 @@ describe('#integration driver', () => { const connections1 = openConnectionFrom(driver) expect(connections1.length).toEqual(1) - expect(driver.homeDatabaseCache.get(sharedNeo4j.authToken.principal)).toBe('neo4j') + expect(driver.homeDatabaseCache.get(sharedNeo4j.authToken.cacheKey)).toBe('neo4j') expect(session1._database).toBe('neo4j') const session2 = driver.session({ auth: sharedNeo4j.authToken }) - expect(session2._databaseGuess.database).toBe('neo4j') + expect(session2._databaseGuess).toBe('neo4j') await session2.run('CREATE () RETURN 43') } }) diff --git a/packages/neo4j-driver/test/internal/connection-channel.test.js b/packages/neo4j-driver/test/internal/connection-channel.test.js index 34e94663d..7e42a8c6a 100644 --- a/packages/neo4j-driver/test/internal/connection-channel.test.js +++ b/packages/neo4j-driver/test/internal/connection-channel.test.js @@ -158,6 +158,7 @@ describe('#integration ChannelConnection', () => { Logger.noOp(), null, null, + undefined, () => channel ) .then(c => { From ed2b494cd0a284bf06c664f7f4062d7227be5b79 Mon Sep 17 00:00:00 2001 From: MaxAake <61233757+MaxAake@users.noreply.github.com> Date: Thu, 14 Nov 2024 13:08:30 +0100 Subject: [PATCH 30/84] deno --- packages/neo4j-driver-deno/lib/core/auth.ts | 1 - packages/neo4j-driver-deno/lib/core/driver.ts | 4 ++-- packages/neo4j-driver-deno/lib/core/session.ts | 12 ++++++------ 3 files changed, 8 insertions(+), 9 deletions(-) diff --git a/packages/neo4j-driver-deno/lib/core/auth.ts b/packages/neo4j-driver-deno/lib/core/auth.ts index b6e944827..023988a5c 100644 --- a/packages/neo4j-driver-deno/lib/core/auth.ts +++ b/packages/neo4j-driver-deno/lib/core/auth.ts @@ -15,7 +15,6 @@ * limitations under the License. */ - /** * @property {function(username: string, password: string, realm: ?string)} basic the function to create a * basic authentication token. diff --git a/packages/neo4j-driver-deno/lib/core/driver.ts b/packages/neo4j-driver-deno/lib/core/driver.ts index abd9412ff..eac46c7e6 100644 --- a/packages/neo4j-driver-deno/lib/core/driver.ts +++ b/packages/neo4j-driver-deno/lib/core/driver.ts @@ -882,8 +882,8 @@ class Driver { const sessionMode = Session._validateSessionMode(defaultAccessMode) const connectionProvider = this._getOrCreateConnectionProvider() let cachedHomeDatabase - if(database !== undefined) { - cachedHomeDatabase = this.homeDatabaseCache.get(impersonatedUser ?? auth?.cacheKey ?? "DEFAULT") + if (database !== undefined) { + cachedHomeDatabase = this.homeDatabaseCache.get(impersonatedUser ?? auth?.cacheKey ?? 'DEFAULT') } const homeDatabaseCallback = this._homeDatabaseCallback.bind(this) const removeFailureFromCache = this._removeFailureFromCache.bind(this) diff --git a/packages/neo4j-driver-deno/lib/core/session.ts b/packages/neo4j-driver-deno/lib/core/session.ts index 691f067df..55e561eab 100644 --- a/packages/neo4j-driver-deno/lib/core/session.ts +++ b/packages/neo4j-driver-deno/lib/core/session.ts @@ -167,7 +167,7 @@ class Session { this._bookmarkManager = bookmarkManager this._notificationFilter = notificationFilter this._log = log - this._databaseGuess = config?.cachedHomeDatabase + this._databaseGuess = config?.cachedHomeDatabase } /** @@ -519,10 +519,10 @@ class Session { * @returns {void} */ _onDatabaseNameResolved (database?: string): void { - this._databaseGuess = database + this._databaseGuess = database if (!this._databaseNameResolved) { if (this._homeDatabaseCallback != null) { - this._homeDatabaseCallback(this._impersonatedUser ?? this._auth?.cacheKey ?? "DEFAULT", database) + this._homeDatabaseCallback(this._impersonatedUser ?? this._auth?.cacheKey ?? 'DEFAULT', database) } const normalizedDatabase = database ?? '' this._database = normalizedDatabase @@ -533,11 +533,11 @@ class Session { } _beginDbCallback (database: string): void { - if(this._connectionHolderWithMode(this._mode).connectionProvider()?.SSREnabled() !== null && this._connectionHolderWithMode(this._mode).connectionProvider()?.SSREnabled() !== true){ + if (this._connectionHolderWithMode(this._mode).connectionProvider()?.SSREnabled() !== null && this._connectionHolderWithMode(this._mode).connectionProvider()?.SSREnabled() !== true) { this._databaseGuess = database if (!this._databaseNameResolved) { if (this._homeDatabaseCallback != null) { - this._homeDatabaseCallback(this._impersonatedUser ?? this._auth?.cacheKey ?? "DEFAULT", database) + this._homeDatabaseCallback(this._impersonatedUser ?? this._auth?.cacheKey ?? 'DEFAULT', database) } const normalizedDatabase = database ?? '' this._database = normalizedDatabase @@ -612,7 +612,7 @@ class Session { * @returns {void} */ _onCompleteCallback (meta: { bookmark: string | string[], db?: string }, previousBookmarks?: Bookmarks): void { - if(meta.db !== undefined) { + if (meta.db !== undefined) { this._beginDbCallback(meta.db) } this._updateBookmarks(new Bookmarks(meta.bookmark), previousBookmarks, meta.db) From 22c795bc72c4a9825a811cedfb4f8e88d442b5e6 Mon Sep 17 00:00:00 2001 From: MaxAake <61233757+MaxAake@users.noreply.github.com> Date: Thu, 14 Nov 2024 15:55:21 +0100 Subject: [PATCH 31/84] fixing silly self-sabotage --- .../src/connection-provider/connection-provider-routing.js | 3 ++- packages/core/src/driver.ts | 5 +---- packages/core/src/session.ts | 3 ++- .../connection-provider/connection-provider-routing.js | 3 ++- packages/neo4j-driver-deno/lib/core/driver.ts | 5 +---- packages/neo4j-driver-deno/lib/core/session.ts | 4 +++- 6 files changed, 11 insertions(+), 12 deletions(-) diff --git a/packages/bolt-connection/src/connection-provider/connection-provider-routing.js b/packages/bolt-connection/src/connection-provider/connection-provider-routing.js index 8594c3039..dad85ef59 100644 --- a/packages/bolt-connection/src/connection-provider/connection-provider-routing.js +++ b/packages/bolt-connection/src/connection-provider/connection-provider-routing.js @@ -160,7 +160,8 @@ export default class RoutingConnectionProvider extends PooledConnectionProvider this._handleSecurityError(error, address, conn, context.database) ) let currentRoutingTable - if (this.SSREnabled() && homeDb !== undefined) { + console.error('AQ DB', database) + if (this.SSREnabled() && homeDb !== undefined && database === '') { currentRoutingTable = this._routingTableRegistry.get( homeDb, () => new RoutingTable({ database: homeDb }) diff --git a/packages/core/src/driver.ts b/packages/core/src/driver.ts index 4672ca009..b9eefdea4 100644 --- a/packages/core/src/driver.ts +++ b/packages/core/src/driver.ts @@ -881,10 +881,7 @@ class Driver { }): Session { const sessionMode = Session._validateSessionMode(defaultAccessMode) const connectionProvider = this._getOrCreateConnectionProvider() - let cachedHomeDatabase - if (database !== undefined) { - cachedHomeDatabase = this.homeDatabaseCache.get(impersonatedUser ?? auth?.cacheKey ?? 'DEFAULT') - } + const cachedHomeDatabase = this.homeDatabaseCache.get(impersonatedUser ?? auth?.cacheKey ?? 'DEFAULT') const homeDatabaseCallback = this._homeDatabaseCallback.bind(this) const removeFailureFromCache = this._removeFailureFromCache.bind(this) const bookmarks = bookmarkOrBookmarks != null diff --git a/packages/core/src/session.ts b/packages/core/src/session.ts index 56fc0ccda..6a063d899 100644 --- a/packages/core/src/session.ts +++ b/packages/core/src/session.ts @@ -533,7 +533,8 @@ class Session { } _beginDbCallback (database: string): void { - if (this._connectionHolderWithMode(this._mode).connectionProvider()?.SSREnabled() !== null && this._connectionHolderWithMode(this._mode).connectionProvider()?.SSREnabled() !== true) { + // eslint-disable-next-line + if (this._connectionHolderWithMode(this._mode).connectionProvider()?.SSREnabled()) { this._databaseGuess = database if (!this._databaseNameResolved) { if (this._homeDatabaseCallback != null) { diff --git a/packages/neo4j-driver-deno/lib/bolt-connection/connection-provider/connection-provider-routing.js b/packages/neo4j-driver-deno/lib/bolt-connection/connection-provider/connection-provider-routing.js index 7d829e52b..528186dcf 100644 --- a/packages/neo4j-driver-deno/lib/bolt-connection/connection-provider/connection-provider-routing.js +++ b/packages/neo4j-driver-deno/lib/bolt-connection/connection-provider/connection-provider-routing.js @@ -160,7 +160,8 @@ export default class RoutingConnectionProvider extends PooledConnectionProvider this._handleSecurityError(error, address, conn, context.database) ) let currentRoutingTable - if (this.SSREnabled() && homeDb !== undefined) { + console.error('AQ DB', database) + if (this.SSREnabled() && homeDb !== undefined && database === '') { currentRoutingTable = this._routingTableRegistry.get( homeDb, () => new RoutingTable({ database: homeDb }) diff --git a/packages/neo4j-driver-deno/lib/core/driver.ts b/packages/neo4j-driver-deno/lib/core/driver.ts index eac46c7e6..58217d1d4 100644 --- a/packages/neo4j-driver-deno/lib/core/driver.ts +++ b/packages/neo4j-driver-deno/lib/core/driver.ts @@ -881,10 +881,7 @@ class Driver { }): Session { const sessionMode = Session._validateSessionMode(defaultAccessMode) const connectionProvider = this._getOrCreateConnectionProvider() - let cachedHomeDatabase - if (database !== undefined) { - cachedHomeDatabase = this.homeDatabaseCache.get(impersonatedUser ?? auth?.cacheKey ?? 'DEFAULT') - } + let cachedHomeDatabase = this.homeDatabaseCache.get(impersonatedUser ?? auth?.cacheKey ?? 'DEFAULT') const homeDatabaseCallback = this._homeDatabaseCallback.bind(this) const removeFailureFromCache = this._removeFailureFromCache.bind(this) const bookmarks = bookmarkOrBookmarks != null diff --git a/packages/neo4j-driver-deno/lib/core/session.ts b/packages/neo4j-driver-deno/lib/core/session.ts index 55e561eab..d671f292d 100644 --- a/packages/neo4j-driver-deno/lib/core/session.ts +++ b/packages/neo4j-driver-deno/lib/core/session.ts @@ -533,7 +533,9 @@ class Session { } _beginDbCallback (database: string): void { - if (this._connectionHolderWithMode(this._mode).connectionProvider()?.SSREnabled() !== null && this._connectionHolderWithMode(this._mode).connectionProvider()?.SSREnabled() !== true) { + // eslint-disable-next-line + // @ts-ignore + if (this._connectionHolderWithMode(this._mode).connectionProvider()?.SSREnabled()) { this._databaseGuess = database if (!this._databaseNameResolved) { if (this._homeDatabaseCallback != null) { From 0a3901423939363b3e5e43a984683af656c6fe08 Mon Sep 17 00:00:00 2001 From: MaxAake <61233757+MaxAake@users.noreply.github.com> Date: Thu, 14 Nov 2024 15:56:13 +0100 Subject: [PATCH 32/84] deno --- packages/neo4j-driver-deno/lib/core/driver.ts | 2 +- packages/neo4j-driver-deno/lib/core/session.ts | 1 - 2 files changed, 1 insertion(+), 2 deletions(-) diff --git a/packages/neo4j-driver-deno/lib/core/driver.ts b/packages/neo4j-driver-deno/lib/core/driver.ts index 58217d1d4..060244e5f 100644 --- a/packages/neo4j-driver-deno/lib/core/driver.ts +++ b/packages/neo4j-driver-deno/lib/core/driver.ts @@ -881,7 +881,7 @@ class Driver { }): Session { const sessionMode = Session._validateSessionMode(defaultAccessMode) const connectionProvider = this._getOrCreateConnectionProvider() - let cachedHomeDatabase = this.homeDatabaseCache.get(impersonatedUser ?? auth?.cacheKey ?? 'DEFAULT') + const cachedHomeDatabase = this.homeDatabaseCache.get(impersonatedUser ?? auth?.cacheKey ?? 'DEFAULT') const homeDatabaseCallback = this._homeDatabaseCallback.bind(this) const removeFailureFromCache = this._removeFailureFromCache.bind(this) const bookmarks = bookmarkOrBookmarks != null diff --git a/packages/neo4j-driver-deno/lib/core/session.ts b/packages/neo4j-driver-deno/lib/core/session.ts index d671f292d..17b14fb23 100644 --- a/packages/neo4j-driver-deno/lib/core/session.ts +++ b/packages/neo4j-driver-deno/lib/core/session.ts @@ -534,7 +534,6 @@ class Session { _beginDbCallback (database: string): void { // eslint-disable-next-line - // @ts-ignore if (this._connectionHolderWithMode(this._mode).connectionProvider()?.SSREnabled()) { this._databaseGuess = database if (!this._databaseNameResolved) { From 428d7163bb68672e6c1021ce336611252ad7ba00 Mon Sep 17 00:00:00 2001 From: MaxAake <61233757+MaxAake@users.noreply.github.com> Date: Fri, 15 Nov 2024 09:00:50 +0100 Subject: [PATCH 33/84] remove lingering debug log and fix unit test --- .../src/connection-provider/connection-provider-routing.js | 1 - .../connection-provider/connection-provider-routing.test.js | 4 ++-- .../connection-provider/connection-provider-routing.js | 1 - 3 files changed, 2 insertions(+), 4 deletions(-) diff --git a/packages/bolt-connection/src/connection-provider/connection-provider-routing.js b/packages/bolt-connection/src/connection-provider/connection-provider-routing.js index dad85ef59..73efa85c8 100644 --- a/packages/bolt-connection/src/connection-provider/connection-provider-routing.js +++ b/packages/bolt-connection/src/connection-provider/connection-provider-routing.js @@ -160,7 +160,6 @@ export default class RoutingConnectionProvider extends PooledConnectionProvider this._handleSecurityError(error, address, conn, context.database) ) let currentRoutingTable - console.error('AQ DB', database) if (this.SSREnabled() && homeDb !== undefined && database === '') { currentRoutingTable = this._routingTableRegistry.get( homeDb, diff --git a/packages/bolt-connection/test/connection-provider/connection-provider-routing.test.js b/packages/bolt-connection/test/connection-provider/connection-provider-routing.test.js index 45fbe2216..297e87d78 100644 --- a/packages/bolt-connection/test/connection-provider/connection-provider-routing.test.js +++ b/packages/bolt-connection/test/connection-provider/connection-provider-routing.test.js @@ -2772,7 +2772,7 @@ describe.each([ await connectionProvider.acquireConnection({ accessMode: READ, impersonatedUser: user, onDatabaseNameResolved }) - expect(onDatabaseNameResolved).toHaveBeenCalledWith('homedb', undefined) + expect(onDatabaseNameResolved).toHaveBeenCalledWith('homedb') }) it.each(usersDataSet)('should call onDatabaseNameResolved with the resolved db acquiring named db [user=%s]', async (user) => { @@ -2798,7 +2798,7 @@ describe.each([ await connectionProvider.acquireConnection({ accessMode: READ, impersonatedUser: user, onDatabaseNameResolved, database: 'databaseA' }) - expect(onDatabaseNameResolved).toHaveBeenCalledWith('databaseA', undefined) + expect(onDatabaseNameResolved).toHaveBeenCalledWith('databaseA') }) }) diff --git a/packages/neo4j-driver-deno/lib/bolt-connection/connection-provider/connection-provider-routing.js b/packages/neo4j-driver-deno/lib/bolt-connection/connection-provider/connection-provider-routing.js index 528186dcf..a35ca8dd8 100644 --- a/packages/neo4j-driver-deno/lib/bolt-connection/connection-provider/connection-provider-routing.js +++ b/packages/neo4j-driver-deno/lib/bolt-connection/connection-provider/connection-provider-routing.js @@ -160,7 +160,6 @@ export default class RoutingConnectionProvider extends PooledConnectionProvider this._handleSecurityError(error, address, conn, context.database) ) let currentRoutingTable - console.error('AQ DB', database) if (this.SSREnabled() && homeDb !== undefined && database === '') { currentRoutingTable = this._routingTableRegistry.get( homeDb, From aab842c027cf3e9c94f0981df7bcc98c17ca9ae0 Mon Sep 17 00:00:00 2001 From: MaxAake <61233757+MaxAake@users.noreply.github.com> Date: Fri, 15 Nov 2024 09:16:15 +0100 Subject: [PATCH 34/84] Update index.test.ts --- packages/neo4j-driver-lite/test/unit/index.test.ts | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/packages/neo4j-driver-lite/test/unit/index.test.ts b/packages/neo4j-driver-lite/test/unit/index.test.ts index 2ff63249d..98971e38a 100644 --- a/packages/neo4j-driver-lite/test/unit/index.test.ts +++ b/packages/neo4j-driver-lite/test/unit/index.test.ts @@ -263,7 +263,8 @@ describe('index', () => { verifyConnectivityAndGetServerInfo: async () => new ServerInfo({}), getNegotiatedProtocolVersion: async () => 5.0, verifyAuthentication: async () => true, - supportsSessionAuth: async () => true + supportsSessionAuth: async () => true, + SSREnabled: () => false } }) expect(session).toBeDefined() From ab4487769c1b8e91b8e59715fc2ddcbbf0635f42 Mon Sep 17 00:00:00 2001 From: MaxAake <61233757+MaxAake@users.noreply.github.com> Date: Fri, 15 Nov 2024 10:48:30 +0100 Subject: [PATCH 35/84] identify scheme in cache key to ensure default key is unique --- packages/core/src/auth.ts | 8 ++++---- packages/core/src/driver.ts | 3 ++- packages/core/src/session.ts | 6 ++++-- packages/core/test/auth.test.ts | 10 +++++----- packages/core/test/driver.test.ts | 2 +- packages/neo4j-driver-deno/lib/core/auth.ts | 8 ++++---- packages/neo4j-driver-deno/lib/core/driver.ts | 3 ++- packages/neo4j-driver-deno/lib/core/session.ts | 6 ++++-- packages/neo4j-driver/test/auth.test.js | 10 +++++----- packages/neo4j-driver/test/driver.test.js | 7 +++++++ 10 files changed, 38 insertions(+), 25 deletions(-) diff --git a/packages/core/src/auth.ts b/packages/core/src/auth.ts index 023988a5c..e4ed1ea6e 100644 --- a/packages/core/src/auth.ts +++ b/packages/core/src/auth.ts @@ -32,7 +32,7 @@ const auth = { scheme: 'basic', principal: username, credentials: password, - cacheKey: username, + cacheKey: 'basic:' + username, realm } } else { @@ -44,14 +44,14 @@ const auth = { scheme: 'kerberos', principal: '', // This empty string is required for backwards compatibility. credentials: base64EncodedTicket, - cacheKey: base64EncodedTicket + cacheKey: 'kerberos:' + base64EncodedTicket } }, bearer: (base64EncodedToken: string) => { return { scheme: 'bearer', credentials: base64EncodedToken, - cacheKey: base64EncodedToken + cacheKey: 'bearer:' + base64EncodedToken } }, none: () => { @@ -79,7 +79,7 @@ const auth = { if (isNotEmpty(parameters)) { output.parameters = parameters } - output.cacheKey = principal + (isNotEmpty(credentials) ? credentials : '') + (isNotEmpty(realm) ? realm : '') + scheme + output.cacheKey = scheme + ':' + principal + (isNotEmpty(credentials) ? credentials : '') + (isNotEmpty(realm) ? realm : '') return output } } diff --git a/packages/core/src/driver.ts b/packages/core/src/driver.ts index b9eefdea4..6e0baf509 100644 --- a/packages/core/src/driver.ts +++ b/packages/core/src/driver.ts @@ -881,7 +881,8 @@ class Driver { }): Session { const sessionMode = Session._validateSessionMode(defaultAccessMode) const connectionProvider = this._getOrCreateConnectionProvider() - const cachedHomeDatabase = this.homeDatabaseCache.get(impersonatedUser ?? auth?.cacheKey ?? 'DEFAULT') + // eslint-disable-next-line + const cachedHomeDatabase = this.homeDatabaseCache.get((impersonatedUser ? 'basic:' + impersonatedUser : undefined) ?? auth?.cacheKey ?? 'DEFAULT') const homeDatabaseCallback = this._homeDatabaseCallback.bind(this) const removeFailureFromCache = this._removeFailureFromCache.bind(this) const bookmarks = bookmarkOrBookmarks != null diff --git a/packages/core/src/session.ts b/packages/core/src/session.ts index 6a063d899..64da17736 100644 --- a/packages/core/src/session.ts +++ b/packages/core/src/session.ts @@ -522,7 +522,8 @@ class Session { this._databaseGuess = database if (!this._databaseNameResolved) { if (this._homeDatabaseCallback != null) { - this._homeDatabaseCallback(this._impersonatedUser ?? this._auth?.cacheKey ?? 'DEFAULT', database) + // eslint-disable-next-line + this._homeDatabaseCallback((this._impersonatedUser ? 'basic:' + this._impersonatedUser : undefined) ?? this._auth?.cacheKey ?? 'DEFAULT', database) } const normalizedDatabase = database ?? '' this._database = normalizedDatabase @@ -538,7 +539,8 @@ class Session { this._databaseGuess = database if (!this._databaseNameResolved) { if (this._homeDatabaseCallback != null) { - this._homeDatabaseCallback(this._impersonatedUser ?? this._auth?.cacheKey ?? 'DEFAULT', database) + // eslint-disable-next-line + this._homeDatabaseCallback((this._impersonatedUser ? 'basic:' + this._impersonatedUser : undefined) ?? this._auth?.cacheKey ?? 'DEFAULT', database) } const normalizedDatabase = database ?? '' this._database = normalizedDatabase diff --git a/packages/core/test/auth.test.ts b/packages/core/test/auth.test.ts index 302448a06..2b0033357 100644 --- a/packages/core/test/auth.test.ts +++ b/packages/core/test/auth.test.ts @@ -18,7 +18,7 @@ import auth from '../src/auth' describe('auth', () => { test('.bearer()', () => { - expect(auth.bearer('==Qyahiadakkda')).toEqual({ scheme: 'bearer', credentials: '==Qyahiadakkda', cacheKey: '==Qyahiadakkda' }) + expect(auth.bearer('==Qyahiadakkda')).toEqual({ scheme: 'bearer', credentials: '==Qyahiadakkda', cacheKey: 'bearer:==Qyahiadakkda' }) }) test.each([ @@ -30,7 +30,7 @@ describe('auth', () => { credentials: 'pass', realm: 'realm', parameters: { param: 'param' }, - cacheKey: 'user' + 'pass' + 'realm' + 'scheme' + cacheKey: 'scheme:' + 'user' + 'pass' + 'realm' } ], [ @@ -38,7 +38,7 @@ describe('auth', () => { { scheme: 'scheme', principal: 'user', - cacheKey: 'user' + 'scheme' + cacheKey: 'scheme:' + 'user' } ], [ @@ -46,7 +46,7 @@ describe('auth', () => { { scheme: 'scheme', principal: 'user', - cacheKey: 'user' + 'scheme' + cacheKey: 'scheme:' + 'user' } ], [ @@ -54,7 +54,7 @@ describe('auth', () => { { scheme: 'scheme', principal: 'user', - cacheKey: 'user' + 'scheme' + cacheKey: 'scheme:' + 'user' } ] ])('.custom()', (args, output) => { diff --git a/packages/core/test/driver.test.ts b/packages/core/test/driver.test.ts index c321b409f..41899f41a 100644 --- a/packages/core/test/driver.test.ts +++ b/packages/core/test/driver.test.ts @@ -85,7 +85,7 @@ describe('Driver', () => { scheme: 'basic', principal: 'the imposter', credentials: 'super safe password', - cacheKey: 'the imposter' + cacheKey: 'basic:the imposter' } const session = driver?.session({ auth }) diff --git a/packages/neo4j-driver-deno/lib/core/auth.ts b/packages/neo4j-driver-deno/lib/core/auth.ts index 023988a5c..bfd976523 100644 --- a/packages/neo4j-driver-deno/lib/core/auth.ts +++ b/packages/neo4j-driver-deno/lib/core/auth.ts @@ -32,7 +32,7 @@ const auth = { scheme: 'basic', principal: username, credentials: password, - cacheKey: username, + cacheKey: 'basic:' + username, realm } } else { @@ -44,14 +44,14 @@ const auth = { scheme: 'kerberos', principal: '', // This empty string is required for backwards compatibility. credentials: base64EncodedTicket, - cacheKey: base64EncodedTicket + cacheKey: 'kerberos:' + base64EncodedTicket } }, bearer: (base64EncodedToken: string) => { return { scheme: 'bearer', credentials: base64EncodedToken, - cacheKey: base64EncodedToken + cacheKey: 'bearer:' + base64EncodedToken } }, none: () => { @@ -79,7 +79,7 @@ const auth = { if (isNotEmpty(parameters)) { output.parameters = parameters } - output.cacheKey = principal + (isNotEmpty(credentials) ? credentials : '') + (isNotEmpty(realm) ? realm : '') + scheme + output.cacheKey = scheme + ":" + principal + (isNotEmpty(credentials) ? credentials : '') + (isNotEmpty(realm) ? realm : '') return output } } diff --git a/packages/neo4j-driver-deno/lib/core/driver.ts b/packages/neo4j-driver-deno/lib/core/driver.ts index 060244e5f..00ac613ee 100644 --- a/packages/neo4j-driver-deno/lib/core/driver.ts +++ b/packages/neo4j-driver-deno/lib/core/driver.ts @@ -881,7 +881,8 @@ class Driver { }): Session { const sessionMode = Session._validateSessionMode(defaultAccessMode) const connectionProvider = this._getOrCreateConnectionProvider() - const cachedHomeDatabase = this.homeDatabaseCache.get(impersonatedUser ?? auth?.cacheKey ?? 'DEFAULT') + // eslint-disable-next-line + const cachedHomeDatabase = this.homeDatabaseCache.get((impersonatedUser ? 'basic:' + impersonatedUser : undefined) ?? auth?.cacheKey ?? 'DEFAULT') const homeDatabaseCallback = this._homeDatabaseCallback.bind(this) const removeFailureFromCache = this._removeFailureFromCache.bind(this) const bookmarks = bookmarkOrBookmarks != null diff --git a/packages/neo4j-driver-deno/lib/core/session.ts b/packages/neo4j-driver-deno/lib/core/session.ts index 17b14fb23..cc8740439 100644 --- a/packages/neo4j-driver-deno/lib/core/session.ts +++ b/packages/neo4j-driver-deno/lib/core/session.ts @@ -522,7 +522,8 @@ class Session { this._databaseGuess = database if (!this._databaseNameResolved) { if (this._homeDatabaseCallback != null) { - this._homeDatabaseCallback(this._impersonatedUser ?? this._auth?.cacheKey ?? 'DEFAULT', database) + // eslint-disable-next-line + this._homeDatabaseCallback((this._impersonatedUser ? 'basic:' + this._impersonatedUser : undefined) ?? this._auth?.cacheKey ?? 'DEFAULT', database) } const normalizedDatabase = database ?? '' this._database = normalizedDatabase @@ -538,7 +539,8 @@ class Session { this._databaseGuess = database if (!this._databaseNameResolved) { if (this._homeDatabaseCallback != null) { - this._homeDatabaseCallback(this._impersonatedUser ?? this._auth?.cacheKey ?? 'DEFAULT', database) + // eslint-disable-next-line + this._homeDatabaseCallback((this._impersonatedUser ? 'basic:' + this._impersonatedUser : undefined) ?? this._auth?.cacheKey ?? 'DEFAULT', database) } const normalizedDatabase = database ?? '' this._database = normalizedDatabase diff --git a/packages/neo4j-driver/test/auth.test.js b/packages/neo4j-driver/test/auth.test.js index 216972c50..d1b41ee81 100644 --- a/packages/neo4j-driver/test/auth.test.js +++ b/packages/neo4j-driver/test/auth.test.js @@ -24,7 +24,7 @@ describe('#unit auth', () => { scheme: 'basic', principal: 'cat', credentials: 'dog', - cacheKey: 'cat' + cacheKey: 'basic:cat' }) }) @@ -35,7 +35,7 @@ describe('#unit auth', () => { principal: 'cat', credentials: 'dog', realm: 'apartment', - cacheKey: 'cat' + cacheKey: 'basic:cat' }) }) @@ -45,7 +45,7 @@ describe('#unit auth', () => { scheme: 'kerberos', principal: '', credentials: 'my-ticket', - cacheKey: 'my-ticket' + cacheKey: 'basic:my-ticket' }) }) @@ -56,7 +56,7 @@ describe('#unit auth', () => { principal: 'cat', credentials: 'dog', realm: 'apartment', - cacheKey: 'catdogapartmentpets' + cacheKey: 'pets:catdogapartment' }) }) @@ -71,7 +71,7 @@ describe('#unit auth', () => { credentials: 'dog', realm: 'apartment', parameters: { key1: 'value1', key2: 42 }, - cacheKey: 'catdogapartmentpets' + cacheKey: 'pets:catdogapartment' }) }) }) diff --git a/packages/neo4j-driver/test/driver.test.js b/packages/neo4j-driver/test/driver.test.js index a0dbdf8f9..7e71b2a63 100644 --- a/packages/neo4j-driver/test/driver.test.js +++ b/packages/neo4j-driver/test/driver.test.js @@ -181,6 +181,12 @@ describe('#unit driver', () => { expect(session._log).toBe(driver._log) expect(session._session._log).toBe(driver._log) }) + + it('should build homedb cache from callback functions') + driver = neo4j.driver( + `neo4j+ssc://${sharedNeo4j.hostnameWithBoltPort}`, + sharedNeo4j.authToken + ) }) }) @@ -553,6 +559,7 @@ describe('#integration driver', () => { expect(session1._database).toBe('neo4j') const session2 = driver.session({ auth: sharedNeo4j.authToken }) expect(session2._databaseGuess).toBe('neo4j') + expect(session2._database).toBe('') await session2.run('CREATE () RETURN 43') } }) From c3dbbc308e77227fd8398cc994260246c987e30a Mon Sep 17 00:00:00 2001 From: MaxAake <61233757+MaxAake@users.noreply.github.com> Date: Fri, 15 Nov 2024 11:50:10 +0100 Subject: [PATCH 36/84] deno --- packages/neo4j-driver-deno/lib/core/auth.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/neo4j-driver-deno/lib/core/auth.ts b/packages/neo4j-driver-deno/lib/core/auth.ts index bfd976523..e4ed1ea6e 100644 --- a/packages/neo4j-driver-deno/lib/core/auth.ts +++ b/packages/neo4j-driver-deno/lib/core/auth.ts @@ -44,7 +44,7 @@ const auth = { scheme: 'kerberos', principal: '', // This empty string is required for backwards compatibility. credentials: base64EncodedTicket, - cacheKey: 'kerberos:' + base64EncodedTicket + cacheKey: 'kerberos:' + base64EncodedTicket } }, bearer: (base64EncodedToken: string) => { @@ -79,7 +79,7 @@ const auth = { if (isNotEmpty(parameters)) { output.parameters = parameters } - output.cacheKey = scheme + ":" + principal + (isNotEmpty(credentials) ? credentials : '') + (isNotEmpty(realm) ? realm : '') + output.cacheKey = scheme + ':' + principal + (isNotEmpty(credentials) ? credentials : '') + (isNotEmpty(realm) ? realm : '') return output } } From d32cd01b3e3bffe3d2f598e2f13140e070722b43 Mon Sep 17 00:00:00 2001 From: MaxAake <61233757+MaxAake@users.noreply.github.com> Date: Fri, 15 Nov 2024 13:51:58 +0100 Subject: [PATCH 37/84] capped homedb cache size --- packages/core/src/auth.ts | 2 +- packages/core/src/driver.ts | 10 ++++++++++ packages/neo4j-driver-deno/lib/core/auth.ts | 2 +- packages/neo4j-driver-deno/lib/core/driver.ts | 10 ++++++++++ packages/neo4j-driver/test/auth.test.js | 2 +- packages/neo4j-driver/test/driver.test.js | 19 ++++++++++++++++++- 6 files changed, 41 insertions(+), 4 deletions(-) diff --git a/packages/core/src/auth.ts b/packages/core/src/auth.ts index e4ed1ea6e..f4cea9fd5 100644 --- a/packages/core/src/auth.ts +++ b/packages/core/src/auth.ts @@ -36,7 +36,7 @@ const auth = { realm } } else { - return { scheme: 'basic', principal: username, credentials: password, cacheKey: username } + return { scheme: 'basic', principal: username, credentials: password, cacheKey: 'basic:' + username } } }, kerberos: (base64EncodedTicket: string) => { diff --git a/packages/core/src/driver.ts b/packages/core/src/driver.ts index 6e0baf509..83b18b8b7 100644 --- a/packages/core/src/driver.ts +++ b/packages/core/src/driver.ts @@ -843,8 +843,18 @@ class Driver { ) } + _checkHomeDbSize (): void { + while (this.homeDatabaseCache.size > 1000) { + const iterator = this.homeDatabaseCache.entries() + const entry = iterator.next() + const key = entry.value[0] + this.homeDatabaseCache.delete(key) + } + } + _homeDatabaseCallback (user: string, database: any): void { this.homeDatabaseCache.set(user, database) + this._checkHomeDbSize() } _removeFailureFromCache (database: string): void { diff --git a/packages/neo4j-driver-deno/lib/core/auth.ts b/packages/neo4j-driver-deno/lib/core/auth.ts index e4ed1ea6e..f4cea9fd5 100644 --- a/packages/neo4j-driver-deno/lib/core/auth.ts +++ b/packages/neo4j-driver-deno/lib/core/auth.ts @@ -36,7 +36,7 @@ const auth = { realm } } else { - return { scheme: 'basic', principal: username, credentials: password, cacheKey: username } + return { scheme: 'basic', principal: username, credentials: password, cacheKey: 'basic:' + username } } }, kerberos: (base64EncodedTicket: string) => { diff --git a/packages/neo4j-driver-deno/lib/core/driver.ts b/packages/neo4j-driver-deno/lib/core/driver.ts index 00ac613ee..7c961a08d 100644 --- a/packages/neo4j-driver-deno/lib/core/driver.ts +++ b/packages/neo4j-driver-deno/lib/core/driver.ts @@ -843,8 +843,18 @@ class Driver { ) } + _checkHomeDbSize(): void { + while(this.homeDatabaseCache.size > 1000) { + let iterator = this.homeDatabaseCache.entries(); + let entry = iterator.next(); + let key = entry.value[0]; + this.homeDatabaseCache.delete(key); + } + } + _homeDatabaseCallback (user: string, database: any): void { this.homeDatabaseCache.set(user, database) + this._checkHomeDbSize() } _removeFailureFromCache (database: string): void { diff --git a/packages/neo4j-driver/test/auth.test.js b/packages/neo4j-driver/test/auth.test.js index d1b41ee81..20323ac21 100644 --- a/packages/neo4j-driver/test/auth.test.js +++ b/packages/neo4j-driver/test/auth.test.js @@ -45,7 +45,7 @@ describe('#unit auth', () => { scheme: 'kerberos', principal: '', credentials: 'my-ticket', - cacheKey: 'basic:my-ticket' + cacheKey: 'kerberos:my-ticket' }) }) diff --git a/packages/neo4j-driver/test/driver.test.js b/packages/neo4j-driver/test/driver.test.js index 7e71b2a63..d37f3d9fd 100644 --- a/packages/neo4j-driver/test/driver.test.js +++ b/packages/neo4j-driver/test/driver.test.js @@ -181,12 +181,29 @@ describe('#unit driver', () => { expect(session._log).toBe(driver._log) expect(session._session._log).toBe(driver._log) }) + }) + + it('should build homedb cache from callback functions', () => { + driver = neo4j.driver( + `neo4j+ssc://${sharedNeo4j.hostnameWithBoltPort}`, + sharedNeo4j.authToken + ) + driver._homeDatabaseCallback('DEFAULT', 'neo4j') + expect(driver.homeDatabaseCache.get('DEFAULT')).toBe('neo4j') + }) - it('should build homedb cache from callback functions') + it('should cap homeDb size', () => { driver = neo4j.driver( `neo4j+ssc://${sharedNeo4j.hostnameWithBoltPort}`, sharedNeo4j.authToken ) + for (let i = 0; i < 1100; i++) { + driver._homeDatabaseCallback(i.toString(), 'neo4j') + } + expect(driver.homeDatabaseCache.get('1')).toEqual(undefined) + expect(driver.homeDatabaseCache.get('99')).toEqual(undefined) + expect(driver.homeDatabaseCache.get('101')).toEqual('neo4j') + expect(driver.homeDatabaseCache.get('1001')).toEqual('neo4j') }) }) From 7bdcdd1431df3622839345b7362afa631d2d8fad Mon Sep 17 00:00:00 2001 From: MaxAake <61233757+MaxAake@users.noreply.github.com> Date: Fri, 15 Nov 2024 13:52:36 +0100 Subject: [PATCH 38/84] deno --- packages/neo4j-driver-deno/lib/core/driver.ts | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/packages/neo4j-driver-deno/lib/core/driver.ts b/packages/neo4j-driver-deno/lib/core/driver.ts index 7c961a08d..3bc07cdf4 100644 --- a/packages/neo4j-driver-deno/lib/core/driver.ts +++ b/packages/neo4j-driver-deno/lib/core/driver.ts @@ -843,12 +843,12 @@ class Driver { ) } - _checkHomeDbSize(): void { - while(this.homeDatabaseCache.size > 1000) { - let iterator = this.homeDatabaseCache.entries(); - let entry = iterator.next(); - let key = entry.value[0]; - this.homeDatabaseCache.delete(key); + _checkHomeDbSize (): void { + while (this.homeDatabaseCache.size > 1000) { + const iterator = this.homeDatabaseCache.entries() + const entry = iterator.next() + const key = entry.value[0] + this.homeDatabaseCache.delete(key) } } From 6838806e98b501f35332869463efc8b6abaa6b0b Mon Sep 17 00:00:00 2001 From: MaxAake <61233757+MaxAake@users.noreply.github.com> Date: Mon, 18 Nov 2024 16:14:14 +0000 Subject: [PATCH 39/84] add parameters to cachekey --- packages/core/src/auth.ts | 12 ++++-- packages/core/test/auth.test.ts | 33 +++++++++++++++- packages/neo4j-driver-deno/lib/core/auth.ts | 13 +++++-- packages/neo4j-driver/test/auth.test.js | 2 +- packages/neo4j-driver/test/driver.test.js | 43 ++++++++++++--------- 5 files changed, 77 insertions(+), 26 deletions(-) diff --git a/packages/core/src/auth.ts b/packages/core/src/auth.ts index f4cea9fd5..9f9371446 100644 --- a/packages/core/src/auth.ts +++ b/packages/core/src/auth.ts @@ -64,7 +64,7 @@ const auth = { credentials: string, realm: string, scheme: string, - parameters?: object + parameters?: any ) => { const output: any = { scheme, @@ -76,10 +76,16 @@ const auth = { if (isNotEmpty(realm)) { output.realm = realm } - if (isNotEmpty(parameters)) { + let ordered = '' + if (isNotEmpty(parameters) && parameters !== undefined) { output.parameters = parameters + Object.keys(parameters).sort().forEach((key: string) => { + // eslint-disable-next-line @typescript-eslint/restrict-template-expressions + ordered += `${key}:${parameters[key]}` + }) } - output.cacheKey = scheme + ':' + principal + (isNotEmpty(credentials) ? credentials : '') + (isNotEmpty(realm) ? realm : '') + + output.cacheKey = scheme + ':' + principal + (isNotEmpty(credentials) ? credentials : '') + (isNotEmpty(realm) ? realm : '') + ordered return output } } diff --git a/packages/core/test/auth.test.ts b/packages/core/test/auth.test.ts index 2b0033357..f5368d3d6 100644 --- a/packages/core/test/auth.test.ts +++ b/packages/core/test/auth.test.ts @@ -14,6 +14,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ +import { DateTime } from 'neo4j-driver' import auth from '../src/auth' describe('auth', () => { @@ -30,7 +31,7 @@ describe('auth', () => { credentials: 'pass', realm: 'realm', parameters: { param: 'param' }, - cacheKey: 'scheme:' + 'user' + 'pass' + 'realm' + cacheKey: 'scheme:' + 'user' + 'pass' + 'realm' + 'param:param' } ], [ @@ -60,4 +61,34 @@ describe('auth', () => { ])('.custom()', (args, output) => { expect(auth.custom.apply(auth, args)).toEqual(output) }) + + test.each([ + [ + ['user', 'pass', 'realm', 'scheme', { param: 'param' }], + ['user', 'pass', 'realm', 'scheme', { param: 'param' }], + true + ], + [ + ['user', 'pass', 'realm', 'scheme', { param2: 'param2', param: 'param' }], + ['user', 'pass', 'realm', 'scheme', { param: 'param', param2: 'param2' }], + true + ], + [ + ['user', 'pass', 'realm', 'scheme', { datetime: new DateTime(0, 1, 1, 0, 0, 0, 0, 0) }], + ['user', 'pass', 'realm', 'scheme', { datetime: new DateTime(0, 1, 1, 0, 0, 0, 0, 0) }], + true + ], + [ + ['user', 'pass', 'realm', 'scheme', { datetime: new DateTime(0, 1, 1, 0, 0, 0, 0, 0) }], + ['user', 'pass', 'realm', 'scheme', { datetime: new DateTime(0, 1, 2, 0, 0, 0, 0, 0) }], + false + ] + + ])('.custom().cacheKey', (args1, args2, shouldMatch) => { + if (shouldMatch) { + expect(auth.custom.apply(auth, args1).cacheKey).toEqual(auth.custom.apply(auth, args2).cacheKey) + } else { + expect(auth.custom.apply(auth, args1).cacheKey).not.toEqual(auth.custom.apply(auth, args2).cacheKey) + } + }) }) diff --git a/packages/neo4j-driver-deno/lib/core/auth.ts b/packages/neo4j-driver-deno/lib/core/auth.ts index f4cea9fd5..84fbeeaa0 100644 --- a/packages/neo4j-driver-deno/lib/core/auth.ts +++ b/packages/neo4j-driver-deno/lib/core/auth.ts @@ -64,7 +64,7 @@ const auth = { credentials: string, realm: string, scheme: string, - parameters?: object + parameters?: any ) => { const output: any = { scheme, @@ -76,10 +76,17 @@ const auth = { if (isNotEmpty(realm)) { output.realm = realm } - if (isNotEmpty(parameters)) { + let ordered = '' + if (isNotEmpty(parameters) && parameters !== undefined) { output.parameters = parameters + Object.keys(parameters).sort().forEach((key: string) => { + //eslint-disable-next-line @typescript-eslint/restrict-template-expressions + ordered += `${key}:${parameters[key]}` + }) } - output.cacheKey = scheme + ':' + principal + (isNotEmpty(credentials) ? credentials : '') + (isNotEmpty(realm) ? realm : '') + + + output.cacheKey = scheme + ':' + principal + (isNotEmpty(credentials) ? credentials : '') + (isNotEmpty(realm) ? realm : '') + ordered return output } } diff --git a/packages/neo4j-driver/test/auth.test.js b/packages/neo4j-driver/test/auth.test.js index 20323ac21..297fd1405 100644 --- a/packages/neo4j-driver/test/auth.test.js +++ b/packages/neo4j-driver/test/auth.test.js @@ -71,7 +71,7 @@ describe('#unit auth', () => { credentials: 'dog', realm: 'apartment', parameters: { key1: 'value1', key2: 42 }, - cacheKey: 'pets:catdogapartment' + cacheKey: 'pets:catdogapartmentkey1:value1key2:42' }) }) }) diff --git a/packages/neo4j-driver/test/driver.test.js b/packages/neo4j-driver/test/driver.test.js index d37f3d9fd..9d0b5e864 100644 --- a/packages/neo4j-driver/test/driver.test.js +++ b/packages/neo4j-driver/test/driver.test.js @@ -183,27 +183,34 @@ describe('#unit driver', () => { }) }) - it('should build homedb cache from callback functions', () => { - driver = neo4j.driver( + describe('homeDatabaseCache', () => { + it('should build homedb cache from callback functions', () => { + driver = neo4j.driver( `neo4j+ssc://${sharedNeo4j.hostnameWithBoltPort}`, sharedNeo4j.authToken - ) - driver._homeDatabaseCallback('DEFAULT', 'neo4j') - expect(driver.homeDatabaseCache.get('DEFAULT')).toBe('neo4j') - }) + ) + driver._homeDatabaseCallback('DEFAULT', 'neo4j') + expect(driver.homeDatabaseCache.get('DEFAULT')).toBe('neo4j') + driver._homeDatabaseCallback('basic:hi', 'neo4j') + expect(driver.homeDatabaseCache.get('basic:hi')).toBe('neo4j') + driver._removeFailureFromCache('neo4j') + expect(driver.homeDatabaseCache.get('DEFAULT')).toBe(undefined) + expect(driver.homeDatabaseCache.get('basic:hi')).toBe(undefined) + }) - it('should cap homeDb size', () => { - driver = neo4j.driver( - `neo4j+ssc://${sharedNeo4j.hostnameWithBoltPort}`, - sharedNeo4j.authToken - ) - for (let i = 0; i < 1100; i++) { - driver._homeDatabaseCallback(i.toString(), 'neo4j') - } - expect(driver.homeDatabaseCache.get('1')).toEqual(undefined) - expect(driver.homeDatabaseCache.get('99')).toEqual(undefined) - expect(driver.homeDatabaseCache.get('101')).toEqual('neo4j') - expect(driver.homeDatabaseCache.get('1001')).toEqual('neo4j') + it('should cap homeDb size', () => { + driver = neo4j.driver( + `neo4j+ssc://${sharedNeo4j.hostnameWithBoltPort}`, + sharedNeo4j.authToken + ) + for (let i = 0; i < 1100; i++) { + driver._homeDatabaseCallback(i.toString(), 'neo4j') + } + expect(driver.homeDatabaseCache.get('1')).toEqual(undefined) + expect(driver.homeDatabaseCache.get('99')).toEqual(undefined) + expect(driver.homeDatabaseCache.get('101')).toEqual('neo4j') + expect(driver.homeDatabaseCache.get('1001')).toEqual('neo4j') + }) }) }) From dc90e1646a57649f214694271d2a001c34e57bd0 Mon Sep 17 00:00:00 2001 From: MaxAake <61233757+MaxAake@users.noreply.github.com> Date: Mon, 18 Nov 2024 16:35:43 +0000 Subject: [PATCH 40/84] deno --- packages/neo4j-driver-deno/lib/core/auth.ts | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/packages/neo4j-driver-deno/lib/core/auth.ts b/packages/neo4j-driver-deno/lib/core/auth.ts index 84fbeeaa0..9f9371446 100644 --- a/packages/neo4j-driver-deno/lib/core/auth.ts +++ b/packages/neo4j-driver-deno/lib/core/auth.ts @@ -80,12 +80,11 @@ const auth = { if (isNotEmpty(parameters) && parameters !== undefined) { output.parameters = parameters Object.keys(parameters).sort().forEach((key: string) => { - //eslint-disable-next-line @typescript-eslint/restrict-template-expressions + // eslint-disable-next-line @typescript-eslint/restrict-template-expressions ordered += `${key}:${parameters[key]}` }) } - output.cacheKey = scheme + ':' + principal + (isNotEmpty(credentials) ? credentials : '') + (isNotEmpty(realm) ? realm : '') + ordered return output } From 3de0474b3702bb6c1757d64ce8ac21100cce2c73 Mon Sep 17 00:00:00 2001 From: MaxAake <61233757+MaxAake@users.noreply.github.com> Date: Thu, 21 Nov 2024 11:03:56 +0000 Subject: [PATCH 41/84] Update auth.test.ts --- packages/core/test/auth.test.ts | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/packages/core/test/auth.test.ts b/packages/core/test/auth.test.ts index f5368d3d6..d0be3aacd 100644 --- a/packages/core/test/auth.test.ts +++ b/packages/core/test/auth.test.ts @@ -14,7 +14,6 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -import { DateTime } from 'neo4j-driver' import auth from '../src/auth' describe('auth', () => { @@ -74,13 +73,13 @@ describe('auth', () => { true ], [ - ['user', 'pass', 'realm', 'scheme', { datetime: new DateTime(0, 1, 1, 0, 0, 0, 0, 0) }], - ['user', 'pass', 'realm', 'scheme', { datetime: new DateTime(0, 1, 1, 0, 0, 0, 0, 0) }], + ['user', 'pass', 'realm', 'scheme', { param: [1, 2, 3] }], + ['user', 'pass', 'realm', 'scheme', { param: [1, 2, 3] }], true ], [ - ['user', 'pass', 'realm', 'scheme', { datetime: new DateTime(0, 1, 1, 0, 0, 0, 0, 0) }], - ['user', 'pass', 'realm', 'scheme', { datetime: new DateTime(0, 1, 2, 0, 0, 0, 0, 0) }], + ['user', 'pass', 'realm', 'scheme', { param: [1, 2, 3] }], + ['user', 'pass', 'realm', 'scheme', { param: [1, 3, 2] }], false ] From 28d3d8f2aa2e682e28df3d3a7b9776d2b1ea9d94 Mon Sep 17 00:00:00 2001 From: MaxAake <61233757+MaxAake@users.noreply.github.com> Date: Fri, 29 Nov 2024 11:43:01 +0100 Subject: [PATCH 42/84] create Cache class and refine pruning logic --- packages/core/src/driver.ts | 21 ++------ packages/core/src/internal/homedb-cache.ts | 49 +++++++++++++++++++ packages/neo4j-driver-deno/lib/core/driver.ts | 21 ++------ .../lib/core/internal/homedb-cache.ts | 49 +++++++++++++++++++ packages/neo4j-driver/test/driver.test.js | 4 +- 5 files changed, 108 insertions(+), 36 deletions(-) create mode 100644 packages/core/src/internal/homedb-cache.ts create mode 100644 packages/neo4j-driver-deno/lib/core/internal/homedb-cache.ts diff --git a/packages/core/src/driver.ts b/packages/core/src/driver.ts index 83b18b8b7..6bf52eae6 100644 --- a/packages/core/src/driver.ts +++ b/packages/core/src/driver.ts @@ -46,6 +46,7 @@ import resultTransformers, { ResultTransformer } from './result-transformers' import QueryExecutor from './internal/query-executor' import { newError } from './error' import NotificationFilter from './notification-filter' +import HomeDatabaseCache from './internal/homedb-cache' const DEFAULT_MAX_CONNECTION_LIFETIME: number = 60 * 60 * 1000 // 1 hour @@ -473,7 +474,7 @@ class Driver { private readonly _createSession: CreateSession private readonly _defaultExecuteQueryBookmarkManager: BookmarkManager private readonly _queryExecutor: QueryExecutor - homeDatabaseCache: Map + homeDatabaseCache: HomeDatabaseCache /** * You should not be calling this directly, instead use {@link driver}. @@ -513,7 +514,7 @@ class Driver { */ this._connectionProvider = null - this.homeDatabaseCache = new Map() + this.homeDatabaseCache = new HomeDatabaseCache(10000) this._afterConstruction() } @@ -843,26 +844,12 @@ class Driver { ) } - _checkHomeDbSize (): void { - while (this.homeDatabaseCache.size > 1000) { - const iterator = this.homeDatabaseCache.entries() - const entry = iterator.next() - const key = entry.value[0] - this.homeDatabaseCache.delete(key) - } - } - _homeDatabaseCallback (user: string, database: any): void { this.homeDatabaseCache.set(user, database) - this._checkHomeDbSize() } _removeFailureFromCache (database: string): void { - this.homeDatabaseCache.forEach((_, key) => { - if (this.homeDatabaseCache.get(key) === database) { - this.homeDatabaseCache.delete(key) - } - }) + this.homeDatabaseCache.removeFailedDatabase(database) } /** diff --git a/packages/core/src/internal/homedb-cache.ts b/packages/core/src/internal/homedb-cache.ts new file mode 100644 index 000000000..ed57729f5 --- /dev/null +++ b/packages/core/src/internal/homedb-cache.ts @@ -0,0 +1,49 @@ +export default class HomeDatabaseCache { + maxSize: number + map: Map + + constructor (maxSize: number) { + this.maxSize = maxSize + this.map = new Map() + } + + set (user: string, database: string): void { + this.map.set(user, { database, lastUsed: new Date() }) + this._pruneCache() + } + + get (user: string): string | undefined { + const value = this.map.get(user) + if (value !== undefined) { + value.lastUsed = new Date() + return value.database + } + return undefined + } + + delete (user: string): void { + this.map.delete(user) + } + + removeFailedDatabase (database: string): void { + this.map.forEach((_, key) => { + if (this.map.get(key)?.database === database) { + this.map.delete(key) + } + }) + } + + private _pruneCache (): void { + if (this.map.size > 1000) { + const sortedArray = Array.from(this.map.entries()).sort((a, b) => a[1].lastUsed.valueOf() - b[1].lastUsed.valueOf()) + for (let i = 0; i < 70; i++) { // + this.map.delete(sortedArray[i][0]) + } + } + } +} + +interface HomeDatabaseEntry { + database: string + lastUsed: Date +} diff --git a/packages/neo4j-driver-deno/lib/core/driver.ts b/packages/neo4j-driver-deno/lib/core/driver.ts index 3bc07cdf4..1b0e477f6 100644 --- a/packages/neo4j-driver-deno/lib/core/driver.ts +++ b/packages/neo4j-driver-deno/lib/core/driver.ts @@ -46,6 +46,7 @@ import resultTransformers, { ResultTransformer } from './result-transformers.ts' import QueryExecutor from './internal/query-executor.ts' import { newError } from './error.ts' import NotificationFilter from './notification-filter.ts' +import HomeDatabaseCache from './internal/homedb-cache.ts' const DEFAULT_MAX_CONNECTION_LIFETIME: number = 60 * 60 * 1000 // 1 hour @@ -473,7 +474,7 @@ class Driver { private readonly _createSession: CreateSession private readonly _defaultExecuteQueryBookmarkManager: BookmarkManager private readonly _queryExecutor: QueryExecutor - homeDatabaseCache: Map + homeDatabaseCache: HomeDatabaseCache /** * You should not be calling this directly, instead use {@link driver}. @@ -513,7 +514,7 @@ class Driver { */ this._connectionProvider = null - this.homeDatabaseCache = new Map() + this.homeDatabaseCache = new HomeDatabaseCache(10000) this._afterConstruction() } @@ -843,26 +844,12 @@ class Driver { ) } - _checkHomeDbSize (): void { - while (this.homeDatabaseCache.size > 1000) { - const iterator = this.homeDatabaseCache.entries() - const entry = iterator.next() - const key = entry.value[0] - this.homeDatabaseCache.delete(key) - } - } - _homeDatabaseCallback (user: string, database: any): void { this.homeDatabaseCache.set(user, database) - this._checkHomeDbSize() } _removeFailureFromCache (database: string): void { - this.homeDatabaseCache.forEach((_, key) => { - if (this.homeDatabaseCache.get(key) === database) { - this.homeDatabaseCache.delete(key) - } - }) + this.homeDatabaseCache.removeFailedDatabase(database) } /** diff --git a/packages/neo4j-driver-deno/lib/core/internal/homedb-cache.ts b/packages/neo4j-driver-deno/lib/core/internal/homedb-cache.ts new file mode 100644 index 000000000..676ffdb68 --- /dev/null +++ b/packages/neo4j-driver-deno/lib/core/internal/homedb-cache.ts @@ -0,0 +1,49 @@ +export default class HomeDatabaseCache { + maxSize: number; + map: Map + + constructor(maxSize: number) { + this.maxSize = maxSize + this.map = new Map() + } + + set(user: string, database: string): void { + this.map.set(user, {database, lastUsed: new Date()}) + this._pruneCache() + } + + get(user: string): string | undefined { + const value = this.map.get(user) + if(value !== undefined) { + value.lastUsed = new Date() + return value.database + } + return undefined + } + + delete(user: string): void { + this.map.delete(user) + } + + removeFailedDatabase(database: string): void { + this.map.forEach((_, key) => { + if (this.map.get(key)?.database === database) { + this.map.delete(key) + } + }) + } + + private _pruneCache(): void { + if (this.map.size > 1000) { + const sortedArray = Array.from(this.map.entries()).sort((a, b) => a[1].lastUsed.valueOf() - b[1].lastUsed.valueOf()) + for(var i = 0; i < 70; i++) { // + this.map.delete(sortedArray[i][0]) + } + } + } +} + +interface HomeDatabaseEntry { + database: string; + lastUsed: Date; +} \ No newline at end of file diff --git a/packages/neo4j-driver/test/driver.test.js b/packages/neo4j-driver/test/driver.test.js index 9d0b5e864..328fd824b 100644 --- a/packages/neo4j-driver/test/driver.test.js +++ b/packages/neo4j-driver/test/driver.test.js @@ -203,11 +203,11 @@ describe('#unit driver', () => { `neo4j+ssc://${sharedNeo4j.hostnameWithBoltPort}`, sharedNeo4j.authToken ) - for (let i = 0; i < 1100; i++) { + for (let i = 0; i < 1050; i++) { driver._homeDatabaseCallback(i.toString(), 'neo4j') } expect(driver.homeDatabaseCache.get('1')).toEqual(undefined) - expect(driver.homeDatabaseCache.get('99')).toEqual(undefined) + expect(driver.homeDatabaseCache.get('69')).toEqual(undefined) expect(driver.homeDatabaseCache.get('101')).toEqual('neo4j') expect(driver.homeDatabaseCache.get('1001')).toEqual('neo4j') }) From 521836c8cc34541eb544e1241c0c979c23ddec81 Mon Sep 17 00:00:00 2001 From: MaxAake <61233757+MaxAake@users.noreply.github.com> Date: Fri, 29 Nov 2024 13:08:39 +0100 Subject: [PATCH 43/84] refine lastdate and fix test --- packages/core/src/internal/homedb-cache.ts | 10 ++- .../lib/core/internal/homedb-cache.ts | 76 ++++++++++--------- packages/neo4j-driver/test/driver.test.js | 11 ++- 3 files changed, 54 insertions(+), 43 deletions(-) diff --git a/packages/core/src/internal/homedb-cache.ts b/packages/core/src/internal/homedb-cache.ts index ed57729f5..4437e839f 100644 --- a/packages/core/src/internal/homedb-cache.ts +++ b/packages/core/src/internal/homedb-cache.ts @@ -8,14 +8,14 @@ export default class HomeDatabaseCache { } set (user: string, database: string): void { - this.map.set(user, { database, lastUsed: new Date() }) + this.map.set(user, { database, lastUsed: Date.now() }) this._pruneCache() } get (user: string): string | undefined { const value = this.map.get(user) if (value !== undefined) { - value.lastUsed = new Date() + value.lastUsed = Date.now() return value.database } return undefined @@ -34,9 +34,11 @@ export default class HomeDatabaseCache { } private _pruneCache (): void { + console.log(this.map.size) if (this.map.size > 1000) { - const sortedArray = Array.from(this.map.entries()).sort((a, b) => a[1].lastUsed.valueOf() - b[1].lastUsed.valueOf()) + const sortedArray = Array.from(this.map.entries()).sort((a, b) => a[1].lastUsed - b[1].lastUsed) for (let i = 0; i < 70; i++) { // + console.error(sortedArray[i]) this.map.delete(sortedArray[i][0]) } } @@ -45,5 +47,5 @@ export default class HomeDatabaseCache { interface HomeDatabaseEntry { database: string - lastUsed: Date + lastUsed: number } diff --git a/packages/neo4j-driver-deno/lib/core/internal/homedb-cache.ts b/packages/neo4j-driver-deno/lib/core/internal/homedb-cache.ts index 676ffdb68..4437e839f 100644 --- a/packages/neo4j-driver-deno/lib/core/internal/homedb-cache.ts +++ b/packages/neo4j-driver-deno/lib/core/internal/homedb-cache.ts @@ -1,49 +1,51 @@ export default class HomeDatabaseCache { - maxSize: number; - map: Map + maxSize: number + map: Map - constructor(maxSize: number) { - this.maxSize = maxSize - this.map = new Map() - } + constructor (maxSize: number) { + this.maxSize = maxSize + this.map = new Map() + } - set(user: string, database: string): void { - this.map.set(user, {database, lastUsed: new Date()}) - this._pruneCache() - } + set (user: string, database: string): void { + this.map.set(user, { database, lastUsed: Date.now() }) + this._pruneCache() + } - get(user: string): string | undefined { - const value = this.map.get(user) - if(value !== undefined) { - value.lastUsed = new Date() - return value.database - } - return undefined + get (user: string): string | undefined { + const value = this.map.get(user) + if (value !== undefined) { + value.lastUsed = Date.now() + return value.database } + return undefined + } - delete(user: string): void { - this.map.delete(user) - } + delete (user: string): void { + this.map.delete(user) + } - removeFailedDatabase(database: string): void { - this.map.forEach((_, key) => { - if (this.map.get(key)?.database === database) { - this.map.delete(key) - } - }) - } + removeFailedDatabase (database: string): void { + this.map.forEach((_, key) => { + if (this.map.get(key)?.database === database) { + this.map.delete(key) + } + }) + } - private _pruneCache(): void { - if (this.map.size > 1000) { - const sortedArray = Array.from(this.map.entries()).sort((a, b) => a[1].lastUsed.valueOf() - b[1].lastUsed.valueOf()) - for(var i = 0; i < 70; i++) { // - this.map.delete(sortedArray[i][0]) - } - } + private _pruneCache (): void { + console.log(this.map.size) + if (this.map.size > 1000) { + const sortedArray = Array.from(this.map.entries()).sort((a, b) => a[1].lastUsed - b[1].lastUsed) + for (let i = 0; i < 70; i++) { // + console.error(sortedArray[i]) + this.map.delete(sortedArray[i][0]) + } } + } } interface HomeDatabaseEntry { - database: string; - lastUsed: Date; -} \ No newline at end of file + database: string + lastUsed: number +} diff --git a/packages/neo4j-driver/test/driver.test.js b/packages/neo4j-driver/test/driver.test.js index 328fd824b..298d349d6 100644 --- a/packages/neo4j-driver/test/driver.test.js +++ b/packages/neo4j-driver/test/driver.test.js @@ -198,16 +198,23 @@ describe('#unit driver', () => { expect(driver.homeDatabaseCache.get('basic:hi')).toBe(undefined) }) - it('should cap homeDb size', () => { + it('should cap homeDb size by removing least recently used', async () => { driver = neo4j.driver( `neo4j+ssc://${sharedNeo4j.hostnameWithBoltPort}`, sharedNeo4j.authToken ) - for (let i = 0; i < 1050; i++) { + for (let i = 0; i < 999; i++) { + driver._homeDatabaseCallback(i.toString(), 'neo4j') + } + driver._homeDatabaseCallback('5', 'neo4j') + driver.homeDatabaseCache.get('55') + for (let i = 999; i < 1050; i++) { driver._homeDatabaseCallback(i.toString(), 'neo4j') } expect(driver.homeDatabaseCache.get('1')).toEqual(undefined) expect(driver.homeDatabaseCache.get('69')).toEqual(undefined) + expect(driver.homeDatabaseCache.get('5')).toEqual('neo4j') + expect(driver.homeDatabaseCache.get('55')).toEqual('neo4j') expect(driver.homeDatabaseCache.get('101')).toEqual('neo4j') expect(driver.homeDatabaseCache.get('1001')).toEqual('neo4j') }) From 9e92450835592b8c9d35699bfc6d3af8d5ace070 Mon Sep 17 00:00:00 2001 From: MaxAake <61233757+MaxAake@users.noreply.github.com> Date: Tue, 3 Dec 2024 09:31:43 +0100 Subject: [PATCH 44/84] remove debug logging --- packages/core/src/internal/homedb-cache.ts | 4 +--- packages/neo4j-driver-deno/lib/core/internal/homedb-cache.ts | 4 +--- 2 files changed, 2 insertions(+), 6 deletions(-) diff --git a/packages/core/src/internal/homedb-cache.ts b/packages/core/src/internal/homedb-cache.ts index 4437e839f..eae732951 100644 --- a/packages/core/src/internal/homedb-cache.ts +++ b/packages/core/src/internal/homedb-cache.ts @@ -34,11 +34,9 @@ export default class HomeDatabaseCache { } private _pruneCache (): void { - console.log(this.map.size) if (this.map.size > 1000) { const sortedArray = Array.from(this.map.entries()).sort((a, b) => a[1].lastUsed - b[1].lastUsed) - for (let i = 0; i < 70; i++) { // - console.error(sortedArray[i]) + for (let i = 0; i < 70; i++) { // c * max_size * logn(max_size), c is set to 0.01 this.map.delete(sortedArray[i][0]) } } diff --git a/packages/neo4j-driver-deno/lib/core/internal/homedb-cache.ts b/packages/neo4j-driver-deno/lib/core/internal/homedb-cache.ts index 4437e839f..eae732951 100644 --- a/packages/neo4j-driver-deno/lib/core/internal/homedb-cache.ts +++ b/packages/neo4j-driver-deno/lib/core/internal/homedb-cache.ts @@ -34,11 +34,9 @@ export default class HomeDatabaseCache { } private _pruneCache (): void { - console.log(this.map.size) if (this.map.size > 1000) { const sortedArray = Array.from(this.map.entries()).sort((a, b) => a[1].lastUsed - b[1].lastUsed) - for (let i = 0; i < 70; i++) { // - console.error(sortedArray[i]) + for (let i = 0; i < 70; i++) { // c * max_size * logn(max_size), c is set to 0.01 this.map.delete(sortedArray[i][0]) } } From 06db084e32d3f8e5306dbd8dd3ec15b81275e6de Mon Sep 17 00:00:00 2001 From: MaxAake <61233757+MaxAake@users.noreply.github.com> Date: Tue, 3 Dec 2024 10:10:20 +0100 Subject: [PATCH 45/84] some test fixes --- packages/neo4j-driver/test/driver.test.js | 7 ++++--- packages/neo4j-driver/test/examples.test.js | 2 +- packages/neo4j-driver/test/internal/server-version.test.js | 2 +- 3 files changed, 6 insertions(+), 5 deletions(-) diff --git a/packages/neo4j-driver/test/driver.test.js b/packages/neo4j-driver/test/driver.test.js index 298d349d6..8c6776016 100644 --- a/packages/neo4j-driver/test/driver.test.js +++ b/packages/neo4j-driver/test/driver.test.js @@ -206,6 +206,7 @@ describe('#unit driver', () => { for (let i = 0; i < 999; i++) { driver._homeDatabaseCallback(i.toString(), 'neo4j') } + await new Promise(resolve => setTimeout(resolve, 100)) driver._homeDatabaseCallback('5', 'neo4j') driver.homeDatabaseCache.get('55') for (let i = 999; i < 1050; i++) { @@ -306,7 +307,7 @@ describe('#integration driver', () => { ) await session.close() - }, 10000) + }, 20000) it('should fail with correct error message when connecting to port 80', done => { if (testUtils.isClient()) { @@ -343,7 +344,7 @@ describe('#integration driver', () => { done() } }) - }) + }, 60000) it('should handle wrong scheme', () => { expect(() => @@ -695,7 +696,7 @@ describe('#integration driver', () => { it('hasReachableServer failure', async () => { await expectAsync(neo4j.hasReachableServer(`${sharedNeo4j.scheme}://${sharedNeo4j.hostname}:9999`)) .toBeRejected() - }) + }, 120000) const integersWithNativeNumberEquivalent = [ [neo4j.int(0), 0], diff --git a/packages/neo4j-driver/test/examples.test.js b/packages/neo4j-driver/test/examples.test.js index d282e54ca..c158ce7fb 100644 --- a/packages/neo4j-driver/test/examples.test.js +++ b/packages/neo4j-driver/test/examples.test.js @@ -734,7 +734,7 @@ describe('#integration examples', () => { }) .then(() => driver.close()) .then(() => done()) - }, 60000) + }, 120000) it('session example', async () => { const console = consoleOverride diff --git a/packages/neo4j-driver/test/internal/server-version.test.js b/packages/neo4j-driver/test/internal/server-version.test.js index 6e1681442..500e0d9b0 100644 --- a/packages/neo4j-driver/test/internal/server-version.test.js +++ b/packages/neo4j-driver/test/internal/server-version.test.js @@ -172,7 +172,7 @@ describe('#integration ServerVersion', () => { await expectAsync(ServerVersion.fromDriver(driver)).toBeRejected() await driver.close() - }) + }, 120000) }) function verifyVersion ( From 5786c2930fbb3749eae9c6c8fbe0723d52600cd3 Mon Sep 17 00:00:00 2001 From: MaxAake <61233757+MaxAake@users.noreply.github.com> Date: Wed, 4 Dec 2024 10:02:46 +0100 Subject: [PATCH 46/84] expansion of unit tests --- packages/neo4j-driver/test/driver.test.js | 25 +++++++++++++++++++++++ 1 file changed, 25 insertions(+) diff --git a/packages/neo4j-driver/test/driver.test.js b/packages/neo4j-driver/test/driver.test.js index 8c6776016..bc812d57b 100644 --- a/packages/neo4j-driver/test/driver.test.js +++ b/packages/neo4j-driver/test/driver.test.js @@ -193,9 +193,34 @@ describe('#unit driver', () => { expect(driver.homeDatabaseCache.get('DEFAULT')).toBe('neo4j') driver._homeDatabaseCallback('basic:hi', 'neo4j') expect(driver.homeDatabaseCache.get('basic:hi')).toBe('neo4j') + }) + + it('should change homedb entries with new info', () => { + driver = neo4j.driver( + `neo4j+ssc://${sharedNeo4j.hostnameWithBoltPort}`, + sharedNeo4j.authToken + ) + driver._homeDatabaseCallback('DEFAULT', 'neo4j') + expect(driver.homeDatabaseCache.get('DEFAULT')).toBe('neo4j') + driver._homeDatabaseCallback('DEFAULT', 'neo5j') + expect(driver.homeDatabaseCache.get('DEFAULT')).toBe('neo5j') + }) + + it('should remove entries in homeDb cache when their database fails', () => { + driver = neo4j.driver( + `neo4j+ssc://${sharedNeo4j.hostnameWithBoltPort}`, + sharedNeo4j.authToken + ) + driver._homeDatabaseCallback('DEFAULT', 'neo4j') + expect(driver.homeDatabaseCache.get('DEFAULT')).toBe('neo4j') + driver._homeDatabaseCallback('basic:hi', 'neo4j') + expect(driver.homeDatabaseCache.get('basic:hi')).toBe('neo4j') + driver._homeDatabaseCallback('basic:hello', 'neo5j') + expect(driver.homeDatabaseCache.get('basic:hello')).toBe('neo5j') driver._removeFailureFromCache('neo4j') expect(driver.homeDatabaseCache.get('DEFAULT')).toBe(undefined) expect(driver.homeDatabaseCache.get('basic:hi')).toBe(undefined) + expect(driver.homeDatabaseCache.get('basic:hello')).toBe('neo5j') }) it('should cap homeDb size by removing least recently used', async () => { From 0ef51c0bcbca8769789df03df34b5509a32de15d Mon Sep 17 00:00:00 2001 From: MaxAake <61233757+MaxAake@users.noreply.github.com> Date: Wed, 4 Dec 2024 12:34:30 +0100 Subject: [PATCH 47/84] integration tests on driver, session and impersonated auth --- packages/neo4j-driver/test/driver.test.js | 41 ++++++++++++----------- 1 file changed, 21 insertions(+), 20 deletions(-) diff --git a/packages/neo4j-driver/test/driver.test.js b/packages/neo4j-driver/test/driver.test.js index bc812d57b..51674e77c 100644 --- a/packages/neo4j-driver/test/driver.test.js +++ b/packages/neo4j-driver/test/driver.test.js @@ -599,26 +599,27 @@ describe('#integration driver', () => { expect(connections1[0]).not.toEqual(connections2[0]) }) - it('should build home database cache', async () => { - driver = neo4j.driver( - `neo4j://${sharedNeo4j.hostnameWithBoltPort}`, - sharedNeo4j.authToken - ) - if (protocolVersion >= 5.1) { - const session1 = driver.session({ auth: sharedNeo4j.authToken }) - await session1.run('CREATE () RETURN 42') - - // one connection should be established - const connections1 = openConnectionFrom(driver) - expect(connections1.length).toEqual(1) - - expect(driver.homeDatabaseCache.get(sharedNeo4j.authToken.cacheKey)).toBe('neo4j') - expect(session1._database).toBe('neo4j') - const session2 = driver.session({ auth: sharedNeo4j.authToken }) - expect(session2._databaseGuess).toBe('neo4j') - expect(session2._database).toBe('') - await session2.run('CREATE () RETURN 43') - } + describe('HomeDatabaseCache"', () => { + [['with driver auth', {}, 'DEFAULT'], + ['with session auth', { auth: sharedNeo4j.authToken }, sharedNeo4j.authToken.cacheKey], + ['with impersonated user', { impersonatedUser: 'neo4j' }, 'basic:neo4j']].forEach(([string, auth, key]) => { + it('should build home database cache ' + string, async () => { + driver = neo4j.driver( + `neo4j://${sharedNeo4j.hostnameWithBoltPort}`, + sharedNeo4j.authToken + ) + if (protocolVersion >= 5.1) { + const session1 = driver.session(auth) + await session1.run('CREATE () RETURN 42') + expect(driver.homeDatabaseCache.get(key)).toBe('neo4j') // should have set the homedb in cache + expect(session1._database).toBe('neo4j') // should have pinned database to the session + const session2 = driver.session(auth) + expect(session2._databaseGuess).toBe('neo4j') // second session should use the homedb as a guess... + expect(session2._database).toBe('') // ...but should not pin this to the session. + await session2.run('CREATE () RETURN 43') + } + }) + }) }) it('should discard old connections', async () => { From 8d75497192ec5dfe5f4b71268d45a749ef301e1c Mon Sep 17 00:00:00 2001 From: MaxAake <61233757+MaxAake@users.noreply.github.com> Date: Thu, 5 Dec 2024 16:02:53 +0100 Subject: [PATCH 48/84] check for community edition in test --- packages/neo4j-driver/test/driver.test.js | 20 ++++++++++++-------- 1 file changed, 12 insertions(+), 8 deletions(-) diff --git a/packages/neo4j-driver/test/driver.test.js b/packages/neo4j-driver/test/driver.test.js index 51674e77c..52ad44da7 100644 --- a/packages/neo4j-driver/test/driver.test.js +++ b/packages/neo4j-driver/test/driver.test.js @@ -609,14 +609,18 @@ describe('#integration driver', () => { sharedNeo4j.authToken ) if (protocolVersion >= 5.1) { - const session1 = driver.session(auth) - await session1.run('CREATE () RETURN 42') - expect(driver.homeDatabaseCache.get(key)).toBe('neo4j') // should have set the homedb in cache - expect(session1._database).toBe('neo4j') // should have pinned database to the session - const session2 = driver.session(auth) - expect(session2._databaseGuess).toBe('neo4j') // second session should use the homedb as a guess... - expect(session2._database).toBe('') // ...but should not pin this to the session. - await session2.run('CREATE () RETURN 43') + try { + const session1 = driver.session(auth) + await session1.run('CREATE () RETURN 42') + expect(driver.homeDatabaseCache.get(key)).toBe('neo4j') // should have set the homedb in cache + expect(session1._database).toBe('neo4j') // should have pinned database to the session + const session2 = driver.session(auth) + expect(session2._databaseGuess).toBe('neo4j') // second session should use the homedb as a guess... + expect(session2._database).toBe('') // ...but should not pin this to the session. + await session2.run('CREATE () RETURN 43') + } catch (e) { + expect(e.toString()).toContain('not supported in community edition') + } } }) }) From 64a3bd11d1f5f39ceac2a54aa06693a94fb6cb28 Mon Sep 17 00:00:00 2001 From: MaxAake <61233757+MaxAake@users.noreply.github.com> Date: Mon, 9 Dec 2024 11:15:33 +0100 Subject: [PATCH 49/84] better check for impersonation and correct protocol version --- packages/neo4j-driver/test/driver.test.js | 22 +++++++++------------- 1 file changed, 9 insertions(+), 13 deletions(-) diff --git a/packages/neo4j-driver/test/driver.test.js b/packages/neo4j-driver/test/driver.test.js index 52ad44da7..aa3c1c5cb 100644 --- a/packages/neo4j-driver/test/driver.test.js +++ b/packages/neo4j-driver/test/driver.test.js @@ -608,19 +608,15 @@ describe('#integration driver', () => { `neo4j://${sharedNeo4j.hostnameWithBoltPort}`, sharedNeo4j.authToken ) - if (protocolVersion >= 5.1) { - try { - const session1 = driver.session(auth) - await session1.run('CREATE () RETURN 42') - expect(driver.homeDatabaseCache.get(key)).toBe('neo4j') // should have set the homedb in cache - expect(session1._database).toBe('neo4j') // should have pinned database to the session - const session2 = driver.session(auth) - expect(session2._databaseGuess).toBe('neo4j') // second session should use the homedb as a guess... - expect(session2._database).toBe('') // ...but should not pin this to the session. - await session2.run('CREATE () RETURN 43') - } catch (e) { - expect(e.toString()).toContain('not supported in community edition') - } + if (protocolVersion >= 5.8 && (!string.includes('impersonated') || driver.supportsUserImpersonation())) { + const session1 = driver.session(auth) + await session1.run('CREATE () RETURN 42') + expect(driver.homeDatabaseCache.get(key)).toBe('neo4j') // should have set the homedb in cache + expect(session1._database).toBe('neo4j') // should have pinned database to the session + const session2 = driver.session(auth) + expect(session2._databaseGuess).toBe('neo4j') // second session should use the homedb as a guess... + expect(session2._database).toBe('') // ...but should not pin this to the session. + await session2.run('CREATE () RETURN 43') } }) }) From ab871958a75fbf4310616de46c68991366063498 Mon Sep 17 00:00:00 2001 From: MaxAake <61233757+MaxAake@users.noreply.github.com> Date: Mon, 9 Dec 2024 12:00:41 +0100 Subject: [PATCH 50/84] fix typo in tests --- packages/neo4j-driver/test/driver.test.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/neo4j-driver/test/driver.test.js b/packages/neo4j-driver/test/driver.test.js index aa3c1c5cb..513f5ff98 100644 --- a/packages/neo4j-driver/test/driver.test.js +++ b/packages/neo4j-driver/test/driver.test.js @@ -599,7 +599,7 @@ describe('#integration driver', () => { expect(connections1[0]).not.toEqual(connections2[0]) }) - describe('HomeDatabaseCache"', () => { + describe('HomeDatabaseCache', () => { [['with driver auth', {}, 'DEFAULT'], ['with session auth', { auth: sharedNeo4j.authToken }, sharedNeo4j.authToken.cacheKey], ['with impersonated user', { impersonatedUser: 'neo4j' }, 'basic:neo4j']].forEach(([string, auth, key]) => { From a5e97a86d06bc3a4c5928763d783721711e1f277 Mon Sep 17 00:00:00 2001 From: MaxAake <61233757+MaxAake@users.noreply.github.com> Date: Mon, 9 Dec 2024 13:46:00 +0100 Subject: [PATCH 51/84] Update driver.test.js --- packages/neo4j-driver/test/driver.test.js | 22 +++++++++++++--------- 1 file changed, 13 insertions(+), 9 deletions(-) diff --git a/packages/neo4j-driver/test/driver.test.js b/packages/neo4j-driver/test/driver.test.js index 513f5ff98..d7e476db0 100644 --- a/packages/neo4j-driver/test/driver.test.js +++ b/packages/neo4j-driver/test/driver.test.js @@ -608,15 +608,19 @@ describe('#integration driver', () => { `neo4j://${sharedNeo4j.hostnameWithBoltPort}`, sharedNeo4j.authToken ) - if (protocolVersion >= 5.8 && (!string.includes('impersonated') || driver.supportsUserImpersonation())) { - const session1 = driver.session(auth) - await session1.run('CREATE () RETURN 42') - expect(driver.homeDatabaseCache.get(key)).toBe('neo4j') // should have set the homedb in cache - expect(session1._database).toBe('neo4j') // should have pinned database to the session - const session2 = driver.session(auth) - expect(session2._databaseGuess).toBe('neo4j') // second session should use the homedb as a guess... - expect(session2._database).toBe('') // ...but should not pin this to the session. - await session2.run('CREATE () RETURN 43') + if (protocolVersion >= 5.8) { + try { + const session1 = driver.session(auth) + await session1.run('CREATE () RETURN 42') + expect(driver.homeDatabaseCache.get(key)).toBe('neo4j') // should have set the homedb in cache + expect(session1._database).toBe('neo4j') // should have pinned database to the session + const session2 = driver.session(auth) + expect(session2._databaseGuess).toBe('neo4j') // second session should use the homedb as a guess... + expect(session2._database).toBe('') // ...but should not pin this to the session. + await session2.run('CREATE () RETURN 43') + } catch (e) { + expect(e.message.includes('Impersonation is not supported in community edition.')).toBe(true) + } } }) }) From 432a34b2f500a4c7d7979b22d55a78155f3f23b7 Mon Sep 17 00:00:00 2001 From: MaxAake <61233757+MaxAake@users.noreply.github.com> Date: Tue, 10 Dec 2024 14:19:49 +0100 Subject: [PATCH 52/84] improved documentation --- packages/core/src/driver.ts | 13 ++++- packages/core/src/internal/homedb-cache.ts | 51 ++++++++++++++++++- packages/neo4j-driver-deno/lib/core/driver.ts | 13 ++++- .../lib/core/internal/homedb-cache.ts | 51 ++++++++++++++++++- packages/neo4j-driver/test/driver.test.js | 6 +-- 5 files changed, 125 insertions(+), 9 deletions(-) diff --git a/packages/core/src/driver.ts b/packages/core/src/driver.ts index 6bf52eae6..cd6836c66 100644 --- a/packages/core/src/driver.ts +++ b/packages/core/src/driver.ts @@ -56,6 +56,12 @@ const DEFAULT_MAX_CONNECTION_LIFETIME: number = 60 * 60 * 1000 // 1 hour */ const DEFAULT_FETCH_SIZE: number = 1000 +/** + * The maximum number of entries allowed in the home database cache before pruning + * @type {number} + */ +const HOMEDB_CACHE_MAX_SIZE: number = 10000 + /** * Constant that represents read session access mode. * Should be used like this: `driver.session({ defaultAccessMode: neo4j.session.READ })`. @@ -474,7 +480,7 @@ class Driver { private readonly _createSession: CreateSession private readonly _defaultExecuteQueryBookmarkManager: BookmarkManager private readonly _queryExecutor: QueryExecutor - homeDatabaseCache: HomeDatabaseCache + private readonly homeDatabaseCache: HomeDatabaseCache /** * You should not be calling this directly, instead use {@link driver}. @@ -514,7 +520,10 @@ class Driver { */ this._connectionProvider = null - this.homeDatabaseCache = new HomeDatabaseCache(10000) + /** + * @private + */ + this.homeDatabaseCache = new HomeDatabaseCache(HOMEDB_CACHE_MAX_SIZE) this._afterConstruction() } diff --git a/packages/core/src/internal/homedb-cache.ts b/packages/core/src/internal/homedb-cache.ts index eae732951..e5a76d6b8 100644 --- a/packages/core/src/internal/homedb-cache.ts +++ b/packages/core/src/internal/homedb-cache.ts @@ -1,3 +1,25 @@ +/** + * Copyright (c) "Neo4j" + * Neo4j Sweden AB [https://neo4j.com] + * + * 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. + */ + +/** + * Cache which maps users to their last known home database, along with the last time the entry was accessed. + * + * @private + */ export default class HomeDatabaseCache { maxSize: number map: Map @@ -7,11 +29,22 @@ export default class HomeDatabaseCache { this.map = new Map() } + /** + * Updates or add an entry to the cache, and prunes the cache if above the maximum allowed size + * + * @param {string} user cache key for the user to set + * @param {string} database new home database to set for the user + */ set (user: string, database: string): void { this.map.set(user, { database, lastUsed: Date.now() }) this._pruneCache() } + /** + * retrieves the last known home database for a user + * + * @param {string} user cache key for the user to get + */ get (user: string): string | undefined { const value = this.map.get(user) if (value !== undefined) { @@ -21,10 +54,20 @@ export default class HomeDatabaseCache { return undefined } + /** + * removes the entry for a given user in the cache + * + * @param {string} user cache key for the user to remove + */ delete (user: string): void { this.map.delete(user) } + /** + * removes all entries listing a given database from the cache + * + * @param {string} database the name of the database to clear from the cache + */ removeFailedDatabase (database: string): void { this.map.forEach((_, key) => { if (this.map.get(key)?.database === database) { @@ -33,8 +76,11 @@ export default class HomeDatabaseCache { }) } + /** + * Removes a number of the oldest entries in the cache if the number of entries has exceeded the maximum size. + */ private _pruneCache (): void { - if (this.map.size > 1000) { + if (this.map.size > this.maxSize) { const sortedArray = Array.from(this.map.entries()).sort((a, b) => a[1].lastUsed - b[1].lastUsed) for (let i = 0; i < 70; i++) { // c * max_size * logn(max_size), c is set to 0.01 this.map.delete(sortedArray[i][0]) @@ -43,6 +89,9 @@ export default class HomeDatabaseCache { } } +/** + * Interface for an entry in the cache. + */ interface HomeDatabaseEntry { database: string lastUsed: number diff --git a/packages/neo4j-driver-deno/lib/core/driver.ts b/packages/neo4j-driver-deno/lib/core/driver.ts index 1b0e477f6..fa3c36a19 100644 --- a/packages/neo4j-driver-deno/lib/core/driver.ts +++ b/packages/neo4j-driver-deno/lib/core/driver.ts @@ -56,6 +56,12 @@ const DEFAULT_MAX_CONNECTION_LIFETIME: number = 60 * 60 * 1000 // 1 hour */ const DEFAULT_FETCH_SIZE: number = 1000 +/** + * The maximum number of entries allowed in the home database cache before pruning + * @type {number} + */ +const HOMEDB_CACHE_MAX_SIZE: number = 10000 + /** * Constant that represents read session access mode. * Should be used like this: `driver.session({ defaultAccessMode: neo4j.session.READ })`. @@ -474,7 +480,7 @@ class Driver { private readonly _createSession: CreateSession private readonly _defaultExecuteQueryBookmarkManager: BookmarkManager private readonly _queryExecutor: QueryExecutor - homeDatabaseCache: HomeDatabaseCache + private homeDatabaseCache: HomeDatabaseCache /** * You should not be calling this directly, instead use {@link driver}. @@ -514,7 +520,10 @@ class Driver { */ this._connectionProvider = null - this.homeDatabaseCache = new HomeDatabaseCache(10000) + /** + * @private + */ + this.homeDatabaseCache = new HomeDatabaseCache(HOMEDB_CACHE_MAX_SIZE) this._afterConstruction() } diff --git a/packages/neo4j-driver-deno/lib/core/internal/homedb-cache.ts b/packages/neo4j-driver-deno/lib/core/internal/homedb-cache.ts index eae732951..0ed8db1af 100644 --- a/packages/neo4j-driver-deno/lib/core/internal/homedb-cache.ts +++ b/packages/neo4j-driver-deno/lib/core/internal/homedb-cache.ts @@ -1,3 +1,25 @@ +/** + * Copyright (c) "Neo4j" + * Neo4j Sweden AB [https://neo4j.com] + * + * 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. + */ + +/** + * Cache which maps users to their last known home database, along with the last time the entry was accessed. + * + * @private + */ export default class HomeDatabaseCache { maxSize: number map: Map @@ -7,11 +29,22 @@ export default class HomeDatabaseCache { this.map = new Map() } + /** + * Updates or add an entry to the cache, and prunes the cache if above the maximum allowed size + * + * @param {string} user cache key for the user to set + * @param {string} database new home database to set for the user + */ set (user: string, database: string): void { this.map.set(user, { database, lastUsed: Date.now() }) this._pruneCache() } + /** + * retrieves the last known home database for a user + * + * @param {string} user cache key for the user to get + */ get (user: string): string | undefined { const value = this.map.get(user) if (value !== undefined) { @@ -21,10 +54,20 @@ export default class HomeDatabaseCache { return undefined } + /** + * removes the entry for a given user in the cache + * + * @param {string} user cache key for the user to remove + */ delete (user: string): void { this.map.delete(user) } + /** + * removes all entries listing a given database from the cache + * + * @param {string} database the name of the database to clear from the cache + */ removeFailedDatabase (database: string): void { this.map.forEach((_, key) => { if (this.map.get(key)?.database === database) { @@ -33,8 +76,11 @@ export default class HomeDatabaseCache { }) } + /** + * Removes a number of the oldest entries in the cache if the number of entries has exceeded the maximum size. + */ private _pruneCache (): void { - if (this.map.size > 1000) { + if (this.map.size > this.maxSize) { const sortedArray = Array.from(this.map.entries()).sort((a, b) => a[1].lastUsed - b[1].lastUsed) for (let i = 0; i < 70; i++) { // c * max_size * logn(max_size), c is set to 0.01 this.map.delete(sortedArray[i][0]) @@ -43,6 +89,9 @@ export default class HomeDatabaseCache { } } +/** + * Interface for an entry in the cache. + */ interface HomeDatabaseEntry { database: string lastUsed: number diff --git a/packages/neo4j-driver/test/driver.test.js b/packages/neo4j-driver/test/driver.test.js index d7e476db0..d61c53a0f 100644 --- a/packages/neo4j-driver/test/driver.test.js +++ b/packages/neo4j-driver/test/driver.test.js @@ -228,13 +228,13 @@ describe('#unit driver', () => { `neo4j+ssc://${sharedNeo4j.hostnameWithBoltPort}`, sharedNeo4j.authToken ) - for (let i = 0; i < 999; i++) { + for (let i = 0; i < 9999; i++) { driver._homeDatabaseCallback(i.toString(), 'neo4j') } await new Promise(resolve => setTimeout(resolve, 100)) driver._homeDatabaseCallback('5', 'neo4j') driver.homeDatabaseCache.get('55') - for (let i = 999; i < 1050; i++) { + for (let i = 9999; i < 10050; i++) { driver._homeDatabaseCallback(i.toString(), 'neo4j') } expect(driver.homeDatabaseCache.get('1')).toEqual(undefined) @@ -242,7 +242,7 @@ describe('#unit driver', () => { expect(driver.homeDatabaseCache.get('5')).toEqual('neo4j') expect(driver.homeDatabaseCache.get('55')).toEqual('neo4j') expect(driver.homeDatabaseCache.get('101')).toEqual('neo4j') - expect(driver.homeDatabaseCache.get('1001')).toEqual('neo4j') + expect(driver.homeDatabaseCache.get('10001')).toEqual('neo4j') }) }) }) From cdb6778d3918341ac01155a4259813321b13481e Mon Sep 17 00:00:00 2001 From: MaxAake <61233757+MaxAake@users.noreply.github.com> Date: Tue, 10 Dec 2024 14:20:20 +0100 Subject: [PATCH 53/84] deno --- packages/neo4j-driver-deno/lib/core/driver.ts | 2 +- .../lib/core/internal/homedb-cache.ts | 10 +++++----- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/packages/neo4j-driver-deno/lib/core/driver.ts b/packages/neo4j-driver-deno/lib/core/driver.ts index fa3c36a19..3d7e7e81b 100644 --- a/packages/neo4j-driver-deno/lib/core/driver.ts +++ b/packages/neo4j-driver-deno/lib/core/driver.ts @@ -480,7 +480,7 @@ class Driver { private readonly _createSession: CreateSession private readonly _defaultExecuteQueryBookmarkManager: BookmarkManager private readonly _queryExecutor: QueryExecutor - private homeDatabaseCache: HomeDatabaseCache + private readonly homeDatabaseCache: HomeDatabaseCache /** * You should not be calling this directly, instead use {@link driver}. diff --git a/packages/neo4j-driver-deno/lib/core/internal/homedb-cache.ts b/packages/neo4j-driver-deno/lib/core/internal/homedb-cache.ts index 0ed8db1af..e5a76d6b8 100644 --- a/packages/neo4j-driver-deno/lib/core/internal/homedb-cache.ts +++ b/packages/neo4j-driver-deno/lib/core/internal/homedb-cache.ts @@ -17,7 +17,7 @@ /** * Cache which maps users to their last known home database, along with the last time the entry was accessed. - * + * * @private */ export default class HomeDatabaseCache { @@ -31,7 +31,7 @@ export default class HomeDatabaseCache { /** * Updates or add an entry to the cache, and prunes the cache if above the maximum allowed size - * + * * @param {string} user cache key for the user to set * @param {string} database new home database to set for the user */ @@ -42,7 +42,7 @@ export default class HomeDatabaseCache { /** * retrieves the last known home database for a user - * + * * @param {string} user cache key for the user to get */ get (user: string): string | undefined { @@ -56,7 +56,7 @@ export default class HomeDatabaseCache { /** * removes the entry for a given user in the cache - * + * * @param {string} user cache key for the user to remove */ delete (user: string): void { @@ -65,7 +65,7 @@ export default class HomeDatabaseCache { /** * removes all entries listing a given database from the cache - * + * * @param {string} database the name of the database to clear from the cache */ removeFailedDatabase (database: string): void { From 968b449e8fc70c53366be3083a0c8fb3796b1698 Mon Sep 17 00:00:00 2001 From: MaxAake <61233757+MaxAake@users.noreply.github.com> Date: Tue, 10 Dec 2024 17:26:55 +0100 Subject: [PATCH 54/84] clean up --- .../src/connection-provider/connection-provider-routing.js | 2 ++ packages/core/src/driver.ts | 1 + packages/core/src/internal/transaction-executor.ts | 1 + .../connection-provider/connection-provider-routing.js | 2 ++ packages/neo4j-driver-deno/lib/core/driver.ts | 1 + .../lib/core/internal/transaction-executor.ts | 1 + packages/neo4j-driver/src/index.js | 1 + packages/neo4j-driver/test/driver.test.js | 6 +++--- packages/neo4j-driver/test/examples.test.js | 2 +- packages/neo4j-driver/test/internal/server-version.test.js | 2 +- 10 files changed, 14 insertions(+), 5 deletions(-) diff --git a/packages/bolt-connection/src/connection-provider/connection-provider-routing.js b/packages/bolt-connection/src/connection-provider/connection-provider-routing.js index 73efa85c8..b8e665719 100644 --- a/packages/bolt-connection/src/connection-provider/connection-provider-routing.js +++ b/packages/bolt-connection/src/connection-provider/connection-provider-routing.js @@ -203,6 +203,7 @@ export default class RoutingConnectionProvider extends PooledConnectionProvider try { const connection = await this._connectionPool.acquire({ auth }, address) + if (auth) { await this._verifyStickyConnection({ auth, @@ -362,6 +363,7 @@ export default class RoutingConnectionProvider extends PooledConnectionProvider database, () => new RoutingTable({ database }) ) + if (!currentRoutingTable.isStaleFor(accessMode)) { return currentRoutingTable } diff --git a/packages/core/src/driver.ts b/packages/core/src/driver.ts index cd6836c66..5bc0a351c 100644 --- a/packages/core/src/driver.ts +++ b/packages/core/src/driver.ts @@ -927,6 +927,7 @@ class Driver { createHostNameResolver(this._config) ) } + return this._connectionProvider } } diff --git a/packages/core/src/internal/transaction-executor.ts b/packages/core/src/internal/transaction-executor.ts index 11a055e78..724c4c314 100644 --- a/packages/core/src/internal/transaction-executor.ts +++ b/packages/core/src/internal/transaction-executor.ts @@ -88,6 +88,7 @@ export class TransactionExecutor { this._setTimeout = dependencies.setTimeout this._clearTimeout = dependencies.clearTimeout + this._inFlightTimeoutIds = [] this.pipelineBegin = false this.telemetryApi = TELEMETRY_APIS.MANAGED_TRANSACTION diff --git a/packages/neo4j-driver-deno/lib/bolt-connection/connection-provider/connection-provider-routing.js b/packages/neo4j-driver-deno/lib/bolt-connection/connection-provider/connection-provider-routing.js index a35ca8dd8..2dc7907bf 100644 --- a/packages/neo4j-driver-deno/lib/bolt-connection/connection-provider/connection-provider-routing.js +++ b/packages/neo4j-driver-deno/lib/bolt-connection/connection-provider/connection-provider-routing.js @@ -203,6 +203,7 @@ export default class RoutingConnectionProvider extends PooledConnectionProvider try { const connection = await this._connectionPool.acquire({ auth }, address) + if (auth) { await this._verifyStickyConnection({ auth, @@ -362,6 +363,7 @@ export default class RoutingConnectionProvider extends PooledConnectionProvider database, () => new RoutingTable({ database }) ) + if (!currentRoutingTable.isStaleFor(accessMode)) { return currentRoutingTable } diff --git a/packages/neo4j-driver-deno/lib/core/driver.ts b/packages/neo4j-driver-deno/lib/core/driver.ts index 3d7e7e81b..b2297ef57 100644 --- a/packages/neo4j-driver-deno/lib/core/driver.ts +++ b/packages/neo4j-driver-deno/lib/core/driver.ts @@ -927,6 +927,7 @@ class Driver { createHostNameResolver(this._config) ) } + return this._connectionProvider } } diff --git a/packages/neo4j-driver-deno/lib/core/internal/transaction-executor.ts b/packages/neo4j-driver-deno/lib/core/internal/transaction-executor.ts index e334f314e..9e038bca6 100644 --- a/packages/neo4j-driver-deno/lib/core/internal/transaction-executor.ts +++ b/packages/neo4j-driver-deno/lib/core/internal/transaction-executor.ts @@ -88,6 +88,7 @@ export class TransactionExecutor { this._setTimeout = dependencies.setTimeout this._clearTimeout = dependencies.clearTimeout + this._inFlightTimeoutIds = [] this.pipelineBegin = false this.telemetryApi = TELEMETRY_APIS.MANAGED_TRANSACTION diff --git a/packages/neo4j-driver/src/index.js b/packages/neo4j-driver/src/index.js index 16ede9447..911ad9fcd 100644 --- a/packages/neo4j-driver/src/index.js +++ b/packages/neo4j-driver/src/index.js @@ -189,6 +189,7 @@ function driver (url, authToken, config = {}) { typename: routing ? 'Routing' : 'Direct', routing } + return new Driver(meta, config, createConnectionProviderFunction()) function createConnectionProviderFunction () { diff --git a/packages/neo4j-driver/test/driver.test.js b/packages/neo4j-driver/test/driver.test.js index d61c53a0f..f8aeb01ed 100644 --- a/packages/neo4j-driver/test/driver.test.js +++ b/packages/neo4j-driver/test/driver.test.js @@ -332,7 +332,7 @@ describe('#integration driver', () => { ) await session.close() - }, 20000) + }, 10000) it('should fail with correct error message when connecting to port 80', done => { if (testUtils.isClient()) { @@ -369,7 +369,7 @@ describe('#integration driver', () => { done() } }) - }, 60000) + }) it('should handle wrong scheme', () => { expect(() => @@ -726,7 +726,7 @@ describe('#integration driver', () => { it('hasReachableServer failure', async () => { await expectAsync(neo4j.hasReachableServer(`${sharedNeo4j.scheme}://${sharedNeo4j.hostname}:9999`)) .toBeRejected() - }, 120000) + }) const integersWithNativeNumberEquivalent = [ [neo4j.int(0), 0], diff --git a/packages/neo4j-driver/test/examples.test.js b/packages/neo4j-driver/test/examples.test.js index c158ce7fb..d282e54ca 100644 --- a/packages/neo4j-driver/test/examples.test.js +++ b/packages/neo4j-driver/test/examples.test.js @@ -734,7 +734,7 @@ describe('#integration examples', () => { }) .then(() => driver.close()) .then(() => done()) - }, 120000) + }, 60000) it('session example', async () => { const console = consoleOverride diff --git a/packages/neo4j-driver/test/internal/server-version.test.js b/packages/neo4j-driver/test/internal/server-version.test.js index 500e0d9b0..6e1681442 100644 --- a/packages/neo4j-driver/test/internal/server-version.test.js +++ b/packages/neo4j-driver/test/internal/server-version.test.js @@ -172,7 +172,7 @@ describe('#integration ServerVersion', () => { await expectAsync(ServerVersion.fromDriver(driver)).toBeRejected() await driver.close() - }, 120000) + }) }) function verifyVersion ( From d40daf87ae23bf190f8cc6aeb3ad160dc338893f Mon Sep 17 00:00:00 2001 From: MaxAake <61233757+MaxAake@users.noreply.github.com> Date: Tue, 10 Dec 2024 17:27:36 +0100 Subject: [PATCH 55/84] post-cleanup denobuild --- packages/neo4j-driver-deno/lib/core/driver.ts | 2 +- .../neo4j-driver-deno/lib/core/internal/transaction-executor.ts | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/neo4j-driver-deno/lib/core/driver.ts b/packages/neo4j-driver-deno/lib/core/driver.ts index b2297ef57..a7f5dcaef 100644 --- a/packages/neo4j-driver-deno/lib/core/driver.ts +++ b/packages/neo4j-driver-deno/lib/core/driver.ts @@ -927,7 +927,7 @@ class Driver { createHostNameResolver(this._config) ) } - + return this._connectionProvider } } diff --git a/packages/neo4j-driver-deno/lib/core/internal/transaction-executor.ts b/packages/neo4j-driver-deno/lib/core/internal/transaction-executor.ts index 9e038bca6..5e0e2ea87 100644 --- a/packages/neo4j-driver-deno/lib/core/internal/transaction-executor.ts +++ b/packages/neo4j-driver-deno/lib/core/internal/transaction-executor.ts @@ -88,7 +88,7 @@ export class TransactionExecutor { this._setTimeout = dependencies.setTimeout this._clearTimeout = dependencies.clearTimeout - + this._inFlightTimeoutIds = [] this.pipelineBegin = false this.telemetryApi = TELEMETRY_APIS.MANAGED_TRANSACTION From 47429aad8318b5e27a5a9f810e8bc390a092df7e Mon Sep 17 00:00:00 2001 From: MaxAake <61233757+MaxAake@users.noreply.github.com> Date: Wed, 11 Dec 2024 11:41:30 +0100 Subject: [PATCH 56/84] correction --- packages/core/src/driver.ts | 1 + packages/neo4j-driver-deno/lib/core/driver.ts | 1 + 2 files changed, 2 insertions(+) diff --git a/packages/core/src/driver.ts b/packages/core/src/driver.ts index 5bc0a351c..5edb60d29 100644 --- a/packages/core/src/driver.ts +++ b/packages/core/src/driver.ts @@ -894,6 +894,7 @@ class Driver { const bookmarks = bookmarkOrBookmarks != null ? new Bookmarks(bookmarkOrBookmarks) : Bookmarks.empty() + return this._createSession({ mode: sessionMode, database: database ?? '', diff --git a/packages/neo4j-driver-deno/lib/core/driver.ts b/packages/neo4j-driver-deno/lib/core/driver.ts index a7f5dcaef..083e0a378 100644 --- a/packages/neo4j-driver-deno/lib/core/driver.ts +++ b/packages/neo4j-driver-deno/lib/core/driver.ts @@ -894,6 +894,7 @@ class Driver { const bookmarks = bookmarkOrBookmarks != null ? new Bookmarks(bookmarkOrBookmarks) : Bookmarks.empty() + return this._createSession({ mode: sessionMode, database: database ?? '', From ca2fef3577a9de86a4b077e9cf4a5bfd4e16b9be Mon Sep 17 00:00:00 2001 From: MaxAake <61233757+MaxAake@users.noreply.github.com> Date: Thu, 12 Dec 2024 14:15:26 +0100 Subject: [PATCH 57/84] Update testkit.json --- testkit/testkit.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/testkit/testkit.json b/testkit/testkit.json index 83fd2ab1b..931900356 100644 --- a/testkit/testkit.json +++ b/testkit/testkit.json @@ -1,6 +1,6 @@ { "testkit": { "uri": "https://github.com/neo4j-drivers/testkit.git", - "ref": "homedb-cache-spike" + "ref": "5.0" } } From 38e28381ae092eb85031045b618d4f244c7d813d Mon Sep 17 00:00:00 2001 From: MaxAake <61233757+MaxAake@users.noreply.github.com> Date: Tue, 17 Dec 2024 16:15:54 +0100 Subject: [PATCH 58/84] remove failures for read fails, add testkit flag --- .../src/connection-provider/connection-provider-routing.js | 7 ++++++- .../connection-provider/connection-provider-routing.js | 7 ++++++- packages/testkit-backend/src/feature/common.js | 1 + 3 files changed, 13 insertions(+), 2 deletions(-) diff --git a/packages/bolt-connection/src/connection-provider/connection-provider-routing.js b/packages/bolt-connection/src/connection-provider/connection-provider-routing.js index b8e665719..e62c1f2ed 100644 --- a/packages/bolt-connection/src/connection-provider/connection-provider-routing.js +++ b/packages/bolt-connection/src/connection-provider/connection-provider-routing.js @@ -149,7 +149,12 @@ export default class RoutingConnectionProvider extends PooledConnectionProvider const databaseSpecificErrorHandler = new ConnectionErrorHandler( SESSION_EXPIRED, - (error, address) => this._handleUnavailability(error, address, context.database), + (error, address) => { + if (removeFailureFromCache !== undefined) { + removeFailureFromCache(homeDb ?? context.database) + } + return this._handleUnavailability(error, address, context.database) + }, (error, address) => { if (removeFailureFromCache !== undefined) { removeFailureFromCache(homeDb ?? context.database) diff --git a/packages/neo4j-driver-deno/lib/bolt-connection/connection-provider/connection-provider-routing.js b/packages/neo4j-driver-deno/lib/bolt-connection/connection-provider/connection-provider-routing.js index 2dc7907bf..9168e1386 100644 --- a/packages/neo4j-driver-deno/lib/bolt-connection/connection-provider/connection-provider-routing.js +++ b/packages/neo4j-driver-deno/lib/bolt-connection/connection-provider/connection-provider-routing.js @@ -149,7 +149,12 @@ export default class RoutingConnectionProvider extends PooledConnectionProvider const databaseSpecificErrorHandler = new ConnectionErrorHandler( SESSION_EXPIRED, - (error, address) => this._handleUnavailability(error, address, context.database), + (error, address) => { + if (removeFailureFromCache !== undefined) { + removeFailureFromCache(homeDb ?? context.database) + } + return this._handleUnavailability(error, address, context.database) + }, (error, address) => { if (removeFailureFromCache !== undefined) { removeFailureFromCache(homeDb ?? context.database) diff --git a/packages/testkit-backend/src/feature/common.js b/packages/testkit-backend/src/feature/common.js index eeb0aad3d..d6343e221 100644 --- a/packages/testkit-backend/src/feature/common.js +++ b/packages/testkit-backend/src/feature/common.js @@ -47,6 +47,7 @@ const features = [ 'Optimization:MinimalBookmarksSet', 'Optimization:MinimalResets', 'Optimization:AuthPipelining', + 'Optimization:HomeDbCacheBasicPrincipalIsImpersonatedUser', 'Detail:NumberIsNumber' ] From 734ed23672f69be71cad8f209db4c9553f4cb22c Mon Sep 17 00:00:00 2001 From: MaxAake <61233757+MaxAake@users.noreply.github.com> Date: Tue, 7 Jan 2025 13:46:32 +0100 Subject: [PATCH 59/84] addressing comments on the PR --- .../src/bolt/bolt-protocol-v5x6.js | 2 +- .../src/bolt/bolt-protocol-v5x7.js | 2 +- .../src/bolt/bolt-protocol-v5x8.js | 2 +- .../src/bolt/request-message.js | 9 +-- .../connection-provider-routing.js | 20 +++++-- .../src/connection/connection-channel.js | 24 ++++---- packages/core/src/auth.ts | 16 +----- packages/core/src/connection-provider.ts | 8 ++- packages/core/src/driver.ts | 19 ++++--- packages/core/src/internal/auth-utils.ts | 46 ++++++++++++++++ packages/core/src/session.ts | 13 +++-- packages/core/src/types.ts | 1 - packages/core/test/auth.test.ts | 55 +++++++++++++++---- .../bolt/bolt-protocol-v5x6.js | 2 +- .../bolt/bolt-protocol-v5x7.js | 2 +- .../bolt/bolt-protocol-v5x8.js | 2 +- .../bolt-connection/bolt/request-message.js | 9 +-- .../connection-provider-routing.js | 20 +++++-- .../connection/connection-channel.js | 24 ++++---- packages/neo4j-driver-deno/lib/core/auth.ts | 14 +---- .../lib/core/connection-provider.ts | 8 ++- packages/neo4j-driver-deno/lib/core/driver.ts | 20 ++++--- .../lib/core/internal/auth-utils.ts | 50 +++++++++++++++++ .../neo4j-driver-deno/lib/core/session.ts | 13 +++-- packages/neo4j-driver-deno/lib/core/types.ts | 1 - packages/neo4j-driver/test/auth.test.js | 15 ++--- packages/neo4j-driver/test/driver.test.js | 20 +++---- 27 files changed, 276 insertions(+), 141 deletions(-) create mode 100644 packages/core/src/internal/auth-utils.ts create mode 100644 packages/neo4j-driver-deno/lib/core/internal/auth-utils.ts diff --git a/packages/bolt-connection/src/bolt/bolt-protocol-v5x6.js b/packages/bolt-connection/src/bolt/bolt-protocol-v5x6.js index d1177d28b..82f3cc5b8 100644 --- a/packages/bolt-connection/src/bolt/bolt-protocol-v5x6.js +++ b/packages/bolt-connection/src/bolt/bolt-protocol-v5x6.js @@ -16,7 +16,7 @@ */ import BoltProtocolV5x5 from './bolt-protocol-v5x5' -import transformersFactories from './bolt-protocol-v5x5.transformer' +import transformersFactories from './bolt-protocol-v5x6.transformer' import Transformer from './transformer' import { internal } from 'neo4j-driver-core' diff --git a/packages/bolt-connection/src/bolt/bolt-protocol-v5x7.js b/packages/bolt-connection/src/bolt/bolt-protocol-v5x7.js index 719e3b1bc..6636d1d75 100644 --- a/packages/bolt-connection/src/bolt/bolt-protocol-v5x7.js +++ b/packages/bolt-connection/src/bolt/bolt-protocol-v5x7.js @@ -16,7 +16,7 @@ */ import BoltProtocolV5x6 from './bolt-protocol-v5x6' -import transformersFactories from './bolt-protocol-v5x5.transformer' +import transformersFactories from './bolt-protocol-v5x7.transformer' import Transformer from './transformer' import { internal } from 'neo4j-driver-core' diff --git a/packages/bolt-connection/src/bolt/bolt-protocol-v5x8.js b/packages/bolt-connection/src/bolt/bolt-protocol-v5x8.js index 5c17e6952..692b4a110 100644 --- a/packages/bolt-connection/src/bolt/bolt-protocol-v5x8.js +++ b/packages/bolt-connection/src/bolt/bolt-protocol-v5x8.js @@ -16,7 +16,7 @@ */ import BoltProtocolV5x7 from './bolt-protocol-v5x7' -import transformersFactories from './bolt-protocol-v5x5.transformer' +import transformersFactories from './bolt-protocol-v5x8.transformer' import Transformer from './transformer' import { internal } from 'neo4j-driver-core' diff --git a/packages/bolt-connection/src/bolt/request-message.js b/packages/bolt-connection/src/bolt/request-message.js index a5d86ad56..1a08d100b 100644 --- a/packages/bolt-connection/src/bolt/request-message.js +++ b/packages/bolt-connection/src/bolt/request-message.js @@ -84,11 +84,9 @@ export default class RequestMessage { * @return {RequestMessage} new INIT message. */ static init (clientName, authToken) { - const auth = Object.assign({}, authToken) - auth.cacheKey = undefined return new RequestMessage( INIT, - [clientName, auth], + [clientName, authToken], () => `INIT ${clientName} {...}` ) } @@ -132,7 +130,6 @@ export default class RequestMessage { */ static hello (userAgent, authToken, routing = null, patchs = null) { const metadata = Object.assign({ user_agent: userAgent }, authToken) - metadata.cacheKey = undefined if (routing) { metadata.routing = routing } @@ -269,11 +266,9 @@ export default class RequestMessage { * @returns {RequestMessage} new LOGON message */ static logon (authToken) { - const auth = Object.assign({}, authToken) - auth.cacheKey = undefined return new RequestMessage( LOGON, - [auth], + [authToken], () => 'LOGON { ... }' ) } diff --git a/packages/bolt-connection/src/connection-provider/connection-provider-routing.js b/packages/bolt-connection/src/connection-provider/connection-provider-routing.js index e62c1f2ed..8a37fca55 100644 --- a/packages/bolt-connection/src/connection-provider/connection-provider-routing.js +++ b/packages/bolt-connection/src/connection-provider/connection-provider-routing.js @@ -685,11 +685,21 @@ export default class RoutingConnectionProvider extends PooledConnectionProvider } } - _channelSsrCallback (isEnabled, opened) { - if (isEnabled === true) { - this._withSSR = this._withSSR + (opened ? 1 : -1) - } else if (isEnabled === false) { - this._withoutSSR = this._withoutSSR + (opened ? 1 : -1) + _channelSsrCallback (isEnabled, action) { + if (action === 'OPEN') { + if (isEnabled === true) { + this._withSSR = this._withSSR + 1 + } else if (isEnabled === false) { + this._withoutSSR = this._withoutSSR + 1 + } + } else if (action === 'CLOSE') { + if (isEnabled === true) { + this._withSSR = this._withSSR - 1 + } else if (isEnabled === false) { + this._withoutSSR = this._withoutSSR - 1 + } + } else { + throw newError("Channel SSR Callback invoked with action other than 'OPEN' or 'CLOSE'") } } diff --git a/packages/bolt-connection/src/connection/connection-channel.js b/packages/bolt-connection/src/connection/connection-channel.js index aa5449c13..0fe0b078c 100644 --- a/packages/bolt-connection/src/connection/connection-channel.js +++ b/packages/bolt-connection/src/connection/connection-channel.js @@ -34,6 +34,8 @@ let idGenerator = 0 * @param {ConnectionErrorHandler} errorHandler - the error handler for connection errors. * @param {Logger} log - configured logger. * @param {clientCertificate} clientCertificate - configured client certificate + * @param ssrCallback - callback function used to update the counts of ssr enabled and disabled connections + * @param createChannel - function taking a channelConfig object and creating a channel with it * @return {Connection} - new connection. */ export function createChannelConnection ( @@ -43,7 +45,7 @@ export function createChannelConnection ( log, clientCertificate, serversideRouting = null, - ssrCallback = (_) => {}, + ssrCallback, createChannel = channelConfig => new Channel(channelConfig) ) { const channelConfig = new ChannelConfig( @@ -112,9 +114,11 @@ export default class ChannelConnection extends Connection { * @param {ConnectionErrorHandler} errorHandler the error handler. * @param {ServerAddress} address - the server address to connect to. * @param {Logger} log - the configured logger. - * @param {boolean} disableLosslessIntegers if this connection should convert all received integers to native JS numbers. - * @param {Chunker} chunker the chunker - * @param protocolSupplier Bolt protocol supplier + * @param {boolean} disableLosslessIntegers - if this connection should convert all received integers to native JS numbers. + * @param {Chunker} chunker - the chunker + * @param protocolSupplier - Bolt protocol supplier + * @param {boolean} telemetryDisabled - wether telemetry has been disabled in driver config. + * @param ssrCallback - callback function used to update the counts of ssr enabled and disabled connections. */ constructor ( channel, @@ -127,7 +131,7 @@ export default class ChannelConnection extends Connection { notificationFilter, protocolSupplier, telemetryDisabled, - ssrCallback + ssrCallback = (_) => {} ) { super(errorHandler) this._authToken = null @@ -337,14 +341,12 @@ export default class ChannelConnection extends Connection { } const SSREnabledHint = metadata.hints['ssr.enabled'] - if (SSREnabledHint) { + if (SSREnabledHint === true) { this.serversideRouting = true } else { this.serversideRouting = false } - if (this._ssrCallback !== undefined) { - this._ssrCallback(this.serversideRouting, true) - } + this._ssrCallback(this.serversideRouting, 'OPEN') } } resolve(self) @@ -552,9 +554,7 @@ export default class ChannelConnection extends Connection { * @returns {Promise} - A promise that will be resolved when the underlying channel is closed. */ async close () { - if (this._ssrCallback !== undefined) { - this._ssrCallback(this.serversideRouting, false) - } + this._ssrCallback(this.serversideRouting, 'CLOSE') if (this._log.isDebugEnabled()) { this._log.debug('closing') } diff --git a/packages/core/src/auth.ts b/packages/core/src/auth.ts index 9f9371446..d77ddcf6a 100644 --- a/packages/core/src/auth.ts +++ b/packages/core/src/auth.ts @@ -32,26 +32,23 @@ const auth = { scheme: 'basic', principal: username, credentials: password, - cacheKey: 'basic:' + username, realm } } else { - return { scheme: 'basic', principal: username, credentials: password, cacheKey: 'basic:' + username } + return { scheme: 'basic', principal: username, credentials: password } } }, kerberos: (base64EncodedTicket: string) => { return { scheme: 'kerberos', principal: '', // This empty string is required for backwards compatibility. - credentials: base64EncodedTicket, - cacheKey: 'kerberos:' + base64EncodedTicket + credentials: base64EncodedTicket } }, bearer: (base64EncodedToken: string) => { return { scheme: 'bearer', - credentials: base64EncodedToken, - cacheKey: 'bearer:' + base64EncodedToken + credentials: base64EncodedToken } }, none: () => { @@ -76,16 +73,9 @@ const auth = { if (isNotEmpty(realm)) { output.realm = realm } - let ordered = '' if (isNotEmpty(parameters) && parameters !== undefined) { output.parameters = parameters - Object.keys(parameters).sort().forEach((key: string) => { - // eslint-disable-next-line @typescript-eslint/restrict-template-expressions - ordered += `${key}:${parameters[key]}` - }) } - - output.cacheKey = scheme + ':' + principal + (isNotEmpty(credentials) ? credentials : '') + (isNotEmpty(realm) ? realm : '') + ordered return output } } diff --git a/packages/core/src/connection-provider.ts b/packages/core/src/connection-provider.ts index f5dcc0a54..874d125dc 100644 --- a/packages/core/src/connection-provider.ts +++ b/packages/core/src/connection-provider.ts @@ -56,8 +56,10 @@ class ConnectionProvider { * @property {string} param.database - the target database for the to-be-acquired connection * @property {Bookmarks} param.bookmarks - the bookmarks to send to routing discovery * @property {string} param.impersonatedUser - the impersonated user - * @property {function (databaseName:string?)} param.onDatabaseNameResolved - Callback called when the database name get resolved - * @property {function (databaseName:string?)} param.removeFailureFromCache - Callback for deleting lost db from cache + * @property {function (databaseName:string)} param.onDatabaseNameResolved - Callback called when the database name get resolved + * @property {function (databaseName:string)} param.removeFailureFromCache - Callback for deleting lost db from cache + * @property {AuthToken} param.auth - auth token used to authorize for connection acquisition + * @property {string} param.homeDb - the driver's best guess at the current home database for the user * @returns {Promise} */ acquireConnection (param?: { @@ -68,7 +70,7 @@ class ConnectionProvider { onDatabaseNameResolved?: (database: string) => void removeFailureFromCache?: (database: string) => void auth?: AuthToken - homeDb?: any + homeDb?: string }): Promise { throw Error('Not implemented') } diff --git a/packages/core/src/driver.ts b/packages/core/src/driver.ts index 5edb60d29..e2e39a687 100644 --- a/packages/core/src/driver.ts +++ b/packages/core/src/driver.ts @@ -47,6 +47,7 @@ import QueryExecutor from './internal/query-executor' import { newError } from './error' import NotificationFilter from './notification-filter' import HomeDatabaseCache from './internal/homedb-cache' +import { cacheKey } from './internal/auth-utils' const DEFAULT_MAX_CONNECTION_LIFETIME: number = 60 * 60 * 1000 // 1 hour @@ -57,8 +58,7 @@ const DEFAULT_MAX_CONNECTION_LIFETIME: number = 60 * 60 * 1000 // 1 hour const DEFAULT_FETCH_SIZE: number = 1000 /** - * The maximum number of entries allowed in the home database cache before pruning - * @type {number} + * The maximum number of entries allowed in the home database cache before pruning. */ const HOMEDB_CACHE_MAX_SIZE: number = 10000 @@ -104,7 +104,7 @@ type CreateSession = (args: { notificationFilter?: NotificationFilter auth?: AuthToken log: Logger - homeDatabaseCallback?: (user: string, database: any) => void + homeDatabaseCallback?: (impersonatedUser: string, user: string, database: any) => void removeFailureFromCache?: (database: string) => void }) => Session @@ -117,7 +117,6 @@ interface DriverConfig { logging?: LoggingConfig notificationFilter?: NotificationFilter connectionLivenessCheckTimeout?: number - user?: string | undefined } /** @@ -853,8 +852,14 @@ class Driver { ) } - _homeDatabaseCallback (user: string, database: any): void { - this.homeDatabaseCache.set(user, database) + _homeDatabaseCallback (impersonatedUser: string, user: string, database: any): void { + let cacheKey = 'DEFAULT' + if (impersonatedUser !== undefined) { + cacheKey = 'basic:' + impersonatedUser + } else if (user !== undefined) { + cacheKey = user + } + this.homeDatabaseCache.set(cacheKey, database) } _removeFailureFromCache (database: string): void { @@ -888,7 +893,7 @@ class Driver { const sessionMode = Session._validateSessionMode(defaultAccessMode) const connectionProvider = this._getOrCreateConnectionProvider() // eslint-disable-next-line - const cachedHomeDatabase = this.homeDatabaseCache.get((impersonatedUser ? 'basic:' + impersonatedUser : undefined) ?? auth?.cacheKey ?? 'DEFAULT') + const cachedHomeDatabase = this.homeDatabaseCache.get((impersonatedUser ? 'basic:' + impersonatedUser : undefined) ?? cacheKey(auth) ?? 'DEFAULT') const homeDatabaseCallback = this._homeDatabaseCallback.bind(this) const removeFailureFromCache = this._removeFailureFromCache.bind(this) const bookmarks = bookmarkOrBookmarks != null diff --git a/packages/core/src/internal/auth-utils.ts b/packages/core/src/internal/auth-utils.ts new file mode 100644 index 000000000..ad95107e4 --- /dev/null +++ b/packages/core/src/internal/auth-utils.ts @@ -0,0 +1,46 @@ +/** + * Copyright (c) "Neo4j" + * Neo4j Sweden AB [https://neo4j.com] + * + * 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 { AuthToken } from '../types' + +export function cacheKey (auth?: AuthToken): string | undefined { + if (auth === undefined) { + return undefined + } + if (auth.scheme === 'basic') { + return 'basic:' + (auth.principal ?? '') + } else if (auth.scheme === 'kerberos') { + return 'kerberos:' + auth.credentials + } else if (auth.scheme === 'bearer') { + return 'bearer:' + auth.credentials + } else if (auth.scheme === 'none') { + return 'none' + } else { + let ordered = '' + if (auth.parameters !== undefined) { + Object.keys(auth.parameters).sort().forEach((key: string) => { + if (auth.parameters !== undefined) { + // eslint-disable-next-line @typescript-eslint/restrict-template-expressions + ordered += `${key}:${auth.parameters[key]}` + } + }) + } + const credentialString = (auth.credentials !== undefined && auth.credentials !== '') ? 'credentials:' + auth.credentials : '' + const realmString = (auth.realm !== undefined && auth.realm !== '') ? 'realm:' + auth.realm : '' + return 'scheme:' + auth.scheme + 'principal:' + (auth.principal ?? '') + credentialString + realmString + 'parameters:' + ordered + } +} diff --git a/packages/core/src/session.ts b/packages/core/src/session.ts index 64da17736..00c9abcfa 100644 --- a/packages/core/src/session.ts +++ b/packages/core/src/session.ts @@ -37,6 +37,7 @@ import BookmarkManager from './bookmark-manager' import { RecordShape } from './record' import NotificationFilter from './notification-filter' import { Logger } from './internal/logger' +import { cacheKey } from './internal/auth-utils' type ConnectionConsumer = (connection: Connection) => Promise | T type TransactionWork = (tx: Transaction) => Promise | T @@ -89,8 +90,12 @@ class Session { * @param {boolean} args.reactive - Whether this session should create reactive streams * @param {number} args.fetchSize - Defines how many records is pulled in each pulling batch * @param {string} args.impersonatedUser - The username which the user wants to impersonate for the duration of the session. - * @param {AuthToken} args.auth - the target auth for the to-be-acquired connection + * @param {BookmarkManager} args.bookmarkManager - The bookmark manager used for this session. * @param {NotificationFilter} args.notificationFilter - The notification filter used for this session. + * @param {AuthToken} args.auth - the target auth for the to-be-acquired connection + * @param {Logger} args.log - the logger used for logs in this session. + * @param {(impersonatedUser:string, user:string database:string) => void} args.homeDatabaseCallback - callback used to update the home database cache + * @param {(database:string) => void} args.removeFailureFromCache - callback used to remove all entries containing a failing database from the home database cache */ constructor ({ mode, @@ -120,7 +125,7 @@ class Session { notificationFilter?: NotificationFilter auth?: AuthToken log: Logger - homeDatabaseCallback?: (user: string, database: string) => void + homeDatabaseCallback?: (impersonatedUser: string, user: string, database: string) => void removeFailureFromCache?: (database: string) => void }) { this._mode = mode @@ -523,7 +528,7 @@ class Session { if (!this._databaseNameResolved) { if (this._homeDatabaseCallback != null) { // eslint-disable-next-line - this._homeDatabaseCallback((this._impersonatedUser ? 'basic:' + this._impersonatedUser : undefined) ?? this._auth?.cacheKey ?? 'DEFAULT', database) + this._homeDatabaseCallback(this._impersonatedUser, cacheKey(this._auth), database) } const normalizedDatabase = database ?? '' this._database = normalizedDatabase @@ -540,7 +545,7 @@ class Session { if (!this._databaseNameResolved) { if (this._homeDatabaseCallback != null) { // eslint-disable-next-line - this._homeDatabaseCallback((this._impersonatedUser ? 'basic:' + this._impersonatedUser : undefined) ?? this._auth?.cacheKey ?? 'DEFAULT', database) + this._homeDatabaseCallback(this._impersonatedUser, cacheKey(this._auth), database) } const normalizedDatabase = database ?? '' this._database = normalizedDatabase diff --git a/packages/core/src/types.ts b/packages/core/src/types.ts index 36c9e19fb..5e63b9ff1 100644 --- a/packages/core/src/types.ts +++ b/packages/core/src/types.ts @@ -48,7 +48,6 @@ export interface AuthToken { credentials: string realm?: string parameters?: Parameters - cacheKey?: string } export interface BoltAgent { diff --git a/packages/core/test/auth.test.ts b/packages/core/test/auth.test.ts index d0be3aacd..c802610b5 100644 --- a/packages/core/test/auth.test.ts +++ b/packages/core/test/auth.test.ts @@ -15,10 +15,11 @@ * limitations under the License. */ import auth from '../src/auth' +import { cacheKey } from '../src/internal/auth-utils' describe('auth', () => { test('.bearer()', () => { - expect(auth.bearer('==Qyahiadakkda')).toEqual({ scheme: 'bearer', credentials: '==Qyahiadakkda', cacheKey: 'bearer:==Qyahiadakkda' }) + expect(auth.bearer('==Qyahiadakkda')).toEqual({ scheme: 'bearer', credentials: '==Qyahiadakkda' }) }) test.each([ @@ -29,32 +30,28 @@ describe('auth', () => { principal: 'user', credentials: 'pass', realm: 'realm', - parameters: { param: 'param' }, - cacheKey: 'scheme:' + 'user' + 'pass' + 'realm' + 'param:param' + parameters: { param: 'param' } } ], [ ['user', '', '', 'scheme', {}], { scheme: 'scheme', - principal: 'user', - cacheKey: 'scheme:' + 'user' + principal: 'user' } ], [ ['user', undefined, undefined, 'scheme', undefined], { scheme: 'scheme', - principal: 'user', - cacheKey: 'scheme:' + 'user' + principal: 'user' } ], [ ['user', null, null, 'scheme', null], { scheme: 'scheme', - principal: 'user', - cacheKey: 'scheme:' + 'user' + principal: 'user' } ] ])('.custom()', (args, output) => { @@ -83,11 +80,45 @@ describe('auth', () => { false ] - ])('.custom().cacheKey', (args1, args2, shouldMatch) => { + ])('custom token cacheKey', (args1, args2, shouldMatch) => { if (shouldMatch) { - expect(auth.custom.apply(auth, args1).cacheKey).toEqual(auth.custom.apply(auth, args2).cacheKey) + expect(cacheKey(auth.custom.apply(auth, args1))).toEqual(cacheKey(auth.custom.apply(auth, args2))) } else { - expect(auth.custom.apply(auth, args1).cacheKey).not.toEqual(auth.custom.apply(auth, args2).cacheKey) + expect(cacheKey(auth.custom.apply(auth, args1))).not.toEqual(cacheKey(auth.custom.apply(auth, args2))) } }) + + test.each([ + [ + { + scheme: 'basic', + principal: 'user', + credentials: 'password' + }, + 'basic:user' + ], + [ + { + scheme: 'bearer', + credentials: 'Base64EncodedString' + }, + 'bearer:Base64EncodedString' + ], + [ + { + scheme: 'kerberos', + credentials: 'Base64EncodedString' + }, + 'kerberos:Base64EncodedString' + ], + [ + { + scheme: 'none', + credentials: '' + }, + 'none' + ] + ])('token cacheKey', (token, expected) => { + expect(cacheKey(token)).toEqual(expected) + }) }) diff --git a/packages/neo4j-driver-deno/lib/bolt-connection/bolt/bolt-protocol-v5x6.js b/packages/neo4j-driver-deno/lib/bolt-connection/bolt/bolt-protocol-v5x6.js index ea5bd840b..b80cfbb15 100644 --- a/packages/neo4j-driver-deno/lib/bolt-connection/bolt/bolt-protocol-v5x6.js +++ b/packages/neo4j-driver-deno/lib/bolt-connection/bolt/bolt-protocol-v5x6.js @@ -16,7 +16,7 @@ */ import BoltProtocolV5x5 from './bolt-protocol-v5x5.js' -import transformersFactories from './bolt-protocol-v5x5.transformer.js' +import transformersFactories from './bolt-protocol-v5x6.transformer.js' import Transformer from './transformer.js' import { internal } from '../../core/index.ts' diff --git a/packages/neo4j-driver-deno/lib/bolt-connection/bolt/bolt-protocol-v5x7.js b/packages/neo4j-driver-deno/lib/bolt-connection/bolt/bolt-protocol-v5x7.js index 9f417ab50..71f658f0f 100644 --- a/packages/neo4j-driver-deno/lib/bolt-connection/bolt/bolt-protocol-v5x7.js +++ b/packages/neo4j-driver-deno/lib/bolt-connection/bolt/bolt-protocol-v5x7.js @@ -16,7 +16,7 @@ */ import BoltProtocolV5x6 from './bolt-protocol-v5x6.js' -import transformersFactories from './bolt-protocol-v5x5.transformer.js' +import transformersFactories from './bolt-protocol-v5x7.transformer.js' import Transformer from './transformer.js' import { internal } from '../../core/index.ts' diff --git a/packages/neo4j-driver-deno/lib/bolt-connection/bolt/bolt-protocol-v5x8.js b/packages/neo4j-driver-deno/lib/bolt-connection/bolt/bolt-protocol-v5x8.js index 2ae1186a0..0c6434e57 100644 --- a/packages/neo4j-driver-deno/lib/bolt-connection/bolt/bolt-protocol-v5x8.js +++ b/packages/neo4j-driver-deno/lib/bolt-connection/bolt/bolt-protocol-v5x8.js @@ -16,7 +16,7 @@ */ import BoltProtocolV5x7 from './bolt-protocol-v5x7.js' -import transformersFactories from './bolt-protocol-v5x5.transformer.js' +import transformersFactories from './bolt-protocol-v5x8.transformer.js' import Transformer from './transformer.js' import { internal } from '../../core/index.ts' diff --git a/packages/neo4j-driver-deno/lib/bolt-connection/bolt/request-message.js b/packages/neo4j-driver-deno/lib/bolt-connection/bolt/request-message.js index 90232335b..43812e795 100644 --- a/packages/neo4j-driver-deno/lib/bolt-connection/bolt/request-message.js +++ b/packages/neo4j-driver-deno/lib/bolt-connection/bolt/request-message.js @@ -84,11 +84,9 @@ export default class RequestMessage { * @return {RequestMessage} new INIT message. */ static init (clientName, authToken) { - const auth = Object.assign({}, authToken) - auth.cacheKey = undefined return new RequestMessage( INIT, - [clientName, auth], + [clientName, authToken], () => `INIT ${clientName} {...}` ) } @@ -132,7 +130,6 @@ export default class RequestMessage { */ static hello (userAgent, authToken, routing = null, patchs = null) { const metadata = Object.assign({ user_agent: userAgent }, authToken) - metadata.cacheKey = undefined if (routing) { metadata.routing = routing } @@ -269,11 +266,9 @@ export default class RequestMessage { * @returns {RequestMessage} new LOGON message */ static logon (authToken) { - const auth = Object.assign({}, authToken) - auth.cacheKey = undefined return new RequestMessage( LOGON, - [auth], + [authToken], () => 'LOGON { ... }' ) } diff --git a/packages/neo4j-driver-deno/lib/bolt-connection/connection-provider/connection-provider-routing.js b/packages/neo4j-driver-deno/lib/bolt-connection/connection-provider/connection-provider-routing.js index 9168e1386..5410c5a49 100644 --- a/packages/neo4j-driver-deno/lib/bolt-connection/connection-provider/connection-provider-routing.js +++ b/packages/neo4j-driver-deno/lib/bolt-connection/connection-provider/connection-provider-routing.js @@ -685,11 +685,21 @@ export default class RoutingConnectionProvider extends PooledConnectionProvider } } - _channelSsrCallback (isEnabled, opened) { - if (isEnabled === true) { - this._withSSR = this._withSSR + (opened ? 1 : -1) - } else if (isEnabled === false) { - this._withoutSSR = this._withoutSSR + (opened ? 1 : -1) + _channelSsrCallback (isEnabled, action) { + if (action === 'OPEN') { + if (isEnabled === true) { + this._withSSR = this._withSSR + 1 + } else if (isEnabled === false) { + this._withoutSSR = this._withoutSSR + 1 + } + } else if (action === 'CLOSE') { + if (isEnabled === true) { + this._withSSR = this._withSSR - 1 + } else if (isEnabled === false) { + this._withoutSSR = this._withoutSSR - 1 + } + } else { + throw newError("Channel SSR Callback invoked with action other than 'OPEN' or 'CLOSE'") } } diff --git a/packages/neo4j-driver-deno/lib/bolt-connection/connection/connection-channel.js b/packages/neo4j-driver-deno/lib/bolt-connection/connection/connection-channel.js index 42e716461..2452e2147 100644 --- a/packages/neo4j-driver-deno/lib/bolt-connection/connection/connection-channel.js +++ b/packages/neo4j-driver-deno/lib/bolt-connection/connection/connection-channel.js @@ -34,6 +34,8 @@ let idGenerator = 0 * @param {ConnectionErrorHandler} errorHandler - the error handler for connection errors. * @param {Logger} log - configured logger. * @param {clientCertificate} clientCertificate - configured client certificate + * @param ssrCallback - callback function used to update the counts of ssr enabled and disabled connections + * @param createChannel - function taking a channelConfig object and creating a channel with it * @return {Connection} - new connection. */ export function createChannelConnection ( @@ -43,7 +45,7 @@ export function createChannelConnection ( log, clientCertificate, serversideRouting = null, - ssrCallback = (_) => {}, + ssrCallback, createChannel = channelConfig => new Channel(channelConfig) ) { const channelConfig = new ChannelConfig( @@ -112,9 +114,11 @@ export default class ChannelConnection extends Connection { * @param {ConnectionErrorHandler} errorHandler the error handler. * @param {ServerAddress} address - the server address to connect to. * @param {Logger} log - the configured logger. - * @param {boolean} disableLosslessIntegers if this connection should convert all received integers to native JS numbers. - * @param {Chunker} chunker the chunker - * @param protocolSupplier Bolt protocol supplier + * @param {boolean} disableLosslessIntegers - if this connection should convert all received integers to native JS numbers. + * @param {Chunker} chunker - the chunker + * @param protocolSupplier - Bolt protocol supplier + * @param {boolean} telemetryDisabled - wether telemetry has been disabled in driver config. + * @param ssrCallback - callback function used to update the counts of ssr enabled and disabled connections. */ constructor ( channel, @@ -127,7 +131,7 @@ export default class ChannelConnection extends Connection { notificationFilter, protocolSupplier, telemetryDisabled, - ssrCallback + ssrCallback = (_) => {} ) { super(errorHandler) this._authToken = null @@ -337,14 +341,12 @@ export default class ChannelConnection extends Connection { } const SSREnabledHint = metadata.hints['ssr.enabled'] - if (SSREnabledHint) { + if (SSREnabledHint === true) { this.serversideRouting = true } else { this.serversideRouting = false } - if (this._ssrCallback !== undefined) { - this._ssrCallback(this.serversideRouting, true) - } + this._ssrCallback(this.serversideRouting, 'OPEN') } } resolve(self) @@ -552,9 +554,7 @@ export default class ChannelConnection extends Connection { * @returns {Promise} - A promise that will be resolved when the underlying channel is closed. */ async close () { - if (this._ssrCallback !== undefined) { - this._ssrCallback(this.serversideRouting, false) - } + this._ssrCallback(this.serversideRouting, 'CLOSE') if (this._log.isDebugEnabled()) { this._log.debug('closing') } diff --git a/packages/neo4j-driver-deno/lib/core/auth.ts b/packages/neo4j-driver-deno/lib/core/auth.ts index 9f9371446..3d3962e9e 100644 --- a/packages/neo4j-driver-deno/lib/core/auth.ts +++ b/packages/neo4j-driver-deno/lib/core/auth.ts @@ -32,26 +32,23 @@ const auth = { scheme: 'basic', principal: username, credentials: password, - cacheKey: 'basic:' + username, realm } } else { - return { scheme: 'basic', principal: username, credentials: password, cacheKey: 'basic:' + username } + return { scheme: 'basic', principal: username, credentials: password} } }, kerberos: (base64EncodedTicket: string) => { return { scheme: 'kerberos', principal: '', // This empty string is required for backwards compatibility. - credentials: base64EncodedTicket, - cacheKey: 'kerberos:' + base64EncodedTicket + credentials: base64EncodedTicket } }, bearer: (base64EncodedToken: string) => { return { scheme: 'bearer', credentials: base64EncodedToken, - cacheKey: 'bearer:' + base64EncodedToken } }, none: () => { @@ -76,16 +73,9 @@ const auth = { if (isNotEmpty(realm)) { output.realm = realm } - let ordered = '' if (isNotEmpty(parameters) && parameters !== undefined) { output.parameters = parameters - Object.keys(parameters).sort().forEach((key: string) => { - // eslint-disable-next-line @typescript-eslint/restrict-template-expressions - ordered += `${key}:${parameters[key]}` - }) } - - output.cacheKey = scheme + ':' + principal + (isNotEmpty(credentials) ? credentials : '') + (isNotEmpty(realm) ? realm : '') + ordered return output } } diff --git a/packages/neo4j-driver-deno/lib/core/connection-provider.ts b/packages/neo4j-driver-deno/lib/core/connection-provider.ts index 252b7f2a1..dd6b41a90 100644 --- a/packages/neo4j-driver-deno/lib/core/connection-provider.ts +++ b/packages/neo4j-driver-deno/lib/core/connection-provider.ts @@ -56,8 +56,10 @@ class ConnectionProvider { * @property {string} param.database - the target database for the to-be-acquired connection * @property {Bookmarks} param.bookmarks - the bookmarks to send to routing discovery * @property {string} param.impersonatedUser - the impersonated user - * @property {function (databaseName:string?)} param.onDatabaseNameResolved - Callback called when the database name get resolved - * @property {function (databaseName:string?)} param.removeFailureFromCache - Callback for deleting lost db from cache + * @property {function (databaseName:string)} param.onDatabaseNameResolved - Callback called when the database name get resolved + * @property {function (databaseName:string)} param.removeFailureFromCache - Callback for deleting lost db from cache + * @property {AuthToken} param.auth - auth token used to authorize for connection acquisition + * @property {string} param.homeDb - the driver's best guess at the current home database for the user * @returns {Promise} */ acquireConnection (param?: { @@ -68,7 +70,7 @@ class ConnectionProvider { onDatabaseNameResolved?: (database: string) => void removeFailureFromCache?: (database: string) => void auth?: AuthToken - homeDb?: any + homeDb?: string }): Promise { throw Error('Not implemented') } diff --git a/packages/neo4j-driver-deno/lib/core/driver.ts b/packages/neo4j-driver-deno/lib/core/driver.ts index 083e0a378..1d04dc9a1 100644 --- a/packages/neo4j-driver-deno/lib/core/driver.ts +++ b/packages/neo4j-driver-deno/lib/core/driver.ts @@ -47,6 +47,7 @@ import QueryExecutor from './internal/query-executor.ts' import { newError } from './error.ts' import NotificationFilter from './notification-filter.ts' import HomeDatabaseCache from './internal/homedb-cache.ts' +import { cacheKey } from './internal/auth-utils.ts' const DEFAULT_MAX_CONNECTION_LIFETIME: number = 60 * 60 * 1000 // 1 hour @@ -57,8 +58,7 @@ const DEFAULT_MAX_CONNECTION_LIFETIME: number = 60 * 60 * 1000 // 1 hour const DEFAULT_FETCH_SIZE: number = 1000 /** - * The maximum number of entries allowed in the home database cache before pruning - * @type {number} + * The maximum number of entries allowed in the home database cache before pruning. */ const HOMEDB_CACHE_MAX_SIZE: number = 10000 @@ -104,7 +104,7 @@ type CreateSession = (args: { notificationFilter?: NotificationFilter auth?: AuthToken log: Logger - homeDatabaseCallback?: (user: string, database: any) => void + homeDatabaseCallback?: (impersonatedUser: string, user: string, database: any) => void removeFailureFromCache?: (database: string) => void }) => Session @@ -117,7 +117,6 @@ interface DriverConfig { logging?: LoggingConfig notificationFilter?: NotificationFilter connectionLivenessCheckTimeout?: number - user?: string | undefined } /** @@ -853,8 +852,15 @@ class Driver { ) } - _homeDatabaseCallback (user: string, database: any): void { - this.homeDatabaseCache.set(user, database) + _homeDatabaseCallback (impersonatedUser: string, user: string, database: any): void { + let cacheKey = "DEFAULT" + if(impersonatedUser !== undefined) { + cacheKey = "basic:" + impersonatedUser + } + else if(user !== undefined) { + cacheKey = user + } + this.homeDatabaseCache.set(cacheKey, database) } _removeFailureFromCache (database: string): void { @@ -888,7 +894,7 @@ class Driver { const sessionMode = Session._validateSessionMode(defaultAccessMode) const connectionProvider = this._getOrCreateConnectionProvider() // eslint-disable-next-line - const cachedHomeDatabase = this.homeDatabaseCache.get((impersonatedUser ? 'basic:' + impersonatedUser : undefined) ?? auth?.cacheKey ?? 'DEFAULT') + const cachedHomeDatabase = this.homeDatabaseCache.get((impersonatedUser ? 'basic:' + impersonatedUser : undefined) ?? cacheKey(auth) ?? 'DEFAULT') const homeDatabaseCallback = this._homeDatabaseCallback.bind(this) const removeFailureFromCache = this._removeFailureFromCache.bind(this) const bookmarks = bookmarkOrBookmarks != null diff --git a/packages/neo4j-driver-deno/lib/core/internal/auth-utils.ts b/packages/neo4j-driver-deno/lib/core/internal/auth-utils.ts new file mode 100644 index 000000000..24ffccd88 --- /dev/null +++ b/packages/neo4j-driver-deno/lib/core/internal/auth-utils.ts @@ -0,0 +1,50 @@ +/** + * Copyright (c) "Neo4j" + * Neo4j Sweden AB [https://neo4j.com] + * + * 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 { AuthToken } from '../types.ts' + +export function cacheKey(auth?: AuthToken): string | undefined { + if(auth === undefined) { + return undefined + } + if(auth.scheme === 'basic') { + return 'basic:' + (auth.principal ?? "") + } + else if (auth.scheme === 'kerberos') { + return 'kerberos:' + auth.credentials + } + else if (auth.scheme === 'bearer') { + return 'bearer:' + auth.credentials + } + else if (auth.scheme === 'none') { + return "none" + } + else { + let ordered = '' + if (auth.parameters !== undefined) { + Object.keys(auth.parameters).sort().forEach((key: string) => { + if (auth.parameters !== undefined) { + // eslint-disable-next-line @typescript-eslint/restrict-template-expressions + ordered += `${key}:${auth.parameters[key]}` + } + }) + } + let credentialString = (auth.credentials !== undefined && auth.credentials !== "") ? 'credentials:' + auth.credentials : "" + let realmString = (auth.realm !== undefined && auth.realm !== "") ? 'realm:' + auth.realm : "" + return 'scheme:' + auth.scheme + 'principal:' + (auth.principal ?? "") + credentialString + realmString + 'parameters:' + ordered + } +} diff --git a/packages/neo4j-driver-deno/lib/core/session.ts b/packages/neo4j-driver-deno/lib/core/session.ts index cc8740439..b0762bccb 100644 --- a/packages/neo4j-driver-deno/lib/core/session.ts +++ b/packages/neo4j-driver-deno/lib/core/session.ts @@ -37,6 +37,7 @@ import BookmarkManager from './bookmark-manager.ts' import { RecordShape } from './record.ts' import NotificationFilter from './notification-filter.ts' import { Logger } from './internal/logger.ts' +import { cacheKey } from './internal/auth-utils.ts' type ConnectionConsumer = (connection: Connection) => Promise | T type TransactionWork = (tx: Transaction) => Promise | T @@ -89,8 +90,12 @@ class Session { * @param {boolean} args.reactive - Whether this session should create reactive streams * @param {number} args.fetchSize - Defines how many records is pulled in each pulling batch * @param {string} args.impersonatedUser - The username which the user wants to impersonate for the duration of the session. - * @param {AuthToken} args.auth - the target auth for the to-be-acquired connection + * @param {BookmarkManager} args.bookmarkManager - The bookmark manager used for this session. * @param {NotificationFilter} args.notificationFilter - The notification filter used for this session. + * @param {AuthToken} args.auth - the target auth for the to-be-acquired connection + * @param {Logger} args.log - the logger used for logs in this session. + * @param {(impersonatedUser:string, user:string database:string) => void} args.homeDatabaseCallback - callback used to update the home database cache + * @param {(database:string) => void} args.removeFailureFromCache - callback used to remove all entries containing a failing database from the home database cache */ constructor ({ mode, @@ -120,7 +125,7 @@ class Session { notificationFilter?: NotificationFilter auth?: AuthToken log: Logger - homeDatabaseCallback?: (user: string, database: string) => void + homeDatabaseCallback?: (impersonatedUser: string, user: string, database: string) => void removeFailureFromCache?: (database: string) => void }) { this._mode = mode @@ -523,7 +528,7 @@ class Session { if (!this._databaseNameResolved) { if (this._homeDatabaseCallback != null) { // eslint-disable-next-line - this._homeDatabaseCallback((this._impersonatedUser ? 'basic:' + this._impersonatedUser : undefined) ?? this._auth?.cacheKey ?? 'DEFAULT', database) + this._homeDatabaseCallback(this._impersonatedUser, cacheKey(this._auth), database) } const normalizedDatabase = database ?? '' this._database = normalizedDatabase @@ -540,7 +545,7 @@ class Session { if (!this._databaseNameResolved) { if (this._homeDatabaseCallback != null) { // eslint-disable-next-line - this._homeDatabaseCallback((this._impersonatedUser ? 'basic:' + this._impersonatedUser : undefined) ?? this._auth?.cacheKey ?? 'DEFAULT', database) + this._homeDatabaseCallback(this._impersonatedUser, cacheKey(this._auth), database) } const normalizedDatabase = database ?? '' this._database = normalizedDatabase diff --git a/packages/neo4j-driver-deno/lib/core/types.ts b/packages/neo4j-driver-deno/lib/core/types.ts index eb13dc254..d493c400d 100644 --- a/packages/neo4j-driver-deno/lib/core/types.ts +++ b/packages/neo4j-driver-deno/lib/core/types.ts @@ -48,7 +48,6 @@ export interface AuthToken { credentials: string realm?: string parameters?: Parameters - cacheKey?: string } export interface BoltAgent { diff --git a/packages/neo4j-driver/test/auth.test.js b/packages/neo4j-driver/test/auth.test.js index 297fd1405..4f333bbc6 100644 --- a/packages/neo4j-driver/test/auth.test.js +++ b/packages/neo4j-driver/test/auth.test.js @@ -23,8 +23,7 @@ describe('#unit auth', () => { expect(token).toEqual({ scheme: 'basic', principal: 'cat', - credentials: 'dog', - cacheKey: 'basic:cat' + credentials: 'dog' }) }) @@ -34,8 +33,7 @@ describe('#unit auth', () => { scheme: 'basic', principal: 'cat', credentials: 'dog', - realm: 'apartment', - cacheKey: 'basic:cat' + realm: 'apartment' }) }) @@ -44,8 +42,7 @@ describe('#unit auth', () => { expect(token).toEqual({ scheme: 'kerberos', principal: '', - credentials: 'my-ticket', - cacheKey: 'kerberos:my-ticket' + credentials: 'my-ticket' }) }) @@ -55,8 +52,7 @@ describe('#unit auth', () => { scheme: 'pets', principal: 'cat', credentials: 'dog', - realm: 'apartment', - cacheKey: 'pets:catdogapartment' + realm: 'apartment' }) }) @@ -70,8 +66,7 @@ describe('#unit auth', () => { principal: 'cat', credentials: 'dog', realm: 'apartment', - parameters: { key1: 'value1', key2: 42 }, - cacheKey: 'pets:catdogapartmentkey1:value1key2:42' + parameters: { key1: 'value1', key2: 42 } }) }) }) diff --git a/packages/neo4j-driver/test/driver.test.js b/packages/neo4j-driver/test/driver.test.js index f8aeb01ed..ff4c7336b 100644 --- a/packages/neo4j-driver/test/driver.test.js +++ b/packages/neo4j-driver/test/driver.test.js @@ -189,9 +189,9 @@ describe('#unit driver', () => { `neo4j+ssc://${sharedNeo4j.hostnameWithBoltPort}`, sharedNeo4j.authToken ) - driver._homeDatabaseCallback('DEFAULT', 'neo4j') + driver._homeDatabaseCallback(undefined, 'DEFAULT', 'neo4j') expect(driver.homeDatabaseCache.get('DEFAULT')).toBe('neo4j') - driver._homeDatabaseCallback('basic:hi', 'neo4j') + driver._homeDatabaseCallback(undefined, 'basic:hi', 'neo4j') expect(driver.homeDatabaseCache.get('basic:hi')).toBe('neo4j') }) @@ -200,9 +200,9 @@ describe('#unit driver', () => { `neo4j+ssc://${sharedNeo4j.hostnameWithBoltPort}`, sharedNeo4j.authToken ) - driver._homeDatabaseCallback('DEFAULT', 'neo4j') + driver._homeDatabaseCallback(undefined, 'DEFAULT', 'neo4j') expect(driver.homeDatabaseCache.get('DEFAULT')).toBe('neo4j') - driver._homeDatabaseCallback('DEFAULT', 'neo5j') + driver._homeDatabaseCallback(undefined, 'DEFAULT', 'neo5j') expect(driver.homeDatabaseCache.get('DEFAULT')).toBe('neo5j') }) @@ -211,11 +211,11 @@ describe('#unit driver', () => { `neo4j+ssc://${sharedNeo4j.hostnameWithBoltPort}`, sharedNeo4j.authToken ) - driver._homeDatabaseCallback('DEFAULT', 'neo4j') + driver._homeDatabaseCallback(undefined, 'DEFAULT', 'neo4j') expect(driver.homeDatabaseCache.get('DEFAULT')).toBe('neo4j') - driver._homeDatabaseCallback('basic:hi', 'neo4j') + driver._homeDatabaseCallback(undefined, 'basic:hi', 'neo4j') expect(driver.homeDatabaseCache.get('basic:hi')).toBe('neo4j') - driver._homeDatabaseCallback('basic:hello', 'neo5j') + driver._homeDatabaseCallback(undefined, 'basic:hello', 'neo5j') expect(driver.homeDatabaseCache.get('basic:hello')).toBe('neo5j') driver._removeFailureFromCache('neo4j') expect(driver.homeDatabaseCache.get('DEFAULT')).toBe(undefined) @@ -229,13 +229,13 @@ describe('#unit driver', () => { sharedNeo4j.authToken ) for (let i = 0; i < 9999; i++) { - driver._homeDatabaseCallback(i.toString(), 'neo4j') + driver._homeDatabaseCallback(undefined, i.toString(), 'neo4j') } await new Promise(resolve => setTimeout(resolve, 100)) - driver._homeDatabaseCallback('5', 'neo4j') + driver._homeDatabaseCallback(undefined, '5', 'neo4j') driver.homeDatabaseCache.get('55') for (let i = 9999; i < 10050; i++) { - driver._homeDatabaseCallback(i.toString(), 'neo4j') + driver._homeDatabaseCallback(undefined, i.toString(), 'neo4j') } expect(driver.homeDatabaseCache.get('1')).toEqual(undefined) expect(driver.homeDatabaseCache.get('69')).toEqual(undefined) From 600f02896975173d86e044983f501e139c7eaaf0 Mon Sep 17 00:00:00 2001 From: MaxAake <61233757+MaxAake@users.noreply.github.com> Date: Wed, 8 Jan 2025 13:55:40 +0100 Subject: [PATCH 60/84] continued addressing of reviews --- .../src/bolt/bolt-protocol-v1.js | 6 ++-- .../src/bolt/bolt-protocol-v3.js | 6 ++-- .../src/bolt/bolt-protocol-v4x0.js | 6 ++-- .../src/bolt/bolt-protocol-v4x4.js | 6 ++-- .../src/bolt/bolt-protocol-v5x2.js | 6 ++-- .../src/bolt/bolt-protocol-v5x5.js | 6 ++-- .../src/bolt/stream-observers.js | 8 ++++- packages/core/src/connection.ts | 1 + packages/core/src/driver.ts | 5 +-- .../core/src/internal/connection-holder.ts | 6 ++-- packages/core/src/internal/homedb-cache.ts | 6 ++-- packages/core/src/session.ts | 30 ++++------------- .../bolt-connection/bolt/bolt-protocol-v1.js | 6 ++-- .../bolt-connection/bolt/bolt-protocol-v3.js | 6 ++-- .../bolt/bolt-protocol-v4x0.js | 6 ++-- .../bolt/bolt-protocol-v4x4.js | 6 ++-- .../bolt/bolt-protocol-v5x2.js | 6 ++-- .../bolt/bolt-protocol-v5x5.js | 6 ++-- .../bolt-connection/bolt/stream-observers.js | 8 ++++- packages/neo4j-driver-deno/lib/core/auth.ts | 4 +-- .../neo4j-driver-deno/lib/core/connection.ts | 1 + packages/neo4j-driver-deno/lib/core/driver.ts | 10 +++--- .../lib/core/internal/auth-utils.ts | 32 ++++++++----------- .../lib/core/internal/connection-holder.ts | 6 ++-- .../lib/core/internal/homedb-cache.ts | 6 ++-- .../neo4j-driver-deno/lib/core/session.ts | 30 ++++------------- 26 files changed, 114 insertions(+), 111 deletions(-) diff --git a/packages/bolt-connection/src/bolt/bolt-protocol-v1.js b/packages/bolt-connection/src/bolt/bolt-protocol-v1.js index d04b33b4e..0dd76301c 100644 --- a/packages/bolt-connection/src/bolt/bolt-protocol-v1.js +++ b/packages/bolt-connection/src/bolt/bolt-protocol-v1.js @@ -413,7 +413,8 @@ export default class BoltProtocol { afterComplete, flush = true, highRecordWatermark = Number.MAX_VALUE, - lowRecordWatermark = Number.MAX_VALUE + lowRecordWatermark = Number.MAX_VALUE, + onDb } = {} ) { const observer = new ResultStreamObserver({ @@ -425,7 +426,8 @@ export default class BoltProtocol { beforeComplete, afterComplete, highRecordWatermark, - lowRecordWatermark + lowRecordWatermark, + onDb }) // bookmarks and mode are ignored in this version of the protocol diff --git a/packages/bolt-connection/src/bolt/bolt-protocol-v3.js b/packages/bolt-connection/src/bolt/bolt-protocol-v3.js index 1329e80f8..22da505c9 100644 --- a/packages/bolt-connection/src/bolt/bolt-protocol-v3.js +++ b/packages/bolt-connection/src/bolt/bolt-protocol-v3.js @@ -180,7 +180,8 @@ export default class BoltProtocol extends BoltProtocolV2 { afterComplete, flush = true, highRecordWatermark = Number.MAX_VALUE, - lowRecordWatermark = Number.MAX_VALUE + lowRecordWatermark = Number.MAX_VALUE, + onDb } = {} ) { const observer = new ResultStreamObserver({ @@ -192,7 +193,8 @@ export default class BoltProtocol extends BoltProtocolV2 { beforeComplete, afterComplete, highRecordWatermark, - lowRecordWatermark + lowRecordWatermark, + onDb }) // passing in a database name on this protocol version throws an error diff --git a/packages/bolt-connection/src/bolt/bolt-protocol-v4x0.js b/packages/bolt-connection/src/bolt/bolt-protocol-v4x0.js index 12c543e8e..34bd0c6af 100644 --- a/packages/bolt-connection/src/bolt/bolt-protocol-v4x0.js +++ b/packages/bolt-connection/src/bolt/bolt-protocol-v4x0.js @@ -104,7 +104,8 @@ export default class BoltProtocol extends BoltProtocolV3 { reactive = false, fetchSize = FETCH_ALL, highRecordWatermark = Number.MAX_VALUE, - lowRecordWatermark = Number.MAX_VALUE + lowRecordWatermark = Number.MAX_VALUE, + onDb } = {} ) { const observer = new ResultStreamObserver({ @@ -120,7 +121,8 @@ export default class BoltProtocol extends BoltProtocolV3 { beforeComplete, afterComplete, highRecordWatermark, - lowRecordWatermark + lowRecordWatermark, + onDb }) // passing impersonated user on this protocol version throws an error diff --git a/packages/bolt-connection/src/bolt/bolt-protocol-v4x4.js b/packages/bolt-connection/src/bolt/bolt-protocol-v4x4.js index 85f98ad1b..262d9bd94 100644 --- a/packages/bolt-connection/src/bolt/bolt-protocol-v4x4.js +++ b/packages/bolt-connection/src/bolt/bolt-protocol-v4x4.js @@ -97,7 +97,8 @@ export default class BoltProtocol extends BoltProtocolV43 { reactive = false, fetchSize = FETCH_ALL, highRecordWatermark = Number.MAX_VALUE, - lowRecordWatermark = Number.MAX_VALUE + lowRecordWatermark = Number.MAX_VALUE, + onDb } = {} ) { const observer = new ResultStreamObserver({ @@ -113,7 +114,8 @@ export default class BoltProtocol extends BoltProtocolV43 { beforeComplete, afterComplete, highRecordWatermark, - lowRecordWatermark + lowRecordWatermark, + onDb }) // passing notification filter on this protocol version throws an error diff --git a/packages/bolt-connection/src/bolt/bolt-protocol-v5x2.js b/packages/bolt-connection/src/bolt/bolt-protocol-v5x2.js index 891ba8e36..f5d488f58 100644 --- a/packages/bolt-connection/src/bolt/bolt-protocol-v5x2.js +++ b/packages/bolt-connection/src/bolt/bolt-protocol-v5x2.js @@ -130,7 +130,8 @@ export default class BoltProtocol extends BoltProtocolV5x1 { reactive = false, fetchSize = FETCH_ALL, highRecordWatermark = Number.MAX_VALUE, - lowRecordWatermark = Number.MAX_VALUE + lowRecordWatermark = Number.MAX_VALUE, + onDb } = {} ) { const observer = new ResultStreamObserver({ @@ -146,7 +147,8 @@ export default class BoltProtocol extends BoltProtocolV5x1 { beforeComplete, afterComplete, highRecordWatermark, - lowRecordWatermark + lowRecordWatermark, + onDb }) const flushRun = reactive diff --git a/packages/bolt-connection/src/bolt/bolt-protocol-v5x5.js b/packages/bolt-connection/src/bolt/bolt-protocol-v5x5.js index 206d81d4c..700cfcc06 100644 --- a/packages/bolt-connection/src/bolt/bolt-protocol-v5x5.js +++ b/packages/bolt-connection/src/bolt/bolt-protocol-v5x5.js @@ -130,7 +130,8 @@ export default class BoltProtocol extends BoltProtocolV5x4 { reactive = false, fetchSize = FETCH_ALL, highRecordWatermark = Number.MAX_VALUE, - lowRecordWatermark = Number.MAX_VALUE + lowRecordWatermark = Number.MAX_VALUE, + onDb } = {} ) { const observer = new ResultStreamObserver({ @@ -147,7 +148,8 @@ export default class BoltProtocol extends BoltProtocolV5x4 { afterComplete, highRecordWatermark, lowRecordWatermark, - enrichMetadata: this._enrichMetadata + enrichMetadata: this._enrichMetadata, + onDb }) const flushRun = reactive diff --git a/packages/bolt-connection/src/bolt/stream-observers.js b/packages/bolt-connection/src/bolt/stream-observers.js index 336cbb30a..8bda32aea 100644 --- a/packages/bolt-connection/src/bolt/stream-observers.js +++ b/packages/bolt-connection/src/bolt/stream-observers.js @@ -79,7 +79,8 @@ class ResultStreamObserver extends StreamObserver { server, highRecordWatermark = Number.MAX_VALUE, lowRecordWatermark = Number.MAX_VALUE, - enrichMetadata + enrichMetadata, + onDb } = {}) { super() @@ -113,6 +114,7 @@ class ResultStreamObserver extends StreamObserver { this._paused = false this._pulled = !reactive this._haveRecordStreamed = false + this._onDb = onDb } /** @@ -319,6 +321,10 @@ class ResultStreamObserver extends StreamObserver { } } + if (meta.db !== null && this._onDb !== undefined) { + this._onDb(meta.db) + } + if (meta.fields != null) { // remove fields key from metadata object delete meta.fields diff --git a/packages/core/src/connection.ts b/packages/core/src/connection.ts index ed2e72cfa..f24d035dd 100644 --- a/packages/core/src/connection.ts +++ b/packages/core/src/connection.ts @@ -58,6 +58,7 @@ interface RunQueryConfig extends BeginTransactionConfig { highRecordWatermark: number lowRecordWatermark: number reactive: boolean + onDb?: (database: string) => void } /** diff --git a/packages/core/src/driver.ts b/packages/core/src/driver.ts index e2e39a687..55f453ff9 100644 --- a/packages/core/src/driver.ts +++ b/packages/core/src/driver.ts @@ -854,9 +854,9 @@ class Driver { _homeDatabaseCallback (impersonatedUser: string, user: string, database: any): void { let cacheKey = 'DEFAULT' - if (impersonatedUser !== undefined) { + if (impersonatedUser !== undefined && impersonatedUser !== '' && impersonatedUser !== null) { cacheKey = 'basic:' + impersonatedUser - } else if (user !== undefined) { + } else if (user !== undefined && user !== '' && user !== null) { cacheKey = user } this.homeDatabaseCache.set(cacheKey, database) @@ -907,6 +907,7 @@ class Driver { bookmarks, config: { cachedHomeDatabase, + routingDriver: this._supportsRouting(), ...this._config }, reactive, diff --git a/packages/core/src/internal/connection-holder.ts b/packages/core/src/internal/connection-holder.ts index fb56b10b0..9cdae509a 100644 --- a/packages/core/src/internal/connection-holder.ts +++ b/packages/core/src/internal/connection-holder.ts @@ -166,9 +166,9 @@ class ConnectionHolder implements ConnectionHolderInterface { return this._referenceCount } - initializeConnection (homeDatabaseTable?: any): boolean { + initializeConnection (homeDatabase?: string): boolean { if (this._referenceCount === 0 && (this._connectionProvider != null)) { - this._connectionPromise = this._createConnectionPromise(this._connectionProvider, homeDatabaseTable) + this._connectionPromise = this._createConnectionPromise(this._connectionProvider, homeDatabase) } else { this._referenceCount++ return false @@ -180,7 +180,7 @@ class ConnectionHolder implements ConnectionHolderInterface { private async _createConnectionPromise (connectionProvider: ConnectionProvider, homeDatabase?: string): Promise { return await connectionProvider.acquireConnection({ accessMode: this._mode, - database: this._database ?? '', + database: this._database, bookmarks: await this._getBookmarks(), impersonatedUser: this._impersonatedUser, onDatabaseNameResolved: this._onDatabaseNameResolved, diff --git a/packages/core/src/internal/homedb-cache.ts b/packages/core/src/internal/homedb-cache.ts index e5a76d6b8..07cae5859 100644 --- a/packages/core/src/internal/homedb-cache.ts +++ b/packages/core/src/internal/homedb-cache.ts @@ -23,14 +23,16 @@ export default class HomeDatabaseCache { maxSize: number map: Map + pruneCount: number constructor (maxSize: number) { this.maxSize = maxSize + this.pruneCount = Math.max(Math.round(0.01 * maxSize * Math.log(maxSize)), 1) this.map = new Map() } /** - * Updates or add an entry to the cache, and prunes the cache if above the maximum allowed size + * Updates or adds an entry to the cache, and prunes the cache if above the maximum allowed size * * @param {string} user cache key for the user to set * @param {string} database new home database to set for the user @@ -82,7 +84,7 @@ export default class HomeDatabaseCache { private _pruneCache (): void { if (this.map.size > this.maxSize) { const sortedArray = Array.from(this.map.entries()).sort((a, b) => a[1].lastUsed - b[1].lastUsed) - for (let i = 0; i < 70; i++) { // c * max_size * logn(max_size), c is set to 0.01 + for (let i = 0; i < this.pruneCount; i++) { this.map.delete(sortedArray[i][0]) } } diff --git a/packages/core/src/session.ts b/packages/core/src/session.ts index 00c9abcfa..5e0cbc5a4 100644 --- a/packages/core/src/session.ts +++ b/packages/core/src/session.ts @@ -78,6 +78,7 @@ class Session { private readonly _homeDatabaseCallback: Function | undefined private readonly _auth: AuthToken | undefined private _databaseGuess: string | undefined + private readonly _isRoutingSession: boolean /** * @constructor * @protected @@ -173,6 +174,7 @@ class Session { this._notificationFilter = notificationFilter this._log = log this._databaseGuess = config?.cachedHomeDatabase + this._isRoutingSession = config?.routingDriver ?? false } /** @@ -216,7 +218,8 @@ class Session { fetchSize: this._fetchSize, lowRecordWatermark: this._lowRecordWatermark, highRecordWatermark: this._highRecordWatermark, - notificationFilter: this._notificationFilter + notificationFilter: this._notificationFilter, + onDb: this._onDatabaseNameResolved.bind(this) }) }) this._results.push(result) @@ -340,7 +343,7 @@ class Session { highRecordWatermark: this._highRecordWatermark, notificationFilter: this._notificationFilter, apiTelemetryConfig, - onDbCallback: this._beginDbCallback.bind(this) + onDbCallback: this._onDatabaseNameResolved.bind(this) }) tx._begin(() => this._bookmarks(), txConfig) return tx @@ -524,27 +527,10 @@ class Session { * @returns {void} */ _onDatabaseNameResolved (database?: string): void { - this._databaseGuess = database - if (!this._databaseNameResolved) { - if (this._homeDatabaseCallback != null) { - // eslint-disable-next-line - this._homeDatabaseCallback(this._impersonatedUser, cacheKey(this._auth), database) - } - const normalizedDatabase = database ?? '' - this._database = normalizedDatabase - this._readConnectionHolder.setDatabase(normalizedDatabase) - this._writeConnectionHolder.setDatabase(normalizedDatabase) - this._databaseNameResolved = true - } - } - - _beginDbCallback (database: string): void { - // eslint-disable-next-line - if (this._connectionHolderWithMode(this._mode).connectionProvider()?.SSREnabled()) { + if (this._isRoutingSession) { this._databaseGuess = database if (!this._databaseNameResolved) { if (this._homeDatabaseCallback != null) { - // eslint-disable-next-line this._homeDatabaseCallback(this._impersonatedUser, cacheKey(this._auth), database) } const normalizedDatabase = database ?? '' @@ -620,9 +606,6 @@ class Session { * @returns {void} */ _onCompleteCallback (meta: { bookmark: string | string[], db?: string }, previousBookmarks?: Bookmarks): void { - if (meta.db !== undefined) { - this._beginDbCallback(meta.db) - } this._updateBookmarks(new Bookmarks(meta.bookmark), previousBookmarks, meta.db) } @@ -674,7 +657,6 @@ class Session { */ function _createTransactionExecutor (config?: { maxTransactionRetryTime: number | null - commitCallback: any }): TransactionExecutor { const maxRetryTimeMs = config?.maxTransactionRetryTime ?? null return new TransactionExecutor(maxRetryTimeMs) diff --git a/packages/neo4j-driver-deno/lib/bolt-connection/bolt/bolt-protocol-v1.js b/packages/neo4j-driver-deno/lib/bolt-connection/bolt/bolt-protocol-v1.js index a83f32f5a..420747616 100644 --- a/packages/neo4j-driver-deno/lib/bolt-connection/bolt/bolt-protocol-v1.js +++ b/packages/neo4j-driver-deno/lib/bolt-connection/bolt/bolt-protocol-v1.js @@ -413,7 +413,8 @@ export default class BoltProtocol { afterComplete, flush = true, highRecordWatermark = Number.MAX_VALUE, - lowRecordWatermark = Number.MAX_VALUE + lowRecordWatermark = Number.MAX_VALUE, + onDb } = {} ) { const observer = new ResultStreamObserver({ @@ -425,7 +426,8 @@ export default class BoltProtocol { beforeComplete, afterComplete, highRecordWatermark, - lowRecordWatermark + lowRecordWatermark, + onDb }) // bookmarks and mode are ignored in this version of the protocol diff --git a/packages/neo4j-driver-deno/lib/bolt-connection/bolt/bolt-protocol-v3.js b/packages/neo4j-driver-deno/lib/bolt-connection/bolt/bolt-protocol-v3.js index d077cd3b5..2d4636033 100644 --- a/packages/neo4j-driver-deno/lib/bolt-connection/bolt/bolt-protocol-v3.js +++ b/packages/neo4j-driver-deno/lib/bolt-connection/bolt/bolt-protocol-v3.js @@ -180,7 +180,8 @@ export default class BoltProtocol extends BoltProtocolV2 { afterComplete, flush = true, highRecordWatermark = Number.MAX_VALUE, - lowRecordWatermark = Number.MAX_VALUE + lowRecordWatermark = Number.MAX_VALUE, + onDb } = {} ) { const observer = new ResultStreamObserver({ @@ -192,7 +193,8 @@ export default class BoltProtocol extends BoltProtocolV2 { beforeComplete, afterComplete, highRecordWatermark, - lowRecordWatermark + lowRecordWatermark, + onDb }) // passing in a database name on this protocol version throws an error diff --git a/packages/neo4j-driver-deno/lib/bolt-connection/bolt/bolt-protocol-v4x0.js b/packages/neo4j-driver-deno/lib/bolt-connection/bolt/bolt-protocol-v4x0.js index eee49bb46..1751d0c61 100644 --- a/packages/neo4j-driver-deno/lib/bolt-connection/bolt/bolt-protocol-v4x0.js +++ b/packages/neo4j-driver-deno/lib/bolt-connection/bolt/bolt-protocol-v4x0.js @@ -104,7 +104,8 @@ export default class BoltProtocol extends BoltProtocolV3 { reactive = false, fetchSize = FETCH_ALL, highRecordWatermark = Number.MAX_VALUE, - lowRecordWatermark = Number.MAX_VALUE + lowRecordWatermark = Number.MAX_VALUE, + onDb } = {} ) { const observer = new ResultStreamObserver({ @@ -120,7 +121,8 @@ export default class BoltProtocol extends BoltProtocolV3 { beforeComplete, afterComplete, highRecordWatermark, - lowRecordWatermark + lowRecordWatermark, + onDb }) // passing impersonated user on this protocol version throws an error diff --git a/packages/neo4j-driver-deno/lib/bolt-connection/bolt/bolt-protocol-v4x4.js b/packages/neo4j-driver-deno/lib/bolt-connection/bolt/bolt-protocol-v4x4.js index d3a7b427e..5f96245f7 100644 --- a/packages/neo4j-driver-deno/lib/bolt-connection/bolt/bolt-protocol-v4x4.js +++ b/packages/neo4j-driver-deno/lib/bolt-connection/bolt/bolt-protocol-v4x4.js @@ -97,7 +97,8 @@ export default class BoltProtocol extends BoltProtocolV43 { reactive = false, fetchSize = FETCH_ALL, highRecordWatermark = Number.MAX_VALUE, - lowRecordWatermark = Number.MAX_VALUE + lowRecordWatermark = Number.MAX_VALUE, + onDb } = {} ) { const observer = new ResultStreamObserver({ @@ -113,7 +114,8 @@ export default class BoltProtocol extends BoltProtocolV43 { beforeComplete, afterComplete, highRecordWatermark, - lowRecordWatermark + lowRecordWatermark, + onDb }) // passing notification filter on this protocol version throws an error diff --git a/packages/neo4j-driver-deno/lib/bolt-connection/bolt/bolt-protocol-v5x2.js b/packages/neo4j-driver-deno/lib/bolt-connection/bolt/bolt-protocol-v5x2.js index 169d37fbb..064cd4a61 100644 --- a/packages/neo4j-driver-deno/lib/bolt-connection/bolt/bolt-protocol-v5x2.js +++ b/packages/neo4j-driver-deno/lib/bolt-connection/bolt/bolt-protocol-v5x2.js @@ -130,7 +130,8 @@ export default class BoltProtocol extends BoltProtocolV5x1 { reactive = false, fetchSize = FETCH_ALL, highRecordWatermark = Number.MAX_VALUE, - lowRecordWatermark = Number.MAX_VALUE + lowRecordWatermark = Number.MAX_VALUE, + onDb } = {} ) { const observer = new ResultStreamObserver({ @@ -146,7 +147,8 @@ export default class BoltProtocol extends BoltProtocolV5x1 { beforeComplete, afterComplete, highRecordWatermark, - lowRecordWatermark + lowRecordWatermark, + onDb }) const flushRun = reactive diff --git a/packages/neo4j-driver-deno/lib/bolt-connection/bolt/bolt-protocol-v5x5.js b/packages/neo4j-driver-deno/lib/bolt-connection/bolt/bolt-protocol-v5x5.js index f79304f7d..8d4a38535 100644 --- a/packages/neo4j-driver-deno/lib/bolt-connection/bolt/bolt-protocol-v5x5.js +++ b/packages/neo4j-driver-deno/lib/bolt-connection/bolt/bolt-protocol-v5x5.js @@ -130,7 +130,8 @@ export default class BoltProtocol extends BoltProtocolV5x4 { reactive = false, fetchSize = FETCH_ALL, highRecordWatermark = Number.MAX_VALUE, - lowRecordWatermark = Number.MAX_VALUE + lowRecordWatermark = Number.MAX_VALUE, + onDb } = {} ) { const observer = new ResultStreamObserver({ @@ -147,7 +148,8 @@ export default class BoltProtocol extends BoltProtocolV5x4 { afterComplete, highRecordWatermark, lowRecordWatermark, - enrichMetadata: this._enrichMetadata + enrichMetadata: this._enrichMetadata, + onDb }) const flushRun = reactive diff --git a/packages/neo4j-driver-deno/lib/bolt-connection/bolt/stream-observers.js b/packages/neo4j-driver-deno/lib/bolt-connection/bolt/stream-observers.js index fb75d83ab..e409974a5 100644 --- a/packages/neo4j-driver-deno/lib/bolt-connection/bolt/stream-observers.js +++ b/packages/neo4j-driver-deno/lib/bolt-connection/bolt/stream-observers.js @@ -79,7 +79,8 @@ class ResultStreamObserver extends StreamObserver { server, highRecordWatermark = Number.MAX_VALUE, lowRecordWatermark = Number.MAX_VALUE, - enrichMetadata + enrichMetadata, + onDb } = {}) { super() @@ -113,6 +114,7 @@ class ResultStreamObserver extends StreamObserver { this._paused = false this._pulled = !reactive this._haveRecordStreamed = false + this._onDb = onDb } /** @@ -319,6 +321,10 @@ class ResultStreamObserver extends StreamObserver { } } + if (meta.db !== null && this._onDb !== undefined) { + this._onDb(meta.db) + } + if (meta.fields != null) { // remove fields key from metadata object delete meta.fields diff --git a/packages/neo4j-driver-deno/lib/core/auth.ts b/packages/neo4j-driver-deno/lib/core/auth.ts index 3d3962e9e..d77ddcf6a 100644 --- a/packages/neo4j-driver-deno/lib/core/auth.ts +++ b/packages/neo4j-driver-deno/lib/core/auth.ts @@ -35,7 +35,7 @@ const auth = { realm } } else { - return { scheme: 'basic', principal: username, credentials: password} + return { scheme: 'basic', principal: username, credentials: password } } }, kerberos: (base64EncodedTicket: string) => { @@ -48,7 +48,7 @@ const auth = { bearer: (base64EncodedToken: string) => { return { scheme: 'bearer', - credentials: base64EncodedToken, + credentials: base64EncodedToken } }, none: () => { diff --git a/packages/neo4j-driver-deno/lib/core/connection.ts b/packages/neo4j-driver-deno/lib/core/connection.ts index 53cdbdb9d..b45e746ca 100644 --- a/packages/neo4j-driver-deno/lib/core/connection.ts +++ b/packages/neo4j-driver-deno/lib/core/connection.ts @@ -58,6 +58,7 @@ interface RunQueryConfig extends BeginTransactionConfig { highRecordWatermark: number lowRecordWatermark: number reactive: boolean + onDb?: (database: string) => void } /** diff --git a/packages/neo4j-driver-deno/lib/core/driver.ts b/packages/neo4j-driver-deno/lib/core/driver.ts index 1d04dc9a1..fa624d3c7 100644 --- a/packages/neo4j-driver-deno/lib/core/driver.ts +++ b/packages/neo4j-driver-deno/lib/core/driver.ts @@ -853,11 +853,10 @@ class Driver { } _homeDatabaseCallback (impersonatedUser: string, user: string, database: any): void { - let cacheKey = "DEFAULT" - if(impersonatedUser !== undefined) { - cacheKey = "basic:" + impersonatedUser - } - else if(user !== undefined) { + let cacheKey = 'DEFAULT' + if (impersonatedUser !== undefined && impersonatedUser !== "" && impersonatedUser !== null) { + cacheKey = 'basic:' + impersonatedUser + } else if (user !== undefined && user !== "" && user !== null) { cacheKey = user } this.homeDatabaseCache.set(cacheKey, database) @@ -908,6 +907,7 @@ class Driver { bookmarks, config: { cachedHomeDatabase, + routingDriver: this._supportsRouting(), ...this._config }, reactive, diff --git a/packages/neo4j-driver-deno/lib/core/internal/auth-utils.ts b/packages/neo4j-driver-deno/lib/core/internal/auth-utils.ts index 24ffccd88..0245825ea 100644 --- a/packages/neo4j-driver-deno/lib/core/internal/auth-utils.ts +++ b/packages/neo4j-driver-deno/lib/core/internal/auth-utils.ts @@ -17,34 +17,30 @@ import { AuthToken } from '../types.ts' -export function cacheKey(auth?: AuthToken): string | undefined { - if(auth === undefined) { +export function cacheKey (auth?: AuthToken): string | undefined { + if (auth === undefined) { return undefined } - if(auth.scheme === 'basic') { - return 'basic:' + (auth.principal ?? "") - } - else if (auth.scheme === 'kerberos') { + if (auth.scheme === 'basic') { + return 'basic:' + (auth.principal ?? '') + } else if (auth.scheme === 'kerberos') { return 'kerberos:' + auth.credentials - } - else if (auth.scheme === 'bearer') { + } else if (auth.scheme === 'bearer') { return 'bearer:' + auth.credentials - } - else if (auth.scheme === 'none') { - return "none" - } - else { + } else if (auth.scheme === 'none') { + return 'none' + } else { let ordered = '' if (auth.parameters !== undefined) { Object.keys(auth.parameters).sort().forEach((key: string) => { if (auth.parameters !== undefined) { - // eslint-disable-next-line @typescript-eslint/restrict-template-expressions - ordered += `${key}:${auth.parameters[key]}` + // eslint-disable-next-line @typescript-eslint/restrict-template-expressions + ordered += `${key}:${auth.parameters[key]}` } }) } - let credentialString = (auth.credentials !== undefined && auth.credentials !== "") ? 'credentials:' + auth.credentials : "" - let realmString = (auth.realm !== undefined && auth.realm !== "") ? 'realm:' + auth.realm : "" - return 'scheme:' + auth.scheme + 'principal:' + (auth.principal ?? "") + credentialString + realmString + 'parameters:' + ordered + const credentialString = (auth.credentials !== undefined && auth.credentials !== '') ? 'credentials:' + auth.credentials : '' + const realmString = (auth.realm !== undefined && auth.realm !== '') ? 'realm:' + auth.realm : '' + return 'scheme:' + auth.scheme + 'principal:' + (auth.principal ?? '') + credentialString + realmString + 'parameters:' + ordered } } diff --git a/packages/neo4j-driver-deno/lib/core/internal/connection-holder.ts b/packages/neo4j-driver-deno/lib/core/internal/connection-holder.ts index f8a7660ed..54601e8eb 100644 --- a/packages/neo4j-driver-deno/lib/core/internal/connection-holder.ts +++ b/packages/neo4j-driver-deno/lib/core/internal/connection-holder.ts @@ -166,9 +166,9 @@ class ConnectionHolder implements ConnectionHolderInterface { return this._referenceCount } - initializeConnection (homeDatabaseTable?: any): boolean { + initializeConnection (homeDatabase?: string): boolean { if (this._referenceCount === 0 && (this._connectionProvider != null)) { - this._connectionPromise = this._createConnectionPromise(this._connectionProvider, homeDatabaseTable) + this._connectionPromise = this._createConnectionPromise(this._connectionProvider, homeDatabase) } else { this._referenceCount++ return false @@ -180,7 +180,7 @@ class ConnectionHolder implements ConnectionHolderInterface { private async _createConnectionPromise (connectionProvider: ConnectionProvider, homeDatabase?: string): Promise { return await connectionProvider.acquireConnection({ accessMode: this._mode, - database: this._database ?? '', + database: this._database, bookmarks: await this._getBookmarks(), impersonatedUser: this._impersonatedUser, onDatabaseNameResolved: this._onDatabaseNameResolved, diff --git a/packages/neo4j-driver-deno/lib/core/internal/homedb-cache.ts b/packages/neo4j-driver-deno/lib/core/internal/homedb-cache.ts index e5a76d6b8..07cae5859 100644 --- a/packages/neo4j-driver-deno/lib/core/internal/homedb-cache.ts +++ b/packages/neo4j-driver-deno/lib/core/internal/homedb-cache.ts @@ -23,14 +23,16 @@ export default class HomeDatabaseCache { maxSize: number map: Map + pruneCount: number constructor (maxSize: number) { this.maxSize = maxSize + this.pruneCount = Math.max(Math.round(0.01 * maxSize * Math.log(maxSize)), 1) this.map = new Map() } /** - * Updates or add an entry to the cache, and prunes the cache if above the maximum allowed size + * Updates or adds an entry to the cache, and prunes the cache if above the maximum allowed size * * @param {string} user cache key for the user to set * @param {string} database new home database to set for the user @@ -82,7 +84,7 @@ export default class HomeDatabaseCache { private _pruneCache (): void { if (this.map.size > this.maxSize) { const sortedArray = Array.from(this.map.entries()).sort((a, b) => a[1].lastUsed - b[1].lastUsed) - for (let i = 0; i < 70; i++) { // c * max_size * logn(max_size), c is set to 0.01 + for (let i = 0; i < this.pruneCount; i++) { this.map.delete(sortedArray[i][0]) } } diff --git a/packages/neo4j-driver-deno/lib/core/session.ts b/packages/neo4j-driver-deno/lib/core/session.ts index b0762bccb..39983bb04 100644 --- a/packages/neo4j-driver-deno/lib/core/session.ts +++ b/packages/neo4j-driver-deno/lib/core/session.ts @@ -78,6 +78,7 @@ class Session { private readonly _homeDatabaseCallback: Function | undefined private readonly _auth: AuthToken | undefined private _databaseGuess: string | undefined + private _isRoutingSession: boolean /** * @constructor * @protected @@ -173,6 +174,7 @@ class Session { this._notificationFilter = notificationFilter this._log = log this._databaseGuess = config?.cachedHomeDatabase + this._isRoutingSession = config?.routingDriver ?? false } /** @@ -216,7 +218,8 @@ class Session { fetchSize: this._fetchSize, lowRecordWatermark: this._lowRecordWatermark, highRecordWatermark: this._highRecordWatermark, - notificationFilter: this._notificationFilter + notificationFilter: this._notificationFilter, + onDb: this._onDatabaseNameResolved.bind(this) }) }) this._results.push(result) @@ -340,7 +343,7 @@ class Session { highRecordWatermark: this._highRecordWatermark, notificationFilter: this._notificationFilter, apiTelemetryConfig, - onDbCallback: this._beginDbCallback.bind(this) + onDbCallback: this._onDatabaseNameResolved.bind(this) }) tx._begin(() => this._bookmarks(), txConfig) return tx @@ -524,27 +527,10 @@ class Session { * @returns {void} */ _onDatabaseNameResolved (database?: string): void { - this._databaseGuess = database - if (!this._databaseNameResolved) { - if (this._homeDatabaseCallback != null) { - // eslint-disable-next-line - this._homeDatabaseCallback(this._impersonatedUser, cacheKey(this._auth), database) - } - const normalizedDatabase = database ?? '' - this._database = normalizedDatabase - this._readConnectionHolder.setDatabase(normalizedDatabase) - this._writeConnectionHolder.setDatabase(normalizedDatabase) - this._databaseNameResolved = true - } - } - - _beginDbCallback (database: string): void { - // eslint-disable-next-line - if (this._connectionHolderWithMode(this._mode).connectionProvider()?.SSREnabled()) { + if (this._isRoutingSession) { this._databaseGuess = database if (!this._databaseNameResolved) { if (this._homeDatabaseCallback != null) { - // eslint-disable-next-line this._homeDatabaseCallback(this._impersonatedUser, cacheKey(this._auth), database) } const normalizedDatabase = database ?? '' @@ -620,9 +606,6 @@ class Session { * @returns {void} */ _onCompleteCallback (meta: { bookmark: string | string[], db?: string }, previousBookmarks?: Bookmarks): void { - if (meta.db !== undefined) { - this._beginDbCallback(meta.db) - } this._updateBookmarks(new Bookmarks(meta.bookmark), previousBookmarks, meta.db) } @@ -674,7 +657,6 @@ class Session { */ function _createTransactionExecutor (config?: { maxTransactionRetryTime: number | null - commitCallback: any }): TransactionExecutor { const maxRetryTimeMs = config?.maxTransactionRetryTime ?? null return new TransactionExecutor(maxRetryTimeMs) From 837ba63950c88d602a9a799b297747a2d7debdb7 Mon Sep 17 00:00:00 2001 From: MaxAake <61233757+MaxAake@users.noreply.github.com> Date: Wed, 8 Jan 2025 15:44:09 +0100 Subject: [PATCH 61/84] unit tests and renamed auth-util file --- packages/core/src/driver.ts | 2 +- .../internal/{auth-utils.ts => auth-util.ts} | 0 packages/core/src/session.ts | 2 +- packages/core/test/auth.test.ts | 2 +- packages/core/test/driver.test.ts | 3 +- packages/core/test/internal/auth-util.test.ts | 31 ++++++++++ .../core/test/internal/homedb-cache.test.ts | 60 +++++++++++++++++++ packages/neo4j-driver-deno/lib/core/driver.ts | 4 +- .../neo4j-driver-deno/lib/core/session.ts | 2 +- packages/neo4j-driver/test/driver.test.js | 4 +- 10 files changed, 101 insertions(+), 9 deletions(-) rename packages/core/src/internal/{auth-utils.ts => auth-util.ts} (100%) create mode 100644 packages/core/test/internal/auth-util.test.ts create mode 100644 packages/core/test/internal/homedb-cache.test.ts diff --git a/packages/core/src/driver.ts b/packages/core/src/driver.ts index 55f453ff9..545492c0d 100644 --- a/packages/core/src/driver.ts +++ b/packages/core/src/driver.ts @@ -47,7 +47,7 @@ import QueryExecutor from './internal/query-executor' import { newError } from './error' import NotificationFilter from './notification-filter' import HomeDatabaseCache from './internal/homedb-cache' -import { cacheKey } from './internal/auth-utils' +import { cacheKey } from './internal/auth-util' const DEFAULT_MAX_CONNECTION_LIFETIME: number = 60 * 60 * 1000 // 1 hour diff --git a/packages/core/src/internal/auth-utils.ts b/packages/core/src/internal/auth-util.ts similarity index 100% rename from packages/core/src/internal/auth-utils.ts rename to packages/core/src/internal/auth-util.ts diff --git a/packages/core/src/session.ts b/packages/core/src/session.ts index 5e0cbc5a4..0cc70097b 100644 --- a/packages/core/src/session.ts +++ b/packages/core/src/session.ts @@ -37,7 +37,7 @@ import BookmarkManager from './bookmark-manager' import { RecordShape } from './record' import NotificationFilter from './notification-filter' import { Logger } from './internal/logger' -import { cacheKey } from './internal/auth-utils' +import { cacheKey } from './internal/auth-util' type ConnectionConsumer = (connection: Connection) => Promise | T type TransactionWork = (tx: Transaction) => Promise | T diff --git a/packages/core/test/auth.test.ts b/packages/core/test/auth.test.ts index c802610b5..6af75ae35 100644 --- a/packages/core/test/auth.test.ts +++ b/packages/core/test/auth.test.ts @@ -15,7 +15,7 @@ * limitations under the License. */ import auth from '../src/auth' -import { cacheKey } from '../src/internal/auth-utils' +import { cacheKey } from '../src/internal/auth-util' describe('auth', () => { test('.bearer()', () => { diff --git a/packages/core/test/driver.test.ts b/packages/core/test/driver.test.ts index 41899f41a..9d7bbe3a6 100644 --- a/packages/core/test/driver.test.ts +++ b/packages/core/test/driver.test.ts @@ -700,7 +700,8 @@ describe('Driver', () => { fetchSize: 1000, maxConnectionLifetime: 3600000, maxConnectionPoolSize: 100, - connectionTimeout: 30000 + connectionTimeout: 30000, + routingDriver: false }, connectionProvider, database: '', diff --git a/packages/core/test/internal/auth-util.test.ts b/packages/core/test/internal/auth-util.test.ts new file mode 100644 index 000000000..4db045e25 --- /dev/null +++ b/packages/core/test/internal/auth-util.test.ts @@ -0,0 +1,31 @@ +/** + * Copyright (c) "Neo4j" + * Neo4j Sweden AB [https://neo4j.com] + * + * 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 { auth } from 'neo4j-driver-core' +import { cacheKey } from '../../src/internal/auth-util' + +describe('#unit cacheKey()', () => { + it.each([ + ['basic', auth.basic('hello', 'basic'), 'basic:hello'], + ['kerberos', auth.kerberos('kerberosString'), 'kerberos:kerberosString'], + ['bearer', auth.bearer('bearerToken'), 'bearer:bearerToken'], + ['custom without parameters', auth.custom('hello', 'custom', 'realm', 'scheme'), 'scheme:schemeprincipal:hellocredentials:customrealm:realmparameters:'], + ['custom with parameters', auth.custom('hello', 'custom', 'realm', 'scheme', { array: [1, 2, 3] }), 'scheme:schemeprincipal:hellocredentials:customrealm:realmparameters:array:1,2,3'] + ])('should create correct cacheKey for % auth toke', (_, token, expectedKey) => { + expect(cacheKey(token)).toEqual(expectedKey) + }) +}) diff --git a/packages/core/test/internal/homedb-cache.test.ts b/packages/core/test/internal/homedb-cache.test.ts new file mode 100644 index 000000000..455f0beb0 --- /dev/null +++ b/packages/core/test/internal/homedb-cache.test.ts @@ -0,0 +1,60 @@ +/** + * Copyright (c) "Neo4j" + * Neo4j Sweden AB [https://neo4j.com] + * + * 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 HomeDatabaseCache from '../../src/internal/homedb-cache' + +describe('#unit HomeDatabaseCache', () => { + it('should build homedb cache', () => { + const cache = new HomeDatabaseCache(10000) + cache.set('DEFAULT', 'neo4j') + expect(cache.get('DEFAULT')).toBe('neo4j') + cache.set('basic:hi', 'neo4j') + expect(cache.get('basic:hi')).toBe('neo4j') + }) + it('should remove entries in homeDb cache when their database fails', () => { + const cache = new HomeDatabaseCache(10000) + cache.set('DEFAULT', 'neo4j') + expect(cache.get('DEFAULT')).toBe('neo4j') + cache.set('basic:hi', 'neo4j') + expect(cache.get('basic:hi')).toBe('neo4j') + cache.set('basic:hello', 'neo5j') + expect(cache.get('basic:hello')).toBe('neo5j') + cache.removeFailedDatabase('neo4j') + expect(cache.get('DEFAULT')).toBe(undefined) + expect(cache.get('basic:hi')).toBe(undefined) + expect(cache.get('basic:hello')).toBe('neo5j') + }) + + it('should cap homeDb size by removing least recently used', async () => { + const cache = new HomeDatabaseCache(1000) + for (let i = 0; i < 999; i++) { + cache.set(i.toString(), 'neo4j') + } + await new Promise(resolve => setTimeout(resolve, 100)) + cache.set('5', 'neo4j') + cache.get('55') + for (let i = 999; i < 1050; i++) { + cache.set(i.toString(), 'neo4j') + } + expect(cache.get('1')).toEqual(undefined) + expect(cache.get('61')).toEqual(undefined) + expect(cache.get('5')).toEqual('neo4j') + expect(cache.get('55')).toEqual('neo4j') + expect(cache.get('101')).toEqual('neo4j') + expect(cache.get('1001')).toEqual('neo4j') + }) +}) diff --git a/packages/neo4j-driver-deno/lib/core/driver.ts b/packages/neo4j-driver-deno/lib/core/driver.ts index fa624d3c7..01d7812d2 100644 --- a/packages/neo4j-driver-deno/lib/core/driver.ts +++ b/packages/neo4j-driver-deno/lib/core/driver.ts @@ -854,9 +854,9 @@ class Driver { _homeDatabaseCallback (impersonatedUser: string, user: string, database: any): void { let cacheKey = 'DEFAULT' - if (impersonatedUser !== undefined && impersonatedUser !== "" && impersonatedUser !== null) { + if (impersonatedUser !== undefined && impersonatedUser !== '' && impersonatedUser !== null) { cacheKey = 'basic:' + impersonatedUser - } else if (user !== undefined && user !== "" && user !== null) { + } else if (user !== undefined && user !== '' && user !== null) { cacheKey = user } this.homeDatabaseCache.set(cacheKey, database) diff --git a/packages/neo4j-driver-deno/lib/core/session.ts b/packages/neo4j-driver-deno/lib/core/session.ts index 39983bb04..6c23ab067 100644 --- a/packages/neo4j-driver-deno/lib/core/session.ts +++ b/packages/neo4j-driver-deno/lib/core/session.ts @@ -78,7 +78,7 @@ class Session { private readonly _homeDatabaseCallback: Function | undefined private readonly _auth: AuthToken | undefined private _databaseGuess: string | undefined - private _isRoutingSession: boolean + private readonly _isRoutingSession: boolean /** * @constructor * @protected diff --git a/packages/neo4j-driver/test/driver.test.js b/packages/neo4j-driver/test/driver.test.js index ff4c7336b..ee8d2e476 100644 --- a/packages/neo4j-driver/test/driver.test.js +++ b/packages/neo4j-driver/test/driver.test.js @@ -238,10 +238,10 @@ describe('#unit driver', () => { driver._homeDatabaseCallback(undefined, i.toString(), 'neo4j') } expect(driver.homeDatabaseCache.get('1')).toEqual(undefined) - expect(driver.homeDatabaseCache.get('69')).toEqual(undefined) + expect(driver.homeDatabaseCache.get('901')).toEqual(undefined) expect(driver.homeDatabaseCache.get('5')).toEqual('neo4j') expect(driver.homeDatabaseCache.get('55')).toEqual('neo4j') - expect(driver.homeDatabaseCache.get('101')).toEqual('neo4j') + expect(driver.homeDatabaseCache.get('1001')).toEqual('neo4j') expect(driver.homeDatabaseCache.get('10001')).toEqual('neo4j') }) }) From 451714f49efa179923f00cdd2dde0143da64eb6d Mon Sep 17 00:00:00 2001 From: MaxAake <61233757+MaxAake@users.noreply.github.com> Date: Wed, 8 Jan 2025 15:45:28 +0100 Subject: [PATCH 62/84] deno --- packages/neo4j-driver-deno/lib/core/driver.ts | 2 +- .../lib/core/internal/{auth-utils.ts => auth-util.ts} | 0 packages/neo4j-driver-deno/lib/core/session.ts | 2 +- 3 files changed, 2 insertions(+), 2 deletions(-) rename packages/neo4j-driver-deno/lib/core/internal/{auth-utils.ts => auth-util.ts} (100%) diff --git a/packages/neo4j-driver-deno/lib/core/driver.ts b/packages/neo4j-driver-deno/lib/core/driver.ts index 01d7812d2..f27e81093 100644 --- a/packages/neo4j-driver-deno/lib/core/driver.ts +++ b/packages/neo4j-driver-deno/lib/core/driver.ts @@ -47,7 +47,7 @@ import QueryExecutor from './internal/query-executor.ts' import { newError } from './error.ts' import NotificationFilter from './notification-filter.ts' import HomeDatabaseCache from './internal/homedb-cache.ts' -import { cacheKey } from './internal/auth-utils.ts' +import { cacheKey } from './internal/auth-util.ts' const DEFAULT_MAX_CONNECTION_LIFETIME: number = 60 * 60 * 1000 // 1 hour diff --git a/packages/neo4j-driver-deno/lib/core/internal/auth-utils.ts b/packages/neo4j-driver-deno/lib/core/internal/auth-util.ts similarity index 100% rename from packages/neo4j-driver-deno/lib/core/internal/auth-utils.ts rename to packages/neo4j-driver-deno/lib/core/internal/auth-util.ts diff --git a/packages/neo4j-driver-deno/lib/core/session.ts b/packages/neo4j-driver-deno/lib/core/session.ts index 6c23ab067..253b35944 100644 --- a/packages/neo4j-driver-deno/lib/core/session.ts +++ b/packages/neo4j-driver-deno/lib/core/session.ts @@ -37,7 +37,7 @@ import BookmarkManager from './bookmark-manager.ts' import { RecordShape } from './record.ts' import NotificationFilter from './notification-filter.ts' import { Logger } from './internal/logger.ts' -import { cacheKey } from './internal/auth-utils.ts' +import { cacheKey } from './internal/auth-util.ts' type ConnectionConsumer = (connection: Connection) => Promise | T type TransactionWork = (tx: Transaction) => Promise | T From 27c9711c284c2c45dd147a8e5737f8a0b953a4b5 Mon Sep 17 00:00:00 2001 From: MaxAake <61233757+MaxAake@users.noreply.github.com> Date: Thu, 9 Jan 2025 08:43:19 +0100 Subject: [PATCH 63/84] import issue in unit tests --- packages/core/test/internal/auth-util.test.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/core/test/internal/auth-util.test.ts b/packages/core/test/internal/auth-util.test.ts index 4db045e25..f06cc48ef 100644 --- a/packages/core/test/internal/auth-util.test.ts +++ b/packages/core/test/internal/auth-util.test.ts @@ -15,7 +15,7 @@ * limitations under the License. */ -import { auth } from 'neo4j-driver-core' +import auth from '../../src/auth' import { cacheKey } from '../../src/internal/auth-util' describe('#unit cacheKey()', () => { From e404900a2c88b39dee4b1c2537e23d538bf69933 Mon Sep 17 00:00:00 2001 From: MaxAake <61233757+MaxAake@users.noreply.github.com> Date: Thu, 9 Jan 2025 09:08:59 +0100 Subject: [PATCH 64/84] remove removeFailureFromCache callback --- .../connection-provider-routing.js | 19 ++++--------------- packages/core/src/connection-provider.ts | 2 -- packages/core/src/driver.ts | 9 +-------- .../core/src/internal/connection-holder.ts | 6 ------ packages/core/src/internal/homedb-cache.ts | 13 ------------- packages/core/src/session.ts | 6 +----- packages/core/test/driver.test.ts | 1 - .../core/test/internal/homedb-cache.test.ts | 13 ------------- .../connection-provider-routing.js | 19 ++++--------------- .../lib/core/connection-provider.ts | 2 -- packages/neo4j-driver-deno/lib/core/driver.ts | 9 +-------- .../lib/core/internal/connection-holder.ts | 6 ------ .../lib/core/internal/homedb-cache.ts | 13 ------------- .../neo4j-driver-deno/lib/core/session.ts | 6 +----- packages/neo4j-driver/test/driver.test.js | 17 ----------------- 15 files changed, 12 insertions(+), 129 deletions(-) diff --git a/packages/bolt-connection/src/connection-provider/connection-provider-routing.js b/packages/bolt-connection/src/connection-provider/connection-provider-routing.js index 8a37fca55..acacfa696 100644 --- a/packages/bolt-connection/src/connection-provider/connection-provider-routing.js +++ b/packages/bolt-connection/src/connection-provider/connection-provider-routing.js @@ -142,27 +142,16 @@ export default class RoutingConnectionProvider extends PooledConnectionProvider * See {@link ConnectionProvider} for more information about this method and * its arguments. */ - async acquireConnection ({ accessMode, database, bookmarks, impersonatedUser, onDatabaseNameResolved, removeFailureFromCache, auth, homeDb } = {}) { + async acquireConnection ({ accessMode, database, bookmarks, impersonatedUser, onDatabaseNameResolved, auth, homeDb } = {}) { let name let address const context = { database: database || DEFAULT_DB_NAME } const databaseSpecificErrorHandler = new ConnectionErrorHandler( SESSION_EXPIRED, - (error, address) => { - if (removeFailureFromCache !== undefined) { - removeFailureFromCache(homeDb ?? context.database) - } - return this._handleUnavailability(error, address, context.database) - }, - (error, address) => { - if (removeFailureFromCache !== undefined) { - removeFailureFromCache(homeDb ?? context.database) - } - return this._handleWriteFailure(error, address, homeDb ?? context.database) - }, - (error, address, conn) => - this._handleSecurityError(error, address, conn, context.database) + (error, address) => this._handleUnavailability(error, address, context.database), + (error, address) => this._handleWriteFailure(error, address, homeDb ?? context.database), + (error, address, conn) => this._handleSecurityError(error, address, conn, context.database) ) let currentRoutingTable if (this.SSREnabled() && homeDb !== undefined && database === '') { diff --git a/packages/core/src/connection-provider.ts b/packages/core/src/connection-provider.ts index 874d125dc..693c0354e 100644 --- a/packages/core/src/connection-provider.ts +++ b/packages/core/src/connection-provider.ts @@ -57,7 +57,6 @@ class ConnectionProvider { * @property {Bookmarks} param.bookmarks - the bookmarks to send to routing discovery * @property {string} param.impersonatedUser - the impersonated user * @property {function (databaseName:string)} param.onDatabaseNameResolved - Callback called when the database name get resolved - * @property {function (databaseName:string)} param.removeFailureFromCache - Callback for deleting lost db from cache * @property {AuthToken} param.auth - auth token used to authorize for connection acquisition * @property {string} param.homeDb - the driver's best guess at the current home database for the user * @returns {Promise} @@ -68,7 +67,6 @@ class ConnectionProvider { bookmarks: bookmarks.Bookmarks impersonatedUser?: string onDatabaseNameResolved?: (database: string) => void - removeFailureFromCache?: (database: string) => void auth?: AuthToken homeDb?: string }): Promise { diff --git a/packages/core/src/driver.ts b/packages/core/src/driver.ts index 545492c0d..2c6187ef2 100644 --- a/packages/core/src/driver.ts +++ b/packages/core/src/driver.ts @@ -105,7 +105,6 @@ type CreateSession = (args: { auth?: AuthToken log: Logger homeDatabaseCallback?: (impersonatedUser: string, user: string, database: any) => void - removeFailureFromCache?: (database: string) => void }) => Session type CreateQueryExecutor = (createSession: (config: { database?: string, bookmarkManager?: BookmarkManager }) => Session) => QueryExecutor @@ -862,10 +861,6 @@ class Driver { this.homeDatabaseCache.set(cacheKey, database) } - _removeFailureFromCache (database: string): void { - this.homeDatabaseCache.removeFailedDatabase(database) - } - /** * @private */ @@ -895,7 +890,6 @@ class Driver { // eslint-disable-next-line const cachedHomeDatabase = this.homeDatabaseCache.get((impersonatedUser ? 'basic:' + impersonatedUser : undefined) ?? cacheKey(auth) ?? 'DEFAULT') const homeDatabaseCallback = this._homeDatabaseCallback.bind(this) - const removeFailureFromCache = this._removeFailureFromCache.bind(this) const bookmarks = bookmarkOrBookmarks != null ? new Bookmarks(bookmarkOrBookmarks) : Bookmarks.empty() @@ -917,8 +911,7 @@ class Driver { notificationFilter, auth, log: this._log, - homeDatabaseCallback, - removeFailureFromCache + homeDatabaseCallback }) } diff --git a/packages/core/src/internal/connection-holder.ts b/packages/core/src/internal/connection-holder.ts index 9cdae509a..1fa58ffe8 100644 --- a/packages/core/src/internal/connection-holder.ts +++ b/packages/core/src/internal/connection-holder.ts @@ -85,7 +85,6 @@ class ConnectionHolder implements ConnectionHolderInterface { private readonly _impersonatedUser?: string private readonly _getConnectionAcquistionBookmarks: () => Promise private readonly _onDatabaseNameResolved?: (databaseName: string) => void - private readonly removeFailureFromCache?: (databaseName: string) => void private readonly _auth?: AuthToken private readonly _log: Logger private _closed: boolean @@ -99,7 +98,6 @@ class ConnectionHolder implements ConnectionHolderInterface { * @property {ConnectionProvider} params.connectionProvider - the connection provider to acquire connections from. * @property {string?} params.impersonatedUser - the user which will be impersonated * @property {function(databaseName:string)} params.onDatabaseNameResolved - callback called when the database name is resolved - * @property {function (databaseName:string?)} param.removeFailureFromCache - Callback for deleting lost db from cache * @property {function():Promise} params.getConnectionAcquistionBookmarks - called for getting Bookmarks for acquiring connections * @property {AuthToken} params.auth - the target auth for the to-be-acquired connection */ @@ -110,7 +108,6 @@ class ConnectionHolder implements ConnectionHolderInterface { connectionProvider, impersonatedUser, onDatabaseNameResolved, - removeFailureFromCache, getConnectionAcquistionBookmarks, auth, log @@ -121,7 +118,6 @@ class ConnectionHolder implements ConnectionHolderInterface { connectionProvider?: ConnectionProvider impersonatedUser?: string onDatabaseNameResolved?: (database: string) => void - removeFailureFromCache?: (database: string) => void getConnectionAcquistionBookmarks?: () => Promise auth?: AuthToken log: Logger @@ -135,7 +131,6 @@ class ConnectionHolder implements ConnectionHolderInterface { this._referenceCount = 0 this._connectionPromise = Promise.resolve(null) this._onDatabaseNameResolved = onDatabaseNameResolved - this.removeFailureFromCache = removeFailureFromCache this._auth = auth this._log = log this._logError = this._logError.bind(this) @@ -184,7 +179,6 @@ class ConnectionHolder implements ConnectionHolderInterface { bookmarks: await this._getBookmarks(), impersonatedUser: this._impersonatedUser, onDatabaseNameResolved: this._onDatabaseNameResolved, - removeFailureFromCache: this.removeFailureFromCache, auth: this._auth, homeDb: homeDatabase }) diff --git a/packages/core/src/internal/homedb-cache.ts b/packages/core/src/internal/homedb-cache.ts index 07cae5859..7025b1032 100644 --- a/packages/core/src/internal/homedb-cache.ts +++ b/packages/core/src/internal/homedb-cache.ts @@ -65,19 +65,6 @@ export default class HomeDatabaseCache { this.map.delete(user) } - /** - * removes all entries listing a given database from the cache - * - * @param {string} database the name of the database to clear from the cache - */ - removeFailedDatabase (database: string): void { - this.map.forEach((_, key) => { - if (this.map.get(key)?.database === database) { - this.map.delete(key) - } - }) - } - /** * Removes a number of the oldest entries in the cache if the number of entries has exceeded the maximum size. */ diff --git a/packages/core/src/session.ts b/packages/core/src/session.ts index 0cc70097b..20fd37ee5 100644 --- a/packages/core/src/session.ts +++ b/packages/core/src/session.ts @@ -96,7 +96,6 @@ class Session { * @param {AuthToken} args.auth - the target auth for the to-be-acquired connection * @param {Logger} args.log - the logger used for logs in this session. * @param {(impersonatedUser:string, user:string database:string) => void} args.homeDatabaseCallback - callback used to update the home database cache - * @param {(database:string) => void} args.removeFailureFromCache - callback used to remove all entries containing a failing database from the home database cache */ constructor ({ mode, @@ -111,8 +110,7 @@ class Session { notificationFilter, auth, log, - homeDatabaseCallback, - removeFailureFromCache + homeDatabaseCallback }: { mode: SessionMode connectionProvider: ConnectionProvider @@ -127,7 +125,6 @@ class Session { auth?: AuthToken log: Logger homeDatabaseCallback?: (impersonatedUser: string, user: string, database: string) => void - removeFailureFromCache?: (database: string) => void }) { this._mode = mode this._database = database @@ -144,7 +141,6 @@ class Session { connectionProvider, impersonatedUser, onDatabaseNameResolved: this._onDatabaseNameResolved.bind(this), - removeFailureFromCache, getConnectionAcquistionBookmarks: this._getConnectionAcquistionBookmarks, log }) diff --git a/packages/core/test/driver.test.ts b/packages/core/test/driver.test.ts index 9d7bbe3a6..3be5c8f72 100644 --- a/packages/core/test/driver.test.ts +++ b/packages/core/test/driver.test.ts @@ -712,7 +712,6 @@ describe('Driver', () => { // @ts-expect-error log: driver?._log, homeDatabaseCallback: expect.any(Function), - removeFailureFromCache: expect.any(Function), ...extra } } diff --git a/packages/core/test/internal/homedb-cache.test.ts b/packages/core/test/internal/homedb-cache.test.ts index 455f0beb0..0b18ce635 100644 --- a/packages/core/test/internal/homedb-cache.test.ts +++ b/packages/core/test/internal/homedb-cache.test.ts @@ -25,19 +25,6 @@ describe('#unit HomeDatabaseCache', () => { cache.set('basic:hi', 'neo4j') expect(cache.get('basic:hi')).toBe('neo4j') }) - it('should remove entries in homeDb cache when their database fails', () => { - const cache = new HomeDatabaseCache(10000) - cache.set('DEFAULT', 'neo4j') - expect(cache.get('DEFAULT')).toBe('neo4j') - cache.set('basic:hi', 'neo4j') - expect(cache.get('basic:hi')).toBe('neo4j') - cache.set('basic:hello', 'neo5j') - expect(cache.get('basic:hello')).toBe('neo5j') - cache.removeFailedDatabase('neo4j') - expect(cache.get('DEFAULT')).toBe(undefined) - expect(cache.get('basic:hi')).toBe(undefined) - expect(cache.get('basic:hello')).toBe('neo5j') - }) it('should cap homeDb size by removing least recently used', async () => { const cache = new HomeDatabaseCache(1000) diff --git a/packages/neo4j-driver-deno/lib/bolt-connection/connection-provider/connection-provider-routing.js b/packages/neo4j-driver-deno/lib/bolt-connection/connection-provider/connection-provider-routing.js index 5410c5a49..0283fd0d4 100644 --- a/packages/neo4j-driver-deno/lib/bolt-connection/connection-provider/connection-provider-routing.js +++ b/packages/neo4j-driver-deno/lib/bolt-connection/connection-provider/connection-provider-routing.js @@ -142,27 +142,16 @@ export default class RoutingConnectionProvider extends PooledConnectionProvider * See {@link ConnectionProvider} for more information about this method and * its arguments. */ - async acquireConnection ({ accessMode, database, bookmarks, impersonatedUser, onDatabaseNameResolved, removeFailureFromCache, auth, homeDb } = {}) { + async acquireConnection ({ accessMode, database, bookmarks, impersonatedUser, onDatabaseNameResolved, auth, homeDb } = {}) { let name let address const context = { database: database || DEFAULT_DB_NAME } const databaseSpecificErrorHandler = new ConnectionErrorHandler( SESSION_EXPIRED, - (error, address) => { - if (removeFailureFromCache !== undefined) { - removeFailureFromCache(homeDb ?? context.database) - } - return this._handleUnavailability(error, address, context.database) - }, - (error, address) => { - if (removeFailureFromCache !== undefined) { - removeFailureFromCache(homeDb ?? context.database) - } - return this._handleWriteFailure(error, address, homeDb ?? context.database) - }, - (error, address, conn) => - this._handleSecurityError(error, address, conn, context.database) + (error, address) => this._handleUnavailability(error, address, context.database), + (error, address) => this._handleWriteFailure(error, address, homeDb ?? context.database), + (error, address, conn) => this._handleSecurityError(error, address, conn, context.database) ) let currentRoutingTable if (this.SSREnabled() && homeDb !== undefined && database === '') { diff --git a/packages/neo4j-driver-deno/lib/core/connection-provider.ts b/packages/neo4j-driver-deno/lib/core/connection-provider.ts index dd6b41a90..59aff6d33 100644 --- a/packages/neo4j-driver-deno/lib/core/connection-provider.ts +++ b/packages/neo4j-driver-deno/lib/core/connection-provider.ts @@ -57,7 +57,6 @@ class ConnectionProvider { * @property {Bookmarks} param.bookmarks - the bookmarks to send to routing discovery * @property {string} param.impersonatedUser - the impersonated user * @property {function (databaseName:string)} param.onDatabaseNameResolved - Callback called when the database name get resolved - * @property {function (databaseName:string)} param.removeFailureFromCache - Callback for deleting lost db from cache * @property {AuthToken} param.auth - auth token used to authorize for connection acquisition * @property {string} param.homeDb - the driver's best guess at the current home database for the user * @returns {Promise} @@ -68,7 +67,6 @@ class ConnectionProvider { bookmarks: bookmarks.Bookmarks impersonatedUser?: string onDatabaseNameResolved?: (database: string) => void - removeFailureFromCache?: (database: string) => void auth?: AuthToken homeDb?: string }): Promise { diff --git a/packages/neo4j-driver-deno/lib/core/driver.ts b/packages/neo4j-driver-deno/lib/core/driver.ts index f27e81093..a27857843 100644 --- a/packages/neo4j-driver-deno/lib/core/driver.ts +++ b/packages/neo4j-driver-deno/lib/core/driver.ts @@ -105,7 +105,6 @@ type CreateSession = (args: { auth?: AuthToken log: Logger homeDatabaseCallback?: (impersonatedUser: string, user: string, database: any) => void - removeFailureFromCache?: (database: string) => void }) => Session type CreateQueryExecutor = (createSession: (config: { database?: string, bookmarkManager?: BookmarkManager }) => Session) => QueryExecutor @@ -862,10 +861,6 @@ class Driver { this.homeDatabaseCache.set(cacheKey, database) } - _removeFailureFromCache (database: string): void { - this.homeDatabaseCache.removeFailedDatabase(database) - } - /** * @private */ @@ -895,7 +890,6 @@ class Driver { // eslint-disable-next-line const cachedHomeDatabase = this.homeDatabaseCache.get((impersonatedUser ? 'basic:' + impersonatedUser : undefined) ?? cacheKey(auth) ?? 'DEFAULT') const homeDatabaseCallback = this._homeDatabaseCallback.bind(this) - const removeFailureFromCache = this._removeFailureFromCache.bind(this) const bookmarks = bookmarkOrBookmarks != null ? new Bookmarks(bookmarkOrBookmarks) : Bookmarks.empty() @@ -917,8 +911,7 @@ class Driver { notificationFilter, auth, log: this._log, - homeDatabaseCallback, - removeFailureFromCache + homeDatabaseCallback }) } diff --git a/packages/neo4j-driver-deno/lib/core/internal/connection-holder.ts b/packages/neo4j-driver-deno/lib/core/internal/connection-holder.ts index 54601e8eb..f8e1027cb 100644 --- a/packages/neo4j-driver-deno/lib/core/internal/connection-holder.ts +++ b/packages/neo4j-driver-deno/lib/core/internal/connection-holder.ts @@ -85,7 +85,6 @@ class ConnectionHolder implements ConnectionHolderInterface { private readonly _impersonatedUser?: string private readonly _getConnectionAcquistionBookmarks: () => Promise private readonly _onDatabaseNameResolved?: (databaseName: string) => void - private readonly removeFailureFromCache?: (databaseName: string) => void private readonly _auth?: AuthToken private readonly _log: Logger private _closed: boolean @@ -99,7 +98,6 @@ class ConnectionHolder implements ConnectionHolderInterface { * @property {ConnectionProvider} params.connectionProvider - the connection provider to acquire connections from. * @property {string?} params.impersonatedUser - the user which will be impersonated * @property {function(databaseName:string)} params.onDatabaseNameResolved - callback called when the database name is resolved - * @property {function (databaseName:string?)} param.removeFailureFromCache - Callback for deleting lost db from cache * @property {function():Promise} params.getConnectionAcquistionBookmarks - called for getting Bookmarks for acquiring connections * @property {AuthToken} params.auth - the target auth for the to-be-acquired connection */ @@ -110,7 +108,6 @@ class ConnectionHolder implements ConnectionHolderInterface { connectionProvider, impersonatedUser, onDatabaseNameResolved, - removeFailureFromCache, getConnectionAcquistionBookmarks, auth, log @@ -121,7 +118,6 @@ class ConnectionHolder implements ConnectionHolderInterface { connectionProvider?: ConnectionProvider impersonatedUser?: string onDatabaseNameResolved?: (database: string) => void - removeFailureFromCache?: (database: string) => void getConnectionAcquistionBookmarks?: () => Promise auth?: AuthToken log: Logger @@ -135,7 +131,6 @@ class ConnectionHolder implements ConnectionHolderInterface { this._referenceCount = 0 this._connectionPromise = Promise.resolve(null) this._onDatabaseNameResolved = onDatabaseNameResolved - this.removeFailureFromCache = removeFailureFromCache this._auth = auth this._log = log this._logError = this._logError.bind(this) @@ -184,7 +179,6 @@ class ConnectionHolder implements ConnectionHolderInterface { bookmarks: await this._getBookmarks(), impersonatedUser: this._impersonatedUser, onDatabaseNameResolved: this._onDatabaseNameResolved, - removeFailureFromCache: this.removeFailureFromCache, auth: this._auth, homeDb: homeDatabase }) diff --git a/packages/neo4j-driver-deno/lib/core/internal/homedb-cache.ts b/packages/neo4j-driver-deno/lib/core/internal/homedb-cache.ts index 07cae5859..7025b1032 100644 --- a/packages/neo4j-driver-deno/lib/core/internal/homedb-cache.ts +++ b/packages/neo4j-driver-deno/lib/core/internal/homedb-cache.ts @@ -65,19 +65,6 @@ export default class HomeDatabaseCache { this.map.delete(user) } - /** - * removes all entries listing a given database from the cache - * - * @param {string} database the name of the database to clear from the cache - */ - removeFailedDatabase (database: string): void { - this.map.forEach((_, key) => { - if (this.map.get(key)?.database === database) { - this.map.delete(key) - } - }) - } - /** * Removes a number of the oldest entries in the cache if the number of entries has exceeded the maximum size. */ diff --git a/packages/neo4j-driver-deno/lib/core/session.ts b/packages/neo4j-driver-deno/lib/core/session.ts index 253b35944..3f0dc35ae 100644 --- a/packages/neo4j-driver-deno/lib/core/session.ts +++ b/packages/neo4j-driver-deno/lib/core/session.ts @@ -96,7 +96,6 @@ class Session { * @param {AuthToken} args.auth - the target auth for the to-be-acquired connection * @param {Logger} args.log - the logger used for logs in this session. * @param {(impersonatedUser:string, user:string database:string) => void} args.homeDatabaseCallback - callback used to update the home database cache - * @param {(database:string) => void} args.removeFailureFromCache - callback used to remove all entries containing a failing database from the home database cache */ constructor ({ mode, @@ -111,8 +110,7 @@ class Session { notificationFilter, auth, log, - homeDatabaseCallback, - removeFailureFromCache + homeDatabaseCallback }: { mode: SessionMode connectionProvider: ConnectionProvider @@ -127,7 +125,6 @@ class Session { auth?: AuthToken log: Logger homeDatabaseCallback?: (impersonatedUser: string, user: string, database: string) => void - removeFailureFromCache?: (database: string) => void }) { this._mode = mode this._database = database @@ -144,7 +141,6 @@ class Session { connectionProvider, impersonatedUser, onDatabaseNameResolved: this._onDatabaseNameResolved.bind(this), - removeFailureFromCache, getConnectionAcquistionBookmarks: this._getConnectionAcquistionBookmarks, log }) diff --git a/packages/neo4j-driver/test/driver.test.js b/packages/neo4j-driver/test/driver.test.js index ee8d2e476..8d4432bd8 100644 --- a/packages/neo4j-driver/test/driver.test.js +++ b/packages/neo4j-driver/test/driver.test.js @@ -206,23 +206,6 @@ describe('#unit driver', () => { expect(driver.homeDatabaseCache.get('DEFAULT')).toBe('neo5j') }) - it('should remove entries in homeDb cache when their database fails', () => { - driver = neo4j.driver( - `neo4j+ssc://${sharedNeo4j.hostnameWithBoltPort}`, - sharedNeo4j.authToken - ) - driver._homeDatabaseCallback(undefined, 'DEFAULT', 'neo4j') - expect(driver.homeDatabaseCache.get('DEFAULT')).toBe('neo4j') - driver._homeDatabaseCallback(undefined, 'basic:hi', 'neo4j') - expect(driver.homeDatabaseCache.get('basic:hi')).toBe('neo4j') - driver._homeDatabaseCallback(undefined, 'basic:hello', 'neo5j') - expect(driver.homeDatabaseCache.get('basic:hello')).toBe('neo5j') - driver._removeFailureFromCache('neo4j') - expect(driver.homeDatabaseCache.get('DEFAULT')).toBe(undefined) - expect(driver.homeDatabaseCache.get('basic:hi')).toBe(undefined) - expect(driver.homeDatabaseCache.get('basic:hello')).toBe('neo5j') - }) - it('should cap homeDb size by removing least recently used', async () => { driver = neo4j.driver( `neo4j+ssc://${sharedNeo4j.hostnameWithBoltPort}`, From f7df3969d9b4fd4908531c6f8c9f9e3c990b959a Mon Sep 17 00:00:00 2001 From: MaxAake <61233757+MaxAake@users.noreply.github.com> Date: Thu, 9 Jan 2025 10:33:52 +0100 Subject: [PATCH 65/84] restrict onDb callback to only bolt 5.8 run success --- .../src/bolt/bolt-protocol-v1.js | 6 +- .../src/bolt/bolt-protocol-v3.js | 6 +- .../src/bolt/bolt-protocol-v4x0.js | 6 +- .../src/bolt/bolt-protocol-v4x4.js | 6 +- .../src/bolt/bolt-protocol-v5x2.js | 6 +- .../src/bolt/bolt-protocol-v5x5.js | 6 +- .../src/bolt/bolt-protocol-v5x8.js | 67 ++++++++++++++++++- .../bolt-connection/bolt/bolt-protocol-v1.js | 6 +- .../bolt-connection/bolt/bolt-protocol-v3.js | 6 +- .../bolt/bolt-protocol-v4x0.js | 6 +- .../bolt/bolt-protocol-v4x4.js | 6 +- .../bolt/bolt-protocol-v5x2.js | 6 +- .../bolt/bolt-protocol-v5x5.js | 6 +- .../bolt/bolt-protocol-v5x8.js | 67 ++++++++++++++++++- 14 files changed, 156 insertions(+), 50 deletions(-) diff --git a/packages/bolt-connection/src/bolt/bolt-protocol-v1.js b/packages/bolt-connection/src/bolt/bolt-protocol-v1.js index 0dd76301c..d04b33b4e 100644 --- a/packages/bolt-connection/src/bolt/bolt-protocol-v1.js +++ b/packages/bolt-connection/src/bolt/bolt-protocol-v1.js @@ -413,8 +413,7 @@ export default class BoltProtocol { afterComplete, flush = true, highRecordWatermark = Number.MAX_VALUE, - lowRecordWatermark = Number.MAX_VALUE, - onDb + lowRecordWatermark = Number.MAX_VALUE } = {} ) { const observer = new ResultStreamObserver({ @@ -426,8 +425,7 @@ export default class BoltProtocol { beforeComplete, afterComplete, highRecordWatermark, - lowRecordWatermark, - onDb + lowRecordWatermark }) // bookmarks and mode are ignored in this version of the protocol diff --git a/packages/bolt-connection/src/bolt/bolt-protocol-v3.js b/packages/bolt-connection/src/bolt/bolt-protocol-v3.js index 22da505c9..1329e80f8 100644 --- a/packages/bolt-connection/src/bolt/bolt-protocol-v3.js +++ b/packages/bolt-connection/src/bolt/bolt-protocol-v3.js @@ -180,8 +180,7 @@ export default class BoltProtocol extends BoltProtocolV2 { afterComplete, flush = true, highRecordWatermark = Number.MAX_VALUE, - lowRecordWatermark = Number.MAX_VALUE, - onDb + lowRecordWatermark = Number.MAX_VALUE } = {} ) { const observer = new ResultStreamObserver({ @@ -193,8 +192,7 @@ export default class BoltProtocol extends BoltProtocolV2 { beforeComplete, afterComplete, highRecordWatermark, - lowRecordWatermark, - onDb + lowRecordWatermark }) // passing in a database name on this protocol version throws an error diff --git a/packages/bolt-connection/src/bolt/bolt-protocol-v4x0.js b/packages/bolt-connection/src/bolt/bolt-protocol-v4x0.js index 34bd0c6af..12c543e8e 100644 --- a/packages/bolt-connection/src/bolt/bolt-protocol-v4x0.js +++ b/packages/bolt-connection/src/bolt/bolt-protocol-v4x0.js @@ -104,8 +104,7 @@ export default class BoltProtocol extends BoltProtocolV3 { reactive = false, fetchSize = FETCH_ALL, highRecordWatermark = Number.MAX_VALUE, - lowRecordWatermark = Number.MAX_VALUE, - onDb + lowRecordWatermark = Number.MAX_VALUE } = {} ) { const observer = new ResultStreamObserver({ @@ -121,8 +120,7 @@ export default class BoltProtocol extends BoltProtocolV3 { beforeComplete, afterComplete, highRecordWatermark, - lowRecordWatermark, - onDb + lowRecordWatermark }) // passing impersonated user on this protocol version throws an error diff --git a/packages/bolt-connection/src/bolt/bolt-protocol-v4x4.js b/packages/bolt-connection/src/bolt/bolt-protocol-v4x4.js index 262d9bd94..85f98ad1b 100644 --- a/packages/bolt-connection/src/bolt/bolt-protocol-v4x4.js +++ b/packages/bolt-connection/src/bolt/bolt-protocol-v4x4.js @@ -97,8 +97,7 @@ export default class BoltProtocol extends BoltProtocolV43 { reactive = false, fetchSize = FETCH_ALL, highRecordWatermark = Number.MAX_VALUE, - lowRecordWatermark = Number.MAX_VALUE, - onDb + lowRecordWatermark = Number.MAX_VALUE } = {} ) { const observer = new ResultStreamObserver({ @@ -114,8 +113,7 @@ export default class BoltProtocol extends BoltProtocolV43 { beforeComplete, afterComplete, highRecordWatermark, - lowRecordWatermark, - onDb + lowRecordWatermark }) // passing notification filter on this protocol version throws an error diff --git a/packages/bolt-connection/src/bolt/bolt-protocol-v5x2.js b/packages/bolt-connection/src/bolt/bolt-protocol-v5x2.js index f5d488f58..891ba8e36 100644 --- a/packages/bolt-connection/src/bolt/bolt-protocol-v5x2.js +++ b/packages/bolt-connection/src/bolt/bolt-protocol-v5x2.js @@ -130,8 +130,7 @@ export default class BoltProtocol extends BoltProtocolV5x1 { reactive = false, fetchSize = FETCH_ALL, highRecordWatermark = Number.MAX_VALUE, - lowRecordWatermark = Number.MAX_VALUE, - onDb + lowRecordWatermark = Number.MAX_VALUE } = {} ) { const observer = new ResultStreamObserver({ @@ -147,8 +146,7 @@ export default class BoltProtocol extends BoltProtocolV5x1 { beforeComplete, afterComplete, highRecordWatermark, - lowRecordWatermark, - onDb + lowRecordWatermark }) const flushRun = reactive diff --git a/packages/bolt-connection/src/bolt/bolt-protocol-v5x5.js b/packages/bolt-connection/src/bolt/bolt-protocol-v5x5.js index 700cfcc06..206d81d4c 100644 --- a/packages/bolt-connection/src/bolt/bolt-protocol-v5x5.js +++ b/packages/bolt-connection/src/bolt/bolt-protocol-v5x5.js @@ -130,8 +130,7 @@ export default class BoltProtocol extends BoltProtocolV5x4 { reactive = false, fetchSize = FETCH_ALL, highRecordWatermark = Number.MAX_VALUE, - lowRecordWatermark = Number.MAX_VALUE, - onDb + lowRecordWatermark = Number.MAX_VALUE } = {} ) { const observer = new ResultStreamObserver({ @@ -148,8 +147,7 @@ export default class BoltProtocol extends BoltProtocolV5x4 { afterComplete, highRecordWatermark, lowRecordWatermark, - enrichMetadata: this._enrichMetadata, - onDb + enrichMetadata: this._enrichMetadata }) const flushRun = reactive diff --git a/packages/bolt-connection/src/bolt/bolt-protocol-v5x8.js b/packages/bolt-connection/src/bolt/bolt-protocol-v5x8.js index 692b4a110..ce7b86269 100644 --- a/packages/bolt-connection/src/bolt/bolt-protocol-v5x8.js +++ b/packages/bolt-connection/src/bolt/bolt-protocol-v5x8.js @@ -18,11 +18,13 @@ import BoltProtocolV5x7 from './bolt-protocol-v5x7' import transformersFactories from './bolt-protocol-v5x8.transformer' import Transformer from './transformer' +import RequestMessage from './request-message' +import { ResultStreamObserver } from './stream-observers' import { internal } from 'neo4j-driver-core' const { - constants: { BOLT_PROTOCOL_V5_8 } + constants: { BOLT_PROTOCOL_V5_8, FETCH_ALL } } = internal export default class BoltProtocol extends BoltProtocolV5x7 { @@ -36,4 +38,67 @@ export default class BoltProtocol extends BoltProtocolV5x7 { } return this._transformer } + + run ( + query, + parameters, + { + bookmarks, + txConfig, + database, + mode, + impersonatedUser, + notificationFilter, + beforeKeys, + afterKeys, + beforeError, + afterError, + beforeComplete, + afterComplete, + flush = true, + reactive = false, + fetchSize = FETCH_ALL, + highRecordWatermark = Number.MAX_VALUE, + lowRecordWatermark = Number.MAX_VALUE, + onDb + } = {} + ) { + const observer = new ResultStreamObserver({ + server: this._server, + reactive, + fetchSize, + moreFunction: this._requestMore.bind(this), + discardFunction: this._requestDiscard.bind(this), + beforeKeys, + afterKeys, + beforeError, + afterError, + beforeComplete, + afterComplete, + highRecordWatermark, + lowRecordWatermark, + enrichMetadata: this._enrichMetadata, + onDb + }) + + const flushRun = reactive + this.write( + RequestMessage.runWithMetadata5x5(query, parameters, { + bookmarks, + txConfig, + database, + mode, + impersonatedUser, + notificationFilter + }), + observer, + flushRun && flush + ) + + if (!reactive) { + this.write(RequestMessage.pull({ n: fetchSize }), observer, flush) + } + + return observer + } } diff --git a/packages/neo4j-driver-deno/lib/bolt-connection/bolt/bolt-protocol-v1.js b/packages/neo4j-driver-deno/lib/bolt-connection/bolt/bolt-protocol-v1.js index 420747616..a83f32f5a 100644 --- a/packages/neo4j-driver-deno/lib/bolt-connection/bolt/bolt-protocol-v1.js +++ b/packages/neo4j-driver-deno/lib/bolt-connection/bolt/bolt-protocol-v1.js @@ -413,8 +413,7 @@ export default class BoltProtocol { afterComplete, flush = true, highRecordWatermark = Number.MAX_VALUE, - lowRecordWatermark = Number.MAX_VALUE, - onDb + lowRecordWatermark = Number.MAX_VALUE } = {} ) { const observer = new ResultStreamObserver({ @@ -426,8 +425,7 @@ export default class BoltProtocol { beforeComplete, afterComplete, highRecordWatermark, - lowRecordWatermark, - onDb + lowRecordWatermark }) // bookmarks and mode are ignored in this version of the protocol diff --git a/packages/neo4j-driver-deno/lib/bolt-connection/bolt/bolt-protocol-v3.js b/packages/neo4j-driver-deno/lib/bolt-connection/bolt/bolt-protocol-v3.js index 2d4636033..d077cd3b5 100644 --- a/packages/neo4j-driver-deno/lib/bolt-connection/bolt/bolt-protocol-v3.js +++ b/packages/neo4j-driver-deno/lib/bolt-connection/bolt/bolt-protocol-v3.js @@ -180,8 +180,7 @@ export default class BoltProtocol extends BoltProtocolV2 { afterComplete, flush = true, highRecordWatermark = Number.MAX_VALUE, - lowRecordWatermark = Number.MAX_VALUE, - onDb + lowRecordWatermark = Number.MAX_VALUE } = {} ) { const observer = new ResultStreamObserver({ @@ -193,8 +192,7 @@ export default class BoltProtocol extends BoltProtocolV2 { beforeComplete, afterComplete, highRecordWatermark, - lowRecordWatermark, - onDb + lowRecordWatermark }) // passing in a database name on this protocol version throws an error diff --git a/packages/neo4j-driver-deno/lib/bolt-connection/bolt/bolt-protocol-v4x0.js b/packages/neo4j-driver-deno/lib/bolt-connection/bolt/bolt-protocol-v4x0.js index 1751d0c61..eee49bb46 100644 --- a/packages/neo4j-driver-deno/lib/bolt-connection/bolt/bolt-protocol-v4x0.js +++ b/packages/neo4j-driver-deno/lib/bolt-connection/bolt/bolt-protocol-v4x0.js @@ -104,8 +104,7 @@ export default class BoltProtocol extends BoltProtocolV3 { reactive = false, fetchSize = FETCH_ALL, highRecordWatermark = Number.MAX_VALUE, - lowRecordWatermark = Number.MAX_VALUE, - onDb + lowRecordWatermark = Number.MAX_VALUE } = {} ) { const observer = new ResultStreamObserver({ @@ -121,8 +120,7 @@ export default class BoltProtocol extends BoltProtocolV3 { beforeComplete, afterComplete, highRecordWatermark, - lowRecordWatermark, - onDb + lowRecordWatermark }) // passing impersonated user on this protocol version throws an error diff --git a/packages/neo4j-driver-deno/lib/bolt-connection/bolt/bolt-protocol-v4x4.js b/packages/neo4j-driver-deno/lib/bolt-connection/bolt/bolt-protocol-v4x4.js index 5f96245f7..d3a7b427e 100644 --- a/packages/neo4j-driver-deno/lib/bolt-connection/bolt/bolt-protocol-v4x4.js +++ b/packages/neo4j-driver-deno/lib/bolt-connection/bolt/bolt-protocol-v4x4.js @@ -97,8 +97,7 @@ export default class BoltProtocol extends BoltProtocolV43 { reactive = false, fetchSize = FETCH_ALL, highRecordWatermark = Number.MAX_VALUE, - lowRecordWatermark = Number.MAX_VALUE, - onDb + lowRecordWatermark = Number.MAX_VALUE } = {} ) { const observer = new ResultStreamObserver({ @@ -114,8 +113,7 @@ export default class BoltProtocol extends BoltProtocolV43 { beforeComplete, afterComplete, highRecordWatermark, - lowRecordWatermark, - onDb + lowRecordWatermark }) // passing notification filter on this protocol version throws an error diff --git a/packages/neo4j-driver-deno/lib/bolt-connection/bolt/bolt-protocol-v5x2.js b/packages/neo4j-driver-deno/lib/bolt-connection/bolt/bolt-protocol-v5x2.js index 064cd4a61..169d37fbb 100644 --- a/packages/neo4j-driver-deno/lib/bolt-connection/bolt/bolt-protocol-v5x2.js +++ b/packages/neo4j-driver-deno/lib/bolt-connection/bolt/bolt-protocol-v5x2.js @@ -130,8 +130,7 @@ export default class BoltProtocol extends BoltProtocolV5x1 { reactive = false, fetchSize = FETCH_ALL, highRecordWatermark = Number.MAX_VALUE, - lowRecordWatermark = Number.MAX_VALUE, - onDb + lowRecordWatermark = Number.MAX_VALUE } = {} ) { const observer = new ResultStreamObserver({ @@ -147,8 +146,7 @@ export default class BoltProtocol extends BoltProtocolV5x1 { beforeComplete, afterComplete, highRecordWatermark, - lowRecordWatermark, - onDb + lowRecordWatermark }) const flushRun = reactive diff --git a/packages/neo4j-driver-deno/lib/bolt-connection/bolt/bolt-protocol-v5x5.js b/packages/neo4j-driver-deno/lib/bolt-connection/bolt/bolt-protocol-v5x5.js index 8d4a38535..f79304f7d 100644 --- a/packages/neo4j-driver-deno/lib/bolt-connection/bolt/bolt-protocol-v5x5.js +++ b/packages/neo4j-driver-deno/lib/bolt-connection/bolt/bolt-protocol-v5x5.js @@ -130,8 +130,7 @@ export default class BoltProtocol extends BoltProtocolV5x4 { reactive = false, fetchSize = FETCH_ALL, highRecordWatermark = Number.MAX_VALUE, - lowRecordWatermark = Number.MAX_VALUE, - onDb + lowRecordWatermark = Number.MAX_VALUE } = {} ) { const observer = new ResultStreamObserver({ @@ -148,8 +147,7 @@ export default class BoltProtocol extends BoltProtocolV5x4 { afterComplete, highRecordWatermark, lowRecordWatermark, - enrichMetadata: this._enrichMetadata, - onDb + enrichMetadata: this._enrichMetadata }) const flushRun = reactive diff --git a/packages/neo4j-driver-deno/lib/bolt-connection/bolt/bolt-protocol-v5x8.js b/packages/neo4j-driver-deno/lib/bolt-connection/bolt/bolt-protocol-v5x8.js index 0c6434e57..c2d832982 100644 --- a/packages/neo4j-driver-deno/lib/bolt-connection/bolt/bolt-protocol-v5x8.js +++ b/packages/neo4j-driver-deno/lib/bolt-connection/bolt/bolt-protocol-v5x8.js @@ -18,11 +18,13 @@ import BoltProtocolV5x7 from './bolt-protocol-v5x7.js' import transformersFactories from './bolt-protocol-v5x8.transformer.js' import Transformer from './transformer.js' +import RequestMessage from './request-message.js' +import { ResultStreamObserver } from './stream-observers.js' import { internal } from '../../core/index.ts' const { - constants: { BOLT_PROTOCOL_V5_8 } + constants: { BOLT_PROTOCOL_V5_8, FETCH_ALL } } = internal export default class BoltProtocol extends BoltProtocolV5x7 { @@ -36,4 +38,67 @@ export default class BoltProtocol extends BoltProtocolV5x7 { } return this._transformer } + + run ( + query, + parameters, + { + bookmarks, + txConfig, + database, + mode, + impersonatedUser, + notificationFilter, + beforeKeys, + afterKeys, + beforeError, + afterError, + beforeComplete, + afterComplete, + flush = true, + reactive = false, + fetchSize = FETCH_ALL, + highRecordWatermark = Number.MAX_VALUE, + lowRecordWatermark = Number.MAX_VALUE, + onDb + } = {} + ) { + const observer = new ResultStreamObserver({ + server: this._server, + reactive, + fetchSize, + moreFunction: this._requestMore.bind(this), + discardFunction: this._requestDiscard.bind(this), + beforeKeys, + afterKeys, + beforeError, + afterError, + beforeComplete, + afterComplete, + highRecordWatermark, + lowRecordWatermark, + enrichMetadata: this._enrichMetadata, + onDb + }) + + const flushRun = reactive + this.write( + RequestMessage.runWithMetadata5x5(query, parameters, { + bookmarks, + txConfig, + database, + mode, + impersonatedUser, + notificationFilter + }), + observer, + flushRun && flush + ) + + if (!reactive) { + this.write(RequestMessage.pull({ n: fetchSize }), observer, flush) + } + + return observer + } } From b3c7e9e128239f8554d95a241e3c9c69172fa3fe Mon Sep 17 00:00:00 2001 From: MaxAake <61233757+MaxAake@users.noreply.github.com> Date: Thu, 9 Jan 2025 15:25:06 +0100 Subject: [PATCH 66/84] upgrade cacheKey function to implement impersonation and default key --- packages/core/src/driver.ts | 10 ++-------- packages/core/src/internal/auth-util.ts | 7 +++++-- packages/core/src/session.ts | 2 +- packages/core/test/driver.test.ts | 3 +-- packages/neo4j-driver-deno/lib/core/driver.ts | 10 ++-------- .../neo4j-driver-deno/lib/core/internal/auth-util.ts | 7 +++++-- packages/neo4j-driver-deno/lib/core/session.ts | 2 +- packages/neo4j-driver/test/driver.test.js | 2 +- 8 files changed, 18 insertions(+), 25 deletions(-) diff --git a/packages/core/src/driver.ts b/packages/core/src/driver.ts index 2c6187ef2..9564a1667 100644 --- a/packages/core/src/driver.ts +++ b/packages/core/src/driver.ts @@ -851,13 +851,7 @@ class Driver { ) } - _homeDatabaseCallback (impersonatedUser: string, user: string, database: any): void { - let cacheKey = 'DEFAULT' - if (impersonatedUser !== undefined && impersonatedUser !== '' && impersonatedUser !== null) { - cacheKey = 'basic:' + impersonatedUser - } else if (user !== undefined && user !== '' && user !== null) { - cacheKey = user - } + _homeDatabaseCallback (cacheKey: string, database: any): void { this.homeDatabaseCache.set(cacheKey, database) } @@ -888,7 +882,7 @@ class Driver { const sessionMode = Session._validateSessionMode(defaultAccessMode) const connectionProvider = this._getOrCreateConnectionProvider() // eslint-disable-next-line - const cachedHomeDatabase = this.homeDatabaseCache.get((impersonatedUser ? 'basic:' + impersonatedUser : undefined) ?? cacheKey(auth) ?? 'DEFAULT') + const cachedHomeDatabase = this.homeDatabaseCache.get(cacheKey(auth, impersonatedUser)) const homeDatabaseCallback = this._homeDatabaseCallback.bind(this) const bookmarks = bookmarkOrBookmarks != null ? new Bookmarks(bookmarkOrBookmarks) diff --git a/packages/core/src/internal/auth-util.ts b/packages/core/src/internal/auth-util.ts index ad95107e4..b2ec1155a 100644 --- a/packages/core/src/internal/auth-util.ts +++ b/packages/core/src/internal/auth-util.ts @@ -17,9 +17,12 @@ import { AuthToken } from '../types' -export function cacheKey (auth?: AuthToken): string | undefined { +export function cacheKey (auth?: AuthToken, impersonatedUser?: string): string { + if (impersonatedUser !== undefined && impersonatedUser !== '') { + return 'basic:' + impersonatedUser + } if (auth === undefined) { - return undefined + return 'DEFAULT' } if (auth.scheme === 'basic') { return 'basic:' + (auth.principal ?? '') diff --git a/packages/core/src/session.ts b/packages/core/src/session.ts index 20fd37ee5..828c6db8a 100644 --- a/packages/core/src/session.ts +++ b/packages/core/src/session.ts @@ -527,7 +527,7 @@ class Session { this._databaseGuess = database if (!this._databaseNameResolved) { if (this._homeDatabaseCallback != null) { - this._homeDatabaseCallback(this._impersonatedUser, cacheKey(this._auth), database) + this._homeDatabaseCallback(cacheKey(this._auth, this._impersonatedUser), database) } const normalizedDatabase = database ?? '' this._database = normalizedDatabase diff --git a/packages/core/test/driver.test.ts b/packages/core/test/driver.test.ts index 3be5c8f72..de2c8ef0b 100644 --- a/packages/core/test/driver.test.ts +++ b/packages/core/test/driver.test.ts @@ -84,8 +84,7 @@ describe('Driver', () => { const auth = { scheme: 'basic', principal: 'the imposter', - credentials: 'super safe password', - cacheKey: 'basic:the imposter' + credentials: 'super safe password' } const session = driver?.session({ auth }) diff --git a/packages/neo4j-driver-deno/lib/core/driver.ts b/packages/neo4j-driver-deno/lib/core/driver.ts index a27857843..6cb2acffb 100644 --- a/packages/neo4j-driver-deno/lib/core/driver.ts +++ b/packages/neo4j-driver-deno/lib/core/driver.ts @@ -851,13 +851,7 @@ class Driver { ) } - _homeDatabaseCallback (impersonatedUser: string, user: string, database: any): void { - let cacheKey = 'DEFAULT' - if (impersonatedUser !== undefined && impersonatedUser !== '' && impersonatedUser !== null) { - cacheKey = 'basic:' + impersonatedUser - } else if (user !== undefined && user !== '' && user !== null) { - cacheKey = user - } + _homeDatabaseCallback (cacheKey: string, database: any): void { this.homeDatabaseCache.set(cacheKey, database) } @@ -888,7 +882,7 @@ class Driver { const sessionMode = Session._validateSessionMode(defaultAccessMode) const connectionProvider = this._getOrCreateConnectionProvider() // eslint-disable-next-line - const cachedHomeDatabase = this.homeDatabaseCache.get((impersonatedUser ? 'basic:' + impersonatedUser : undefined) ?? cacheKey(auth) ?? 'DEFAULT') + const cachedHomeDatabase = this.homeDatabaseCache.get(cacheKey(auth, impersonatedUser)) const homeDatabaseCallback = this._homeDatabaseCallback.bind(this) const bookmarks = bookmarkOrBookmarks != null ? new Bookmarks(bookmarkOrBookmarks) diff --git a/packages/neo4j-driver-deno/lib/core/internal/auth-util.ts b/packages/neo4j-driver-deno/lib/core/internal/auth-util.ts index 0245825ea..0ea935387 100644 --- a/packages/neo4j-driver-deno/lib/core/internal/auth-util.ts +++ b/packages/neo4j-driver-deno/lib/core/internal/auth-util.ts @@ -17,9 +17,12 @@ import { AuthToken } from '../types.ts' -export function cacheKey (auth?: AuthToken): string | undefined { +export function cacheKey (auth?: AuthToken, impersonatedUser?: string): string { + if(impersonatedUser !== undefined && impersonatedUser !== "") { + return "basic:" + impersonatedUser + } if (auth === undefined) { - return undefined + return "DEFAULT" } if (auth.scheme === 'basic') { return 'basic:' + (auth.principal ?? '') diff --git a/packages/neo4j-driver-deno/lib/core/session.ts b/packages/neo4j-driver-deno/lib/core/session.ts index 3f0dc35ae..338ac1e50 100644 --- a/packages/neo4j-driver-deno/lib/core/session.ts +++ b/packages/neo4j-driver-deno/lib/core/session.ts @@ -527,7 +527,7 @@ class Session { this._databaseGuess = database if (!this._databaseNameResolved) { if (this._homeDatabaseCallback != null) { - this._homeDatabaseCallback(this._impersonatedUser, cacheKey(this._auth), database) + this._homeDatabaseCallback(cacheKey(this._auth, this._impersonatedUser), database) } const normalizedDatabase = database ?? '' this._database = normalizedDatabase diff --git a/packages/neo4j-driver/test/driver.test.js b/packages/neo4j-driver/test/driver.test.js index 8d4432bd8..ede5fd1f4 100644 --- a/packages/neo4j-driver/test/driver.test.js +++ b/packages/neo4j-driver/test/driver.test.js @@ -584,7 +584,7 @@ describe('#integration driver', () => { describe('HomeDatabaseCache', () => { [['with driver auth', {}, 'DEFAULT'], - ['with session auth', { auth: sharedNeo4j.authToken }, sharedNeo4j.authToken.cacheKey], + ['with session auth', { auth: sharedNeo4j.authToken }, 'basic:neo4j'], ['with impersonated user', { impersonatedUser: 'neo4j' }, 'basic:neo4j']].forEach(([string, auth, key]) => { it('should build home database cache ' + string, async () => { driver = neo4j.driver( From 4f335991caba070dea288fc4e0f19d6eab4dc9d9 Mon Sep 17 00:00:00 2001 From: MaxAake <61233757+MaxAake@users.noreply.github.com> Date: Thu, 9 Jan 2025 15:36:18 +0100 Subject: [PATCH 67/84] deno --- packages/neo4j-driver-deno/lib/core/internal/auth-util.ts | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/packages/neo4j-driver-deno/lib/core/internal/auth-util.ts b/packages/neo4j-driver-deno/lib/core/internal/auth-util.ts index 0ea935387..d196c3ff4 100644 --- a/packages/neo4j-driver-deno/lib/core/internal/auth-util.ts +++ b/packages/neo4j-driver-deno/lib/core/internal/auth-util.ts @@ -18,11 +18,11 @@ import { AuthToken } from '../types.ts' export function cacheKey (auth?: AuthToken, impersonatedUser?: string): string { - if(impersonatedUser !== undefined && impersonatedUser !== "") { - return "basic:" + impersonatedUser + if (impersonatedUser !== undefined && impersonatedUser !== '') { + return 'basic:' + impersonatedUser } if (auth === undefined) { - return "DEFAULT" + return 'DEFAULT' } if (auth.scheme === 'basic') { return 'basic:' + (auth.principal ?? '') From 452a09e67e68e14ac392a7da5f2766b8b250ef49 Mon Sep 17 00:00:00 2001 From: MaxAake <61233757+MaxAake@users.noreply.github.com> Date: Fri, 10 Jan 2025 09:47:33 +0100 Subject: [PATCH 68/84] fixing issue with correctly identifying the lack of an impersonated user --- packages/core/src/driver.ts | 2 +- packages/core/src/internal/auth-util.ts | 2 +- packages/core/src/session.ts | 2 +- packages/neo4j-driver-deno/lib/core/driver.ts | 2 +- .../lib/core/internal/auth-util.ts | 2 +- packages/neo4j-driver-deno/lib/core/session.ts | 2 +- packages/neo4j-driver/test/driver.test.js | 14 +++++++------- 7 files changed, 13 insertions(+), 13 deletions(-) diff --git a/packages/core/src/driver.ts b/packages/core/src/driver.ts index 9564a1667..257cc9c4b 100644 --- a/packages/core/src/driver.ts +++ b/packages/core/src/driver.ts @@ -104,7 +104,7 @@ type CreateSession = (args: { notificationFilter?: NotificationFilter auth?: AuthToken log: Logger - homeDatabaseCallback?: (impersonatedUser: string, user: string, database: any) => void + homeDatabaseCallback?: (user: string, database: any) => void }) => Session type CreateQueryExecutor = (createSession: (config: { database?: string, bookmarkManager?: BookmarkManager }) => Session) => QueryExecutor diff --git a/packages/core/src/internal/auth-util.ts b/packages/core/src/internal/auth-util.ts index b2ec1155a..c1b0ebe85 100644 --- a/packages/core/src/internal/auth-util.ts +++ b/packages/core/src/internal/auth-util.ts @@ -18,7 +18,7 @@ import { AuthToken } from '../types' export function cacheKey (auth?: AuthToken, impersonatedUser?: string): string { - if (impersonatedUser !== undefined && impersonatedUser !== '') { + if (impersonatedUser !== undefined && impersonatedUser !== null) { return 'basic:' + impersonatedUser } if (auth === undefined) { diff --git a/packages/core/src/session.ts b/packages/core/src/session.ts index 828c6db8a..2d8179e78 100644 --- a/packages/core/src/session.ts +++ b/packages/core/src/session.ts @@ -124,7 +124,7 @@ class Session { notificationFilter?: NotificationFilter auth?: AuthToken log: Logger - homeDatabaseCallback?: (impersonatedUser: string, user: string, database: string) => void + homeDatabaseCallback?: (user: string, database: string) => void }) { this._mode = mode this._database = database diff --git a/packages/neo4j-driver-deno/lib/core/driver.ts b/packages/neo4j-driver-deno/lib/core/driver.ts index 6cb2acffb..86b43a088 100644 --- a/packages/neo4j-driver-deno/lib/core/driver.ts +++ b/packages/neo4j-driver-deno/lib/core/driver.ts @@ -104,7 +104,7 @@ type CreateSession = (args: { notificationFilter?: NotificationFilter auth?: AuthToken log: Logger - homeDatabaseCallback?: (impersonatedUser: string, user: string, database: any) => void + homeDatabaseCallback?: (user: string, database: any) => void }) => Session type CreateQueryExecutor = (createSession: (config: { database?: string, bookmarkManager?: BookmarkManager }) => Session) => QueryExecutor diff --git a/packages/neo4j-driver-deno/lib/core/internal/auth-util.ts b/packages/neo4j-driver-deno/lib/core/internal/auth-util.ts index d196c3ff4..e94561eb0 100644 --- a/packages/neo4j-driver-deno/lib/core/internal/auth-util.ts +++ b/packages/neo4j-driver-deno/lib/core/internal/auth-util.ts @@ -18,7 +18,7 @@ import { AuthToken } from '../types.ts' export function cacheKey (auth?: AuthToken, impersonatedUser?: string): string { - if (impersonatedUser !== undefined && impersonatedUser !== '') { + if (impersonatedUser !== undefined && impersonatedUser !== null) { return 'basic:' + impersonatedUser } if (auth === undefined) { diff --git a/packages/neo4j-driver-deno/lib/core/session.ts b/packages/neo4j-driver-deno/lib/core/session.ts index 338ac1e50..dacc3ec1d 100644 --- a/packages/neo4j-driver-deno/lib/core/session.ts +++ b/packages/neo4j-driver-deno/lib/core/session.ts @@ -124,7 +124,7 @@ class Session { notificationFilter?: NotificationFilter auth?: AuthToken log: Logger - homeDatabaseCallback?: (impersonatedUser: string, user: string, database: string) => void + homeDatabaseCallback?: (user: string, database: string) => void }) { this._mode = mode this._database = database diff --git a/packages/neo4j-driver/test/driver.test.js b/packages/neo4j-driver/test/driver.test.js index ede5fd1f4..d77f54c2f 100644 --- a/packages/neo4j-driver/test/driver.test.js +++ b/packages/neo4j-driver/test/driver.test.js @@ -189,9 +189,9 @@ describe('#unit driver', () => { `neo4j+ssc://${sharedNeo4j.hostnameWithBoltPort}`, sharedNeo4j.authToken ) - driver._homeDatabaseCallback(undefined, 'DEFAULT', 'neo4j') + driver._homeDatabaseCallback('DEFAULT', 'neo4j') expect(driver.homeDatabaseCache.get('DEFAULT')).toBe('neo4j') - driver._homeDatabaseCallback(undefined, 'basic:hi', 'neo4j') + driver._homeDatabaseCallback('basic:hi', 'neo4j') expect(driver.homeDatabaseCache.get('basic:hi')).toBe('neo4j') }) @@ -200,9 +200,9 @@ describe('#unit driver', () => { `neo4j+ssc://${sharedNeo4j.hostnameWithBoltPort}`, sharedNeo4j.authToken ) - driver._homeDatabaseCallback(undefined, 'DEFAULT', 'neo4j') + driver._homeDatabaseCallback('DEFAULT', 'neo4j') expect(driver.homeDatabaseCache.get('DEFAULT')).toBe('neo4j') - driver._homeDatabaseCallback(undefined, 'DEFAULT', 'neo5j') + driver._homeDatabaseCallback('DEFAULT', 'neo5j') expect(driver.homeDatabaseCache.get('DEFAULT')).toBe('neo5j') }) @@ -212,13 +212,13 @@ describe('#unit driver', () => { sharedNeo4j.authToken ) for (let i = 0; i < 9999; i++) { - driver._homeDatabaseCallback(undefined, i.toString(), 'neo4j') + driver._homeDatabaseCallback(i.toString(), 'neo4j') } await new Promise(resolve => setTimeout(resolve, 100)) - driver._homeDatabaseCallback(undefined, '5', 'neo4j') + driver._homeDatabaseCallback('5', 'neo4j') driver.homeDatabaseCache.get('55') for (let i = 9999; i < 10050; i++) { - driver._homeDatabaseCallback(undefined, i.toString(), 'neo4j') + driver._homeDatabaseCallback(i.toString(), 'neo4j') } expect(driver.homeDatabaseCache.get('1')).toEqual(undefined) expect(driver.homeDatabaseCache.get('901')).toEqual(undefined) From 718e4cd06f5f409ac4cd35a8e11ec46d925b94ff Mon Sep 17 00:00:00 2001 From: MaxAake <61233757+MaxAake@users.noreply.github.com> Date: Tue, 14 Jan 2025 09:02:38 +0100 Subject: [PATCH 69/84] fallback logic for missing ssr hint on new connection --- .../connection-provider-routing.js | 45 +++++++++++-------- .../src/connection/connection-channel.js | 12 ++--- .../connection-provider-routing.js | 45 +++++++++++-------- .../connection/connection-channel.js | 12 ++--- .../testkit-backend/src/feature/common.js | 1 + .../testkit-backend/src/request-handlers.js | 3 ++ 6 files changed, 64 insertions(+), 54 deletions(-) diff --git a/packages/bolt-connection/src/connection-provider/connection-provider-routing.js b/packages/bolt-connection/src/connection-provider/connection-provider-routing.js index acacfa696..19dd0cf69 100644 --- a/packages/bolt-connection/src/connection-provider/connection-provider-routing.js +++ b/packages/bolt-connection/src/connection-provider/connection-provider-routing.js @@ -143,8 +143,6 @@ export default class RoutingConnectionProvider extends PooledConnectionProvider * its arguments. */ async acquireConnection ({ accessMode, database, bookmarks, impersonatedUser, onDatabaseNameResolved, auth, homeDb } = {}) { - let name - let address const context = { database: database || DEFAULT_DB_NAME } const databaseSpecificErrorHandler = new ConnectionErrorHandler( @@ -153,29 +151,40 @@ export default class RoutingConnectionProvider extends PooledConnectionProvider (error, address) => this._handleWriteFailure(error, address, homeDb ?? context.database), (error, address, conn) => this._handleSecurityError(error, address, conn, context.database) ) - let currentRoutingTable + + let conn if (this.SSREnabled() && homeDb !== undefined && database === '') { - currentRoutingTable = this._routingTableRegistry.get( + const currentRoutingTable = this._routingTableRegistry.get( homeDb, () => new RoutingTable({ database: homeDb }) ) + if (currentRoutingTable && !currentRoutingTable.isStaleFor(accessMode)) { + conn = await this.getConnectionFromRoutingTable(currentRoutingTable, auth, accessMode, databaseSpecificErrorHandler) + if (this.SSREnabled()) { + return conn + } + conn.release() + } } - const routingTable = (currentRoutingTable && !currentRoutingTable.isStaleFor(accessMode)) - ? currentRoutingTable - : await this._freshRoutingTable({ - accessMode, - database: context.database, - bookmarks, - impersonatedUser, - auth, - onDatabaseNameResolved: (databaseName) => { - context.database = context.database || databaseName - if (onDatabaseNameResolved) { - onDatabaseNameResolved(databaseName) - } + const routingTable = await this._freshRoutingTable({ + accessMode, + database: context.database, + bookmarks, + impersonatedUser, + auth, + onDatabaseNameResolved: (databaseName) => { + context.database = context.database || databaseName + if (onDatabaseNameResolved) { + onDatabaseNameResolved(databaseName) } - }) + } + }) + return this.getConnectionFromRoutingTable(routingTable, auth, accessMode, databaseSpecificErrorHandler) + } + async getConnectionFromRoutingTable (routingTable, auth, accessMode, databaseSpecificErrorHandler) { + let name + let address // select a target server based on specified access mode if (accessMode === READ) { address = this._loadBalancingStrategy.selectReader(routingTable.readers) diff --git a/packages/bolt-connection/src/connection/connection-channel.js b/packages/bolt-connection/src/connection/connection-channel.js index 0fe0b078c..b57542462 100644 --- a/packages/bolt-connection/src/connection/connection-channel.js +++ b/packages/bolt-connection/src/connection/connection-channel.js @@ -339,15 +339,9 @@ export default class ChannelConnection extends Connection { if (telemetryEnabledHint === true) { this._telemetryDisabledConnection = false } - - const SSREnabledHint = metadata.hints['ssr.enabled'] - if (SSREnabledHint === true) { - this.serversideRouting = true - } else { - this.serversideRouting = false - } - this._ssrCallback(this.serversideRouting, 'OPEN') + this.SSREnabledHint = metadata.hints['ssr.enabled'] } + this._ssrCallback(this.SSREnabledHint ?? false, 'OPEN') } resolve(self) } @@ -554,7 +548,7 @@ export default class ChannelConnection extends Connection { * @returns {Promise} - A promise that will be resolved when the underlying channel is closed. */ async close () { - this._ssrCallback(this.serversideRouting, 'CLOSE') + this._ssrCallback(this.SSREnabledHint ?? false, 'CLOSE') if (this._log.isDebugEnabled()) { this._log.debug('closing') } diff --git a/packages/neo4j-driver-deno/lib/bolt-connection/connection-provider/connection-provider-routing.js b/packages/neo4j-driver-deno/lib/bolt-connection/connection-provider/connection-provider-routing.js index 0283fd0d4..2813b9240 100644 --- a/packages/neo4j-driver-deno/lib/bolt-connection/connection-provider/connection-provider-routing.js +++ b/packages/neo4j-driver-deno/lib/bolt-connection/connection-provider/connection-provider-routing.js @@ -143,8 +143,6 @@ export default class RoutingConnectionProvider extends PooledConnectionProvider * its arguments. */ async acquireConnection ({ accessMode, database, bookmarks, impersonatedUser, onDatabaseNameResolved, auth, homeDb } = {}) { - let name - let address const context = { database: database || DEFAULT_DB_NAME } const databaseSpecificErrorHandler = new ConnectionErrorHandler( @@ -153,29 +151,40 @@ export default class RoutingConnectionProvider extends PooledConnectionProvider (error, address) => this._handleWriteFailure(error, address, homeDb ?? context.database), (error, address, conn) => this._handleSecurityError(error, address, conn, context.database) ) - let currentRoutingTable + + let conn if (this.SSREnabled() && homeDb !== undefined && database === '') { - currentRoutingTable = this._routingTableRegistry.get( + const currentRoutingTable = this._routingTableRegistry.get( homeDb, () => new RoutingTable({ database: homeDb }) ) + if (currentRoutingTable && !currentRoutingTable.isStaleFor(accessMode)) { + conn = await this.getConnectionFromRoutingTable(currentRoutingTable, auth, accessMode, databaseSpecificErrorHandler) + if (this.SSREnabled()) { + return conn + } + conn.release() + } } - const routingTable = (currentRoutingTable && !currentRoutingTable.isStaleFor(accessMode)) - ? currentRoutingTable - : await this._freshRoutingTable({ - accessMode, - database: context.database, - bookmarks, - impersonatedUser, - auth, - onDatabaseNameResolved: (databaseName) => { - context.database = context.database || databaseName - if (onDatabaseNameResolved) { - onDatabaseNameResolved(databaseName) - } + const routingTable = await this._freshRoutingTable({ + accessMode, + database: context.database, + bookmarks, + impersonatedUser, + auth, + onDatabaseNameResolved: (databaseName) => { + context.database = context.database || databaseName + if (onDatabaseNameResolved) { + onDatabaseNameResolved(databaseName) } - }) + } + }) + return this.getConnectionFromRoutingTable(routingTable, auth, accessMode, databaseSpecificErrorHandler) + } + async getConnectionFromRoutingTable (routingTable, auth, accessMode, databaseSpecificErrorHandler) { + let name + let address // select a target server based on specified access mode if (accessMode === READ) { address = this._loadBalancingStrategy.selectReader(routingTable.readers) diff --git a/packages/neo4j-driver-deno/lib/bolt-connection/connection/connection-channel.js b/packages/neo4j-driver-deno/lib/bolt-connection/connection/connection-channel.js index 2452e2147..7fcf4689c 100644 --- a/packages/neo4j-driver-deno/lib/bolt-connection/connection/connection-channel.js +++ b/packages/neo4j-driver-deno/lib/bolt-connection/connection/connection-channel.js @@ -339,15 +339,9 @@ export default class ChannelConnection extends Connection { if (telemetryEnabledHint === true) { this._telemetryDisabledConnection = false } - - const SSREnabledHint = metadata.hints['ssr.enabled'] - if (SSREnabledHint === true) { - this.serversideRouting = true - } else { - this.serversideRouting = false - } - this._ssrCallback(this.serversideRouting, 'OPEN') + this.SSREnabledHint = metadata.hints['ssr.enabled'] } + this._ssrCallback(this.SSREnabledHint ?? false, 'OPEN') } resolve(self) } @@ -554,7 +548,7 @@ export default class ChannelConnection extends Connection { * @returns {Promise} - A promise that will be resolved when the underlying channel is closed. */ async close () { - this._ssrCallback(this.serversideRouting, 'CLOSE') + this._ssrCallback(this.SSREnabledHint ?? false, 'CLOSE') if (this._log.isDebugEnabled()) { this._log.debug('closing') } diff --git a/packages/testkit-backend/src/feature/common.js b/packages/testkit-backend/src/feature/common.js index d6343e221..27eeb44fc 100644 --- a/packages/testkit-backend/src/feature/common.js +++ b/packages/testkit-backend/src/feature/common.js @@ -32,6 +32,7 @@ const features = [ 'Feature:API:ConnectionAcquisitionTimeout', 'Feature:API:Driver.ExecuteQuery', 'Feature:API:Driver.ExecuteQuery:WithAuth', + 'Feature:API:Driver:MaxConnectionLifetime', 'Feature:API:Driver:NotificationsConfig', 'Feature:API:Driver:GetServerInfo', 'Feature:API:Driver.SupportsSessionAuth', diff --git a/packages/testkit-backend/src/request-handlers.js b/packages/testkit-backend/src/request-handlers.js index 4c3a32423..304b1b7dd 100644 --- a/packages/testkit-backend/src/request-handlers.js +++ b/packages/testkit-backend/src/request-handlers.js @@ -67,6 +67,9 @@ export function NewDriver ({ neo4j }, context, data, wire) { if ('connectionTimeoutMs' in data) { config.connectionTimeout = data.connectionTimeoutMs } + if ('maxConnectionLifetimeMs' in data) { + config.maxConnectionLifetime = data.maxConnectionLifetimeMs + } if ('fetchSize' in data) { config.fetchSize = data.fetchSize } From 09f5377d27a5c60b295931e6c54803d1d32819e1 Mon Sep 17 00:00:00 2001 From: MaxAake <61233757+MaxAake@users.noreply.github.com> Date: Tue, 14 Jan 2025 10:50:13 +0100 Subject: [PATCH 70/84] skips timeout test and adds HomeDatabaseCache flag --- packages/testkit-backend/src/feature/common.js | 1 + packages/testkit-backend/src/skipped-tests/common.js | 4 ++++ testkit/testkit.json | 2 +- 3 files changed, 6 insertions(+), 1 deletion(-) diff --git a/packages/testkit-backend/src/feature/common.js b/packages/testkit-backend/src/feature/common.js index 27eeb44fc..19253b8e5 100644 --- a/packages/testkit-backend/src/feature/common.js +++ b/packages/testkit-backend/src/feature/common.js @@ -48,6 +48,7 @@ const features = [ 'Optimization:MinimalBookmarksSet', 'Optimization:MinimalResets', 'Optimization:AuthPipelining', + 'Optimization:HomeDatabaseCache', 'Optimization:HomeDbCacheBasicPrincipalIsImpersonatedUser', 'Detail:NumberIsNumber' ] diff --git a/packages/testkit-backend/src/skipped-tests/common.js b/packages/testkit-backend/src/skipped-tests/common.js index 2f1e6f6ee..0f0882de7 100644 --- a/packages/testkit-backend/src/skipped-tests/common.js +++ b/packages/testkit-backend/src/skipped-tests/common.js @@ -197,6 +197,10 @@ const skippedTests = [ skip( 'Needs trying all DNS resolved addresses for hosts in the routing table', ifEndsWith('.test_ipv6_read').and(startsWith('stub.routing.test_routing_')) + ), + skip( + 'Driver has separate timeouts for every connection it attempts to open. This will change in 6.0', + ifEquals('stub.homedb.test_homedb.TestHomeDbMixedCluster.test_connection_acquisition_timeout_during_fallback') ) ] diff --git a/testkit/testkit.json b/testkit/testkit.json index 931900356..bb4f7cbb0 100644 --- a/testkit/testkit.json +++ b/testkit/testkit.json @@ -1,6 +1,6 @@ { "testkit": { "uri": "https://github.com/neo4j-drivers/testkit.git", - "ref": "5.0" + "ref": "home-db-optimization-more-tests" } } From 6192d6e1615c7d363cfb5a2ece9c381445f6ec30 Mon Sep 17 00:00:00 2001 From: MaxAake <61233757+MaxAake@users.noreply.github.com> Date: Thu, 16 Jan 2025 13:47:15 +0100 Subject: [PATCH 71/84] Update testkit.json --- testkit/testkit.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/testkit/testkit.json b/testkit/testkit.json index bb4f7cbb0..931900356 100644 --- a/testkit/testkit.json +++ b/testkit/testkit.json @@ -1,6 +1,6 @@ { "testkit": { "uri": "https://github.com/neo4j-drivers/testkit.git", - "ref": "home-db-optimization-more-tests" + "ref": "5.0" } } From 7148ddafebc2e26c19dc34ca0d7db4f659aea4bf Mon Sep 17 00:00:00 2001 From: MaxAake <61233757+MaxAake@users.noreply.github.com> Date: Mon, 20 Jan 2025 11:12:11 +0100 Subject: [PATCH 72/84] remove some unnecessary checks --- packages/core/src/auth.ts | 4 ++-- packages/core/src/internal/auth-util.ts | 7 +++---- packages/neo4j-driver-deno/lib/core/auth.ts | 4 ++-- packages/neo4j-driver-deno/lib/core/internal/auth-util.ts | 7 +++---- 4 files changed, 10 insertions(+), 12 deletions(-) diff --git a/packages/core/src/auth.ts b/packages/core/src/auth.ts index d77ddcf6a..6237a1c0e 100644 --- a/packages/core/src/auth.ts +++ b/packages/core/src/auth.ts @@ -61,7 +61,7 @@ const auth = { credentials: string, realm: string, scheme: string, - parameters?: any + parameters?: object ) => { const output: any = { scheme, @@ -73,7 +73,7 @@ const auth = { if (isNotEmpty(realm)) { output.realm = realm } - if (isNotEmpty(parameters) && parameters !== undefined) { + if (isNotEmpty(parameters)) { output.parameters = parameters } return output diff --git a/packages/core/src/internal/auth-util.ts b/packages/core/src/internal/auth-util.ts index c1b0ebe85..83a17b4f5 100644 --- a/packages/core/src/internal/auth-util.ts +++ b/packages/core/src/internal/auth-util.ts @@ -36,10 +36,9 @@ export function cacheKey (auth?: AuthToken, impersonatedUser?: string): string { let ordered = '' if (auth.parameters !== undefined) { Object.keys(auth.parameters).sort().forEach((key: string) => { - if (auth.parameters !== undefined) { - // eslint-disable-next-line @typescript-eslint/restrict-template-expressions - ordered += `${key}:${auth.parameters[key]}` - } + // @ts-expect-error: undefined check is already made + // eslint-disable-next-line @typescript-eslint/restrict-template-expressions + ordered += `${key}:${auth.parameters[key]}` }) } const credentialString = (auth.credentials !== undefined && auth.credentials !== '') ? 'credentials:' + auth.credentials : '' diff --git a/packages/neo4j-driver-deno/lib/core/auth.ts b/packages/neo4j-driver-deno/lib/core/auth.ts index d77ddcf6a..6237a1c0e 100644 --- a/packages/neo4j-driver-deno/lib/core/auth.ts +++ b/packages/neo4j-driver-deno/lib/core/auth.ts @@ -61,7 +61,7 @@ const auth = { credentials: string, realm: string, scheme: string, - parameters?: any + parameters?: object ) => { const output: any = { scheme, @@ -73,7 +73,7 @@ const auth = { if (isNotEmpty(realm)) { output.realm = realm } - if (isNotEmpty(parameters) && parameters !== undefined) { + if (isNotEmpty(parameters)) { output.parameters = parameters } return output diff --git a/packages/neo4j-driver-deno/lib/core/internal/auth-util.ts b/packages/neo4j-driver-deno/lib/core/internal/auth-util.ts index e94561eb0..da0bd3be5 100644 --- a/packages/neo4j-driver-deno/lib/core/internal/auth-util.ts +++ b/packages/neo4j-driver-deno/lib/core/internal/auth-util.ts @@ -36,10 +36,9 @@ export function cacheKey (auth?: AuthToken, impersonatedUser?: string): string { let ordered = '' if (auth.parameters !== undefined) { Object.keys(auth.parameters).sort().forEach((key: string) => { - if (auth.parameters !== undefined) { - // eslint-disable-next-line @typescript-eslint/restrict-template-expressions - ordered += `${key}:${auth.parameters[key]}` - } + // @ts-ignore: undefined check is already made + // eslint-disable-next-line @typescript-eslint/restrict-template-expressions + ordered += `${key}:${auth.parameters[key]}` }) } const credentialString = (auth.credentials !== undefined && auth.credentials !== '') ? 'credentials:' + auth.credentials : '' From 0a96302481b0f700884e980545f011a7b29ab187 Mon Sep 17 00:00:00 2001 From: MaxAake <61233757+MaxAake@users.noreply.github.com> Date: Mon, 20 Jan 2025 11:22:37 +0100 Subject: [PATCH 73/84] deno --- packages/neo4j-driver-deno/lib/core/internal/auth-util.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/neo4j-driver-deno/lib/core/internal/auth-util.ts b/packages/neo4j-driver-deno/lib/core/internal/auth-util.ts index da0bd3be5..9b09c0731 100644 --- a/packages/neo4j-driver-deno/lib/core/internal/auth-util.ts +++ b/packages/neo4j-driver-deno/lib/core/internal/auth-util.ts @@ -36,8 +36,8 @@ export function cacheKey (auth?: AuthToken, impersonatedUser?: string): string { let ordered = '' if (auth.parameters !== undefined) { Object.keys(auth.parameters).sort().forEach((key: string) => { - // @ts-ignore: undefined check is already made - // eslint-disable-next-line @typescript-eslint/restrict-template-expressions + // @ts-expect-error: undefined check is already made + // eslint-disable-next-line @typescript-eslint/restrict-template-expressions ordered += `${key}:${auth.parameters[key]}` }) } From 254ae4d0f86871094d670ba61edcb43cc71900b6 Mon Sep 17 00:00:00 2001 From: MaxAake <61233757+MaxAake@users.noreply.github.com> Date: Mon, 20 Jan 2025 13:46:59 +0100 Subject: [PATCH 74/84] correct bolt agent numbering --- packages/bolt-connection/test/bolt/bolt-protocol-v5x8.test.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/bolt-connection/test/bolt/bolt-protocol-v5x8.test.js b/packages/bolt-connection/test/bolt/bolt-protocol-v5x8.test.js index 49eb7cf8f..a02b0e75d 100644 --- a/packages/bolt-connection/test/bolt/bolt-protocol-v5x8.test.js +++ b/packages/bolt-connection/test/bolt/bolt-protocol-v5x8.test.js @@ -278,7 +278,7 @@ describe('#unit BoltProtocolV5x8', () => { const clientName = 'js-driver/1.2.3' const boltAgent = { - product: 'neo4j-javascript/5.8', + product: 'neo4j-javascript/5.28', platform: 'netbsd 1.1.1; Some arch', languageDetails: 'Node/16.0.1 (v8 1.7.0)' } From 0e47227b4e6ed306f656f6fd0f566a20975993ad Mon Sep 17 00:00:00 2001 From: MaxAake <61233757+MaxAake@users.noreply.github.com> Date: Mon, 20 Jan 2025 13:52:48 +0100 Subject: [PATCH 75/84] Update bolt-protocol-v5x8.test.js --- packages/bolt-connection/test/bolt/bolt-protocol-v5x8.test.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/bolt-connection/test/bolt/bolt-protocol-v5x8.test.js b/packages/bolt-connection/test/bolt/bolt-protocol-v5x8.test.js index a02b0e75d..0ba26e7a3 100644 --- a/packages/bolt-connection/test/bolt/bolt-protocol-v5x8.test.js +++ b/packages/bolt-connection/test/bolt/bolt-protocol-v5x8.test.js @@ -320,7 +320,7 @@ describe('#unit BoltProtocolV5x8', () => { utils.spyProtocolWrite(protocol) const boltAgent = { - product: 'neo4j-javascript/5.8', + product: 'neo4j-javascript/5.28', platform: 'netbsd 1.1.1; Some arch', languageDetails: 'Node/16.0.1 (v8 1.7.0)' } From f7de064973dc481b4e062e56b29eddb70728298f Mon Sep 17 00:00:00 2001 From: MaxAake <61233757+MaxAake@users.noreply.github.com> Date: Wed, 22 Jan 2025 14:23:21 +0100 Subject: [PATCH 76/84] minor fixes and collision free cachekeys --- .../connection-provider-routing.js | 4 +- packages/core/src/internal/auth-util.ts | 37 ++++++++++-------- packages/core/src/session.ts | 8 ++-- packages/core/test/internal/auth-util.test.ts | 6 +-- .../connection-provider-routing.js | 4 +- .../lib/core/internal/auth-util.ts | 39 +++++++++++-------- .../neo4j-driver-deno/lib/core/session.ts | 8 ++-- 7 files changed, 58 insertions(+), 48 deletions(-) diff --git a/packages/bolt-connection/src/connection-provider/connection-provider-routing.js b/packages/bolt-connection/src/connection-provider/connection-provider-routing.js index 19dd0cf69..38243b868 100644 --- a/packages/bolt-connection/src/connection-provider/connection-provider-routing.js +++ b/packages/bolt-connection/src/connection-provider/connection-provider-routing.js @@ -687,13 +687,13 @@ export default class RoutingConnectionProvider extends PooledConnectionProvider if (action === 'OPEN') { if (isEnabled === true) { this._withSSR = this._withSSR + 1 - } else if (isEnabled === false) { + } else { this._withoutSSR = this._withoutSSR + 1 } } else if (action === 'CLOSE') { if (isEnabled === true) { this._withSSR = this._withSSR - 1 - } else if (isEnabled === false) { + } else { this._withoutSSR = this._withoutSSR - 1 } } else { diff --git a/packages/core/src/internal/auth-util.ts b/packages/core/src/internal/auth-util.ts index 83a17b4f5..4e33abacd 100644 --- a/packages/core/src/internal/auth-util.ts +++ b/packages/core/src/internal/auth-util.ts @@ -18,7 +18,7 @@ import { AuthToken } from '../types' export function cacheKey (auth?: AuthToken, impersonatedUser?: string): string { - if (impersonatedUser !== undefined && impersonatedUser !== null) { + if (impersonatedUser != null) { return 'basic:' + impersonatedUser } if (auth === undefined) { @@ -26,23 +26,28 @@ export function cacheKey (auth?: AuthToken, impersonatedUser?: string): string { } if (auth.scheme === 'basic') { return 'basic:' + (auth.principal ?? '') - } else if (auth.scheme === 'kerberos') { + } + if (auth.scheme === 'kerberos') { return 'kerberos:' + auth.credentials - } else if (auth.scheme === 'bearer') { + } + if (auth.scheme === 'bearer') { return 'bearer:' + auth.credentials - } else if (auth.scheme === 'none') { + } + if (auth.scheme === 'none') { return 'none' - } else { - let ordered = '' - if (auth.parameters !== undefined) { - Object.keys(auth.parameters).sort().forEach((key: string) => { - // @ts-expect-error: undefined check is already made - // eslint-disable-next-line @typescript-eslint/restrict-template-expressions - ordered += `${key}:${auth.parameters[key]}` - }) - } - const credentialString = (auth.credentials !== undefined && auth.credentials !== '') ? 'credentials:' + auth.credentials : '' - const realmString = (auth.realm !== undefined && auth.realm !== '') ? 'realm:' + auth.realm : '' - return 'scheme:' + auth.scheme + 'principal:' + (auth.principal ?? '') + credentialString + realmString + 'parameters:' + ordered } + return JSON.stringify(orderedObject(auth)) +} + +function orderedObject (obj: object): any[] { + let ordered: any[] = [] + Object.keys(obj).sort().forEach((key: string) => { + // @ts-expect-error: undefined check is already made + let entry: any = obj[key] + if (typeof entry === 'object' && !(entry instanceof Array)) { + entry = orderedObject(entry) + } + ordered = ordered.concat([key, entry]) + }) + return ordered } diff --git a/packages/core/src/session.ts b/packages/core/src/session.ts index 2d8179e78..bd3e01082 100644 --- a/packages/core/src/session.ts +++ b/packages/core/src/session.ts @@ -95,7 +95,7 @@ class Session { * @param {NotificationFilter} args.notificationFilter - The notification filter used for this session. * @param {AuthToken} args.auth - the target auth for the to-be-acquired connection * @param {Logger} args.log - the logger used for logs in this session. - * @param {(impersonatedUser:string, user:string database:string) => void} args.homeDatabaseCallback - callback used to update the home database cache + * @param {(user:string, database:string) => void} args.homeDatabaseCallback - callback used to update the home database cache */ constructor ({ mode, @@ -526,14 +526,14 @@ class Session { if (this._isRoutingSession) { this._databaseGuess = database if (!this._databaseNameResolved) { - if (this._homeDatabaseCallback != null) { - this._homeDatabaseCallback(cacheKey(this._auth, this._impersonatedUser), database) - } const normalizedDatabase = database ?? '' this._database = normalizedDatabase this._readConnectionHolder.setDatabase(normalizedDatabase) this._writeConnectionHolder.setDatabase(normalizedDatabase) this._databaseNameResolved = true + if (this._homeDatabaseCallback != null) { + this._homeDatabaseCallback(cacheKey(this._auth, this._impersonatedUser), database) + } } } } diff --git a/packages/core/test/internal/auth-util.test.ts b/packages/core/test/internal/auth-util.test.ts index f06cc48ef..e2b592ada 100644 --- a/packages/core/test/internal/auth-util.test.ts +++ b/packages/core/test/internal/auth-util.test.ts @@ -23,9 +23,9 @@ describe('#unit cacheKey()', () => { ['basic', auth.basic('hello', 'basic'), 'basic:hello'], ['kerberos', auth.kerberos('kerberosString'), 'kerberos:kerberosString'], ['bearer', auth.bearer('bearerToken'), 'bearer:bearerToken'], - ['custom without parameters', auth.custom('hello', 'custom', 'realm', 'scheme'), 'scheme:schemeprincipal:hellocredentials:customrealm:realmparameters:'], - ['custom with parameters', auth.custom('hello', 'custom', 'realm', 'scheme', { array: [1, 2, 3] }), 'scheme:schemeprincipal:hellocredentials:customrealm:realmparameters:array:1,2,3'] - ])('should create correct cacheKey for % auth toke', (_, token, expectedKey) => { + ['custom without parameters', auth.custom('hello', 'custom', 'realm', 'scheme'), '["credentials","custom","principal","hello","realm","realm","scheme","scheme"]'], + ['custom with parameters', auth.custom('hello', 'custom', 'realm', 'scheme', { array: [1, 2, 3] }), '["credentials","custom","parameters",["array",[1,2,3]],"principal","hello","realm","realm","scheme","scheme"]'] + ])('should create correct cacheKey for % auth token', (_, token, expectedKey) => { expect(cacheKey(token)).toEqual(expectedKey) }) }) diff --git a/packages/neo4j-driver-deno/lib/bolt-connection/connection-provider/connection-provider-routing.js b/packages/neo4j-driver-deno/lib/bolt-connection/connection-provider/connection-provider-routing.js index 2813b9240..46014726f 100644 --- a/packages/neo4j-driver-deno/lib/bolt-connection/connection-provider/connection-provider-routing.js +++ b/packages/neo4j-driver-deno/lib/bolt-connection/connection-provider/connection-provider-routing.js @@ -687,13 +687,13 @@ export default class RoutingConnectionProvider extends PooledConnectionProvider if (action === 'OPEN') { if (isEnabled === true) { this._withSSR = this._withSSR + 1 - } else if (isEnabled === false) { + } else { this._withoutSSR = this._withoutSSR + 1 } } else if (action === 'CLOSE') { if (isEnabled === true) { this._withSSR = this._withSSR - 1 - } else if (isEnabled === false) { + } else { this._withoutSSR = this._withoutSSR - 1 } } else { diff --git a/packages/neo4j-driver-deno/lib/core/internal/auth-util.ts b/packages/neo4j-driver-deno/lib/core/internal/auth-util.ts index 9b09c0731..d50439e31 100644 --- a/packages/neo4j-driver-deno/lib/core/internal/auth-util.ts +++ b/packages/neo4j-driver-deno/lib/core/internal/auth-util.ts @@ -18,7 +18,7 @@ import { AuthToken } from '../types.ts' export function cacheKey (auth?: AuthToken, impersonatedUser?: string): string { - if (impersonatedUser !== undefined && impersonatedUser !== null) { + if (impersonatedUser != null) { return 'basic:' + impersonatedUser } if (auth === undefined) { @@ -26,23 +26,28 @@ export function cacheKey (auth?: AuthToken, impersonatedUser?: string): string { } if (auth.scheme === 'basic') { return 'basic:' + (auth.principal ?? '') - } else if (auth.scheme === 'kerberos') { + } + if (auth.scheme === 'kerberos') { return 'kerberos:' + auth.credentials - } else if (auth.scheme === 'bearer') { - return 'bearer:' + auth.credentials - } else if (auth.scheme === 'none') { + } + if (auth.scheme === 'bearer') { + return 'bearer:' + auth.credentials + } + if (auth.scheme === 'none') { return 'none' - } else { - let ordered = '' - if (auth.parameters !== undefined) { - Object.keys(auth.parameters).sort().forEach((key: string) => { - // @ts-expect-error: undefined check is already made - // eslint-disable-next-line @typescript-eslint/restrict-template-expressions - ordered += `${key}:${auth.parameters[key]}` - }) - } - const credentialString = (auth.credentials !== undefined && auth.credentials !== '') ? 'credentials:' + auth.credentials : '' - const realmString = (auth.realm !== undefined && auth.realm !== '') ? 'realm:' + auth.realm : '' - return 'scheme:' + auth.scheme + 'principal:' + (auth.principal ?? '') + credentialString + realmString + 'parameters:' + ordered } + return JSON.stringify(orderedObject(auth)) } + +function orderedObject(obj: object): any[] { + let ordered: any[] = [] + Object.keys(obj).sort().forEach((key: string) => { + // @ts-expect-error: undefined check is already made + let entry:any = obj[key] + if(typeof entry === "object" && !(entry instanceof Array)) { + entry = orderedObject(entry) + } + ordered = ordered.concat([key, entry]) + }) + return ordered +} \ No newline at end of file diff --git a/packages/neo4j-driver-deno/lib/core/session.ts b/packages/neo4j-driver-deno/lib/core/session.ts index dacc3ec1d..a2055a954 100644 --- a/packages/neo4j-driver-deno/lib/core/session.ts +++ b/packages/neo4j-driver-deno/lib/core/session.ts @@ -95,7 +95,7 @@ class Session { * @param {NotificationFilter} args.notificationFilter - The notification filter used for this session. * @param {AuthToken} args.auth - the target auth for the to-be-acquired connection * @param {Logger} args.log - the logger used for logs in this session. - * @param {(impersonatedUser:string, user:string database:string) => void} args.homeDatabaseCallback - callback used to update the home database cache + * @param {(user:string, database:string) => void} args.homeDatabaseCallback - callback used to update the home database cache */ constructor ({ mode, @@ -526,14 +526,14 @@ class Session { if (this._isRoutingSession) { this._databaseGuess = database if (!this._databaseNameResolved) { - if (this._homeDatabaseCallback != null) { - this._homeDatabaseCallback(cacheKey(this._auth, this._impersonatedUser), database) - } const normalizedDatabase = database ?? '' this._database = normalizedDatabase this._readConnectionHolder.setDatabase(normalizedDatabase) this._writeConnectionHolder.setDatabase(normalizedDatabase) this._databaseNameResolved = true + if (this._homeDatabaseCallback != null) { + this._homeDatabaseCallback(cacheKey(this._auth, this._impersonatedUser), database) + } } } } From 7c0eee0dd6744dd1c043f382cf60e315a3d5b604 Mon Sep 17 00:00:00 2001 From: MaxAake <61233757+MaxAake@users.noreply.github.com> Date: Wed, 22 Jan 2025 14:38:12 +0100 Subject: [PATCH 77/84] deno --- .../neo4j-driver-deno/lib/core/internal/auth-util.ts | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/packages/neo4j-driver-deno/lib/core/internal/auth-util.ts b/packages/neo4j-driver-deno/lib/core/internal/auth-util.ts index d50439e31..e4dca157d 100644 --- a/packages/neo4j-driver-deno/lib/core/internal/auth-util.ts +++ b/packages/neo4j-driver-deno/lib/core/internal/auth-util.ts @@ -31,7 +31,7 @@ export function cacheKey (auth?: AuthToken, impersonatedUser?: string): string { return 'kerberos:' + auth.credentials } if (auth.scheme === 'bearer') { - return 'bearer:' + auth.credentials + return 'bearer:' + auth.credentials } if (auth.scheme === 'none') { return 'none' @@ -39,15 +39,15 @@ export function cacheKey (auth?: AuthToken, impersonatedUser?: string): string { return JSON.stringify(orderedObject(auth)) } -function orderedObject(obj: object): any[] { +function orderedObject (obj: object): any[] { let ordered: any[] = [] Object.keys(obj).sort().forEach((key: string) => { // @ts-expect-error: undefined check is already made - let entry:any = obj[key] - if(typeof entry === "object" && !(entry instanceof Array)) { + let entry: any = obj[key] + if (typeof entry === 'object' && !(entry instanceof Array)) { entry = orderedObject(entry) } ordered = ordered.concat([key, entry]) }) return ordered -} \ No newline at end of file +} From 22b4a75df7c69092fa73ea16df18525d0e5c45cc Mon Sep 17 00:00:00 2001 From: MaxAake <61233757+MaxAake@users.noreply.github.com> Date: Wed, 22 Jan 2025 16:25:28 +0100 Subject: [PATCH 78/84] better solution --- packages/core/src/internal/auth-util.ts | 7 ++++--- packages/core/test/internal/auth-util.test.ts | 4 ++-- packages/neo4j-driver-deno/lib/core/internal/auth-util.ts | 7 ++++--- 3 files changed, 10 insertions(+), 8 deletions(-) diff --git a/packages/core/src/internal/auth-util.ts b/packages/core/src/internal/auth-util.ts index 4e33abacd..3d43165ba 100644 --- a/packages/core/src/internal/auth-util.ts +++ b/packages/core/src/internal/auth-util.ts @@ -39,15 +39,16 @@ export function cacheKey (auth?: AuthToken, impersonatedUser?: string): string { return JSON.stringify(orderedObject(auth)) } -function orderedObject (obj: object): any[] { - let ordered: any[] = [] +function orderedObject (obj: object): object { + const ordered = {} Object.keys(obj).sort().forEach((key: string) => { // @ts-expect-error: undefined check is already made let entry: any = obj[key] if (typeof entry === 'object' && !(entry instanceof Array)) { entry = orderedObject(entry) } - ordered = ordered.concat([key, entry]) + // @ts-expect-error: undefined check is already made + ordered[key] = entry }) return ordered } diff --git a/packages/core/test/internal/auth-util.test.ts b/packages/core/test/internal/auth-util.test.ts index e2b592ada..dfa806cee 100644 --- a/packages/core/test/internal/auth-util.test.ts +++ b/packages/core/test/internal/auth-util.test.ts @@ -23,8 +23,8 @@ describe('#unit cacheKey()', () => { ['basic', auth.basic('hello', 'basic'), 'basic:hello'], ['kerberos', auth.kerberos('kerberosString'), 'kerberos:kerberosString'], ['bearer', auth.bearer('bearerToken'), 'bearer:bearerToken'], - ['custom without parameters', auth.custom('hello', 'custom', 'realm', 'scheme'), '["credentials","custom","principal","hello","realm","realm","scheme","scheme"]'], - ['custom with parameters', auth.custom('hello', 'custom', 'realm', 'scheme', { array: [1, 2, 3] }), '["credentials","custom","parameters",["array",[1,2,3]],"principal","hello","realm","realm","scheme","scheme"]'] + ['custom without parameters', auth.custom('hello', 'custom', 'realm', 'scheme'), '{"credentials":"custom","principal":"hello","realm":"realm","scheme":"scheme"}'], + ['custom with parameters', auth.custom('hello', 'custom', 'realm', 'scheme', { array: [1, 2, 3] }), '{"credentials":"custom","parameters":{"array":[1,2,3]},"principal":"hello","realm":"realm","scheme":"scheme"}'] ])('should create correct cacheKey for % auth token', (_, token, expectedKey) => { expect(cacheKey(token)).toEqual(expectedKey) }) diff --git a/packages/neo4j-driver-deno/lib/core/internal/auth-util.ts b/packages/neo4j-driver-deno/lib/core/internal/auth-util.ts index e4dca157d..3fc84a928 100644 --- a/packages/neo4j-driver-deno/lib/core/internal/auth-util.ts +++ b/packages/neo4j-driver-deno/lib/core/internal/auth-util.ts @@ -39,15 +39,16 @@ export function cacheKey (auth?: AuthToken, impersonatedUser?: string): string { return JSON.stringify(orderedObject(auth)) } -function orderedObject (obj: object): any[] { - let ordered: any[] = [] +function orderedObject (obj: object): object { + let ordered = {} Object.keys(obj).sort().forEach((key: string) => { // @ts-expect-error: undefined check is already made let entry: any = obj[key] if (typeof entry === 'object' && !(entry instanceof Array)) { entry = orderedObject(entry) } - ordered = ordered.concat([key, entry]) + // @ts-expect-error: undefined check is already made + ordered[key] = entry }) return ordered } From ef3add561037784836da23d6f4cf3959326c0c9a Mon Sep 17 00:00:00 2001 From: MaxAake <61233757+MaxAake@users.noreply.github.com> Date: Wed, 22 Jan 2025 16:26:05 +0100 Subject: [PATCH 79/84] deno --- packages/neo4j-driver-deno/lib/core/internal/auth-util.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/neo4j-driver-deno/lib/core/internal/auth-util.ts b/packages/neo4j-driver-deno/lib/core/internal/auth-util.ts index 3fc84a928..e7937f63c 100644 --- a/packages/neo4j-driver-deno/lib/core/internal/auth-util.ts +++ b/packages/neo4j-driver-deno/lib/core/internal/auth-util.ts @@ -40,7 +40,7 @@ export function cacheKey (auth?: AuthToken, impersonatedUser?: string): string { } function orderedObject (obj: object): object { - let ordered = {} + const ordered = {} Object.keys(obj).sort().forEach((key: string) => { // @ts-expect-error: undefined check is already made let entry: any = obj[key] From 5e66a899ba6c59aae6c1d1da9ef8721004338b23 Mon Sep 17 00:00:00 2001 From: MaxAake <61233757+MaxAake@users.noreply.github.com> Date: Thu, 23 Jan 2025 10:13:23 +0100 Subject: [PATCH 80/84] switch to prewritten stringify --- packages/core/src/internal/auth-util.ts | 3 ++- packages/neo4j-driver-deno/lib/core/internal/auth-util.ts | 3 ++- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/packages/core/src/internal/auth-util.ts b/packages/core/src/internal/auth-util.ts index 3d43165ba..28eacc8b1 100644 --- a/packages/core/src/internal/auth-util.ts +++ b/packages/core/src/internal/auth-util.ts @@ -15,6 +15,7 @@ * limitations under the License. */ +import { stringify } from '../json' import { AuthToken } from '../types' export function cacheKey (auth?: AuthToken, impersonatedUser?: string): string { @@ -36,7 +37,7 @@ export function cacheKey (auth?: AuthToken, impersonatedUser?: string): string { if (auth.scheme === 'none') { return 'none' } - return JSON.stringify(orderedObject(auth)) + return stringify(orderedObject(auth)) } function orderedObject (obj: object): object { diff --git a/packages/neo4j-driver-deno/lib/core/internal/auth-util.ts b/packages/neo4j-driver-deno/lib/core/internal/auth-util.ts index e7937f63c..b66240e2a 100644 --- a/packages/neo4j-driver-deno/lib/core/internal/auth-util.ts +++ b/packages/neo4j-driver-deno/lib/core/internal/auth-util.ts @@ -15,6 +15,7 @@ * limitations under the License. */ +import { stringify } from '../json.ts' import { AuthToken } from '../types.ts' export function cacheKey (auth?: AuthToken, impersonatedUser?: string): string { @@ -36,7 +37,7 @@ export function cacheKey (auth?: AuthToken, impersonatedUser?: string): string { if (auth.scheme === 'none') { return 'none' } - return JSON.stringify(orderedObject(auth)) + return stringify(orderedObject(auth)) } function orderedObject (obj: object): object { From 4c3a739cefa78584a0c378d13eb115261c1b2055 Mon Sep 17 00:00:00 2001 From: MaxAake <61233757+MaxAake@users.noreply.github.com> Date: Thu, 23 Jan 2025 11:33:16 +0100 Subject: [PATCH 81/84] correct expect-error-message --- packages/core/src/internal/auth-util.ts | 4 ++-- packages/neo4j-driver-deno/lib/core/internal/auth-util.ts | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/packages/core/src/internal/auth-util.ts b/packages/core/src/internal/auth-util.ts index 28eacc8b1..d2a9461cf 100644 --- a/packages/core/src/internal/auth-util.ts +++ b/packages/core/src/internal/auth-util.ts @@ -43,12 +43,12 @@ export function cacheKey (auth?: AuthToken, impersonatedUser?: string): string { function orderedObject (obj: object): object { const ordered = {} Object.keys(obj).sort().forEach((key: string) => { - // @ts-expect-error: undefined check is already made + // @ts-expect-error: no way to avoid implicit 'any' let entry: any = obj[key] if (typeof entry === 'object' && !(entry instanceof Array)) { entry = orderedObject(entry) } - // @ts-expect-error: undefined check is already made + // @ts-expect-error: no way to avoid implicit 'any' ordered[key] = entry }) return ordered diff --git a/packages/neo4j-driver-deno/lib/core/internal/auth-util.ts b/packages/neo4j-driver-deno/lib/core/internal/auth-util.ts index b66240e2a..16b7db071 100644 --- a/packages/neo4j-driver-deno/lib/core/internal/auth-util.ts +++ b/packages/neo4j-driver-deno/lib/core/internal/auth-util.ts @@ -43,12 +43,12 @@ export function cacheKey (auth?: AuthToken, impersonatedUser?: string): string { function orderedObject (obj: object): object { const ordered = {} Object.keys(obj).sort().forEach((key: string) => { - // @ts-expect-error: undefined check is already made + // @ts-expect-error: no way to avoid implicit 'any' let entry: any = obj[key] if (typeof entry === 'object' && !(entry instanceof Array)) { entry = orderedObject(entry) } - // @ts-expect-error: undefined check is already made + // @ts-expect-error: no way to avoid implicit 'any' ordered[key] = entry }) return ordered From 18b386071fdfb7912ce2a94a112e0dd74aff6eb0 Mon Sep 17 00:00:00 2001 From: MaxAake <61233757+MaxAake@users.noreply.github.com> Date: Thu, 23 Jan 2025 15:45:24 +0100 Subject: [PATCH 82/84] Moves sorting of JSON elements to stringify function. --- packages/core/src/internal/auth-util.ts | 16 +----------- packages/core/src/json.ts | 23 ++++++++++++++--- packages/core/test/auth.test.ts | 5 ++++ .../lib/core/internal/auth-util.ts | 16 +----------- packages/neo4j-driver-deno/lib/core/json.ts | 25 ++++++++++++++++--- 5 files changed, 47 insertions(+), 38 deletions(-) diff --git a/packages/core/src/internal/auth-util.ts b/packages/core/src/internal/auth-util.ts index d2a9461cf..3d5e4ed4e 100644 --- a/packages/core/src/internal/auth-util.ts +++ b/packages/core/src/internal/auth-util.ts @@ -37,19 +37,5 @@ export function cacheKey (auth?: AuthToken, impersonatedUser?: string): string { if (auth.scheme === 'none') { return 'none' } - return stringify(orderedObject(auth)) -} - -function orderedObject (obj: object): object { - const ordered = {} - Object.keys(obj).sort().forEach((key: string) => { - // @ts-expect-error: no way to avoid implicit 'any' - let entry: any = obj[key] - if (typeof entry === 'object' && !(entry instanceof Array)) { - entry = orderedObject(entry) - } - // @ts-expect-error: no way to avoid implicit 'any' - ordered[key] = entry - }) - return ordered + return stringify(auth, { sortedElements: true }) } diff --git a/packages/core/src/json.ts b/packages/core/src/json.ts index cba26b0ba..77ad12d5a 100644 --- a/packages/core/src/json.ts +++ b/packages/core/src/json.ts @@ -19,6 +19,7 @@ import { isBrokenObject, getBrokenObjectReason } from './internal/object-util' interface StringifyOpts { useCustomToString?: boolean + sortedElements?: boolean } /** @@ -40,13 +41,27 @@ export function stringify (val: any, opts?: StringifyOpts): string { return `${value}n` } + if (opts?.sortedElements === true && + typeof value === 'object' && + !Array.isArray(value)) { + return Object.keys(value).sort().reduce( + (obj, key) => { + // @ts-expect-error: no way to avoid implicit 'any' + obj[key] = value[key] + return obj + }, + {} + ) + } + if (opts?.useCustomToString === true && - typeof value === 'object' && - !Array.isArray(value) && - typeof value.toString === 'function' && - value.toString !== Object.prototype.toString) { + typeof value === 'object' && + !Array.isArray(value) && + typeof value.toString === 'function' && + value.toString !== Object.prototype.toString) { return value?.toString() } + return value }) } diff --git a/packages/core/test/auth.test.ts b/packages/core/test/auth.test.ts index 6af75ae35..abd3ae142 100644 --- a/packages/core/test/auth.test.ts +++ b/packages/core/test/auth.test.ts @@ -69,6 +69,11 @@ describe('auth', () => { ['user', 'pass', 'realm', 'scheme', { param: 'param', param2: 'param2' }], true ], + [ + ['user', 'pass', 'realm', 'scheme', { a: { param2: 'param2', param: 'param' } }], + ['user', 'pass', 'realm', 'scheme', { a: { param: 'param', param2: 'param2' } }], + true + ], [ ['user', 'pass', 'realm', 'scheme', { param: [1, 2, 3] }], ['user', 'pass', 'realm', 'scheme', { param: [1, 2, 3] }], diff --git a/packages/neo4j-driver-deno/lib/core/internal/auth-util.ts b/packages/neo4j-driver-deno/lib/core/internal/auth-util.ts index 16b7db071..d62258fd8 100644 --- a/packages/neo4j-driver-deno/lib/core/internal/auth-util.ts +++ b/packages/neo4j-driver-deno/lib/core/internal/auth-util.ts @@ -37,19 +37,5 @@ export function cacheKey (auth?: AuthToken, impersonatedUser?: string): string { if (auth.scheme === 'none') { return 'none' } - return stringify(orderedObject(auth)) -} - -function orderedObject (obj: object): object { - const ordered = {} - Object.keys(obj).sort().forEach((key: string) => { - // @ts-expect-error: no way to avoid implicit 'any' - let entry: any = obj[key] - if (typeof entry === 'object' && !(entry instanceof Array)) { - entry = orderedObject(entry) - } - // @ts-expect-error: no way to avoid implicit 'any' - ordered[key] = entry - }) - return ordered + return stringify(auth, {stableObjectOrder: true}) } diff --git a/packages/neo4j-driver-deno/lib/core/json.ts b/packages/neo4j-driver-deno/lib/core/json.ts index 25142ec12..e96ce131a 100644 --- a/packages/neo4j-driver-deno/lib/core/json.ts +++ b/packages/neo4j-driver-deno/lib/core/json.ts @@ -19,6 +19,7 @@ import { isBrokenObject, getBrokenObjectReason } from './internal/object-util.ts interface StringifyOpts { useCustomToString?: boolean + stableObjectOrder?: boolean } /** @@ -40,13 +41,29 @@ export function stringify (val: any, opts?: StringifyOpts): string { return `${value}n` } + if (opts?.stableObjectOrder === true && + typeof value === 'object' && + !Array.isArray(value)) + { + return Object.keys(value).sort().reduce( + (obj, key) => { + // @ts-expect-error: no way to avoid implicit 'any' + obj[key] = value[key]; + return obj; + }, + {} + ); + } + if (opts?.useCustomToString === true && - typeof value === 'object' && - !Array.isArray(value) && - typeof value.toString === 'function' && - value.toString !== Object.prototype.toString) { + typeof value === 'object' && + !Array.isArray(value) && + typeof value.toString === 'function' && + value.toString !== Object.prototype.toString) + { return value?.toString() } + return value }) } From 4331f709cac6aa824d8c09214ce3bada7f881feb Mon Sep 17 00:00:00 2001 From: MaxAake <61233757+MaxAake@users.noreply.github.com> Date: Thu, 23 Jan 2025 15:56:34 +0100 Subject: [PATCH 83/84] deno --- .../lib/core/internal/auth-util.ts | 2 +- packages/neo4j-driver-deno/lib/core/json.ts | 20 +++++++++---------- 2 files changed, 10 insertions(+), 12 deletions(-) diff --git a/packages/neo4j-driver-deno/lib/core/internal/auth-util.ts b/packages/neo4j-driver-deno/lib/core/internal/auth-util.ts index d62258fd8..bb9c700f5 100644 --- a/packages/neo4j-driver-deno/lib/core/internal/auth-util.ts +++ b/packages/neo4j-driver-deno/lib/core/internal/auth-util.ts @@ -37,5 +37,5 @@ export function cacheKey (auth?: AuthToken, impersonatedUser?: string): string { if (auth.scheme === 'none') { return 'none' } - return stringify(auth, {stableObjectOrder: true}) + return stringify(auth, { sortedElements: true }) } diff --git a/packages/neo4j-driver-deno/lib/core/json.ts b/packages/neo4j-driver-deno/lib/core/json.ts index e96ce131a..cba576505 100644 --- a/packages/neo4j-driver-deno/lib/core/json.ts +++ b/packages/neo4j-driver-deno/lib/core/json.ts @@ -19,7 +19,7 @@ import { isBrokenObject, getBrokenObjectReason } from './internal/object-util.ts interface StringifyOpts { useCustomToString?: boolean - stableObjectOrder?: boolean + sortedElements?: boolean } /** @@ -41,26 +41,24 @@ export function stringify (val: any, opts?: StringifyOpts): string { return `${value}n` } - if (opts?.stableObjectOrder === true && + if (opts?.sortedElements === true && typeof value === 'object' && - !Array.isArray(value)) - { + !Array.isArray(value)) { return Object.keys(value).sort().reduce( - (obj, key) => { + (obj, key) => { // @ts-expect-error: no way to avoid implicit 'any' - obj[key] = value[key]; - return obj; - }, + obj[key] = value[key] + return obj + }, {} - ); + ) } if (opts?.useCustomToString === true && typeof value === 'object' && !Array.isArray(value) && typeof value.toString === 'function' && - value.toString !== Object.prototype.toString) - { + value.toString !== Object.prototype.toString) { return value?.toString() } From e034313627f54c7e4a57be295551209b2a21e754 Mon Sep 17 00:00:00 2001 From: MaxAake <61233757+MaxAake@users.noreply.github.com> Date: Fri, 24 Jan 2025 09:00:26 +0100 Subject: [PATCH 84/84] formatting change --- packages/core/src/json.ts | 12 ++++++------ packages/neo4j-driver-deno/lib/core/json.ts | 12 ++++++------ 2 files changed, 12 insertions(+), 12 deletions(-) diff --git a/packages/core/src/json.ts b/packages/core/src/json.ts index 77ad12d5a..b2e7c3131 100644 --- a/packages/core/src/json.ts +++ b/packages/core/src/json.ts @@ -42,8 +42,8 @@ export function stringify (val: any, opts?: StringifyOpts): string { } if (opts?.sortedElements === true && - typeof value === 'object' && - !Array.isArray(value)) { + typeof value === 'object' && + !Array.isArray(value)) { return Object.keys(value).sort().reduce( (obj, key) => { // @ts-expect-error: no way to avoid implicit 'any' @@ -55,10 +55,10 @@ export function stringify (val: any, opts?: StringifyOpts): string { } if (opts?.useCustomToString === true && - typeof value === 'object' && - !Array.isArray(value) && - typeof value.toString === 'function' && - value.toString !== Object.prototype.toString) { + typeof value === 'object' && + !Array.isArray(value) && + typeof value.toString === 'function' && + value.toString !== Object.prototype.toString) { return value?.toString() } diff --git a/packages/neo4j-driver-deno/lib/core/json.ts b/packages/neo4j-driver-deno/lib/core/json.ts index cba576505..003286a55 100644 --- a/packages/neo4j-driver-deno/lib/core/json.ts +++ b/packages/neo4j-driver-deno/lib/core/json.ts @@ -42,8 +42,8 @@ export function stringify (val: any, opts?: StringifyOpts): string { } if (opts?.sortedElements === true && - typeof value === 'object' && - !Array.isArray(value)) { + typeof value === 'object' && + !Array.isArray(value)) { return Object.keys(value).sort().reduce( (obj, key) => { // @ts-expect-error: no way to avoid implicit 'any' @@ -55,10 +55,10 @@ export function stringify (val: any, opts?: StringifyOpts): string { } if (opts?.useCustomToString === true && - typeof value === 'object' && - !Array.isArray(value) && - typeof value.toString === 'function' && - value.toString !== Object.prototype.toString) { + typeof value === 'object' && + !Array.isArray(value) && + typeof value.toString === 'function' && + value.toString !== Object.prototype.toString) { return value?.toString() }