Skip to content

Introduce Driver.executeQuery #1006

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 39 commits into from
Jan 26, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
39 commits
Select commit Hold shift + click to select a range
d7079f7
Introduce `Driver.executeQuery`
bigmontz Oct 10, 2022
0716482
Verify 'routing' export
bigmontz Oct 10, 2022
fd7b85d
Add tests for `executeQuery`
bigmontz Oct 11, 2022
d5ee8a0
Add type-safety check
bigmontz Oct 11, 2022
e4512fc
Add tests to `QueryExecutor`
bigmontz Oct 11, 2022
686670d
Add tests to EagerResult and createEagerResultFromResult
bigmontz Oct 11, 2022
027197c
Fix QueryExecutor issue when session.close fails
bigmontz Oct 12, 2022
2ac9cbf
Add initial tk-backend implementation
bigmontz Oct 12, 2022
0427623
backend:fix params mapping
bigmontz Oct 12, 2022
ecfa12e
backend: support routing in the ExecuteQuery
bigmontz Oct 12, 2022
978d227
backend: ExecuteQuery config.database
bigmontz Oct 12, 2022
6ef9ac6
backend: add impersonated user config
bigmontz Oct 12, 2022
9968650
backend: fix routing configuration
bigmontz Oct 12, 2022
4d175bc
backend: add feature flag
bigmontz Oct 12, 2022
391b58e
Add bookmark manager configuration
bigmontz Oct 12, 2022
d6f6791
backend: improve bookmark manager configuration
bigmontz Oct 12, 2022
4fd1428
Update feature ExecuteQuery name in tk
bigmontz Oct 13, 2022
5aba016
Apply suggestions from code review
bigmontz Oct 13, 2022
65c6318
Unskipping tests
bigmontz Oct 13, 2022
7311935
Indentation, readability and sync deno
bigmontz Oct 13, 2022
2bb328b
Remove un-needed type definitions
bigmontz Oct 13, 2022
79a3287
Remove unknown typing
bigmontz Oct 13, 2022
91a0e88
Validate routing configuration
bigmontz Oct 13, 2022
51608b4
Clean type assertion
bigmontz Oct 13, 2022
1f8bedb
Improve docs
bigmontz Oct 13, 2022
cdeb041
Apply suggestions from code review
bigmontz Oct 14, 2022
ac72ff6
Generate deno code
bigmontz Oct 14, 2022
a8815fd
backend: adjust bookmarkManagerId=-1 as set to null
bigmontz Oct 14, 2022
9bddb62
Improving typing
bigmontz Oct 17, 2022
05a6d8c
Fix rebase mistakes
bigmontz Jan 23, 2023
31aac09
Move the Result Transformers to their own file
bigmontz Jan 23, 2023
c090c52
Add mappedResultTransformer
bigmontz Jan 24, 2023
97d7e08
Add tests to .mappedResultTransformer
bigmontz Jan 24, 2023
3ab85e1
Improve docs
bigmontz Jan 24, 2023
396fea3
Add feedback link
bigmontz Jan 24, 2023
415d0c2
Add missing docs
bigmontz Jan 24, 2023
209f186
Apply suggestions from code review
bigmontz Jan 25, 2023
6f1826a
Address comments in the PR
bigmontz Jan 25, 2023
11a51e3
Improve docs
bigmontz Jan 25, 2023
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions packages/core/src/connection-provider.ts
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,7 @@ class ConnectionProvider {
* @property {Bookmarks} param.bookmarks - the bookmarks to send to routing discovery
* @property {string} param.impersonatedUser - the impersonated user
* @property {function (databaseName:string?)} param.onDatabaseNameResolved - Callback called when the database name get resolved
* @returns {Promise<Connection>}
*/
acquireConnection (param?: {
accessMode?: string
Expand Down
217 changes: 210 additions & 7 deletions packages/core/src/driver.ts
Original file line number Diff line number Diff line change
Expand Up @@ -37,10 +37,15 @@ import {
EncryptionLevel,
LoggingConfig,
TrustStrategy,
SessionMode
SessionMode,
Query
} from './types'
import { ServerAddress } from './internal/server-address'
import BookmarkManager from './bookmark-manager'
import BookmarkManager, { bookmarkManager } from './bookmark-manager'
import EagerResult from './result-eager'
import resultTransformers, { ResultTransformer } from './result-transformers'
import QueryExecutor from './internal/query-executor'
import { newError } from './error'

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

Expand Down Expand Up @@ -91,6 +96,8 @@ type CreateSession = (args: {
bookmarkManager?: BookmarkManager
}) => Session

type CreateQueryExecutor = (createSession: (config: { database?: string, bookmarkManager?: BookmarkManager }) => Session) => QueryExecutor

interface DriverConfig {
encrypted?: EncryptionLevel | boolean
trust?: TrustStrategy
Expand Down Expand Up @@ -231,6 +238,88 @@ class SessionConfig {
}
}

type RoutingControl = 'WRITERS' | 'READERS'
const WRITERS: RoutingControl = 'WRITERS'
const READERS: RoutingControl = 'READERS'
/**
* @typedef {'WRITERS'|'READERS'} RoutingControl
*/
/**
* Constants that represents routing modes.
*
* @example
* driver.executeQuery("<QUERY>", <PARAMETERS>, { routing: neo4j.routing.WRITERS })
*/
const routing = {
WRITERS,
READERS
}

Object.freeze(routing)

/**
* The query configuration
* @interface
* @experimental This can be changed or removed anytime.
* @see https://github.com/neo4j/neo4j-javascript-driver/discussions/1052
*/
class QueryConfig<T = EagerResult> {
routing?: RoutingControl
database?: string
impersonatedUser?: string
bookmarkManager?: BookmarkManager | null
resultTransformer?: ResultTransformer<T>

/**
* @constructor
* @private
*/
private constructor () {
/**
* Define the type of cluster member the query will be routed to.
*
* @type {RoutingControl}
*/
this.routing = routing.WRITERS

/**
* Define the transformation will be applied to the Result before return from the
* query method.
*
* @type {ResultTransformer}
* @see {@link resultTransformers} for provided implementations.
*/
this.resultTransformer = undefined

/**
* The database this session will operate on.
*
* @type {string|undefined}
*/
this.database = ''

/**
* The username which the user wants to impersonate for the duration of the query.
*
* @type {string|undefined}
*/
this.impersonatedUser = undefined

/**
* Configure a BookmarkManager for the session to use
*
* A BookmarkManager is a piece of software responsible for keeping casual consistency between different pieces of work by sharing bookmarks
* between the them.
*
* By default, it uses the driver's non mutable driver level bookmark manager. See, {@link Driver.queryBookmarkManager}
*
* Can be set to null to disable causal chaining.
* @type {BookmarkManager|null}
*/
this.bookmarkManager = undefined
}
}

/**
* A driver maintains one or more {@link Session}s with a remote
* Neo4j instance. Through the {@link Session}s you can send queries
Expand All @@ -249,21 +338,24 @@ class Driver {
private readonly _createConnectionProvider: CreateConnectionProvider
private _connectionProvider: ConnectionProvider | null
private readonly _createSession: CreateSession
private readonly _queryBookmarkManager: BookmarkManager
private readonly _queryExecutor: QueryExecutor

/**
* You should not be calling this directly, instead use {@link driver}.
* @constructor
* @protected
* @param {Object} meta Metainformation about the driver
* @param {Object} config
* @param {function(id: number, config:Object, log:Logger, hostNameResolver: ConfiguredCustomResolver): ConnectionProvider } createConnectonProvider Creates the connection provider
* @param {function(id: number, config:Object, log:Logger, hostNameResolver: ConfiguredCustomResolver): ConnectionProvider } createConnectionProvider Creates the connection provider
* @param {function(args): Session } createSession Creates the a session
*/
constructor (
meta: MetaInfo,
config: DriverConfig = {},
createConnectonProvider: CreateConnectionProvider,
createSession: CreateSession = args => new Session(args)
createConnectionProvider: CreateConnectionProvider,
createSession: CreateSession = args => new Session(args),
createQueryExecutor: CreateQueryExecutor = createQuery => new QueryExecutor(createQuery)
) {
sanitizeConfig(config)

Expand All @@ -275,8 +367,10 @@ class Driver {
this._meta = meta
this._config = config
this._log = log
this._createConnectionProvider = createConnectonProvider
this._createConnectionProvider = createConnectionProvider
this._createSession = createSession
this._queryBookmarkManager = bookmarkManager()
this._queryExecutor = createQueryExecutor(this.session.bind(this))

/**
* Reference to the connection provider. Initialized lazily by {@link _getOrCreateConnectionProvider}.
Expand All @@ -288,6 +382,113 @@ class Driver {
this._afterConstruction()
}

/**
* The bookmark managed used by {@link Driver.executeQuery}
*
* @experimental This can be changed or removed anytime.
* @type {BookmarkManager}
* @returns {BookmarkManager}
*/
get queryBookmarkManager (): BookmarkManager {
return this._queryBookmarkManager
}

/**
* Executes a query in a retriable context and returns a {@link EagerResult}.
*
* This method is a shortcut for a {@link Session#executeRead} and {@link Session#executeWrite}.
*
* NOTE: Because it is an explicit transaction from the server point of view, Cypher queries using
* "CALL {} IN TRANSACTIONS" or the older "USING PERIODIC COMMIT" construct will not work (call
* {@link Session#run} for these).
*
* @example
* // Run a simple write query
* const { keys, records, summary } = await driver.executeQuery('CREATE (p:Person{ name: $name }) RETURN p', { name: 'Person1'})
*
* @example
* // Run a read query
* const { keys, records, summary } = await driver.executeQuery(
* 'MATCH (p:Person{ name: $name }) RETURN p',
* { name: 'Person1'},
* { routing: neo4j.routing.READERS})
*
* @example
* // Run a read query returning a Person Nodes per elementId
* const peopleMappedById = await driver.executeQuery(
* 'MATCH (p:Person{ name: $name }) RETURN p',
* { name: 'Person1'},
* {
* resultTransformer: neo4j.resultTransformers.mappedResultTransformer({
* map(record) {
* const p = record.get('p')
* return [p.elementId, p]
* },
* collect(elementIdPersonPairArray) {
* return new Map(elementIdPersonPairArray)
* }
* })
* }
* )
*
* const person = peopleMappedById.get("<ELEMENT_ID>")
*
* @example
* // these lines
* const transformedResult = await driver.executeQuery(
* "<QUERY>",
* <PARAMETERS>,
* {
* routing: neo4j.routing.WRITERS,
* resultTransformer: transformer,
* database: "<DATABASE>",
* impersonatedUser: "<USER>",
* bookmarkManager: bookmarkManager
* })
* // are equivalent to those
* const session = driver.session({
* database: "<DATABASE>",
* impersonatedUser: "<USER>",
* bookmarkManager: bookmarkManager
* })
*
* try {
* const transformedResult = await session.executeWrite(tx => {
* const result = tx.run("<QUERY>", <PARAMETERS>)
* return transformer(result)
* })
* } finally {
* await session.close()
* }
*
* @public
* @experimental This can be changed or removed anytime.
* @param {string | {text: string, parameters?: object}} query - Cypher query to execute
* @param {Object} parameters - Map with parameters to use in the query
* @param {QueryConfig<T>} config - The query configuration
* @returns {Promise<T>}
*
* @see {@link resultTransformers} for provided result transformers.
* @see https://github.com/neo4j/neo4j-javascript-driver/discussions/1052
*/
async executeQuery<T> (query: Query, parameters?: any, config: QueryConfig<T> = {}): Promise<T> {
const bookmarkManager = config.bookmarkManager === null ? undefined : (config.bookmarkManager ?? this.queryBookmarkManager)
const resultTransformer = (config.resultTransformer ?? resultTransformers.eagerResultTransformer()) as ResultTransformer<T>
const routingConfig: string = config.routing ?? routing.WRITERS

if (routingConfig !== routing.READERS && routingConfig !== routing.WRITERS) {
throw newError(`Illegal query routing config: "${routingConfig}"`)
}

return await this._queryExecutor.execute({
resultTransformer,
bookmarkManager,
routing: routingConfig,
database: config.database,
impersonatedUser: config.impersonatedUser
}, query, parameters)
}

/**
* Verifies connectivity of this driver by trying to open a connection with the provided driver options.
*
Expand Down Expand Up @@ -456,6 +657,7 @@ class Driver {

/**
* @protected
* @returns {void}
*/
_afterConstruction (): void {
this._log.info(
Expand Down Expand Up @@ -627,5 +829,6 @@ function createHostNameResolver (config: any): ConfiguredCustomResolver {
return new ConfiguredCustomResolver(config.resolver)
}

export { Driver, READ, WRITE, SessionConfig }
export { Driver, READ, WRITE, routing, SessionConfig, QueryConfig }
export type { RoutingControl }
export default Driver
19 changes: 15 additions & 4 deletions packages/core/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -67,6 +67,7 @@ import ResultSummary, {
Stats
} from './result-summary'
import Result, { QueryResult, ResultObserver } from './result'
import EagerResult from './result-eager'
import ConnectionProvider from './connection-provider'
import Connection from './connection'
import Transaction from './transaction'
Expand All @@ -76,9 +77,10 @@ import Session, { TransactionConfig } from './session'
import Driver, * as driver from './driver'
import auth from './auth'
import BookmarkManager, { BookmarkManagerConfig, bookmarkManager } from './bookmark-manager'
import { SessionConfig } from './driver'
import { SessionConfig, QueryConfig, RoutingControl, routing } from './driver'
import * as types from './types'
import * as json from './json'
import resultTransformers, { ResultTransformer } from './result-transformers'
import * as internal from './internal' // todo: removed afterwards

/**
Expand Down Expand Up @@ -139,6 +141,7 @@ const forExport = {
QueryStatistics,
Stats,
Result,
EagerResult,
Transaction,
ManagedTransaction,
TransactionPromise,
Expand All @@ -149,7 +152,9 @@ const forExport = {
driver,
json,
auth,
bookmarkManager
bookmarkManager,
routing,
resultTransformers
}

export {
Expand Down Expand Up @@ -198,6 +203,7 @@ export {
QueryStatistics,
Stats,
Result,
EagerResult,
ConnectionProvider,
Connection,
Transaction,
Expand All @@ -209,7 +215,9 @@ export {
driver,
json,
auth,
bookmarkManager
bookmarkManager,
routing,
resultTransformers
}

export type {
Expand All @@ -221,7 +229,10 @@ export type {
TransactionConfig,
BookmarkManager,
BookmarkManagerConfig,
SessionConfig
SessionConfig,
QueryConfig,
RoutingControl,
ResultTransformer
}

export default forExport
Loading