Skip to content

Commit 04e41a3

Browse files
authored
Introduce transactionConfig to Driver.executeQuery (neo4j#1160)
Transaction config enables the user to configure timeouts and set metadata for 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' } } }) ```
1 parent f58a089 commit 04e41a3

File tree

7 files changed

+86
-16
lines changed

7 files changed

+86
-16
lines changed

packages/core/src/driver.ts

+13-3
Original file line numberDiff line numberDiff line change
@@ -30,7 +30,7 @@ import {
3030
DEFAULT_POOL_MAX_SIZE
3131
} from './internal/constants'
3232
import { Logger } from './internal/logger'
33-
import Session from './session'
33+
import Session, { TransactionConfig } from './session'
3434
import { ServerInfo } from './result-summary'
3535
import { ENCRYPTION_ON } from './internal/util'
3636
import {
@@ -357,6 +357,7 @@ class QueryConfig<T = EagerResult> {
357357
impersonatedUser?: string
358358
bookmarkManager?: BookmarkManager | null
359359
resultTransformer?: ResultTransformer<T>
360+
transactionConfig?: TransactionConfig
360361

361362
/**
362363
* @constructor
@@ -402,9 +403,17 @@ class QueryConfig<T = EagerResult> {
402403
* By default, it uses the driver's non mutable driver level bookmark manager. See, {@link Driver.executeQueryBookmarkManager}
403404
*
404405
* Can be set to null to disable causal chaining.
405-
* @type {BookmarkManager|null}
406+
* @type {BookmarkManager|undefined|null}
406407
*/
407408
this.bookmarkManager = undefined
409+
410+
/**
411+
* Configuration for all transactions started to execute the query.
412+
*
413+
* @type {TransactionConfig|undefined}
414+
*
415+
*/
416+
this.transactionConfig = undefined
408417
}
409418
}
410419

@@ -569,7 +578,8 @@ class Driver {
569578
bookmarkManager,
570579
routing: routingConfig,
571580
database: config.database,
572-
impersonatedUser: config.impersonatedUser
581+
impersonatedUser: config.impersonatedUser,
582+
transactionConfig: config.transactionConfig
573583
}, query, parameters)
574584
}
575585

packages/core/src/internal/query-executor.ts

+4-3
Original file line numberDiff line numberDiff line change
@@ -18,21 +18,22 @@
1818
*/
1919

2020
import BookmarkManager from '../bookmark-manager'
21-
import Session from '../session'
21+
import Session, { TransactionConfig } from '../session'
2222
import Result from '../result'
2323
import ManagedTransaction from '../transaction-managed'
2424
import { Query } from '../types'
2525
import { TELEMETRY_APIS } from './constants'
2626

2727
type SessionFactory = (config: { database?: string, bookmarkManager?: BookmarkManager, impersonatedUser?: string }) => Session
2828

29-
type TransactionFunction<T> = (transactionWork: (tx: ManagedTransaction) => Promise<T>) => Promise<T>
29+
type TransactionFunction<T> = (transactionWork: (tx: ManagedTransaction) => Promise<T>, transactionConfig?: TransactionConfig) => Promise<T>
3030

3131
interface ExecutionConfig<T> {
3232
routing: 'WRITE' | 'READ'
3333
database?: string
3434
impersonatedUser?: string
3535
bookmarkManager?: BookmarkManager
36+
transactionConfig?: TransactionConfig
3637
resultTransformer: (result: Result) => Promise<T>
3738
}
3839

@@ -59,7 +60,7 @@ export default class QueryExecutor {
5960
return await executeInTransaction(async (tx: ManagedTransaction) => {
6061
const result = tx.run(query, parameters)
6162
return await config.resultTransformer(result)
62-
})
63+
}, config.transactionConfig)
6364
} finally {
6465
await session.close()
6566
}

packages/core/test/driver.test.ts

