@@ -37,10 +37,15 @@ import {
3737 EncryptionLevel ,
3838 LoggingConfig ,
3939 TrustStrategy ,
40- SessionMode
40+ SessionMode ,
41+ Query
4142} from './types'
4243import { 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'
4449
4550const DEFAULT_MAX_CONNECTION_LIFETIME : number = 60 * 60 * 1000 // 1 hour
4651
@@ -91,6 +96,8 @@ type CreateSession = (args: {
9196 bookmarkManager ?: BookmarkManager
9297} ) => Session
9398
99+ type CreateQueryExecutor = ( createSession : ( config : { database ?: string , bookmarkManager ?: BookmarkManager } ) => Session ) => QueryExecutor
100+
94101interface DriverConfig {
95102 encrypted ?: EncryptionLevel | boolean
96103 trust ?: TrustStrategy
@@ -231,6 +238,88 @@ class SessionConfig {
231238 }
232239}
233240
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+
234323/**
235324 * A driver maintains one or more {@link Session}s with a remote
236325 * Neo4j instance. Through the {@link Session}s you can send queries
@@ -249,21 +338,24 @@ class Driver {
249338 private readonly _createConnectionProvider : CreateConnectionProvider
250339 private _connectionProvider : ConnectionProvider | null
251340 private readonly _createSession : CreateSession
341+ private readonly _queryBookmarkManager : BookmarkManager
342+ private readonly _queryExecutor : QueryExecutor
252343
253344 /**
254345 * You should not be calling this directly, instead use {@link driver}.
255346 * @constructor
256347 * @protected
257348 * @param {Object } meta Metainformation about the driver
258349 * @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
260351 * @param {function(args): Session } createSession Creates the a session
261352 */
262353 constructor (
263354 meta : MetaInfo ,
264355 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 )
267359 ) {
268360 sanitizeConfig ( config )
269361
@@ -275,8 +367,10 @@ class Driver {
275367 this . _meta = meta
276368 this . _config = config
277369 this . _log = log
278- this . _createConnectionProvider = createConnectonProvider
370+ this . _createConnectionProvider = createConnectionProvider
279371 this . _createSession = createSession
372+ this . _queryBookmarkManager = bookmarkManager ( )
373+ this . _queryExecutor = createQueryExecutor ( this . session . bind ( this ) )
280374
281375 /**
282376 * Reference to the connection provider. Initialized lazily by {@link _getOrCreateConnectionProvider}.
@@ -288,6 +382,113 @@ class Driver {
288382 this . _afterConstruction ( )
289383 }
290384
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+
291492 /**
292493 * Verifies connectivity of this driver by trying to open a connection with the provided driver options.
293494 *
@@ -456,6 +657,7 @@ class Driver {
456657
457658 /**
458659 * @protected
660+ * @returns {void }
459661 */
460662 _afterConstruction ( ) : void {
461663 this . _log . info (
@@ -627,5 +829,6 @@ function createHostNameResolver (config: any): ConfiguredCustomResolver {
627829 return new ConfiguredCustomResolver ( config . resolver )
628830}
629831
630- export { Driver , READ , WRITE , SessionConfig }
832+ export { Driver , READ , WRITE , routing , SessionConfig , QueryConfig }
833+ export type { RoutingControl }
631834export default Driver
0 commit comments