@@ -40,7 +40,11 @@ import {
40
40
SessionMode
41
41
} from './types'
42
42
import { ServerAddress } from './internal/server-address'
43
- import BookmarkManager from './bookmark-manager'
43
+ import BookmarkManager , { bookmarkManager } from './bookmark-manager'
44
+ import EagerResult , { createEagerResultFromResult } from './result-eager'
45
+ import ManagedTransaction from './transaction-managed'
46
+ import Result from './result'
47
+ import { Dict } from './record'
44
48
45
49
const DEFAULT_MAX_CONNECTION_LIFETIME : number = 60 * 60 * 1000 // 1 hour
46
50
@@ -159,7 +163,7 @@ class SessionConfig {
159
163
* Enabling it is done by supplying an BookmarkManager implementation instance to this param.
160
164
* A default implementation could be acquired by calling the factory function {@link bookmarkManager}.
161
165
*
162
- * **Warning**: Share the same BookmarkManager instance accross all session can have a negative impact
166
+ * **Warning**: Share the same BookmarkManager instance across all session can have a negative impact
163
167
* on performance since all the queries will wait for the latest changes being propagated across the cluster.
164
168
* For keeping consistency between a group of queries, use {@link Session} for grouping them.
165
169
* For keeping consistency between a group of sessions, use {@link BookmarkManager} instance for groupping them.
@@ -179,7 +183,7 @@ class SessionConfig {
179
183
*
180
184
* // Reading Driver User will wait of the changes being propagated to the server before RUN the query
181
185
* // So the 'Driver User' person should exist in the Result, unless deleted.
182
- * const linkedSesssion2 = await linkedSession2.run('CREATE (p:Person {name: $name}) RETURN p', { name: 'Driver User'})
186
+ * const linkedSession2 = await linkedSession2.run('CREATE (p:Person {name: $name}) RETURN p', { name: 'Driver User'})
183
187
*
184
188
* await linkedSession1.close()
185
189
* await linkedSession2.close()
@@ -193,6 +197,85 @@ class SessionConfig {
193
197
}
194
198
}
195
199
200
+ type RoutingControl = 'WRITERS' | 'READERS'
201
+ const WRITERS : RoutingControl = 'WRITERS'
202
+ const READERS : RoutingControl = 'READERS'
203
+ /**
204
+ * @typedef {'WRITERS'|'READERS' } RoutingControl
205
+ */
206
+ /**
207
+ * Constant that represents writers routing control.
208
+ *
209
+ * @example
210
+ * driver.query("<QUERY>", <PARAMETERS>, { routing: neo4j.routing.WRITERS })
211
+ */
212
+ const routing = {
213
+ WRITERS ,
214
+ READERS
215
+ }
216
+
217
+ Object . freeze ( routing )
218
+
219
+ /**
220
+ * The query configuration
221
+ * @interface
222
+ */
223
+ class QueryConfig < Entries extends Dict = Dict , T = EagerResult < Entries > > {
224
+ routing ?: RoutingControl
225
+ database ?: string
226
+ impersonatedUser ?: string
227
+ bookmarkManager ?: BookmarkManager
228
+ resultTransformer ?: ( result : Result ) => Promise < T >
229
+
230
+ /**
231
+ * @constructor
232
+ * @private
233
+ */
234
+ private constructor ( ) {
235
+ /**
236
+ * Define the type of cluster member the query will be routed to.
237
+ *
238
+ * @type {RoutingControl }
239
+ */
240
+ this . routing = routing . WRITERS
241
+
242
+ /**
243
+ * Define the transformation will be applied to the Result before return from the
244
+ * query method.
245
+ *
246
+ * @type {function<T>(result:Result): Promise<T> }
247
+ */
248
+ this . resultTransformer = undefined
249
+
250
+ /**
251
+ * The database this session will operate on.
252
+ *
253
+ * @type {string|undefined }
254
+ */
255
+ this . database = ''
256
+
257
+ /**
258
+ * The username which the user wants to impersonate for the duration of the query.
259
+ *
260
+ * @type {string|undefined }
261
+ */
262
+ this . impersonatedUser = undefined
263
+
264
+ /**
265
+ * Configure a BookmarkManager for the session to use
266
+ *
267
+ * A BookmarkManager is a piece of software responsible for keeping casual consistency between different sessions by sharing bookmarks
268
+ * between the them.
269
+ *
270
+ * By default, it uses the drivers non mutable driver level bookmark manager.
271
+ *
272
+ * Can be set to null to disable causal chaining.
273
+ * @type {BookmarkManager }
274
+ */
275
+ this . bookmarkManager = undefined
276
+ }
277
+ }
278
+
196
279
/**
197
280
* A driver maintains one or more {@link Session}s with a remote
198
281
* Neo4j instance. Through the {@link Session}s you can send queries
@@ -211,20 +294,21 @@ class Driver {
211
294
private readonly _createConnectionProvider : CreateConnectionProvider
212
295
private _connectionProvider : ConnectionProvider | null
213
296
private readonly _createSession : CreateSession
297
+ private readonly _queryBookmarkManager : BookmarkManager
214
298
215
299
/**
216
300
* You should not be calling this directly, instead use {@link driver}.
217
301
* @constructor
218
302
* @protected
219
303
* @param {Object } meta Metainformation about the driver
220
304
* @param {Object } config
221
- * @param {function(id: number, config:Object, log:Logger, hostNameResolver: ConfiguredCustomResolver): ConnectionProvider } createConnectonProvider Creates the connection provider
305
+ * @param {function(id: number, config:Object, log:Logger, hostNameResolver: ConfiguredCustomResolver): ConnectionProvider } createConnectionProvider Creates the connection provider
222
306
* @param {function(args): Session } createSession Creates the a session
223
307
*/
224
308
constructor (
225
309
meta : MetaInfo ,
226
310
config : DriverConfig = { } ,
227
- createConnectonProvider : CreateConnectionProvider ,
311
+ createConnectionProvider : CreateConnectionProvider ,
228
312
createSession : CreateSession = args => new Session ( args )
229
313
) {
230
314
sanitizeConfig ( config )
@@ -237,8 +321,9 @@ class Driver {
237
321
this . _meta = meta
238
322
this . _config = config
239
323
this . _log = log
240
- this . _createConnectionProvider = createConnectonProvider
324
+ this . _createConnectionProvider = createConnectionProvider
241
325
this . _createSession = createSession
326
+ this . _queryBookmarkManager = bookmarkManager ( )
242
327
243
328
/**
244
329
* Reference to the connection provider. Initialized lazily by {@link _getOrCreateConnectionProvider}.
@@ -250,6 +335,85 @@ class Driver {
250
335
this . _afterConstruction ( )
251
336
}
252
337
338
+ /**
339
+ * The bookmark managed used by {@link Driver.executeQuery}
340
+ *
341
+ * @type {BookmarkManager }
342
+ * @returns {BookmarkManager }
343
+ */
344
+ get queryBookmarkManager ( ) : BookmarkManager {
345
+ return this . _queryBookmarkManager
346
+ }
347
+
348
+ /**
349
+ * Executes a query in a retriable context and returns a {@link EagerResult}.
350
+ *
351
+ * This method is a shortcut for a transaction function
352
+ *
353
+ * @example
354
+ * // Run a simple write query
355
+ * const { keys, records, summary } = await driver.executeQuery('CREATE (p:Person{ name: $name }) RETURN p', { name: 'Person1'})
356
+ *
357
+ * @example
358
+ * // Run a read query
359
+ * const { keys, records, summary } = await driver.executeQuery(
360
+ * 'MATCH (p:Person{ name: $name }) RETURN p',
361
+ * { name: 'Person1'},
362
+ * { routing: neo4j.routing.READERS})
363
+ *
364
+ * @example
365
+ * // this lines
366
+ * const transformedResult = await driver.executeQuery(
367
+ * "<QUERY>",
368
+ * <PARAMETERS>,
369
+ * QueryConfig {
370
+ * routing: neo4j.routing.WRITERS,
371
+ * resultTransformer: transformer,
372
+ * database: "<DATABASE>",
373
+ * impersonatedUser: "<USER>",
374
+ * bookmarkManager: bookmarkManager
375
+ * }
376
+ * )
377
+ * // are equivalent to this ones
378
+ * const session = driver.session({
379
+ * database: "<DATABASE>",
380
+ * impersonatedUser: "<USER>",
381
+ * bookmarkManager: bookmarkManager
382
+ * })
383
+ *
384
+ * try {
385
+ * const transformedResult = await session.executeWrite(tx => {
386
+ * const result = tx.run("<QUERY>", <PARAMETERS>)
387
+ * return transformer(result)
388
+ * })
389
+ * } finally {
390
+ * await session.close()
391
+ * }
392
+ *
393
+ * @public
394
+ * @param {string } query - Cypher query to execute
395
+ * @param {Object } parameters - Map with parameters to use in query
396
+ * @param {QueryConfig<T> } config - The query configuration
397
+ * @returns {Promise<T> }
398
+ */
399
+ async executeQuery < Entries extends Dict = Dict , T = EagerResult < Entries > > ( query : string , parameters ?: any , config : QueryConfig < T > = { } ) : Promise < T > {
400
+ const bookmarkManager = config . bookmarkManager === null ? undefined : config . bookmarkManager ?? this . queryBookmarkManager
401
+ const session = this . session ( { database : config . database , bookmarkManager } )
402
+ try {
403
+ const execute = config . routing === routing . READERS
404
+ ? session . executeRead . bind ( session )
405
+ : session . executeWrite . bind ( session )
406
+ const transformer = config . resultTransformer ?? createEagerResultFromResult
407
+
408
+ return ( await execute ( ( tx : ManagedTransaction ) => {
409
+ const result = tx . run ( query , parameters )
410
+ return transformer ( result )
411
+ } ) ) as unknown as T
412
+ } finally {
413
+ await session . close ( )
414
+ }
415
+ }
416
+
253
417
/**
254
418
* Verifies connectivity of this driver by trying to open a connection with the provided driver options.
255
419
*
@@ -418,6 +582,7 @@ class Driver {
418
582
419
583
/**
420
584
* @protected
585
+ * @returns {void }
421
586
*/
422
587
_afterConstruction ( ) : void {
423
588
this . _log . info (
@@ -589,6 +754,6 @@ function createHostNameResolver (config: any): ConfiguredCustomResolver {
589
754
return new ConfiguredCustomResolver ( config . resolver )
590
755
}
591
756
592
- export { Driver , READ , WRITE }
593
- export type { SessionConfig }
757
+ export { Driver , READ , WRITE , routing }
758
+ export type { SessionConfig , QueryConfig , RoutingControl }
594
759
export default Driver
0 commit comments