+9-2
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@
1717
* limitations under the License.
1818
*/
1919
/* eslint-disable @typescript-eslint/promise-function-async */
20-
import { bookmarkManager, ConnectionProvider, EagerResult, newError, NotificationFilter, Result, ResultSummary, ServerInfo, Session } from '../src'
20+
import { bookmarkManager, ConnectionProvider, EagerResult, newError, NotificationFilter, Result, ResultSummary, ServerInfo, Session, TransactionConfig } from '../src'
2121
import Driver, { QueryConfig, READ, routing } from '../src/driver'
2222
import { Bookmarks } from '../src/internal/bookmarks'
2323
import { Logger } from '../src/internal/logger'
@@ -469,6 +469,12 @@ describe('Driver', () => {
469469

470470
describe('when config is defined', () => {
471471
const theBookmarkManager = bookmarkManager()
472+
const aTransactionConfig: TransactionConfig = {
473+
timeout: 1234,
474+
metadata: {
475+
key: 'value'
476+
}
477+
}
472478
async function aTransformer (result: Result): Promise<string> {
473479
const summary = await result.summary()
474480
return summary.database.name ?? 'no-db-set'
@@ -482,7 +488,8 @@ describe('Driver', () => {
482488
['config.impersonatedUser="the_user"', 'q', {}, { impersonatedUser: 'the_user' }, extendsDefaultWith({ impersonatedUser: 'the_user' })],
483489
['config.bookmarkManager=null', 'q', {}, { bookmarkManager: null }, extendsDefaultWith({ bookmarkManager: undefined })],
484490
['config.bookmarkManager set to non-null/empty', 'q', {}, { bookmarkManager: theBookmarkManager }, extendsDefaultWith({ bookmarkManager: theBookmarkManager })],
485-
['config.resultTransformer set', 'q', {}, { resultTransformer: aTransformer }, extendsDefaultWith({ resultTransformer: aTransformer })]
491+
['config.resultTransformer set', 'q', {}, { resultTransformer: aTransformer }, extendsDefaultWith({ resultTransformer: aTransformer })],
492+
['config.transactionConfig set', 'q', {}, { transactionConfig: aTransactionConfig }, extendsDefaultWith({ transactionConfig: aTransactionConfig })]
486493
])('should handle the params for %s', async (_, query, params, config, buildExpectedConfig) => {
487494
const spiedExecute = jest.spyOn(queryExecutor, 'execute')
488495

packages/core/test/internal/query-executor.test.ts

+34
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,12 @@ type ManagedTransactionWork<T> = (tx: ManagedTransaction) => Promise<T> | T
2828

2929
describe('QueryExecutor', () => {
3030
const aBookmarkManager = bookmarkManager()
31+
const aTransactionConfig: TransactionConfig = {
32+
timeout: 1234,
33+
metadata: {
34+
key: 'value'
35+
}
36+
}
3137

3238
it.each([
3339
['bookmarkManager set', { bookmarkManager: aBookmarkManager }, { bookmarkManager: aBookmarkManager }],
@@ -87,6 +93,20 @@ describe('QueryExecutor', () => {
8793
expect(spyOnExecuteRead).toHaveBeenCalled()
8894
})
8995

96+
it.each([
97+
[aTransactionConfig],
98+
[undefined],
99+
[null]
100+
])('should call executeRead with transactionConfig=%s', async (transactionConfig: TransactionConfig) => {
101+
const { queryExecutor, sessionsCreated } = createExecutor()
102+
103+
await queryExecutor.execute({ ...baseConfig, transactionConfig }, 'query')
104+
105+
expect(sessionsCreated.length).toBe(1)
106+
const [{ spyOnExecuteRead }] = sessionsCreated
107+
expect(spyOnExecuteRead).toHaveBeenCalledWith(expect.any(Function), transactionConfig)
108+
})
109+
90110
it('should configure the session with pipeline begin and correct api metrics', async () => {
91111
const { queryExecutor, sessionsCreated } = createExecutor()
92112

@@ -229,6 +249,20 @@ describe('QueryExecutor', () => {
229249
expect(spyOnExecuteWrite).toHaveBeenCalled()
230250
})
231251

252+
it.each([
253+
[aTransactionConfig],
254+
[undefined],
255+
[null]
256+
])('should call executeWrite with transactionConfig=%s', async (transactionConfig: TransactionConfig) => {
257+
const { queryExecutor, sessionsCreated } = createExecutor()
258+
259+
await queryExecutor.execute({ ...baseConfig, transactionConfig }, 'query')
260+
261+
expect(sessionsCreated.length).toBe(1)
262+
const [{ spyOnExecuteWrite }] = sessionsCreated
263+
expect(spyOnExecuteWrite).toHaveBeenCalledWith(expect.any(Function), transactionConfig)
264+
})
265+
232266
it('should configure the session with pipeline begin and api telemetry', async () => {
233267
const { queryExecutor, sessionsCreated } = createExecutor()
234268

packages/neo4j-driver-deno/lib/core/driver.ts

+13-3
Original file line numberDiff line numberDiff line change
@@ -30,7 +30,7 @@ import {
3030
DEFAULT_POOL_MAX_SIZE
3131
} from './internal/constants.ts'
3232
import { Logger } from './internal/logger.ts'
33-
import Session from './session.ts'
33+
import Session, { TransactionConfig } from './session.ts'
3434
import { ServerInfo } from './result-summary.ts'
3535
import { ENCRYPTION_ON } from './internal/util.ts'
3636
import {
@@ -357,6 +357,7 @@ class QueryConfig<T = EagerResult> {
357357
impersonatedUser?: string
358358
bookmarkManager?: BookmarkManager | null
359359
resultTransformer?: ResultTransformer<T>
360+
transactionConfig?: TransactionConfig
360361

361362
/**
362363
* @constructor
@@ -402,9 +403,17 @@ class QueryConfig<T = EagerResult> {
402403
* By default, it uses the driver's non mutable driver level bookmark manager. See, {@link Driver.executeQueryBookmarkManager}
403404
*
404405
* Can be set to null to disable causal chaining.
405-
* @type {BookmarkManager|null}
406+
* @type {BookmarkManager|undefined|null}
406407
*/
407408
this.bookmarkManager = undefined
409+
410+
/**
411+
* Configuration for all transactions started to execute the query.
412+
*
413+
* @type {TransactionConfig|undefined}
414+
*
415+
*/
416+
this.transactionConfig = undefined
408417
}
409418
}
410419

@@ -569,7 +578,8 @@ class Driver {
569578
bookmarkManager,
570579
routing: routingConfig,
571580
database: config.database,
572-
impersonatedUser: config.impersonatedUser
581+
impersonatedUser: config.impersonatedUser,
582+
transactionConfig: config.transactionConfig
573583
}, query, parameters)
574584
}
575585

packages/neo4j-driver-deno/lib/core/internal/query-executor.ts

+4-3
Original file line numberDiff line numberDiff line change
@@ -18,21 +18,22 @@
1818
*/
1919

2020
import BookmarkManager from '../bookmark-manager.ts'
21-
import Session from '../session.ts'
21+
import Session, { TransactionConfig } from '../session.ts'
2222
import Result from '../result.ts'
2323
import ManagedTransaction from '../transaction-managed.ts'
2424
import { Query } from '../types.ts'
2525
import { TELEMETRY_APIS } from './constants.ts'
2626

2727
type SessionFactory = (config: { database?: string, bookmarkManager?: BookmarkManager, impersonatedUser?: string }) => Session
2828

29-
type TransactionFunction<T> = (transactionWork: (tx: ManagedTransaction) => Promise<T>) => Promise<T>
29+
type TransactionFunction<T> = (transactionWork: (tx: ManagedTransaction) => Promise<T>, transactionConfig?: TransactionConfig) => Promise<T>
3030

3131
interface ExecutionConfig<T> {
3232
routing: 'WRITE' | 'READ'
3333
database?: string
3434
impersonatedUser?: string
3535
bookmarkManager?: BookmarkManager
36+
transactionConfig?: TransactionConfig
3637
resultTransformer: (result: Result) => Promise<T>
3738
}
3839

@@ -59,7 +60,7 @@ export default class QueryExecutor {
5960
return await executeInTransaction(async (tx: ManagedTransaction) => {
6061
const result = tx.run(query, parameters)
6162
return await config.resultTransformer(result)
62-
})
63+
}, config.transactionConfig)
6364
} finally {
6465
await session.close()
6566
}

packages/testkit-backend/src/request-handlers.js

+9-2
Original file line numberDiff line numberDiff line change
@@ -676,11 +676,11 @@ export function ExecuteQuery ({ neo4j }, context, { driverId, cypher, params, co
676676
}
677677
}
678678

679-
if ('database' in config) {
679+
if (config.database != null) {
680680
configuration.database = config.database
681681
}
682682

683-
if ('impersonatedUser' in config) {
683+
if (config.impersonatedUser != null) {
684684
configuration.impersonatedUser = config.impersonatedUser
685685
}
686686

@@ -696,6 +696,13 @@ export function ExecuteQuery ({ neo4j }, context, { driverId, cypher, params, co
696696
configuration.bookmarkManager = null
697697
}
698698
}
699+
700+
if (config.txMeta != null || config.timeout != null) {
701+
configuration.transactionConfig = {
702+
metadata: context.binder.objectToNative(config.txMeta),
703+
timeout: config.timeout
704+
}
705+
}
699706
}
700707

701708
driver.executeQuery(cypher, params, configuration)

0 commit comments

Comments
 (0)