From 8097d7bc080de647c7b8db5cdc267cb01f6747c5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Antonio=20Barc=C3=A9los?= Date: Wed, 22 Nov 2023 10:18:37 +0100 Subject: [PATCH 1/6] Introduce `transactionConfig` to `Driver.executeQuery` Transaction config enables the user configure timeouts and set transaction metadata to the transaction. This configuration is already present in APIs such as `Session.executeRead` and `Session.executeWrite`. Usage example: ```typescript const { records } = await driver.executeQuery(myQuery, myParams, { database: 'neo4j', transactionConfig: { timeout: 3000, metadata: { key: 'value' } } }) ``` --- packages/core/src/driver.ts | 14 ++++++-- packages/core/src/internal/query-executor.ts | 7 ++-- packages/core/test/driver.test.ts | 11 ++++-- .../core/test/internal/query-executor.test.ts | 34 +++++++++++++++++++ 4 files changed, 59 insertions(+), 7 deletions(-) diff --git a/packages/core/src/driver.ts b/packages/core/src/driver.ts index 09f3353bc..5c67c9e74 100644 --- a/packages/core/src/driver.ts +++ b/packages/core/src/driver.ts @@ -30,7 +30,7 @@ import { DEFAULT_POOL_MAX_SIZE } from './internal/constants' import { Logger } from './internal/logger' -import Session from './session' +import Session, { TransactionConfig } from './session' import { ServerInfo } from './result-summary' import { ENCRYPTION_ON } from './internal/util' import { @@ -357,6 +357,7 @@ class QueryConfig { impersonatedUser?: string bookmarkManager?: BookmarkManager | null resultTransformer?: ResultTransformer + transactionConfig?: TransactionConfig /** * @constructor @@ -405,6 +406,14 @@ class QueryConfig { * @type {BookmarkManager|null} */ this.bookmarkManager = undefined + + /** + * Configuration for all transactions started to execute the query. + * + * @type {TransactionConfig|undefined} + * + */ + this.transactionConfig = undefined } } @@ -569,7 +578,8 @@ class Driver { bookmarkManager, routing: routingConfig, database: config.database, - impersonatedUser: config.impersonatedUser + impersonatedUser: config.impersonatedUser, + transactionConfig: config.transactionConfig }, query, parameters) } diff --git a/packages/core/src/internal/query-executor.ts b/packages/core/src/internal/query-executor.ts index dfee3fea1..c9e8feea3 100644 --- a/packages/core/src/internal/query-executor.ts +++ b/packages/core/src/internal/query-executor.ts @@ -18,7 +18,7 @@ */ import BookmarkManager from '../bookmark-manager' -import Session from '../session' +import Session, { TransactionConfig } from '../session' import Result from '../result' import ManagedTransaction from '../transaction-managed' import { Query } from '../types' @@ -26,13 +26,14 @@ import { TELEMETRY_APIS } from './constants' type SessionFactory = (config: { database?: string, bookmarkManager?: BookmarkManager, impersonatedUser?: string }) => Session -type TransactionFunction = (transactionWork: (tx: ManagedTransaction) => Promise) => Promise +type TransactionFunction = (transactionWork: (tx: ManagedTransaction) => Promise, transactionConfig?: TransactionConfig) => Promise interface ExecutionConfig { routing: 'WRITE' | 'READ' database?: string impersonatedUser?: string bookmarkManager?: BookmarkManager + transactionConfig?: TransactionConfig resultTransformer: (result: Result) => Promise } @@ -59,7 +60,7 @@ export default class QueryExecutor { return await executeInTransaction(async (tx: ManagedTransaction) => { const result = tx.run(query, parameters) return await config.resultTransformer(result) - }) + }, config.transactionConfig) } finally { await session.close() } diff --git a/packages/core/test/driver.test.ts b/packages/core/test/driver.test.ts index 798e15468..0cbba3626 100644 --- a/packages/core/test/driver.test.ts +++ b/packages/core/test/driver.test.ts @@ -17,7 +17,7 @@ * limitations under the License. */ /* eslint-disable @typescript-eslint/promise-function-async */ -import { bookmarkManager, ConnectionProvider, EagerResult, newError, NotificationFilter, Result, ResultSummary, ServerInfo, Session } from '../src' +import { bookmarkManager, ConnectionProvider, EagerResult, newError, NotificationFilter, Result, ResultSummary, ServerInfo, Session, TransactionConfig } from '../src' import Driver, { QueryConfig, READ, routing } from '../src/driver' import { Bookmarks } from '../src/internal/bookmarks' import { Logger } from '../src/internal/logger' @@ -469,6 +469,12 @@ describe('Driver', () => { describe('when config is defined', () => { const theBookmarkManager = bookmarkManager() + const aTransactionConfig: TransactionConfig = { + timeout: 1234, + metadata: { + key: 'value' + } + } async function aTransformer (result: Result): Promise { const summary = await result.summary() return summary.database.name ?? 'no-db-set' @@ -482,7 +488,8 @@ describe('Driver', () => { ['config.impersonatedUser="the_user"', 'q', {}, { impersonatedUser: 'the_user' }, extendsDefaultWith({ impersonatedUser: 'the_user' })], ['config.bookmarkManager=null', 'q', {}, { bookmarkManager: null }, extendsDefaultWith({ bookmarkManager: undefined })], ['config.bookmarkManager set to non-null/empty', 'q', {}, { bookmarkManager: theBookmarkManager }, extendsDefaultWith({ bookmarkManager: theBookmarkManager })], - ['config.resultTransformer set', 'q', {}, { resultTransformer: aTransformer }, extendsDefaultWith({ resultTransformer: aTransformer })] + ['config.resultTransformer set', 'q', {}, { resultTransformer: aTransformer }, extendsDefaultWith({ resultTransformer: aTransformer })], + ['config.transactionConfig set', 'q', {}, { transactionConfig: aTransactionConfig }, extendsDefaultWith({ transactionConfig: aTransactionConfig })] ])('should handle the params for %s', async (_, query, params, config, buildExpectedConfig) => { const spiedExecute = jest.spyOn(queryExecutor, 'execute') diff --git a/packages/core/test/internal/query-executor.test.ts b/packages/core/test/internal/query-executor.test.ts index d636acfc5..ac27e4d0b 100644 --- a/packages/core/test/internal/query-executor.test.ts +++ b/packages/core/test/internal/query-executor.test.ts @@ -28,6 +28,12 @@ type ManagedTransactionWork = (tx: ManagedTransaction) => Promise | T describe('QueryExecutor', () => { const aBookmarkManager = bookmarkManager() + const aTransactionConfig: TransactionConfig = { + timeout: 1234, + metadata: { + key: 'value' + } + } it.each([ ['bookmarkManager set', { bookmarkManager: aBookmarkManager }, { bookmarkManager: aBookmarkManager }], @@ -87,6 +93,20 @@ describe('QueryExecutor', () => { expect(spyOnExecuteRead).toHaveBeenCalled() }) + it.each([ + [aTransactionConfig], + [undefined], + [null] + ])('should call executeRead with transactionConfig=%s', async (transactionConfig: TransactionConfig) => { + const { queryExecutor, sessionsCreated } = createExecutor() + + await queryExecutor.execute({ ...baseConfig, transactionConfig }, 'query') + + expect(sessionsCreated.length).toBe(1) + const [{ spyOnExecuteRead }] = sessionsCreated + expect(spyOnExecuteRead).toHaveBeenCalledWith(expect.any(Function), transactionConfig) + }) + it('should configure the session with pipeline begin and correct api metrics', async () => { const { queryExecutor, sessionsCreated } = createExecutor() @@ -229,6 +249,20 @@ describe('QueryExecutor', () => { expect(spyOnExecuteWrite).toHaveBeenCalled() }) + it.each([ + [aTransactionConfig], + [undefined], + [null] + ])('should call executeWrite with transactionConfig=%s', async (transactionConfig: TransactionConfig) => { + const { queryExecutor, sessionsCreated } = createExecutor() + + await queryExecutor.execute({ ...baseConfig, transactionConfig }, 'query') + + expect(sessionsCreated.length).toBe(1) + const [{ spyOnExecuteWrite }] = sessionsCreated + expect(spyOnExecuteWrite).toHaveBeenCalledWith(expect.any(Function), transactionConfig) + }) + it('should configure the session with pipeline begin and api telemetry', async () => { const { queryExecutor, sessionsCreated } = createExecutor() From 07b56e824374471778961bf2e1fe24cbac51b57a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Antonio=20Barc=C3=A9los?= Date: Wed, 22 Nov 2023 10:33:32 +0100 Subject: [PATCH 2/6] sync deno --- packages/neo4j-driver-deno/lib/core/driver.ts | 14 ++++++++++++-- .../lib/core/internal/query-executor.ts | 7 ++++--- 2 files changed, 16 insertions(+), 5 deletions(-) diff --git a/packages/neo4j-driver-deno/lib/core/driver.ts b/packages/neo4j-driver-deno/lib/core/driver.ts index e151bde47..ae3739b43 100644 --- a/packages/neo4j-driver-deno/lib/core/driver.ts +++ b/packages/neo4j-driver-deno/lib/core/driver.ts @@ -30,7 +30,7 @@ import { DEFAULT_POOL_MAX_SIZE } from './internal/constants.ts' import { Logger } from './internal/logger.ts' -import Session from './session.ts' +import Session, { TransactionConfig } from './session.ts' import { ServerInfo } from './result-summary.ts' import { ENCRYPTION_ON } from './internal/util.ts' import { @@ -357,6 +357,7 @@ class QueryConfig { impersonatedUser?: string bookmarkManager?: BookmarkManager | null resultTransformer?: ResultTransformer + transactionConfig?: TransactionConfig /** * @constructor @@ -405,6 +406,14 @@ class QueryConfig { * @type {BookmarkManager|null} */ this.bookmarkManager = undefined + + /** + * Configuration for all transactions started to execute the query. + * + * @type {TransactionConfig|undefined} + * + */ + this.transactionConfig = undefined } } @@ -569,7 +578,8 @@ class Driver { bookmarkManager, routing: routingConfig, database: config.database, - impersonatedUser: config.impersonatedUser + impersonatedUser: config.impersonatedUser, + transactionConfig: config.transactionConfig }, query, parameters) } diff --git a/packages/neo4j-driver-deno/lib/core/internal/query-executor.ts b/packages/neo4j-driver-deno/lib/core/internal/query-executor.ts index 8704c2d1e..cde16438b 100644 --- a/packages/neo4j-driver-deno/lib/core/internal/query-executor.ts +++ b/packages/neo4j-driver-deno/lib/core/internal/query-executor.ts @@ -18,7 +18,7 @@ */ import BookmarkManager from '../bookmark-manager.ts' -import Session from '../session.ts' +import Session, { TransactionConfig } from '../session.ts' import Result from '../result.ts' import ManagedTransaction from '../transaction-managed.ts' import { Query } from '../types.ts' @@ -26,13 +26,14 @@ import { TELEMETRY_APIS } from './constants.ts' type SessionFactory = (config: { database?: string, bookmarkManager?: BookmarkManager, impersonatedUser?: string }) => Session -type TransactionFunction = (transactionWork: (tx: ManagedTransaction) => Promise) => Promise +type TransactionFunction = (transactionWork: (tx: ManagedTransaction) => Promise, transactionConfig?: TransactionConfig) => Promise interface ExecutionConfig { routing: 'WRITE' | 'READ' database?: string impersonatedUser?: string bookmarkManager?: BookmarkManager + transactionConfig?: TransactionConfig resultTransformer: (result: Result) => Promise } @@ -59,7 +60,7 @@ export default class QueryExecutor { return await executeInTransaction(async (tx: ManagedTransaction) => { const result = tx.run(query, parameters) return await config.resultTransformer(result) - }) + }, config.transactionConfig) } finally { await session.close() } From 3ea193b07fb74471ae32c7b3b2dacaca6e40f7af Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Antonio=20Barc=C3=A9los?= Date: Wed, 22 Nov 2023 11:54:20 +0100 Subject: [PATCH 3/6] adjust bmm docs --- packages/core/src/driver.ts | 2 +- packages/neo4j-driver-deno/lib/core/driver.ts | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/core/src/driver.ts b/packages/core/src/driver.ts index 5c67c9e74..ea754e04a 100644 --- a/packages/core/src/driver.ts +++ b/packages/core/src/driver.ts @@ -403,7 +403,7 @@ class QueryConfig { * By default, it uses the driver's non mutable driver level bookmark manager. See, {@link Driver.executeQueryBookmarkManager} * * Can be set to null to disable causal chaining. - * @type {BookmarkManager|null} + * @type {BookmarkManager|undefined|null} */ this.bookmarkManager = undefined diff --git a/packages/neo4j-driver-deno/lib/core/driver.ts b/packages/neo4j-driver-deno/lib/core/driver.ts index ae3739b43..6e308139d 100644 --- a/packages/neo4j-driver-deno/lib/core/driver.ts +++ b/packages/neo4j-driver-deno/lib/core/driver.ts @@ -403,7 +403,7 @@ class QueryConfig { * By default, it uses the driver's non mutable driver level bookmark manager. See, {@link Driver.executeQueryBookmarkManager} * * Can be set to null to disable causal chaining. - * @type {BookmarkManager|null} + * @type {BookmarkManager|undefined|null} */ this.bookmarkManager = undefined From 54b78566ba6e8c232d3a71eed3a993764379aad5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Antonio=20Barc=C3=A9los?= Date: Wed, 22 Nov 2023 13:51:53 +0100 Subject: [PATCH 4/6] Update testkit --- packages/testkit-backend/src/request-handlers.js | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/packages/testkit-backend/src/request-handlers.js b/packages/testkit-backend/src/request-handlers.js index bdb98bc2b..e9a0fbdf7 100644 --- a/packages/testkit-backend/src/request-handlers.js +++ b/packages/testkit-backend/src/request-handlers.js @@ -696,6 +696,13 @@ export function ExecuteQuery ({ neo4j }, context, { driverId, cypher, params, co configuration.bookmarkManager = null } } + + if ('txMeta' in config || 'timeout' in config) { + configuration.transactionConfig = { + metadata: context.binder.objectToNative(config.txMeta), + timeout: config.timeout + } + } } driver.executeQuery(cypher, params, configuration) From 2e9dcb6ca9b6fe6892b448d6192c278e650d4f8e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Antonio=20Barc=C3=A9los?= Date: Wed, 22 Nov 2023 14:52:16 +0100 Subject: [PATCH 5/6] properties will always be send as null if not set in backend --- packages/testkit-backend/src/request-handlers.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/testkit-backend/src/request-handlers.js b/packages/testkit-backend/src/request-handlers.js index e9a0fbdf7..31df84731 100644 --- a/packages/testkit-backend/src/request-handlers.js +++ b/packages/testkit-backend/src/request-handlers.js @@ -697,7 +697,7 @@ export function ExecuteQuery ({ neo4j }, context, { driverId, cypher, params, co } } - if ('txMeta' in config || 'timeout' in config) { + if (config.txMeta !== null || config.timeout !== null) { configuration.transactionConfig = { metadata: context.binder.objectToNative(config.txMeta), timeout: config.timeout From 9d05ea62f498bbd77111ad5d3477134b59336ba0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Antonio=20Barc=C3=A9los?= Date: Wed, 22 Nov 2023 14:54:20 +0100 Subject: [PATCH 6/6] adjust other config in ExecuteQuery request handler --- packages/testkit-backend/src/request-handlers.js | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/packages/testkit-backend/src/request-handlers.js b/packages/testkit-backend/src/request-handlers.js index 31df84731..19bb4ed23 100644 --- a/packages/testkit-backend/src/request-handlers.js +++ b/packages/testkit-backend/src/request-handlers.js @@ -676,11 +676,11 @@ export function ExecuteQuery ({ neo4j }, context, { driverId, cypher, params, co } } - if ('database' in config) { + if (config.database != null) { configuration.database = config.database } - if ('impersonatedUser' in config) { + if (config.impersonatedUser != null) { configuration.impersonatedUser = config.impersonatedUser } @@ -697,7 +697,7 @@ export function ExecuteQuery ({ neo4j }, context, { driverId, cypher, params, co } } - if (config.txMeta !== null || config.timeout !== null) { + if (config.txMeta != null || config.timeout != null) { configuration.transactionConfig = { metadata: context.binder.objectToNative(config.txMeta), timeout: config.timeout