@@ -37,10 +37,15 @@ import {
37
37
EncryptionLevel ,
38
38
LoggingConfig ,
39
39
TrustStrategy ,
40
- SessionMode
40
+ SessionMode ,
41
+ Query
41
42
} from './types'
42
43
import { ServerAddress } from './internal/server-address'
43
- import BookmarkManager from './bookmark-manager'
44
+ import BookmarkManager , { bookmarkManager } from './bookmark-manager'
45
+ import EagerResult from './result-eager'
46
+ import resultTransformers , { ResultTransformer } from './result-transformers'
47
+ import QueryExecutor from './internal/query-executor'
48
+ import { newError } from './error'
44
49
45
50
const DEFAULT_MAX_CONNECTION_LIFETIME : number = 60 * 60 * 1000 // 1 hour
46
51
@@ -91,6 +96,8 @@ type CreateSession = (args: {
91
96
bookmarkManager ?: BookmarkManager
92
97
} ) => Session
93
98
99
+ type CreateQueryExecutor = ( createSession : ( config : { database ?: string , bookmarkManager ?: BookmarkManager } ) => Session ) => QueryExecutor
100
+
94
101
interface DriverConfig {
95
102
encrypted ?: EncryptionLevel | boolean
96
103
trust ?: TrustStrategy
@@ -231,6 +238,88 @@ class SessionConfig {
231
238
}
232
239
}
233
240
241
+ type RoutingControl = 'WRITERS' | 'READERS'
242
+ const WRITERS : RoutingControl = 'WRITERS'
243
+ const READERS : RoutingControl = 'READERS'
244
+ /**
245
+ * @typedef {'WRITERS'|'READERS' } RoutingControl
246
+ */
247
+ /**
248
+ * Constants that represents routing modes.
249
+ *
250
+ * @example
251
+ * driver.executeQuery("<QUERY>", <PARAMETERS>, { routing: neo4j.routing.WRITERS })
252
+ */
253
+ const routing = {
254
+ WRITERS ,
255
+ READERS
256
+ }
257
+
258
+ Object . freeze ( routing )
259
+
260
+ /**
261
+ * The query configuration
262
+ * @interface
263
+ * @experimental This can be changed or removed anytime.
264
+ * @see https://github.com/neo4j/neo4j-javascript-driver/discussions/1052
265
+ */
266
+ class QueryConfig < T = EagerResult > {
267
+ routing ?: RoutingControl
268
+ database ?: string
269
+ impersonatedUser ?: string
270
+ bookmarkManager ?: BookmarkManager | null
271
+ resultTransformer ?: ResultTransformer < T >
272
+
273
+ /**
274
+ * @constructor
275
+ * @private
276
+ */
277
+ private constructor ( ) {
278
+ /**
279
+ * Define the type of cluster member the query will be routed to.
280
+ *
281
+ * @type {RoutingControl }
282
+ */
283
+ this . routing = routing . WRITERS
284
+
285
+ /**
286
+ * Define the transformation will be applied to the Result before return from the
287
+ * query method.
288
+ *
289
+ * @type {ResultTransformer }
290
+ * @see {@link resultTransformers } for provided implementations.
291
+ */
292
+ this . resultTransformer = undefined
293
+
294
+ /**
295
+ * The database this session will operate on.
296
+ *
297
+ * @type {string|undefined }
298
+ */
299
+ this . database = ''
300
+
301
+ /**
302
+ * The username which the user wants to impersonate for the duration of the query.
303
+ *
304
+ * @type {string|undefined }
305
+ */
306
+ this . impersonatedUser = undefined
307
+
308
+ /**
309
+ * Configure a BookmarkManager for the session to use
310
+ *
311
+ * A BookmarkManager is a piece of software responsible for keeping casual consistency between different pieces of work by sharing bookmarks
312
+ * between the them.
313
+ *
314
+ * By default, it uses the driver's non mutable driver level bookmark manager. See, {@link Driver.queryBookmarkManager}
315
+ *
316
+ * Can be set to null to disable causal chaining.
317
+ * @type {BookmarkManager|null }
318
+ */
319
+ this . bookmarkManager = undefined
320
+ }
321
+ }
322
+
234
323
/**
235
324
* A driver maintains one or more {@link Session}s with a remote
236
325
* Neo4j instance. Through the {@link Session}s you can send queries
@@ -249,21 +338,24 @@ class Driver {
249
338
private readonly _createConnectionProvider : CreateConnectionProvider
250
339
private _connectionProvider : ConnectionProvider | null
251
340
private readonly _createSession : CreateSession
341
+ private readonly _queryBookmarkManager : BookmarkManager
342
+ private readonly _queryExecutor : QueryExecutor
252
343
253
344
/**
254
345
* You should not be calling this directly, instead use {@link driver}.
255
346
* @constructor
256
347
* @protected
257
348
* @param {Object } meta Metainformation about the driver
258
349
* @param {Object } config
259
- * @param {function(id: number, config:Object, log:Logger, hostNameResolver: ConfiguredCustomResolver): ConnectionProvider } createConnectonProvider Creates the connection provider
350
+ * @param {function(id: number, config:Object, log:Logger, hostNameResolver: ConfiguredCustomResolver): ConnectionProvider } createConnectionProvider Creates the connection provider
260
351
* @param {function(args): Session } createSession Creates the a session
261
352
*/
262
353
constructor (
263
354
meta : MetaInfo ,
264
355
config : DriverConfig = { } ,
265
- createConnectonProvider : CreateConnectionProvider ,
266
- createSession : CreateSession = args => new Session ( args )
356
+ createConnectionProvider : CreateConnectionProvider ,
357
+ createSession : CreateSession = args => new Session ( args ) ,
358
+ createQueryExecutor : CreateQueryExecutor = createQuery => new QueryExecutor ( createQuery )
267
359
) {
268
360
sanitizeConfig ( config )
269
361
@@ -275,8 +367,10 @@ class Driver {
275
367
this . _meta = meta
276
368
this . _config = config
277
369
this . _log = log
278
- this . _createConnectionProvider = createConnectonProvider
370
+ this . _createConnectionProvider = createConnectionProvider
279
371
this . _createSession = createSession
372
+ this . _queryBookmarkManager = bookmarkManager ( )
373
+ this . _queryExecutor = createQueryExecutor ( this . session . bind ( this ) )
280
374
281
375
/**
282
376
* Reference to the connection provider. Initialized lazily by {@link _getOrCreateConnectionProvider}.
@@ -288,6 +382,113 @@ class Driver {
288
382
this . _afterConstruction ( )
289
383
}
290
384
385
+ /**
386
+ * The bookmark managed used by {@link Driver.executeQuery}
387
+ *
388
+ * @experimental This can be changed or removed anytime.
389
+ * @type {BookmarkManager }
390
+ * @returns {BookmarkManager }
391
+ */
392
+ get queryBookmarkManager ( ) : BookmarkManager {
393
+ return this . _queryBookmarkManager
394
+ }
395
+
396
+ /**
397
+ * Executes a query in a retriable context and returns a {@link EagerResult}.
398
+ *
399
+ * This method is a shortcut for a {@link Session#executeRead} and {@link Session#executeWrite}.
400
+ *
401
+ * NOTE: Because it is an explicit transaction from the server point of view, Cypher queries using
402
+ * "CALL {} IN TRANSACTIONS" or the older "USING PERIODIC COMMIT" construct will not work (call
403
+ * {@link Session#run} for these).
404
+ *
405
+ * @example
406
+ * // Run a simple write query
407
+ * const { keys, records, summary } = await driver.executeQuery('CREATE (p:Person{ name: $name }) RETURN p', { name: 'Person1'})
408
+ *
409
+ * @example
410
+ * // Run a read query
411
+ * const { keys, records, summary } = await driver.executeQuery(
412
+ * 'MATCH (p:Person{ name: $name }) RETURN p',
413
+ * { name: 'Person1'},
414
+ * { routing: neo4j.routing.READERS})
415
+ *
416
+ * @example
417
+ * // Run a read query returning a Person Nodes per elementId
418
+ * const peopleMappedById = await driver.executeQuery(
419
+ * 'MATCH (p:Person{ name: $name }) RETURN p',
420
+ * { name: 'Person1'},
421
+ * {
422
+ * resultTransformer: neo4j.resultTransformers.mappedResultTransformer({
423
+ * map(record) {
424
+ * const p = record.get('p')
425
+ * return [p.elementId, p]
426
+ * },
427
+ * collect(elementIdPersonPairArray) {
428
+ * return new Map(elementIdPersonPairArray)
429
+ * }
430
+ * })
431
+ * }
432
+ * )
433
+ *
434
+ * const person = peopleMappedById.get("<ELEMENT_ID>")
435
+ *
436
+ * @example
437
+ * // these lines
438
+ * const transformedResult = await driver.executeQuery(
439
+ * "<QUERY>",
440
+ * <PARAMETERS>,
441
+ * {
442
+ * routing: neo4j.routing.WRITERS,
443
+ * resultTransformer: transformer,
444
+ * database: "<DATABASE>",
445
+ * impersonatedUser: "<USER>",
446
+ * bookmarkManager: bookmarkManager
447
+ * })
448
+ * // are equivalent to those
449
+ * const session = driver.session({
450
+ * database: "<DATABASE>",
451
+ * impersonatedUser: "<USER>",
452
+ * bookmarkManager: bookmarkManager
453
+ * })
454
+ *
455
+ * try {
456
+ * const transformedResult = await session.executeWrite(tx => {
457
+ * const result = tx.run("<QUERY>", <PARAMETERS>)
458
+ * return transformer(result)
459
+ * })
460
+ * } finally {
461
+ * await session.close()
462
+ * }
463
+ *
464
+ * @public
465
+ * @experimental This can be changed or removed anytime.
466
+ * @param {string | {text: string, parameters?: object} } query - Cypher query to execute
467
+ * @param {Object } parameters - Map with parameters to use in the query
468
+ * @param {QueryConfig<T> } config - The query configuration
469
+ * @returns {Promise<T> }
470
+ *
471
+ * @see {@link resultTransformers } for provided result transformers.
472
+ * @see https://github.com/neo4j/neo4j-javascript-driver/discussions/1052
473
+ */
474
+ async executeQuery < T > ( query : Query , parameters ?: any , config : QueryConfig < T > = { } ) : Promise < T > {
475
+ const bookmarkManager = config . bookmarkManager === null ? undefined : ( config . bookmarkManager ?? this . queryBookmarkManager )
476
+ const resultTransformer = ( config . resultTransformer ?? resultTransformers . eagerResultTransformer ( ) ) as ResultTransformer < T >
477
+ const routingConfig : string = config . routing ?? routing . WRITERS
478
+
479
+ if ( routingConfig !== routing . READERS && routingConfig !== routing . WRITERS ) {
480
+ throw newError ( `Illegal query routing config: "${ routingConfig } "` )
481
+ }
482
+
483
+ return await this . _queryExecutor . execute ( {
484
+ resultTransformer,
485
+ bookmarkManager,
486
+ routing : routingConfig ,
487
+ database : config . database ,
488
+ impersonatedUser : config . impersonatedUser
489
+ } , query , parameters )
490
+ }
491
+
291
492
/**
292
493
* Verifies connectivity of this driver by trying to open a connection with the provided driver options.
293
494
*
@@ -456,6 +657,7 @@ class Driver {
456
657
457
658
/**
458
659
* @protected
660
+ * @returns {void }
459
661
*/
460
662
_afterConstruction ( ) : void {
461
663
this . _log . info (
@@ -627,5 +829,6 @@ function createHostNameResolver (config: any): ConfiguredCustomResolver {
627
829
return new ConfiguredCustomResolver ( config . resolver )
628
830
}
629
831
630
- export { Driver , READ , WRITE , SessionConfig }
832
+ export { Driver , READ , WRITE , routing , SessionConfig , QueryConfig }
833
+ export type { RoutingControl }
631
834
export default Driver
0 commit comments