diff --git a/src/driver.js b/src/driver.js index c67e9bd3d..afa6e9b76 100644 --- a/src/driver.js +++ b/src/driver.js @@ -109,12 +109,13 @@ class Driver { /** * Verifies connectivity of this driver by trying to open a connection with the provided driver options. + * @param {string} [db=''] the target database to verify connectivity for. * @returns {Promise} promise resolved with server info or rejected with error. */ - verifyConnectivity () { + verifyConnectivity ({ db = '' } = {}) { const connectionProvider = this._getOrCreateConnectionProvider() const connectivityVerifier = new ConnectivityVerifier(connectionProvider) - return connectivityVerifier.verify() + return connectivityVerifier.verify({ db }) } /** @@ -178,18 +179,29 @@ class Driver { * it is closed, the underlying connection will be released to the connection * pool and made available for others to use. * - * @param {string} [mode=WRITE] the access mode of this session, allowed values are {@link READ} and {@link WRITE}. - * @param {string|string[]} [bookmarkOrBookmarks=null] the initial reference or references to some previous + * @param {string} [defaultAccessMode=WRITE] the access mode of this session, allowed values are {@link READ} and {@link WRITE}. + * @param {string|string[]} [bookmarks=null] the initial reference or references to some previous * transactions. Value is optional and absence indicates that that the bookmarks do not exist or are unknown. + * @param {string} [db=''] the database this session will connect to. * @return {Session} new session. */ - session (mode, bookmarkOrBookmarks) { - const sessionMode = Driver._validateSessionMode(mode) + session ({ + defaultAccessMode = WRITE, + bookmarks: bookmarkOrBookmarks, + db = '' + } = {}) { + const sessionMode = Driver._validateSessionMode(defaultAccessMode) const connectionProvider = this._getOrCreateConnectionProvider() const bookmark = bookmarkOrBookmarks ? new Bookmark(bookmarkOrBookmarks) : Bookmark.empty() - return new Session(sessionMode, connectionProvider, bookmark, this._config) + return new Session({ + mode: sessionMode, + db, + connectionProvider, + bookmark, + config: this._config + }) } static _validateSessionMode (rawMode) { diff --git a/src/internal/bolt-protocol-util.js b/src/internal/bolt-protocol-util.js new file mode 100644 index 000000000..154f34826 --- /dev/null +++ b/src/internal/bolt-protocol-util.js @@ -0,0 +1,60 @@ +/** + * Copyright (c) 2002-2019 "Neo4j," + * Neo4j Sweden AB [http://neo4j.com] + * + * This file is part of Neo4j. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +import { newError } from '../error' + +/** + * @param {TxConfig} txConfig the auto-commit transaction configuration. + * @param {Connection} connection the connection. + * @param {StreamObserver} observer the response observer. + */ +function assertTxConfigIsEmpty (txConfig, connection, observer) { + if (txConfig && !txConfig.isEmpty()) { + const error = newError( + 'Driver is connected to the database that does not support transaction configuration. ' + + 'Please upgrade to neo4j 3.5.0 or later in order to use this functionality' + ) + + // unsupported API was used, consider this a fatal error for the current connection + connection._handleFatalError(error) + observer.onError(error) + throw error + } +} + +/** + * Asserts that the passed-in database name is empty. + * @param {string} db + * @param {Connection} connection + * @param {StreamObserver} observer + */ +function assertDbIsEmpty (db, connection, observer) { + if (db) { + const error = newError( + 'Driver is connected to the database that does not support multiple databases. ' + + 'Please upgrade to neo4j 4.0.0 or later in order to use this functionality' + ) + + // unsupported API was used, consider this a fatal error for the current connection + connection._handleFatalError(error) + observer.onError(error) + throw error + } +} + +export { assertDbIsEmpty, assertTxConfigIsEmpty } diff --git a/src/internal/bolt-protocol-v1.js b/src/internal/bolt-protocol-v1.js index af0434003..3a2cfb328 100644 --- a/src/internal/bolt-protocol-v1.js +++ b/src/internal/bolt-protocol-v1.js @@ -18,10 +18,10 @@ */ import RequestMessage from './request-message' import * as v1 from './packstream-v1' -import { newError } from '../error' import Bookmark from './bookmark' import TxConfig from './tx-config' import { ACCESS_MODE_WRITE } from './constants' +import { assertDbIsEmpty, assertTxConfigIsEmpty } from './bolt-protocol-util' export default class BoltProtocol { /** @@ -78,13 +78,15 @@ export default class BoltProtocol { /** * Begin an explicit transaction. + * @param {StreamObserver} observer the response observer. * @param {Bookmark} bookmark the bookmark. * @param {TxConfig} txConfig the configuration. + * @param {string} db the target database name. * @param {string} mode the access mode. - * @param {StreamObserver} observer the response observer. */ - beginTransaction (bookmark, txConfig, mode, observer) { + beginTransaction (observer, { bookmark, txConfig, db, mode }) { assertTxConfigIsEmpty(txConfig, this._connection, observer) + assertDbIsEmpty(db, this._connection, observer) const runMessage = RequestMessage.run( 'BEGIN', @@ -103,14 +105,11 @@ export default class BoltProtocol { commitTransaction (observer) { // WRITE access mode is used as a place holder here, it has // no effect on behaviour for Bolt V1 & V2 - this.run( - 'COMMIT', - {}, - Bookmark.empty(), - TxConfig.empty(), - ACCESS_MODE_WRITE, - observer - ) + this.run('COMMIT', {}, observer, { + bookmark: Bookmark.empty(), + txConfig: TxConfig.empty(), + mode: ACCESS_MODE_WRITE + }) } /** @@ -120,28 +119,28 @@ export default class BoltProtocol { rollbackTransaction (observer) { // WRITE access mode is used as a place holder here, it has // no effect on behaviour for Bolt V1 & V2 - this.run( - 'ROLLBACK', - {}, - Bookmark.empty(), - TxConfig.empty(), - ACCESS_MODE_WRITE, - observer - ) + this.run('ROLLBACK', {}, observer, { + bookmark: Bookmark.empty(), + txConfig: TxConfig.empty(), + mode: ACCESS_MODE_WRITE + }) } /** * Send a Cypher statement through the underlying connection. * @param {string} statement the cypher statement. * @param {object} parameters the statement parameters. + * @param {StreamObserver} observer the response observer. * @param {Bookmark} bookmark the bookmark. * @param {TxConfig} txConfig the auto-commit transaction configuration. + * @param {string} db the target database name. * @param {string} mode the access mode. - * @param {StreamObserver} observer the response observer. */ - run (statement, parameters, bookmark, txConfig, mode, observer) { - // bookmark and mode are ignored in this versioon of the protocol + run (statement, parameters, observer, { bookmark, txConfig, db, mode }) { + // bookmark and mode are ignored in this version of the protocol assertTxConfigIsEmpty(txConfig, this._connection, observer) + // passing in a db name on this protocol version throws an error + assertDbIsEmpty(db, this._connection, observer) const runMessage = RequestMessage.run(statement, parameters) const pullAllMessage = RequestMessage.pullAll() @@ -167,22 +166,3 @@ export default class BoltProtocol { return new v1.Unpacker(disableLosslessIntegers) } } - -/** - * @param {TxConfig} txConfig the auto-commit transaction configuration. - * @param {Connection} connection the connection. - * @param {StreamObserver} observer the response observer. - */ -function assertTxConfigIsEmpty (txConfig, connection, observer) { - if (!txConfig.isEmpty()) { - const error = newError( - 'Driver is connected to the database that does not support transaction configuration. ' + - 'Please upgrade to neo4j 3.5.0 or later in order to use this functionality' - ) - - // unsupported API was used, consider this a fatal error for the current connection - connection._handleFatalError(error) - observer.onError(error) - throw error - } -} diff --git a/src/internal/bolt-protocol-v3.js b/src/internal/bolt-protocol-v3.js index 175ef5dee..bad399f09 100644 --- a/src/internal/bolt-protocol-v3.js +++ b/src/internal/bolt-protocol-v3.js @@ -18,6 +18,7 @@ */ import BoltProtocolV2 from './bolt-protocol-v2' import RequestMessage from './request-message' +import { assertDbIsEmpty } from './bolt-protocol-util' export default class BoltProtocol extends BoltProtocolV2 { transformMetadata (metadata) { @@ -47,9 +48,10 @@ export default class BoltProtocol extends BoltProtocolV2 { this._connection.write(message, observer, true) } - beginTransaction (bookmark, txConfig, mode, observer) { + beginTransaction (observer, { bookmark, txConfig, db, mode }) { + assertDbIsEmpty(db, this._connection, observer) prepareToHandleSingleResponse(observer) - const message = RequestMessage.begin(bookmark, txConfig, mode) + const message = RequestMessage.begin({ bookmark, txConfig, mode }) this._connection.write(message, observer, true) } @@ -65,14 +67,15 @@ export default class BoltProtocol extends BoltProtocolV2 { this._connection.write(message, observer, true) } - run (statement, parameters, bookmark, txConfig, mode, observer) { - const runMessage = RequestMessage.runWithMetadata( - statement, - parameters, + run (statement, parameters, observer, { bookmark, txConfig, db, mode }) { + // passing in a db name on this protocol version throws an error + assertDbIsEmpty(db, this._connection, observer) + + const runMessage = RequestMessage.runWithMetadata(statement, parameters, { bookmark, txConfig, mode - ) + }) const pullAllMessage = RequestMessage.pullAll() this._connection.write(runMessage, observer, false) diff --git a/src/internal/bolt-protocol-v4.js b/src/internal/bolt-protocol-v4.js new file mode 100644 index 000000000..f5534a8e7 --- /dev/null +++ b/src/internal/bolt-protocol-v4.js @@ -0,0 +1,40 @@ +/** + * Copyright (c) 2002-2019 "Neo4j," + * Neo4j Sweden AB [http://neo4j.com] + * + * This file is part of Neo4j. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +import BoltProtocolV3 from './bolt-protocol-v3' +import RequestMessage from './request-message' + +export default class BoltProtocol extends BoltProtocolV3 { + beginTransaction (observer, { bookmark, txConfig, db, mode }) { + const message = RequestMessage.begin({ bookmark, txConfig, db, mode }) + this._connection.write(message, observer, true) + } + + run (statement, parameters, observer, { bookmark, txConfig, db, mode }) { + const runMessage = RequestMessage.runWithMetadata(statement, parameters, { + bookmark, + txConfig, + db, + mode + }) + const pullMessage = RequestMessage.pull() + + this._connection.write(runMessage, observer, false) + this._connection.write(pullMessage, observer, true) + } +} diff --git a/src/internal/connection-holder.js b/src/internal/connection-holder.js index 01bf77ea5..c81ae63cd 100644 --- a/src/internal/connection-holder.js +++ b/src/internal/connection-holder.js @@ -18,6 +18,8 @@ */ import { newError } from '../error' +import { assertString } from './util' +import { ACCESS_MODE_WRITE } from './constants' /** * Utility to lazily initialize connections and return them back to the pool when unused. @@ -26,10 +28,12 @@ export default class ConnectionHolder { /** * @constructor * @param {string} mode - the access mode for new connection holder. + * @param {string} db - the target database name. * @param {ConnectionProvider} connectionProvider - the connection provider to acquire connections from. */ - constructor (mode, connectionProvider) { + constructor ({ mode = ACCESS_MODE_WRITE, db = '', connectionProvider } = {}) { this._mode = mode + this._db = db ? assertString(db, 'db') : '' this._connectionProvider = connectionProvider this._referenceCount = 0 this._connectionPromise = Promise.resolve(null) @@ -43,6 +47,14 @@ export default class ConnectionHolder { return this._mode } + /** + * Returns the target database name + * @returns {string} db name + */ + db () { + return this._db + } + /** * Make this holder initialize new connection if none exists already. * @return {undefined} @@ -50,7 +62,8 @@ export default class ConnectionHolder { initializeConnection () { if (this._referenceCount === 0) { this._connectionPromise = this._connectionProvider.acquireConnection( - this._mode + this._mode, + this._db ) } this._referenceCount++ diff --git a/src/internal/connection-providers.js b/src/internal/connection-providers.js index 355b34563..02f3258ab 100644 --- a/src/internal/connection-providers.js +++ b/src/internal/connection-providers.js @@ -27,7 +27,7 @@ import RoutingUtil from './routing-util' const UNAUTHORIZED_ERROR_CODE = 'Neo.ClientError.Security.Unauthorized' class ConnectionProvider { - acquireConnection (mode) { + acquireConnection (accessMode, db) { throw new Error('Abstract function') } @@ -50,7 +50,7 @@ export class DirectConnectionProvider extends ConnectionProvider { this._driverOnErrorCallback = driverOnErrorCallback } - acquireConnection (mode) { + acquireConnection (accessMode, db) { const connectionPromise = this._connectionPool.acquire(this._hostPort) return this._withAdditionalOnErrorCallback( connectionPromise, @@ -81,7 +81,7 @@ export class LoadBalancer extends ConnectionProvider { this._useSeedRouter = false } - acquireConnection (accessMode) { + acquireConnection (accessMode, db) { const connectionPromise = this._freshRoutingTable(accessMode).then( routingTable => { if (accessMode === READ) { @@ -282,7 +282,7 @@ export class LoadBalancer extends ConnectionProvider { .acquire(routerAddress) .then(connection => { const connectionProvider = new SingleConnectionProvider(connection) - return new Session(READ, connectionProvider) + return new Session({ mode: READ, connectionProvider }) }) .catch(error => { // unable to acquire connection towards the given router @@ -340,7 +340,7 @@ export class SingleConnectionProvider extends ConnectionProvider { this._connection = connection } - acquireConnection (mode) { + acquireConnection (mode, db) { const connection = this._connection this._connection = null return Promise.resolve(connection) diff --git a/src/internal/connectivity-verifier.js b/src/internal/connectivity-verifier.js index 450dc7b44..0a013c69f 100644 --- a/src/internal/connectivity-verifier.js +++ b/src/internal/connectivity-verifier.js @@ -37,8 +37,8 @@ export default class ConnectivityVerifier { * Try to obtain a working connection from the connection provider. * @returns {Promise} promise resolved with server info or rejected with error. */ - verify () { - return acquireAndReleaseDummyConnection(this._connectionProvider) + verify ({ db = '' } = {}) { + return acquireAndReleaseDummyConnection(this._connectionProvider, db) } } @@ -47,8 +47,12 @@ export default class ConnectivityVerifier { * @param {ConnectionProvider} connectionProvider the provider to obtain connections from. * @return {Promise} promise resolved with server info or rejected with error. */ -function acquireAndReleaseDummyConnection (connectionProvider) { - const connectionHolder = new ConnectionHolder(READ, connectionProvider) +function acquireAndReleaseDummyConnection (connectionProvider, db) { + const connectionHolder = new ConnectionHolder({ + mode: READ, + db, + connectionProvider + }) connectionHolder.initializeConnection() const dummyObserver = new StreamObserver() const connectionPromise = connectionHolder.getConnection(dummyObserver) diff --git a/src/internal/http/http-driver.js b/src/internal/http/http-driver.js index c9eeb7f1b..9a71bf850 100644 --- a/src/internal/http/http-driver.js +++ b/src/internal/http/http-driver.js @@ -28,12 +28,12 @@ export default class HttpDriver extends Driver { } session () { - return new HttpSession( - this._hostPort, - this._authToken, - this._config, - this._sessionTracker - ) + return new HttpSession({ + url: this._hostPort, + authToken: this._authToken, + config: this._config, + sessionTracker: this._sessionTracker + }) } close () { diff --git a/src/internal/http/http-session.js b/src/internal/http/http-session.js index 37e0e17ee..5379e636f 100644 --- a/src/internal/http/http-session.js +++ b/src/internal/http/http-session.js @@ -26,8 +26,8 @@ import { EMPTY_CONNECTION_HOLDER } from '../connection-holder' import Result from '../../result' export default class HttpSession extends Session { - constructor (url, authToken, config, sessionTracker) { - super(WRITE, null, null, config) + constructor ({ url, authToken, config, db = '', sessionTracker } = {}) { + super({ mode: WRITE, connectionProvider: null, bookmark: null, db, config }) this._ongoingTransactionIds = [] this._serverInfoSupplier = createServerInfoSupplier(url) this._requestRunner = new HttpRequestRunner(url, authToken) @@ -35,7 +35,7 @@ export default class HttpSession extends Session { this._sessionTracker.sessionOpened(this) } - run (statement, parameters = {}) { + run (statement, parameters = {}, transactionConfig) { const { query, params } = validateStatementAndParameters( statement, parameters diff --git a/src/internal/protocol-handshaker.js b/src/internal/protocol-handshaker.js index 7de182301..93b16b1ea 100644 --- a/src/internal/protocol-handshaker.js +++ b/src/internal/protocol-handshaker.js @@ -22,6 +22,7 @@ import { newError } from '../error' import BoltProtocolV1 from './bolt-protocol-v1' import BoltProtocolV2 from './bolt-protocol-v2' import BoltProtocolV3 from './bolt-protocol-v3' +import BoltProtocolV4 from './bolt-protocol-v4' const HTTP_MAGIC_PREAMBLE = 1213486160 // == 0x48545450 == "HTTP" const BOLT_MAGIC_PREAMBLE = 0x6060b017 @@ -90,6 +91,12 @@ export default class ProtocolHandshaker { this._chunker, this._disableLosslessIntegers ) + case 4: + return new BoltProtocolV4( + this._connection, + this._chunker, + this._disableLosslessIntegers + ) case HTTP_MAGIC_PREAMBLE: throw newError( 'Server responded HTTP. Make sure you are not trying to connect to the http endpoint ' + @@ -112,10 +119,10 @@ function newHandshakeBuffer () { handshakeBuffer.writeInt32(BOLT_MAGIC_PREAMBLE) // proposed versions + handshakeBuffer.writeInt32(4) handshakeBuffer.writeInt32(3) handshakeBuffer.writeInt32(2) handshakeBuffer.writeInt32(1) - handshakeBuffer.writeInt32(0) // reset the reader position handshakeBuffer.reset() diff --git a/src/internal/request-message.js b/src/internal/request-message.js index 694ca59ac..d4e32ef26 100644 --- a/src/internal/request-message.js +++ b/src/internal/request-message.js @@ -18,6 +18,8 @@ */ import { ACCESS_MODE_READ } from './constants' +import { int } from '../integer' +import { assertString, isString } from './util' /* eslint-disable no-unused-vars */ // Signature bytes for each request message type @@ -40,6 +42,9 @@ const PULL = 0x3f // 0011 1111 // PULL const READ_MODE = 'r' /* eslint-enable no-unused-vars */ +const NO_STATEMENT_ID = -1 +const ALL = -1 + export default class RequestMessage { constructor (signature, fields, toString) { this.signature = signature @@ -110,11 +115,12 @@ export default class RequestMessage { * Create a new BEGIN message. * @param {Bookmark} bookmark the bookmark. * @param {TxConfig} txConfig the configuration. + * @param {string} db the database name. * @param {string} mode the access mode. * @return {RequestMessage} new BEGIN message. */ - static begin (bookmark, txConfig, mode) { - const metadata = buildTxMetadata(bookmark, txConfig, mode) + static begin ({ bookmark, txConfig, db, mode } = {}) { + const metadata = buildTxMetadata(bookmark, txConfig, db, mode) return new RequestMessage( BEGIN, [metadata], @@ -144,11 +150,16 @@ export default class RequestMessage { * @param {object} parameters the statement parameters. * @param {Bookmark} bookmark the bookmark. * @param {TxConfig} txConfig the configuration. + * @param {string} db the database name. * @param {string} mode the access mode. * @return {RequestMessage} new RUN message with additional metadata. */ - static runWithMetadata (statement, parameters, bookmark, txConfig, mode) { - const metadata = buildTxMetadata(bookmark, txConfig, mode) + static runWithMetadata ( + statement, + parameters, + { bookmark, txConfig, db, mode } = {} + ) { + const metadata = buildTxMetadata(bookmark, txConfig, db, mode) return new RequestMessage( RUN, [statement, parameters, metadata], @@ -166,16 +177,47 @@ export default class RequestMessage { static goodbye () { return GOODBYE_MESSAGE } + + /** + * Generates a new PULL message with additional metadata. + * @param {Integer|number} stmtId + * @param {Integer|number} n + * @return {RequestMessage} the PULL message. + */ + static pull ({ stmtId = NO_STATEMENT_ID, n = ALL } = {}) { + const metadata = buildStreamMetadata(stmtId, n) + return new RequestMessage( + PULL, + [metadata], + () => `PULL ${JSON.stringify(metadata)}` + ) + } + + /** + * Generates a new DISCARD message with additional metadata. + * @param {Integer|number} stmtId + * @param {Integer|number} n + * @return {RequestMessage} the PULL message. + */ + static discard ({ stmtId = NO_STATEMENT_ID, n = ALL } = {}) { + const metadata = buildStreamMetadata(stmtId, n) + return new RequestMessage( + DISCARD, + [metadata], + () => `DISCARD ${JSON.stringify(metadata)}` + ) + } } /** * Create an object that represent transaction metadata. * @param {Bookmark} bookmark the bookmark. * @param {TxConfig} txConfig the configuration. + * @param {string} db the database name. * @param {string} mode the access mode. * @return {object} a metadata object. */ -function buildTxMetadata (bookmark, txConfig, mode) { +function buildTxMetadata (bookmark, txConfig, db, mode) { const metadata = {} if (!bookmark.isEmpty()) { metadata['bookmarks'] = bookmark.values() @@ -186,12 +228,29 @@ function buildTxMetadata (bookmark, txConfig, mode) { if (txConfig.metadata) { metadata['tx_metadata'] = txConfig.metadata } + if (db) { + metadata['db'] = assertString(db, 'db') + } if (mode === ACCESS_MODE_READ) { metadata['mode'] = READ_MODE } return metadata } +/** + * Create an object that represents streaming metadata. + * @param {Integer|number} stmtId The statement id to stream its results. + * @param {Integer|number} n The number of records to stream. + * @returns {object} a metadata object. + */ +function buildStreamMetadata (stmtId, n) { + const metadata = { n: int(n) } + if (stmtId !== NO_STATEMENT_ID) { + metadata['stmt_id'] = int(stmtId) + } + return metadata +} + // constants for messages that never change const PULL_ALL_MESSAGE = new RequestMessage(PULL_ALL, [], () => 'PULL_ALL') const RESET_MESSAGE = new RequestMessage(RESET, [], () => 'RESET') diff --git a/src/internal/routing-util.js b/src/internal/routing-util.js index 5621929ea..fb4c2af21 100644 --- a/src/internal/routing-util.js +++ b/src/internal/routing-util.js @@ -137,16 +137,11 @@ export default class RoutingUtil { params = {} } - connection - .protocol() - .run( - query, - params, - Bookmark.empty(), - TxConfig.empty(), - ACCESS_MODE_WRITE, - streamObserver - ) + connection.protocol().run(query, params, streamObserver, { + bookmark: Bookmark.empty(), + txConfig: TxConfig.empty(), + mode: ACCESS_MODE_WRITE + }) }) } } diff --git a/src/session.js b/src/session.js index 4f7e15af2..8d6939190 100644 --- a/src/session.js +++ b/src/session.js @@ -62,16 +62,19 @@ class Session { * @param {Bookmark} bookmark - the initial bookmark for this session. * @param {Object} [config={}] - this driver configuration. */ - constructor (mode, connectionProvider, bookmark, config) { + constructor ({ mode, connectionProvider, bookmark, db, config }) { this._mode = mode - this._readConnectionHolder = new ConnectionHolder( - ACCESS_MODE_READ, + this._db = db + this._readConnectionHolder = new ConnectionHolder({ + mode: ACCESS_MODE_READ, + db, connectionProvider - ) - this._writeConnectionHolder = new ConnectionHolder( - ACCESS_MODE_WRITE, + }) + this._writeConnectionHolder = new ConnectionHolder({ + mode: ACCESS_MODE_WRITE, + db, connectionProvider - ) + }) this._open = true this._hasTx = false this._lastBookmark = bookmark @@ -97,16 +100,12 @@ class Session { : TxConfig.empty() return this._run(query, params, (connection, streamObserver) => - connection - .protocol() - .run( - query, - params, - this._lastBookmark, - autoCommitTxConfig, - this._mode, - streamObserver - ) + connection.protocol().run(query, params, streamObserver, { + bookmark: this._lastBookmark, + txConfig: autoCommitTxConfig, + mode: this._mode, + db: this._db + }) ) } diff --git a/src/transaction.js b/src/transaction.js index 9f649a965..f6a84806c 100644 --- a/src/transaction.js +++ b/src/transaction.js @@ -48,14 +48,12 @@ class Transaction { this._connectionHolder .getConnection(streamObserver) .then(conn => - conn - .protocol() - .beginTransaction( - bookmark, - txConfig, - this._connectionHolder.mode(), - streamObserver - ) + conn.protocol().beginTransaction(streamObserver, { + bookmark: bookmark, + txConfig: txConfig, + mode: this._connectionHolder.mode(), + db: this._connectionHolder.db() + }) ) .catch(error => streamObserver.onError(error)) } @@ -184,16 +182,12 @@ let _states = { connectionHolder .getConnection(observer) .then(conn => - conn - .protocol() - .run( - statement, - parameters, - bookmark, - txConfig, - connectionHolder.mode(), - observer - ) + conn.protocol().run(statement, parameters, observer, { + bookmark: bookmark, + txConfig: txConfig, + mode: connectionHolder.mode(), + db: connectionHolder.db() + }) ) .catch(error => observer.onError(error)) diff --git a/test/bolt-v4.test.js b/test/bolt-v4.test.js new file mode 100644 index 000000000..cf648b784 --- /dev/null +++ b/test/bolt-v4.test.js @@ -0,0 +1,178 @@ +/** + * Copyright (c) 2002-2019 "Neo4j," + * Neo4j Sweden AB [http://neo4j.com] + * + * This file is part of Neo4j. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import neo4j from '../src' +import sharedNeo4j from './internal/shared-neo4j' +import { ServerVersion, VERSION_4_0_0 } from '../src/internal/server-version' + +describe('Bolt V4 API', () => { + let driver + let session + let serverVersion + let originalTimeout + + beforeEach(done => { + driver = neo4j.driver('bolt://localhost', sharedNeo4j.authToken) + session = driver.session() + originalTimeout = jasmine.DEFAULT_TIMEOUT_INTERVAL + jasmine.DEFAULT_TIMEOUT_INTERVAL = 20000 + + session.run('MATCH (n) DETACH DELETE n').then(result => { + serverVersion = ServerVersion.fromString(result.summary.server.version) + done() + }) + }) + + afterEach(() => { + jasmine.DEFAULT_TIMEOUT_INTERVAL = originalTimeout + session.close() + driver.close() + }) + + describe('multi-database', () => { + describe('earlier versions', () => { + it('should fail run if not supported', done => { + if (databaseSupportsBoltV4()) { + done() + return + } + + const session = driver.session({ db: 'adb' }) + + session + .run('RETURN 1') + .then(() => done.fail('Failure expected')) + .catch(error => { + expectBoltV4NotSupportedError(error) + session.close() + done() + }) + }) + + it('should fail beginTransaction if not supported', done => { + if (databaseSupportsBoltV4()) { + done() + return + } + + const session = driver.session({ db: 'adb' }) + const tx = session.beginTransaction() + + tx.run('RETURN 1') + .then(() => done.fail('Failure expected')) + .catch(error => { + expectBoltV4NotSupportedError(error) + session.close() + done() + }) + }) + + it('should fail readTransaction if not supported', done => { + if (databaseSupportsBoltV4()) { + done() + return + } + + const session = driver.session({ db: 'adb' }) + + session + .readTransaction(tx => tx.run('RETURN 1')) + .then(() => done.fail('Failure expected')) + .catch(error => { + expectBoltV4NotSupportedError(error) + session.close() + done() + }) + }) + + it('should fail writeTransaction if not supported', done => { + if (databaseSupportsBoltV4()) { + done() + return + } + + const session = driver.session({ db: 'adb' }) + + session + .writeTransaction(tx => tx.run('RETURN 1')) + .then(() => done.fail('Failure expected')) + .catch(error => { + expectBoltV4NotSupportedError(error) + session.close() + done() + }) + }) + }) + + it('should fail if connecting to a non-existing database', done => { + if (!databaseSupportsBoltV4()) { + done() + return + } + + const neoSession = driver.session({ db: 'testdb' }) + + neoSession + .run('RETURN 1') + .then(result => { + done.fail('Failure expected') + }) + .catch(error => { + expect(error.code).toContain('DatabaseNotFound') + done() + }) + .finally(() => neoSession.close()) + }) + + describe('neo4j database', () => { + it('should be able to create a node', done => { + if (!databaseSupportsBoltV4()) { + done() + return + } + + const neoSession = driver.session({ db: 'neo4j' }) + + neoSession + .run('CREATE (n { db: $db }) RETURN n.db', { db: 'neo4j' }) + .then(result => { + expect(result.records.length).toBe(1) + expect(result.records[0].get('n.db')).toBe('neo4j') + done() + }) + .catch(error => { + done.fail(error) + }) + .finally(() => neoSession.close()) + }) + }) + }) + + function expectBoltV4NotSupportedError (error) { + expect( + error.message.indexOf( + 'Driver is connected to the database that does not support multiple databases' + ) + ).toBeGreaterThan(-1) + } + + function databaseSupportsBoltV4 () { + return serverVersion.compareTo(VERSION_4_0_0) >= 0 + } +}) diff --git a/test/examples.test.js b/test/examples.test.js index 3b0d11de5..c4d9bc403 100644 --- a/test/examples.test.js +++ b/test/examples.test.js @@ -193,7 +193,7 @@ describe('examples', () => { 'b.acme.com:7676', 'c.acme.com:8787' ]) - const session = driver.session(neo4j.WRITE) + const session = driver.session({ defaultAccessMode: neo4j.WRITE }) session .run('CREATE (n:Person { name: $name })', { name: name }) @@ -628,7 +628,7 @@ describe('examples', () => { const savedBookmarks = [] // Create the first person and employment relationship. - const session1 = driver.session(neo4j.WRITE) + const session1 = driver.session({ defaultAccessMode: neo4j.WRITE }) const first = session1 .writeTransaction(tx => addCompany(tx, 'Wayne Enterprises')) .then(() => session1.writeTransaction(tx => addPerson(tx, 'Alice'))) @@ -644,7 +644,7 @@ describe('examples', () => { }) // Create the second person and employment relationship. - const session2 = driver.session(neo4j.WRITE) + const session2 = driver.session({ defaultAccessMode: neo4j.WRITE }) const second = session2 .writeTransaction(tx => addCompany(tx, 'LexCorp')) .then(() => session2.writeTransaction(tx => addPerson(tx, 'Bob'))) @@ -659,7 +659,10 @@ describe('examples', () => { // Create a friendship between the two people created above. const last = Promise.all([first, second]).then(ignore => { - const session3 = driver.session(neo4j.WRITE, savedBookmarks) + const session3 = driver.session({ + defaultAccessMode: neo4j.WRITE, + bookmarks: savedBookmarks + }) return session3 .writeTransaction(tx => makeFriends(tx, 'Alice', 'Bob')) diff --git a/test/internal/bolt-protocol-v1.test.js b/test/internal/bolt-protocol-v1.test.js index f421ea715..bb74344eb 100644 --- a/test/internal/bolt-protocol-v1.test.js +++ b/test/internal/bolt-protocol-v1.test.js @@ -22,28 +22,13 @@ import RequestMessage from '../../src/internal/request-message' import Bookmark from '../../src/internal/bookmark' import TxConfig from '../../src/internal/tx-config' import { WRITE } from '../../src/driver' - -class MessageRecorder { - constructor () { - this.messages = [] - this.observers = [] - this.flushes = [] - } - - write (message, observer, flush) { - this.messages.push(message) - this.observers.push(observer) - this.flushes.push(flush) - } - - verifyMessageCount (expected) { - expect(this.messages.length).toEqual(expected) - expect(this.observers.length).toEqual(expected) - expect(this.flushes.length).toEqual(expected) - } -} +import utils from './test-utils' describe('BoltProtocolV1', () => { + beforeEach(() => { + jasmine.addMatchers(utils.matchers) + }) + it('should not change metadata', () => { const metadata = { result_available_after: 1, @@ -51,7 +36,11 @@ describe('BoltProtocolV1', () => { t_first: 3, t_last: 4 } - const protocol = new BoltProtocolV1(new MessageRecorder(), null, false) + const protocol = new BoltProtocolV1( + new utils.MessageRecordingConnection(), + null, + false + ) const transformedMetadata = protocol.transformMetadata(metadata) @@ -64,7 +53,7 @@ describe('BoltProtocolV1', () => { }) it('should initialize the connection', () => { - const recorder = new MessageRecorder() + const recorder = new utils.MessageRecordingConnection() const protocol = new BoltProtocolV1(recorder, null, false) const clientName = 'js-driver/1.2.3' @@ -74,45 +63,39 @@ describe('BoltProtocolV1', () => { protocol.initialize(clientName, authToken, observer) recorder.verifyMessageCount(1) - verifyMessage( - RequestMessage.init(clientName, authToken), - recorder.messages[0] + expect(recorder.messages[0]).toBeMessage( + RequestMessage.init(clientName, authToken) ) expect(recorder.observers).toEqual([observer]) expect(recorder.flushes).toEqual([true]) }) it('should run a statement', () => { - const recorder = new MessageRecorder() + const recorder = new utils.MessageRecordingConnection() const protocol = new BoltProtocolV1(recorder, null, false) const statement = 'RETURN $x, $y' const parameters = { x: 'x', y: 'y' } const observer = {} - protocol.run( - statement, - parameters, - Bookmark.empty(), - TxConfig.empty(), - WRITE, - observer - ) + protocol.run(statement, parameters, observer, { + bookmark: Bookmark.empty(), + txConfig: TxConfig.empty(), + mode: WRITE + }) recorder.verifyMessageCount(2) - verifyMessage( - RequestMessage.run(statement, parameters), - recorder.messages[0] + expect(recorder.messages[0]).toBeMessage( + RequestMessage.run(statement, parameters) ) - verifyMessage(RequestMessage.pullAll(), recorder.messages[1]) - + expect(recorder.messages[1]).toBeMessage(RequestMessage.pullAll()) expect(recorder.observers).toEqual([observer, observer]) expect(recorder.flushes).toEqual([false, true]) }) it('should reset the connection', () => { - const recorder = new MessageRecorder() + const recorder = new utils.MessageRecordingConnection() const protocol = new BoltProtocolV1(recorder, null, false) const observer = {} @@ -120,34 +103,36 @@ describe('BoltProtocolV1', () => { protocol.reset(observer) recorder.verifyMessageCount(1) - verifyMessage(RequestMessage.reset(), recorder.messages[0]) + expect(recorder.messages[0]).toBeMessage(RequestMessage.reset()) expect(recorder.observers).toEqual([observer]) expect(recorder.flushes).toEqual([true]) }) it('should begin a transaction', () => { - const recorder = new MessageRecorder() + const recorder = new utils.MessageRecordingConnection() const protocol = new BoltProtocolV1(recorder, null, false) const bookmark = new Bookmark('neo4j:bookmark:v1:tx42') const observer = {} - protocol.beginTransaction(bookmark, TxConfig.empty(), WRITE, observer) + protocol.beginTransaction(observer, { + bookmark: bookmark, + txConfig: TxConfig.empty(), + mode: WRITE + }) recorder.verifyMessageCount(2) - verifyMessage( - RequestMessage.run('BEGIN', bookmark.asBeginTransactionParameters()), - recorder.messages[0] + expect(recorder.messages[0]).toBeMessage( + RequestMessage.run('BEGIN', bookmark.asBeginTransactionParameters()) ) - verifyMessage(RequestMessage.pullAll(), recorder.messages[1]) - + expect(recorder.messages[1]).toBeMessage(RequestMessage.pullAll()) expect(recorder.observers).toEqual([observer, observer]) expect(recorder.flushes).toEqual([false, false]) }) it('should commit a transaction', () => { - const recorder = new MessageRecorder() + const recorder = new utils.MessageRecordingConnection() const protocol = new BoltProtocolV1(recorder, null, false) const observer = {} @@ -156,15 +141,14 @@ describe('BoltProtocolV1', () => { recorder.verifyMessageCount(2) - verifyMessage(RequestMessage.run('COMMIT', {}), recorder.messages[0]) - verifyMessage(RequestMessage.pullAll(), recorder.messages[1]) - + expect(recorder.messages[0]).toBeMessage(RequestMessage.run('COMMIT', {})) + expect(recorder.messages[1]).toBeMessage(RequestMessage.pullAll()) expect(recorder.observers).toEqual([observer, observer]) expect(recorder.flushes).toEqual([false, true]) }) it('should rollback a transaction', () => { - const recorder = new MessageRecorder() + const recorder = new utils.MessageRecordingConnection() const protocol = new BoltProtocolV1(recorder, null, false) const observer = {} @@ -173,15 +157,105 @@ describe('BoltProtocolV1', () => { recorder.verifyMessageCount(2) - verifyMessage(RequestMessage.run('ROLLBACK', {}), recorder.messages[0]) - verifyMessage(RequestMessage.pullAll(), recorder.messages[1]) - + expect(recorder.messages[0]).toBeMessage(RequestMessage.run('ROLLBACK', {})) + expect(recorder.messages[1]).toBeMessage(RequestMessage.pullAll()) expect(recorder.observers).toEqual([observer, observer]) expect(recorder.flushes).toEqual([false, true]) }) -}) -function verifyMessage (expected, actual) { - expect(actual.signature).toEqual(expected.signature) - expect(actual.fields).toEqual(expected.fields) -} + describe('Bolt V3', () => { + function verifyError (fn) { + const recorder = new utils.MessageRecordingConnection() + const protocol = new BoltProtocolV1(recorder, null, false) + const observer = { + onError: () => {} + } + + expect(() => fn(protocol, observer)).toThrowError( + 'Driver is connected to the database that does not support transaction configuration. ' + + 'Please upgrade to neo4j 3.5.0 or later in order to use this functionality' + ) + } + + describe('beginTransaction', () => { + function verifyBeginTransaction (txConfig) { + verifyError((protocol, observer) => + protocol.beginTransaction(observer, { txConfig }) + ) + } + + it('should throw error when txConfig.timeout is set', () => { + verifyBeginTransaction(new TxConfig({ timeout: 5000 })) + }) + + it('should throw error when txConfig.metadata is set', () => { + verifyBeginTransaction(new TxConfig({ metadata: { x: 1, y: true } })) + }) + + it('should throw error when txConfig is set', () => { + verifyBeginTransaction( + new TxConfig({ timeout: 5000, metadata: { x: 1, y: true } }) + ) + }) + }) + + describe('run', () => { + function verifyRun (txConfig) { + verifyError((protocol, observer) => + protocol.run('statement', {}, observer, { txConfig }) + ) + } + + it('should throw error when txConfig.timeout is set', () => { + verifyRun(new TxConfig({ timeout: 5000 })) + }) + + it('should throw error when txConfig.metadata is set', () => { + verifyRun(new TxConfig({ metadata: { x: 1, y: true } })) + }) + + it('should throw error when txConfig is set', () => { + verifyRun(new TxConfig({ timeout: 5000, metadata: { x: 1, y: true } })) + }) + }) + }) + + describe('Bolt V4', () => { + function verifyError (fn) { + const recorder = new utils.MessageRecordingConnection() + const protocol = new BoltProtocolV1(recorder, null, false) + const observer = { + onError: () => {} + } + + expect(() => fn(protocol, observer)).toThrowError( + 'Driver is connected to the database that does not support multiple databases. ' + + 'Please upgrade to neo4j 4.0.0 or later in order to use this functionality' + ) + } + + describe('beginTransaction', () => { + function verifyBeginTransaction (db) { + verifyError((protocol, observer) => + protocol.beginTransaction(observer, { db }) + ) + } + + it('should throw error when db is set', () => { + verifyBeginTransaction('test') + }) + }) + + describe('run', () => { + function verifyRun (db) { + verifyError((protocol, observer) => + protocol.run('statement', {}, observer, { db }) + ) + } + + it('should throw error when db is set', () => { + verifyRun('test') + }) + }) + }) +}) diff --git a/test/internal/bolt-protocol-v3.test.js b/test/internal/bolt-protocol-v3.test.js index df6ceccaf..aa308aa49 100644 --- a/test/internal/bolt-protocol-v3.test.js +++ b/test/internal/bolt-protocol-v3.test.js @@ -18,8 +18,17 @@ */ import BoltProtocolV3 from '../../src/internal/bolt-protocol-v3' +import RequestMessage from '../../src/internal/request-message' +import utils from './test-utils' +import Bookmark from '../../src/internal/bookmark' +import TxConfig from '../../src/internal/tx-config' +import { WRITE } from '../../src/driver' describe('BoltProtocolV3', () => { + beforeEach(() => { + jasmine.addMatchers(utils.matchers) + }) + it('should update metadata', () => { const metadata = { t_first: 1, t_last: 2, db_hits: 3, some_other_key: 4 } const protocol = new BoltProtocolV3(null, null, false) @@ -33,4 +42,153 @@ describe('BoltProtocolV3', () => { some_other_key: 4 }) }) + + it('should initialize connection', () => { + const recorder = new utils.MessageRecordingConnection() + const protocol = new BoltProtocolV3(recorder, null, false) + + const clientName = 'js-driver/1.2.3' + const authToken = { username: 'neo4j', password: 'secret' } + const observer = {} + + protocol.initialize(clientName, authToken, observer) + + recorder.verifyMessageCount(1) + expect(recorder.messages[0]).toBeMessage( + RequestMessage.hello(clientName, authToken) + ) + expect(recorder.observers).toEqual([observer]) + expect(recorder.flushes).toEqual([true]) + }) + + it('should run a statement', () => { + const bookmark = new Bookmark([ + 'neo4j:bookmark:v1:tx1', + 'neo4j:bookmark:v1:tx2' + ]) + const txConfig = new TxConfig({ + timeout: 5000, + metadata: { x: 1, y: 'something' } + }) + const recorder = new utils.MessageRecordingConnection() + const protocol = new BoltProtocolV3(recorder, null, false) + + const statement = 'RETURN $x, $y' + const parameters = { x: 'x', y: 'y' } + const observer = {} + + protocol.run(statement, parameters, observer, { + bookmark, + txConfig, + mode: WRITE + }) + + recorder.verifyMessageCount(2) + + expect(recorder.messages[0]).toBeMessage( + RequestMessage.runWithMetadata(statement, parameters, { + bookmark, + txConfig, + mode: WRITE + }) + ) + expect(recorder.messages[1]).toBeMessage(RequestMessage.pullAll()) + expect(recorder.observers).toEqual([observer, observer]) + expect(recorder.flushes).toEqual([false, true]) + }) + + it('should begin a transaction', () => { + const bookmark = new Bookmark([ + 'neo4j:bookmark:v1:tx1', + 'neo4j:bookmark:v1:tx2' + ]) + const txConfig = new TxConfig({ + timeout: 5000, + metadata: { x: 1, y: 'something' } + }) + const recorder = new utils.MessageRecordingConnection() + const protocol = new BoltProtocolV3(recorder, null, false) + + const observer = {} + + protocol.beginTransaction(observer, { + bookmark, + txConfig, + mode: WRITE + }) + + recorder.verifyMessageCount(1) + expect(recorder.messages[0]).toBeMessage( + RequestMessage.begin({ bookmark, txConfig, mode: WRITE }) + ) + expect(recorder.observers).toEqual([observer]) + expect(recorder.flushes).toEqual([true]) + }) + + it('should commit', () => { + const recorder = new utils.MessageRecordingConnection() + const protocol = new BoltProtocolV3(recorder, null, false) + + const observer = {} + + protocol.commitTransaction(observer) + + recorder.verifyMessageCount(1) + expect(recorder.messages[0]).toBeMessage(RequestMessage.commit()) + expect(recorder.observers).toEqual([observer]) + expect(recorder.flushes).toEqual([true]) + }) + + it('should rollback', () => { + const recorder = new utils.MessageRecordingConnection() + const protocol = new BoltProtocolV3(recorder, null, false) + + const observer = {} + + protocol.rollbackTransaction(observer) + + recorder.verifyMessageCount(1) + expect(recorder.messages[0]).toBeMessage(RequestMessage.rollback()) + expect(recorder.observers).toEqual([observer]) + expect(recorder.flushes).toEqual([true]) + }) + + describe('Bolt V4', () => { + function verifyError (fn) { + const recorder = new utils.MessageRecordingConnection() + const protocol = new BoltProtocolV3(recorder, null, false) + const observer = { + onError: () => {} + } + + expect(() => fn(protocol, observer)).toThrowError( + 'Driver is connected to the database that does not support multiple databases. ' + + 'Please upgrade to neo4j 4.0.0 or later in order to use this functionality' + ) + } + + describe('beginTransaction', () => { + function verifyBeginTransaction (db) { + verifyError((protocol, observer) => + protocol.beginTransaction(observer, { db }) + ) + } + + it('should throw error when db is set', () => { + verifyBeginTransaction('test') + }) + }) + + describe('run', () => { + function verifyRun (db) { + verifyError((protocol, observer) => + protocol.run('statement', {}, observer, { db }) + ) + } + + it('should throw error when db is set', () => { + verifyRun('test') + }) + }) + }) }) diff --git a/test/internal/bolt-protocol-v4.test.js b/test/internal/bolt-protocol-v4.test.js new file mode 100644 index 000000000..8ccc47ccd --- /dev/null +++ b/test/internal/bolt-protocol-v4.test.js @@ -0,0 +1,100 @@ +/** + * Copyright (c) 2002-2019 "Neo4j," + * Neo4j Sweden AB [http://neo4j.com] + * + * This file is part of Neo4j. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import BoltProtocolV4 from '../../src/internal/bolt-protocol-v4' +import RequestMessage from '../../src/internal/request-message' +import utils from './test-utils' +import Bookmark from '../../src/internal/bookmark' +import TxConfig from '../../src/internal/tx-config' +import { WRITE } from '../../src/driver' + +describe('BoltProtocolV4', () => { + beforeEach(() => { + jasmine.addMatchers(utils.matchers) + }) + + it('should run a statement', () => { + const db = 'testdb' + const bookmark = new Bookmark([ + 'neo4j:bookmark:v1:tx1', + 'neo4j:bookmark:v1:tx2' + ]) + const txConfig = new TxConfig({ + timeout: 5000, + metadata: { x: 1, y: 'something' } + }) + const recorder = new utils.MessageRecordingConnection() + const protocol = new BoltProtocolV4(recorder, null, false) + + const statement = 'RETURN $x, $y' + const parameters = { x: 'x', y: 'y' } + const observer = {} + + protocol.run(statement, parameters, observer, { + bookmark, + txConfig, + db, + mode: WRITE + }) + + recorder.verifyMessageCount(2) + + expect(recorder.messages[0]).toBeMessage( + RequestMessage.runWithMetadata(statement, parameters, { + bookmark, + txConfig, + db, + mode: WRITE + }) + ) + expect(recorder.messages[1]).toBeMessage(RequestMessage.pull()) + expect(recorder.observers).toEqual([observer, observer]) + expect(recorder.flushes).toEqual([false, true]) + }) + + it('should begin a transaction', () => { + const db = 'testdb' + const bookmark = new Bookmark([ + 'neo4j:bookmark:v1:tx1', + 'neo4j:bookmark:v1:tx2' + ]) + const txConfig = new TxConfig({ + timeout: 5000, + metadata: { x: 1, y: 'something' } + }) + const recorder = new utils.MessageRecordingConnection() + const protocol = new BoltProtocolV4(recorder, null, false) + + const observer = {} + + protocol.beginTransaction(observer, { + bookmark, + txConfig, + db, + mode: WRITE + }) + + recorder.verifyMessageCount(1) + expect(recorder.messages[0]).toBeMessage( + RequestMessage.begin({ bookmark, txConfig, db, mode: WRITE }) + ) + expect(recorder.observers).toEqual([observer]) + expect(recorder.flushes).toEqual([true]) + }) +}) diff --git a/test/internal/connection-holder.test.js b/test/internal/connection-holder.test.js index 7a28600ff..9f616274b 100644 --- a/test/internal/connection-holder.test.js +++ b/test/internal/connection-holder.test.js @@ -21,7 +21,7 @@ import ConnectionHolder, { EMPTY_CONNECTION_HOLDER } from '../../src/internal/connection-holder' import { SingleConnectionProvider } from '../../src/internal/connection-providers' -import { READ } from '../../src/driver' +import { READ, WRITE } from '../../src/driver' import FakeConnection from './fake-connection' import StreamObserver from '../../src/internal/stream-observer' @@ -50,7 +50,10 @@ describe('ConnectionHolder', () => { const connectionProvider = new RecordingConnectionProvider([ new FakeConnection() ]) - const connectionHolder = new ConnectionHolder(READ, connectionProvider) + const connectionHolder = new ConnectionHolder({ + mode: READ, + connectionProvider + }) connectionHolder.initializeConnection() @@ -60,7 +63,10 @@ describe('ConnectionHolder', () => { it('should return acquired during initialization connection', done => { const connection = new FakeConnection() const connectionProvider = newSingleConnectionProvider(connection) - const connectionHolder = new ConnectionHolder(READ, connectionProvider) + const connectionHolder = new ConnectionHolder({ + mode: READ, + connectionProvider + }) connectionHolder.initializeConnection() @@ -73,7 +79,10 @@ describe('ConnectionHolder', () => { it('should make stream observer aware about connection when initialization successful', done => { const connection = new FakeConnection().withServerVersion('Neo4j/9.9.9') const connectionProvider = newSingleConnectionProvider(connection) - const connectionHolder = new ConnectionHolder(READ, connectionProvider) + const connectionHolder = new ConnectionHolder({ + mode: READ, + connectionProvider + }) const streamObserver = new StreamObserver() connectionHolder.initializeConnection() @@ -88,7 +97,10 @@ describe('ConnectionHolder', () => { const errorMessage = 'Failed to acquire or initialize the connection' const connectionPromise = Promise.reject(new Error(errorMessage)) const connectionProvider = newSingleConnectionProvider(connectionPromise) - const connectionHolder = new ConnectionHolder(READ, connectionProvider) + const connectionHolder = new ConnectionHolder({ + mode: READ, + connectionProvider + }) const streamObserver = new StreamObserver() connectionHolder.initializeConnection() @@ -102,7 +114,10 @@ describe('ConnectionHolder', () => { it('should release connection with single user', done => { const connection = new FakeConnection() const connectionProvider = newSingleConnectionProvider(connection) - const connectionHolder = new ConnectionHolder(READ, connectionProvider) + const connectionHolder = new ConnectionHolder({ + mode: READ, + connectionProvider + }) connectionHolder.initializeConnection() @@ -115,7 +130,10 @@ describe('ConnectionHolder', () => { it('should not release connection with multiple users', done => { const connection = new FakeConnection() const connectionProvider = newSingleConnectionProvider(connection) - const connectionHolder = new ConnectionHolder(READ, connectionProvider) + const connectionHolder = new ConnectionHolder({ + mode: READ, + connectionProvider + }) connectionHolder.initializeConnection() connectionHolder.initializeConnection() @@ -130,7 +148,10 @@ describe('ConnectionHolder', () => { it('should release connection with multiple users when all users release', done => { const connection = new FakeConnection() const connectionProvider = newSingleConnectionProvider(connection) - const connectionHolder = new ConnectionHolder(READ, connectionProvider) + const connectionHolder = new ConnectionHolder({ + mode: READ, + connectionProvider + }) connectionHolder.initializeConnection() connectionHolder.initializeConnection() @@ -149,7 +170,10 @@ describe('ConnectionHolder', () => { it('should do nothing when closed and not initialized', done => { const connection = new FakeConnection() const connectionProvider = newSingleConnectionProvider(connection) - const connectionHolder = new ConnectionHolder(READ, connectionProvider) + const connectionHolder = new ConnectionHolder({ + mode: READ, + connectionProvider + }) connectionHolder.close().then(() => { expect(connection.isNeverReleased()).toBeTruthy() @@ -160,7 +184,10 @@ describe('ConnectionHolder', () => { it('should close even when users exist', done => { const connection = new FakeConnection() const connectionProvider = newSingleConnectionProvider(connection) - const connectionHolder = new ConnectionHolder(READ, connectionProvider) + const connectionHolder = new ConnectionHolder({ + mode: READ, + connectionProvider + }) connectionHolder.initializeConnection() connectionHolder.initializeConnection() @@ -178,7 +205,10 @@ describe('ConnectionHolder', () => { connection1, connection2 ]) - const connectionHolder = new ConnectionHolder(READ, connectionProvider) + const connectionHolder = new ConnectionHolder({ + mode: READ, + connectionProvider + }) connectionHolder.initializeConnection() @@ -200,7 +230,10 @@ describe('ConnectionHolder', () => { connection1, connection2 ]) - const connectionHolder = new ConnectionHolder(READ, connectionProvider) + const connectionHolder = new ConnectionHolder({ + mode: READ, + connectionProvider + }) connectionHolder.initializeConnection() @@ -214,6 +247,47 @@ describe('ConnectionHolder', () => { }) }) }) + + it('should return passed mode', () => { + function verifyMode (connectionProvider, mode) { + expect(connectionProvider.mode()).toBe(mode) + } + + verifyMode(new ConnectionHolder(), WRITE) + verifyMode(new ConnectionHolder({ mode: WRITE }), WRITE) + verifyMode(new ConnectionHolder({ mode: READ }), READ) + }) + + it('should default to empty db', () => { + function verifyDefault (connectionProvider) { + expect(connectionProvider.db()).toBe('') + } + + const connectionProvider = newSingleConnectionProvider(new FakeConnection()) + + verifyDefault(new ConnectionHolder()) + verifyDefault(new ConnectionHolder({ mode: READ, connectionProvider })) + verifyDefault(new ConnectionHolder({ mode: WRITE, connectionProvider })) + verifyDefault( + new ConnectionHolder({ mode: WRITE, db: '', connectionProvider }) + ) + verifyDefault( + new ConnectionHolder({ mode: WRITE, db: null, connectionProvider }) + ) + verifyDefault( + new ConnectionHolder({ mode: WRITE, db: undefined, connectionProvider }) + ) + }) + + it('should return passed db', () => { + const connectionProvider = newSingleConnectionProvider(new FakeConnection()) + const connectionHolder = new ConnectionHolder({ + db: 'testdb', + connectionProvider + }) + + expect(connectionHolder.db()).toBe('testdb') + }) }) class RecordingConnectionProvider extends SingleConnectionProvider { @@ -223,7 +297,7 @@ class RecordingConnectionProvider extends SingleConnectionProvider { this.acquireConnectionInvoked = 0 } - acquireConnection (mode) { + acquireConnection (mode, db) { return this.connectionPromises[this.acquireConnectionInvoked++] } } diff --git a/test/internal/connection.test.js b/test/internal/connection.test.js index 69418c7b6..d6e393d41 100644 --- a/test/internal/connection.test.js +++ b/test/internal/connection.test.js @@ -97,16 +97,11 @@ describe('Connection', () => { streamObserver.subscribe(pullAllObserver) connection.connect('mydriver/0.0.0', basicAuthToken()).then(() => { - connection - .protocol() - .run( - 'RETURN 1.0', - {}, - Bookmark.empty(), - TxConfig.empty(), - WRITE, - streamObserver - ) + connection.protocol().run('RETURN 1.0', {}, streamObserver, { + bookmark: Bookmark.empty(), + txConfig: TxConfig.empty(), + mode: WRITE + }) }) }) @@ -122,12 +117,12 @@ describe('Connection', () => { connection._negotiateProtocol() const boltMagicPreamble = '60 60 b0 17' + const protocolVersion4 = '00 00 00 04' const protocolVersion3 = '00 00 00 03' const protocolVersion2 = '00 00 00 02' const protocolVersion1 = '00 00 00 01' - const noProtocolVersion = '00 00 00 00' expect(channel.toHex()).toBe( - `${boltMagicPreamble} ${protocolVersion3} ${protocolVersion2} ${protocolVersion1} ${noProtocolVersion}` + `${boltMagicPreamble} ${protocolVersion4} ${protocolVersion3} ${protocolVersion2} ${protocolVersion1}` ) }) @@ -257,7 +252,12 @@ describe('Connection', () => { connection => connection .protocol() - .run('RETURN 1', {}, Bookmark.empty(), TxConfig.empty(), {}), + .run( + 'RETURN 1', + {}, + {}, + { bookmark: Bookmark.empty(), txConfig: TxConfig.empty() } + ), done ) }) diff --git a/test/internal/http/http-session-tracker.test.js b/test/internal/http/http-session-tracker.test.js index afabddb40..5a44b911e 100644 --- a/test/internal/http/http-session-tracker.test.js +++ b/test/internal/http/http-session-tracker.test.js @@ -69,12 +69,12 @@ describe('http session tracker', () => { class FakeHttpSession extends HttpSession { constructor (sessionTracker) { - super( - urlUtil.parseDatabaseUrl('http://localhost:7474'), - sharedNeo4j.authToken, - {}, - sessionTracker - ) + super({ + url: urlUtil.parseDatabaseUrl('http://localhost:7474'), + authToken: sharedNeo4j.authToken, + config: {}, + sessionTracker: sessionTracker + }) this.timesClosed = 0 } diff --git a/test/internal/http/http-session.test.js b/test/internal/http/http-session.test.js index 17be37883..a81c7877d 100644 --- a/test/internal/http/http-session.test.js +++ b/test/internal/http/http-session.test.js @@ -29,12 +29,12 @@ describe('http session', () => { return } - const session = new HttpSession( - urlUtil.parseDatabaseUrl('http://localhost:7474'), - sharedNeo4j.authToken, - {}, - new HttpSessionTracker() - ) + const session = new HttpSession({ + url: urlUtil.parseDatabaseUrl('http://localhost:7474'), + authToken: sharedNeo4j.authToken, + config: {}, + sessionTracker: new HttpSessionTracker() + }) expect(() => session.run('RETURN $value', [1, 2, 3])).toThrowError( TypeError diff --git a/test/internal/node/direct.driver.boltkit.test.js b/test/internal/node/direct.driver.boltkit.test.js index 261bea994..bd3040e59 100644 --- a/test/internal/node/direct.driver.boltkit.test.js +++ b/test/internal/node/direct.driver.boltkit.test.js @@ -75,7 +75,10 @@ describe('direct driver with stub server', () => { boltStub.run(() => { const driver = boltStub.newDriver('bolt://127.0.0.1:9001') - const session = driver.session(READ, 'neo4j:bookmark:v1:tx42') + const session = driver.session({ + defaultAccessMode: READ, + bookmarks: ['neo4j:bookmark:v1:tx42'] + }) const tx = session.beginTransaction() tx.run('MATCH (n) RETURN n.name AS name').then(result => { const records = result.records @@ -111,7 +114,10 @@ describe('direct driver with stub server', () => { boltStub.run(() => { const driver = boltStub.newDriver('bolt://127.0.0.1:9001') - const session = driver.session(WRITE, 'neo4j:bookmark:v1:tx42') + const session = driver.session({ + defaultAccessMode: WRITE, + bookmarks: ['neo4j:bookmark:v1:tx42'] + }) const tx = session.beginTransaction() tx.run("CREATE (n {name:'Bob'})").then(result => { const records = result.records @@ -145,7 +151,10 @@ describe('direct driver with stub server', () => { boltStub.run(() => { const driver = boltStub.newDriver('bolt://127.0.0.1:9001') - const session = driver.session(WRITE, 'neo4j:bookmark:v1:tx42') + const session = driver.session({ + defaultAccessMode: WRITE, + bookmarks: ['neo4j:bookmark:v1:tx42'] + }) const writeTx = session.beginTransaction() writeTx.run("CREATE (n {name:'Bob'})").then(result => { const records = result.records @@ -192,7 +201,10 @@ describe('direct driver with stub server', () => { boltStub.run(() => { const driver = boltStub.newDriver('bolt://127.0.0.1:9001') - const session = driver.session(WRITE, 'neo4j:bookmark:v1:tx42') + const session = driver.session({ + defaultAccessMode: WRITE, + bookmarks: ['neo4j:bookmark:v1:tx42'] + }) const writeTx = session.beginTransaction() writeTx.run("CREATE (n {name:'Bob'})").then(result => { const records = result.records @@ -239,7 +251,10 @@ describe('direct driver with stub server', () => { boltStub.run(() => { const driver = boltStub.newDriver('bolt://127.0.0.1:9001') - const session = driver.session(WRITE, 'neo4j:bookmark:v1:tx42') + const session = driver.session({ + defaultAccessMode: WRITE, + bookmarks: ['neo4j:bookmark:v1:tx42'] + }) const writeTx = session.beginTransaction() writeTx.run("CREATE (n {name:'Bob'})").then(result => { const records = result.records diff --git a/test/internal/node/routing.driver.boltkit.test.js b/test/internal/node/routing.driver.boltkit.test.js index 434e4dde8..4963aec09 100644 --- a/test/internal/node/routing.driver.boltkit.test.js +++ b/test/internal/node/routing.driver.boltkit.test.js @@ -88,7 +88,7 @@ describe('routing driver with stub server', () => { boltStub.run(() => { const driver = boltStub.newDriver('bolt+routing://127.0.0.1:9001') - const session = driver.session(READ) + const session = driver.session({ defaultAccessMode: READ }) session.run('MATCH (n) RETURN n.name').then(() => { expect( hasAddressInConnectionPool(driver, '127.0.0.1:9001') @@ -130,7 +130,7 @@ describe('routing driver with stub server', () => { boltStub.run(() => { const driver = boltStub.newDriver('bolt+routing://127.0.0.1:9042') - const session = driver.session(neo4j.session.READ) + const session = driver.session({ defaultAccessMode: READ }) session.run('MATCH (n) RETURN n.name').then(() => { session.close() @@ -236,7 +236,7 @@ describe('routing driver with stub server', () => { const driver = boltStub.newDriver('bolt+routing://127.0.0.1:9001') // When - const session = driver.session(neo4j.READ) + const session = driver.session({ defaultAccessMode: READ }) session .run('MATCH (n) RETURN n.name') .catch(err => { @@ -273,7 +273,7 @@ describe('routing driver with stub server', () => { boltStub.run(() => { const driver = boltStub.newDriver('bolt+routing://127.0.0.1:9001') // When - const session = driver.session(neo4j.session.READ) + const session = driver.session({ defaultAccessMode: READ }) session.run('MATCH (n) RETURN n.name').then(res => { session.close() @@ -325,7 +325,7 @@ describe('routing driver with stub server', () => { boltStub.run(() => { const driver = boltStub.newDriver('bolt+routing://127.0.0.1:9999') // When - const session1 = driver.session(neo4j.session.READ) + const session1 = driver.session({ defaultAccessMode: READ }) session1.run('MATCH (n) RETURN n.name').then(res => { // Then expect(res.records[0].get('n.name')).toEqual('Bob') @@ -333,7 +333,7 @@ describe('routing driver with stub server', () => { expect(res.records[2].get('n.name')).toEqual('Tina') session1.close() - const session2 = driver.session(neo4j.session.READ) + const session2 = driver.session({ defaultAccessMode: READ }) session2.run('MATCH (n) RETURN n.name').then(res => { // Then expect(res.records[0].get('n.name')).toEqual('Bob') @@ -381,14 +381,14 @@ describe('routing driver with stub server', () => { boltStub.run(() => { const driver = boltStub.newDriver('bolt+routing://127.0.0.1:9001') // When - const session1 = driver.session(neo4j.session.READ) + const session1 = driver.session({ defaultAccessMode: READ }) session1.run('MATCH (n) RETURN n.name').then(res => { // Then expect(res.records[0].get('n.name')).toEqual('Bob') expect(res.records[1].get('n.name')).toEqual('Alice') expect(res.records[2].get('n.name')).toEqual('Tina') session1.close() - const session2 = driver.session(neo4j.session.READ) + const session2 = driver.session({ defaultAccessMode: READ }) session2.run('MATCH (n) RETURN n.name').then(res => { // Then expect(res.records[0].get('n.name')).toEqual('Bob') @@ -430,7 +430,7 @@ describe('routing driver with stub server', () => { boltStub.run(() => { const driver = boltStub.newDriver('bolt+routing://127.0.0.1:9001') // When - const session = driver.session(neo4j.session.READ) + const session = driver.session({ defaultAccessMode: READ }) session.run('MATCH (n) RETURN n.name').catch(err => { expect(err.code).toEqual(neo4j.error.SESSION_EXPIRED) driver.close() @@ -463,7 +463,7 @@ describe('routing driver with stub server', () => { boltStub.run(() => { const driver = boltStub.newDriver('bolt+routing://127.0.0.1:9001') // When - const session = driver.session(neo4j.session.WRITE) + const session = driver.session({ defaultAccessMode: WRITE }) session.run("CREATE (n {name:'Bob'})").then(() => { // Then driver.close() @@ -500,9 +500,9 @@ describe('routing driver with stub server', () => { boltStub.run(() => { const driver = boltStub.newDriver('bolt+routing://127.0.0.1:9001') // When - const session1 = driver.session(neo4j.session.WRITE) + const session1 = driver.session({ defaultAccessMode: WRITE }) session1.run("CREATE (n {name:'Bob'})").then(() => { - const session2 = driver.session(neo4j.session.WRITE) + const session2 = driver.session({ defaultAccessMode: WRITE }) session2.run("CREATE (n {name:'Bob'})").then(() => { // Then driver.close() @@ -539,7 +539,7 @@ describe('routing driver with stub server', () => { boltStub.run(() => { const driver = boltStub.newDriver('bolt+routing://127.0.0.1:9001') // When - const session = driver.session(neo4j.session.WRITE) + const session = driver.session({ defaultAccessMode: WRITE }) session.run('MATCH (n) RETURN n.name').catch(err => { expect(err.code).toEqual(neo4j.error.SESSION_EXPIRED) driver.close() @@ -572,7 +572,7 @@ describe('routing driver with stub server', () => { boltStub.run(() => { const driver = boltStub.newDriver('bolt+routing://127.0.0.1:9001') // When - const session = driver.session(neo4j.session.READ) + const session = driver.session({ defaultAccessMode: READ }) session.run('MATCH (n) RETURN n.name').then(() => { // Then assertHasRouters(driver, [ @@ -612,7 +612,7 @@ describe('routing driver with stub server', () => { boltStub.run(() => { const driver = boltStub.newDriver('bolt+routing://127.0.0.1:9001') // When - const session = driver.session(neo4j.session.READ) + const session = driver.session({ defaultAccessMode: READ }) session.run('MATCH (n) RETURN n.name').catch(() => { session.close() // Then @@ -653,7 +653,7 @@ describe('routing driver with stub server', () => { boltStub.run(() => { const driver = boltStub.newDriver('bolt+routing://127.0.0.1:9001') // When - const session = driver.session(neo4j.session.READ) + const session = driver.session({ defaultAccessMode: READ }) session.run('MATCH (n) RETURN n.name').catch(() => { session.close() // Then @@ -695,9 +695,9 @@ describe('routing driver with stub server', () => { boltStub.run(() => { const driver = boltStub.newDriver('bolt+routing://127.0.0.1:9001') // When - const session1 = driver.session(neo4j.session.READ) + const session1 = driver.session({ defaultAccessMode: READ }) session1.run('MATCH (n) RETURN n.name').catch(() => { - const session2 = driver.session(neo4j.session.READ) + const session2 = driver.session({ defaultAccessMode: READ }) session2.run('MATCH (n) RETURN n.name').then(() => { driver.close() seedServer.exit(code1 => { @@ -835,7 +835,7 @@ describe('routing driver with stub server', () => { boltStub.run(() => { const driver = boltStub.newDriver('bolt+routing://127.0.0.1:9001') // When - const session = driver.session(neo4j.session.WRITE) + const session = driver.session({ defaultAccessMode: WRITE }) session.run('MATCH (n) RETURN n.name').catch(err => { expect(err.code).toEqual(neo4j.error.SESSION_EXPIRED) driver.close() @@ -936,11 +936,11 @@ describe('routing driver with stub server', () => { boltStub.run(() => { const driver = boltStub.newDriver('bolt+routing://127.0.0.1:9002') // When - const session1 = driver.session(neo4j.session.WRITE) + const session1 = driver.session({ defaultAccessMode: WRITE }) session1.run("CREATE (n {name:'Bob'})").then(() => { session1.close(() => { const openConnectionsCount = numberOfOpenConnections(driver) - const session2 = driver.session(neo4j.session.WRITE) + const session2 = driver.session({ defaultAccessMode: WRITE }) session2.run('CREATE ()').then(() => { // driver should have same amount of open connections at this point; // no new connections should be created, existing connections should be reused @@ -988,9 +988,9 @@ describe('routing driver with stub server', () => { boltStub.run(() => { const driver = boltStub.newDriver('bolt+routing://127.0.0.1:9001') // When - const readSession = driver.session(neo4j.session.READ) + const readSession = driver.session({ defaultAccessMode: READ }) readSession.run('MATCH (n) RETURN n.name').then(readResult => { - const writeSession = driver.session(neo4j.session.WRITE) + const writeSession = driver.session({ defaultAccessMode: WRITE }) writeSession.run("CREATE (n {name:'Bob'})").then(writeResult => { const readServerInfo = readResult.summary.server const writeServerInfo = writeResult.summary.server @@ -1044,12 +1044,12 @@ describe('routing driver with stub server', () => { boltStub.run(() => { const driver = boltStub.newDriver('bolt+routing://127.0.0.1:9001') // When - const readSession = driver.session(neo4j.session.READ) + const readSession = driver.session({ defaultAccessMode: READ }) readSession.run('MATCH (n) RETURN n.name').subscribe({ onNext: () => {}, onError: () => {}, onCompleted: readSummary => { - const writeSession = driver.session(neo4j.session.WRITE) + const writeSession = driver.session({ defaultAccessMode: WRITE }) writeSession.run("CREATE (n {name:'Bob'})").subscribe({ onNext: () => {}, onError: () => {}, @@ -1398,7 +1398,10 @@ describe('routing driver with stub server', () => { boltStub.run(() => { const driver = boltStub.newDriver('bolt+routing://127.0.0.1:9001') - const session = driver.session(READ, 'neo4j:bookmark:v1:tx42') + const session = driver.session({ + defaultAccessMode: READ, + bookmarks: ['neo4j:bookmark:v1:tx42'] + }) const tx = session.beginTransaction() tx.run('MATCH (n) RETURN n.name AS name').then(result => { const records = result.records @@ -1442,7 +1445,7 @@ describe('routing driver with stub server', () => { boltStub.run(() => { const driver = boltStub.newDriver('bolt+routing://127.0.0.1:9001') - const session = driver.session(null, 'neo4j:bookmark:v1:tx42') + const session = driver.session({ bookmarks: ['neo4j:bookmark:v1:tx42'] }) const writeTx = session.beginTransaction() writeTx.run("CREATE (n {name:'Bob'})").then(() => { writeTx.commit().then(() => { @@ -1836,7 +1839,7 @@ describe('routing driver with stub server', () => { const driver = boltStub.newDriver('bolt+routing://127.0.0.1:9010') // run a dummy query to force routing table initialization - const session = driver.session(READ) + const session = driver.session({ defaultAccessMode: READ }) session.run('MATCH (n) RETURN n.name').then(result => { expect(result.records.length).toEqual(3) session.close(() => { @@ -2041,7 +2044,7 @@ describe('routing driver with stub server', () => { boltStub.run(() => { const driver = boltStub.newDriver('bolt+routing://127.0.0.1:9010') - const session = driver.session(READ) + const session = driver.session({ defaultAccessMode: READ }) session.run('MATCH (n) RETURN n.name').then(result1 => { expect(result1.records.length).toEqual(3) @@ -2087,7 +2090,7 @@ describe('routing driver with stub server', () => { boltStub.run(() => { const driver = boltStub.newDriver('bolt+routing://127.0.0.1:9010') - const session = driver.session(READ) + const session = driver.session({ defaultAccessMode: READ }) session.run('MATCH (n) RETURN n.name').then(result => { session.close(() => { expect(result.records.map(record => record.get(0))).toEqual([ @@ -2144,7 +2147,7 @@ describe('routing driver with stub server', () => { 'Tina' ]) - const writeSession = driver.session(WRITE) + const writeSession = driver.session({ defaultAccessMode: WRITE }) writeSession.run("CREATE (n {name:'Bob'})").catch(error => { expect(error.code).toEqual(neo4j.error.SESSION_EXPIRED) @@ -2209,7 +2212,7 @@ describe('routing driver with stub server', () => { 9010 ) boltStub.run(() => { - const writeSession = driver.session(WRITE) + const writeSession = driver.session({ defaultAccessMode: WRITE }) writeSession.run("CREATE (n {name:'Bob'})").then(result => { writeSession.close(() => { expect(result.records).toEqual([]) @@ -2263,7 +2266,7 @@ describe('routing driver with stub server', () => { boltStub.run(() => { const driver = boltStub.newDriver('bolt+routing://127.0.0.1:9010') - const readSession = driver.session(READ) + const readSession = driver.session({ defaultAccessMode: READ }) readSession.run('MATCH (n) RETURN n.name').then(result => { readSession.close(() => { expect(result.records.map(record => record.get(0))).toEqual([ @@ -2276,7 +2279,7 @@ describe('routing driver with stub server', () => { '127.0.0.1:9020' ]) - const writeSession = driver.session(WRITE) + const writeSession = driver.session({ defaultAccessMode: WRITE }) writeSession.run("CREATE (n {name:'Bob'})").then(result => { writeSession.close(() => { expect(result.records).toEqual([]) @@ -2358,7 +2361,7 @@ describe('routing driver with stub server', () => { 'neo4j:bookmark:v1:tx16', 'neo4j:bookmark:v1:tx68' ] - const session = driver.session(WRITE, bookmarks) + const session = driver.session({ defaultAccessMode: WRITE, bookmarks }) const tx = session.beginTransaction() tx.run(`CREATE (n {name:'Bob'})`).then(() => { @@ -2466,7 +2469,7 @@ describe('routing driver with stub server', () => { resolver: resolverFunction }) - const session = driver.session(READ) + const session = driver.session({ defaultAccessMode: READ }) // run a query that should trigger discovery against 9001 and then read from it session .run('MATCH (n) RETURN n.name AS name') @@ -2550,7 +2553,7 @@ describe('routing driver with stub server', () => { boltStub.run(() => { const driver = boltStub.newDriver('bolt+routing://127.0.0.1:9001') // When - const session = driver.session(neo4j.session.READ) + const session = driver.session({ defaultAccessMode: READ }) session.run('MATCH (n) RETURN n.name').then(res => { session.close() @@ -2588,7 +2591,7 @@ describe('routing driver with stub server', () => { boltStub.run(() => { const driver = boltStub.newDriver('bolt+routing://127.0.0.1:9001') // When - const session = driver.session(neo4j.session.READ) + const session = driver.session({ defaultAccessMode: READ }) session .readTransaction(tx => tx.run('MATCH (n) RETURN n.name')) .then(res => { @@ -2628,7 +2631,7 @@ describe('routing driver with stub server', () => { boltStub.run(() => { const driver = boltStub.newDriver('bolt+routing://127.0.0.1:9001') // When - const session = driver.session(neo4j.session.WRITE) + const session = driver.session({ defaultAccessMode: WRITE }) session.run("CREATE (n {name:'Bob'})").then(res => { session.close() driver.close() @@ -2661,7 +2664,7 @@ describe('routing driver with stub server', () => { boltStub.run(() => { const driver = boltStub.newDriver('bolt+routing://127.0.0.1:9001') // When - const session = driver.session(neo4j.session.WRITE) + const session = driver.session({ defaultAccessMode: WRITE }) session .writeTransaction(tx => tx.run("CREATE (n {name:'Bob'})")) .then(res => { @@ -2702,7 +2705,7 @@ describe('routing driver with stub server', () => { boltStub.run(() => { const driver = boltStub.newDriver('bolt+routing://127.0.0.1:9010') - const session = driver.session(accessMode) + const session = driver.session({ defaultAccessMode: accessMode }) session.run(query).catch(error => { expect(error.message).toEqual('Database is busy doing store copy') expect(error.code).toEqual( @@ -2764,7 +2767,10 @@ describe('routing driver with stub server', () => { boltStub.run(() => { const driver = boltStub.newDriver('bolt+routing://127.0.0.1:9001') - const session = driver.session(accessMode, bookmark) + const session = driver.session({ + defaultAccessMode: accessMode, + bookmarks: [bookmark] + }) const tx = session.beginTransaction() tx.run("CREATE (n {name:'Bob'})").then(() => { tx.commit().then(() => { @@ -2807,7 +2813,7 @@ describe('routing driver with stub server', () => { driverConfig ) - const session = driver.session(READ) + const session = driver.session({ defaultAccessMode: READ }) session .run('MATCH (n) RETURN n.name') .then(result => { @@ -3012,7 +3018,7 @@ describe('routing driver with stub server', () => { resolver: resolverFunction }) - const session = driver.session(READ) + const session = driver.session({ defaultAccessMode: READ }) session .run('MATCH (n) RETURN n.name') .then(result => { diff --git a/test/internal/protocol-handshaker.test.js b/test/internal/protocol-handshaker.test.js index 1d14b7240..fca9debe3 100644 --- a/test/internal/protocol-handshaker.test.js +++ b/test/internal/protocol-handshaker.test.js @@ -42,13 +42,13 @@ describe('ProtocolHandshaker', () => { expect(writtenBuffers.length).toEqual(1) const boltMagicPreamble = '60 60 b0 17' + const protocolVersion4 = '00 00 00 04' const protocolVersion3 = '00 00 00 03' const protocolVersion2 = '00 00 00 02' const protocolVersion1 = '00 00 00 01' - const noProtocolVersion = '00 00 00 00' expect(writtenBuffers[0].toHex()).toEqual( - `${boltMagicPreamble} ${protocolVersion3} ${protocolVersion2} ${protocolVersion1} ${noProtocolVersion}` + `${boltMagicPreamble} ${protocolVersion4} ${protocolVersion3} ${protocolVersion2} ${protocolVersion1}` ) }) diff --git a/test/internal/request-message.test.js b/test/internal/request-message.test.js index 18f62ac55..b4016d256 100644 --- a/test/internal/request-message.test.js +++ b/test/internal/request-message.test.js @@ -87,7 +87,7 @@ describe('RequestMessage', () => { ]) const txConfig = new TxConfig({ timeout: 42, metadata: { key: 42 } }) - const message = RequestMessage.begin(bookmark, txConfig, mode) + const message = RequestMessage.begin({ bookmark, txConfig, mode }) const expectedMetadata = { bookmarks: bookmark.values(), @@ -136,13 +136,11 @@ describe('RequestMessage', () => { metadata: { a: 'a', b: 'b' } }) - const message = RequestMessage.runWithMetadata( - statement, - parameters, + const message = RequestMessage.runWithMetadata(statement, parameters, { bookmark, txConfig, mode - ) + }) const expectedMetadata = { bookmarks: bookmark.values(), @@ -170,4 +168,51 @@ describe('RequestMessage', () => { expect(message.fields).toEqual([]) expect(message.toString()).toEqual('GOODBYE') }) + + describe('BoltV4', () => { + function verify (message, signature, metadata, name) { + expect(message.signature).toEqual(signature) + expect(message.fields).toEqual([metadata]) + expect(message.toString()).toEqual(`${name} ${JSON.stringify(metadata)}`) + } + + it('should create PULL message', () => { + verify(RequestMessage.pull(), 0x3f, { n: int(-1) }, 'PULL') + }) + + it('should create PULL message with n only', () => { + verify(RequestMessage.pull({ n: 501 }), 0x3f, { n: int(501) }, 'PULL') + }) + + it('should create PULL message with stmt_id and n', () => { + verify( + RequestMessage.pull({ stmtId: 27, n: 1023 }), + 0x3f, + { n: int(1023), stmt_id: int(27) }, + 'PULL' + ) + }) + + it('should create DISCARD message', () => { + verify(RequestMessage.discard(), 0x2f, { n: int(-1) }, 'DISCARD') + }) + + it('should create DISCARD message with n', () => { + verify( + RequestMessage.discard({ n: 501 }), + 0x2f, + { n: int(501) }, + 'DISCARD' + ) + }) + + it('should create DISCARD message with stmt_id and n', () => { + verify( + RequestMessage.discard({ stmtId: 27, n: 1023 }), + 0x2f, + { n: int(1023), stmt_id: int(27) }, + 'DISCARD' + ) + }) + }) }) diff --git a/test/internal/test-utils.js b/test/internal/test-utils.js index 27d900a2c..1037f7b98 100644 --- a/test/internal/test-utils.js +++ b/test/internal/test-utils.js @@ -16,6 +16,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ + function isClient () { return typeof window !== 'undefined' && window.document } @@ -49,6 +50,65 @@ const matchers = { return result } } + }, + toBeMessage: function (util, customEqualityTesters) { + return { + compare: function (actual, expected) { + if (expected === undefined) { + expected = {} + } + + let result = {} + let failures = [] + + if (!util.equals(expected.signature, actual.signature)) { + failures.push( + `signature '${actual.signature}' to match '${expected.signature}'` + ) + } + + if (!util.equals(expected.fields, actual.fields)) { + failures.push( + `fields '[${JSON.stringify( + actual.fields + )}]' to match '[${JSON.stringify(expected.fields)}]'` + ) + } + + result.pass = failures.length === 0 + if (result.pass) { + result.message = `Expected message '${actual}' to match '${expected}'` + } else { + result.message = `Expected message '[${failures}]', but it didn't` + } + return result + } + } + } +} + +class MessageRecordingConnection { + constructor () { + this.messages = [] + this.observers = [] + this.flushes = [] + this.fatalErrors = [] + } + + write (message, observer, flush) { + this.messages.push(message) + this.observers.push(observer) + this.flushes.push(flush) + } + + _handleFatalError (error) { + this.fatalErrors.push(error) + } + + verifyMessageCount (expected) { + expect(this.messages.length).toEqual(expected) + expect(this.observers.length).toEqual(expected) + expect(this.flushes.length).toEqual(expected) } } @@ -56,5 +116,6 @@ export default { isClient, isServer, fakeStandardDateWithOffset, - matchers + matchers, + MessageRecordingConnection } diff --git a/test/session.test.js b/test/session.test.js index 1554d56a3..d610081b5 100644 --- a/test/session.test.js +++ b/test/session.test.js @@ -439,7 +439,9 @@ describe('session', () => { }) it('should allow creation of a ' + neo4j.session.READ + ' session', done => { - const readSession = driver.session(neo4j.session.READ) + const readSession = driver.session({ + defaultAccessMode: neo4j.session.READ + }) readSession.run('RETURN 1').then(() => { readSession.close() done() @@ -447,7 +449,9 @@ describe('session', () => { }) it('should allow creation of a ' + neo4j.session.WRITE + ' session', done => { - const writeSession = driver.session(neo4j.session.WRITE) + const writeSession = driver.session({ + defaultAccessMode: neo4j.session.WRITE + }) writeSession.run('CREATE ()').then(() => { writeSession.close() done() @@ -455,7 +459,9 @@ describe('session', () => { }) it('should fail for illegal session mode', () => { - expect(() => driver.session('ILLEGAL_MODE')).toThrow() + expect(() => + driver.session({ defaultAccessMode: 'ILLEGAL_MODE' }) + ).toThrow() }) it('should release connection to the pool after run', done => { @@ -925,7 +931,7 @@ describe('session', () => { expect(_.uniq(bookmarks).length).toEqual(nodeCount) bookmarks.forEach(bookmark => expect(_.isString(bookmark)).toBeTruthy()) - const session = driver.session(READ, bookmarks) + const session = driver.session({ defaultAccessMode: READ, bookmarks }) try { const result = await session.run('MATCH (n) RETURN count(n)') const count = result.records[0].get(0).toInt() @@ -1206,7 +1212,7 @@ describe('session', () => { const connectionProvider = new SingleConnectionProvider( Promise.resolve(connection) ) - const session = new Session(READ, connectionProvider) + const session = new Session({ mode: READ, connectionProvider }) session.beginTransaction() // force session to acquire new connection return session } diff --git a/test/stress.test.js b/test/stress.test.js index 88874e69f..a0bfdc9e5 100644 --- a/test/stress.test.js +++ b/test/stress.test.js @@ -527,9 +527,12 @@ describe('stress tests', () => { function newSession (context, accessMode, useBookmark) { if (useBookmark) { - return context.driver.session(accessMode, context.bookmark) + return context.driver.session({ + defaultAccessMode: accessMode, + bookmarks: [context.bookmark] + }) } - return context.driver.session(accessMode) + return context.driver.session({ defaultAccessMode: accessMode }) } function modeFromEnvOrDefault (envVariableName) { diff --git a/test/types/driver.test.ts b/test/types/driver.test.ts index 0d3c42834..afdb904bc 100644 --- a/test/types/driver.test.ts +++ b/test/types/driver.test.ts @@ -75,12 +75,18 @@ const writeMode2: string = WRITE const driver: Driver = dummy const session1: Session = driver.session() -const session2: Session = driver.session('READ') -const session3: Session = driver.session(READ) -const session4: Session = driver.session('WRITE') -const session5: Session = driver.session(WRITE) -const session6: Session = driver.session(READ, 'bookmark1') -const session7: Session = driver.session(WRITE, 'bookmark2') +const session2: Session = driver.session({ defaultAccessMode: 'READ' }) +const session3: Session = driver.session({ defaultAccessMode: READ }) +const session4: Session = driver.session({ defaultAccessMode: 'WRITE' }) +const session5: Session = driver.session({ defaultAccessMode: WRITE }) +const session6: Session = driver.session({ + defaultAccessMode: READ, + bookmarks: 'bookmark1' +}) +const session7: Session = driver.session({ + defaultAccessMode: WRITE, + bookmarks: 'bookmark2' +}) session1.run('RETURN 1').then(result => { session1.close() diff --git a/types/driver.d.ts b/types/driver.d.ts index 62a73c718..414d860f8 100644 --- a/types/driver.d.ts +++ b/types/driver.d.ts @@ -62,7 +62,15 @@ declare const READ: SessionMode declare const WRITE: SessionMode declare interface Driver { - session(mode?: SessionMode, bookmark?: string): Session + session({ + defaultAccessMode, + bookmarks, + db + }?: { + defaultAccessMode?: SessionMode + bookmarks?: string | string[] + db?: string + }): Session close(): void