Skip to content

Commit 558abbe

Browse files
committed
Introduce Driver.executeQuery
This method gives the user a simple interface and obvious place to start with the driver. Behind this method the full retry mechanism will be present. The results are eagerly returned and in memory. With this, we have removed the need for the user to have knowledge of transactions, routing control, streaming of results and cursor lifetimes, and any of the more complex concepts that are exposed when using the session object.
1 parent 898680e commit 558abbe

File tree

14 files changed

+575
-30
lines changed

14 files changed

+575
-30
lines changed

packages/core/src/connection-provider.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -43,6 +43,7 @@ class ConnectionProvider {
4343
* @property {Bookmarks} param.bookmarks - the bookmarks to send to routing discovery
4444
* @property {string} param.impersonatedUser - the impersonated user
4545
* @property {function (databaseName:string?)} param.onDatabaseNameResolved - Callback called when the database name get resolved
46+
* @returns {Promise<Connection>}
4647
*/
4748
acquireConnection (param?: {
4849
accessMode?: string

packages/core/src/driver.ts

Lines changed: 173 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -40,7 +40,11 @@ import {
4040
SessionMode
4141
} from './types'
4242
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'
4448

4549
const DEFAULT_MAX_CONNECTION_LIFETIME: number = 60 * 60 * 1000 // 1 hour
4650

@@ -159,7 +163,7 @@ class SessionConfig {
159163
* Enabling it is done by supplying an BookmarkManager implementation instance to this param.
160164
* A default implementation could be acquired by calling the factory function {@link bookmarkManager}.
161165
*
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
163167
* on performance since all the queries will wait for the latest changes being propagated across the cluster.
164168
* For keeping consistency between a group of queries, use {@link Session} for grouping them.
165169
* For keeping consistency between a group of sessions, use {@link BookmarkManager} instance for groupping them.
@@ -179,7 +183,7 @@ class SessionConfig {
179183
*
180184
* // Reading Driver User will wait of the changes being propagated to the server before RUN the query
181185
* // 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'})
183187
*
184188
* await linkedSession1.close()
185189
* await linkedSession2.close()
@@ -193,6 +197,85 @@ class SessionConfig {
193197
}
194198
}
195199

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+
196279
/**
197280
* A driver maintains one or more {@link Session}s with a remote
198281
* Neo4j instance. Through the {@link Session}s you can send queries
@@ -211,20 +294,21 @@ class Driver {
211294
private readonly _createConnectionProvider: CreateConnectionProvider
212295
private _connectionProvider: ConnectionProvider | null
213296
private readonly _createSession: CreateSession
297+
private readonly _queryBookmarkManager: BookmarkManager
214298

215299
/**
216300
* You should not be calling this directly, instead use {@link driver}.
217301
* @constructor
218302
* @protected
219303
* @param {Object} meta Metainformation about the driver
220304
* @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
222306
* @param {function(args): Session } createSession Creates the a session
223307
*/
224308
constructor (
225309
meta: MetaInfo,
226310
config: DriverConfig = {},
227-
createConnectonProvider: CreateConnectionProvider,
311+
createConnectionProvider: CreateConnectionProvider,
228312
createSession: CreateSession = args => new Session(args)
229313
) {
230314
sanitizeConfig(config)
@@ -237,8 +321,9 @@ class Driver {
237321
this._meta = meta
238322
this._config = config
239323
this._log = log
240-
this._createConnectionProvider = createConnectonProvider
324+
this._createConnectionProvider = createConnectionProvider
241325
this._createSession = createSession
326+
this._queryBookmarkManager = bookmarkManager()
242327

243328
/**
244329
* Reference to the connection provider. Initialized lazily by {@link _getOrCreateConnectionProvider}.
@@ -250,6 +335,85 @@ class Driver {
250335
this._afterConstruction()
251336
}
252337

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+
253417
/**
254418
* Verifies connectivity of this driver by trying to open a connection with the provided driver options.
255419
*
@@ -418,6 +582,7 @@ class Driver {
418582

419583
/**
420584
* @protected
585+
* @returns {void}
421586
*/
422587
_afterConstruction (): void {
423588
this._log.info(
@@ -589,6 +754,6 @@ function createHostNameResolver (config: any): ConfiguredCustomResolver {
589754
return new ConfiguredCustomResolver(config.resolver)
590755
}
591756

592-
export { Driver, READ, WRITE }
593-
export type { SessionConfig }
757+
export { Driver, READ, WRITE, routing }
758+
export type { SessionConfig, QueryConfig, RoutingControl }
594759
export default Driver

packages/core/src/index.ts

Lines changed: 11 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -67,6 +67,7 @@ import ResultSummary, {
6767
Stats
6868
} from './result-summary'
6969
import Result, { QueryResult, ResultObserver } from './result'
70+
import EagerResult from './result-eager'
7071
import ConnectionProvider from './connection-provider'
7172
import Connection from './connection'
7273
import Transaction from './transaction'
@@ -76,7 +77,7 @@ import Session, { TransactionConfig } from './session'
7677
import Driver, * as driver from './driver'
7778
import auth from './auth'
7879
import BookmarkManager, { BookmarkManagerConfig, bookmarkManager } from './bookmark-manager'
79-
import { SessionConfig } from './driver'
80+
import { SessionConfig, QueryConfig, RoutingControl, routing } from './driver'
8081
import * as types from './types'
8182
import * as json from './json'
8283
import * as internal from './internal' // todo: removed afterwards
@@ -139,6 +140,7 @@ const forExport = {
139140
QueryStatistics,
140141
Stats,
141142
Result,
143+
EagerResult,
142144
Transaction,
143145
ManagedTransaction,
144146
TransactionPromise,
@@ -149,7 +151,8 @@ const forExport = {
149151
driver,
150152
json,
151153
auth,
152-
bookmarkManager
154+
bookmarkManager,
155+
routing
153156
}
154157

155158
export {
@@ -198,6 +201,7 @@ export {
198201
QueryStatistics,
199202
Stats,
200203
Result,
204+
EagerResult,
201205
ConnectionProvider,
202206
Connection,
203207
Transaction,
@@ -209,7 +213,8 @@ export {
209213
driver,
210214
json,
211215
auth,
212-
bookmarkManager
216+
bookmarkManager,
217+
routing
213218
}
214219

215220
export type {
@@ -221,7 +226,9 @@ export type {
221226
TransactionConfig,
222227
BookmarkManager,
223228
BookmarkManagerConfig,
224-
SessionConfig
229+
SessionConfig,
230+
QueryConfig,
231+
RoutingControl
225232
}
226233

227234
export default forExport

packages/core/src/record.ts

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -243,3 +243,7 @@ class Record<
243243
}
244244

245245
export default Record
246+
247+
export type {
248+
Dict
249+
}

0 commit comments

Comments
 (0)