diff --git a/src/v1/driver.js b/src/v1/driver.js
index 4dd03c593..771120761 100644
--- a/src/v1/driver.js
+++ b/src/v1/driver.js
@@ -23,6 +23,7 @@ import {connect} from './internal/connector';
import StreamObserver from './internal/stream-observer';
import {newError, SERVICE_UNAVAILABLE} from './error';
import {DirectConnectionProvider} from './internal/connection-providers';
+import Bookmark from './internal/bookmark';
const READ = 'READ', WRITE = 'WRITE';
/**
@@ -115,13 +116,14 @@ class Driver {
* 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} [bookmark=null] the initial reference to some previous transaction. Value is optional and
- * absence indicates that that the bookmark does not exist or is unknown.
+ * @param {string|string[]} [bookmarkOrBookmarks=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.
* @return {Session} new session.
*/
- session(mode, bookmark) {
+ session(mode, bookmarkOrBookmarks) {
const sessionMode = Driver._validateSessionMode(mode);
const connectionProvider = this._getOrCreateConnectionProvider();
+ const bookmark = new Bookmark(bookmarkOrBookmarks);
return this._createSession(sessionMode, connectionProvider, bookmark, this._config);
}
diff --git a/src/v1/internal/bookmark.js b/src/v1/internal/bookmark.js
new file mode 100644
index 000000000..ccb350130
--- /dev/null
+++ b/src/v1/internal/bookmark.js
@@ -0,0 +1,141 @@
+/**
+ * Copyright (c) 2002-2017 "Neo Technology,","
+ * Network Engine for Objects in Lund AB [http://neotechnology.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 * as util from './util';
+
+const BOOKMARK_KEY = 'bookmark';
+const BOOKMARKS_KEY = 'bookmarks';
+const BOOKMARK_PREFIX = 'neo4j:bookmark:v1:tx';
+
+const UNKNOWN_BOOKMARK_VALUE = -1;
+
+export default class Bookmark {
+
+ /**
+ * @constructor
+ * @param {string|string[]} values single bookmark as string or multiple bookmarks as a string array.
+ */
+ constructor(values) {
+ this._values = asStringArray(values);
+ this._maxValue = maxBookmark(this._values);
+ }
+
+ /**
+ * Check if the given bookmark is meaningful and can be send to the database.
+ * @return {boolean} returns true
bookmark has a value, false
otherwise.
+ */
+ isEmpty() {
+ return this._maxValue === null;
+ }
+
+ /**
+ * Get maximum value of this bookmark as string.
+ * @return {string|null} the maximum value or null
if it is not defined.
+ */
+ maxBookmarkAsString() {
+ return this._maxValue;
+ }
+
+ /**
+ * Get this bookmark as an object for begin transaction call.
+ * @return {object} the value of this bookmark as object.
+ */
+ asBeginTransactionParameters() {
+ if (this.isEmpty()) {
+ return {};
+ }
+
+ // Driver sends {bookmark: "max", bookmarks: ["one", "two", "max"]} instead of simple
+ // {bookmarks: ["one", "two", "max"]} for backwards compatibility reasons. Old servers can only accept single
+ // bookmark that is why driver has to parse and compare given list of bookmarks. This functionality will
+ // eventually be removed.
+ return {
+ [BOOKMARK_KEY]: this._maxValue,
+ [BOOKMARKS_KEY]: this._values
+ };
+ }
+}
+
+/**
+ * Converts given value to an array.
+ * @param {string|string[]} [value=undefined] argument to convert.
+ * @return {string[]} value converted to an array.
+ */
+function asStringArray(value) {
+ if (!value) {
+ return [];
+ }
+
+ if (util.isString(value)) {
+ return [value];
+ }
+
+ if (Array.isArray(value)) {
+ const result = [];
+ for (let i = 0; i < value.length; i++) {
+ const element = value[i];
+ if (!util.isString(element)) {
+ throw new TypeError(`Bookmark should be a string, given: '${element}'`);
+ }
+ result.push(element);
+ }
+ return result;
+ }
+
+ throw new TypeError(`Bookmark should either be a string or a string array, given: '${value}'`);
+}
+
+/**
+ * Find latest bookmark in the given array of bookmarks.
+ * @param {string[]} bookmarks array of bookmarks.
+ * @return {string|null} latest bookmark value.
+ */
+function maxBookmark(bookmarks) {
+ if (!bookmarks || bookmarks.length === 0) {
+ return null;
+ }
+
+ let maxBookmark = bookmarks[0];
+ let maxValue = bookmarkValue(maxBookmark);
+
+ for (let i = 1; i < bookmarks.length; i++) {
+ const bookmark = bookmarks[i];
+ const value = bookmarkValue(bookmark);
+
+ if (value > maxValue) {
+ maxBookmark = bookmark;
+ maxValue = value;
+ }
+ }
+
+ return maxBookmark;
+}
+
+/**
+ * Calculate numeric value for the given bookmark.
+ * @param {string} bookmark argument to get numeric value for.
+ * @return {number} value of the bookmark.
+ */
+function bookmarkValue(bookmark) {
+ if (bookmark && bookmark.indexOf(BOOKMARK_PREFIX) === 0) {
+ const result = parseInt(bookmark.substring(BOOKMARK_PREFIX.length));
+ return result ? result : UNKNOWN_BOOKMARK_VALUE;
+ }
+ return UNKNOWN_BOOKMARK_VALUE;
+}
diff --git a/src/v1/internal/util.js b/src/v1/internal/util.js
index e7fd5c66e..50811e23a 100644
--- a/src/v1/internal/util.js
+++ b/src/v1/internal/util.js
@@ -116,6 +116,7 @@ function trimAndVerify(string, name, url) {
export {
isEmptyObjectOrNull,
+ isString,
assertString,
parseScheme,
parseUrl,
diff --git a/src/v1/session.js b/src/v1/session.js
index 74770a936..5ff5f13e6 100644
--- a/src/v1/session.js
+++ b/src/v1/session.js
@@ -24,6 +24,7 @@ import {assertString} from './internal/util';
import ConnectionHolder from './internal/connection-holder';
import Driver, {READ, WRITE} from './driver';
import TransactionExecutor from './internal/transaction-executor';
+import Bookmark from './internal/bookmark';
/**
* A Session instance is used for handling the connection and
@@ -37,7 +38,7 @@ class Session {
* @constructor
* @param {string} mode the default access mode for this session.
* @param {ConnectionProvider} connectionProvider - the connection provider to acquire connections from.
- * @param {string} [bookmark=undefined] - the initial bookmark for this session.
+ * @param {Bookmark} bookmark - the initial bookmark for this session.
* @param {Object} [config={}] - this driver configuration.
*/
constructor(mode, connectionProvider, bookmark, config) {
@@ -94,21 +95,17 @@ class Session {
*
* While a transaction is open the session cannot be used to run statements outside the transaction.
*
- * @param {string} bookmark - a reference to a previous transaction. DEPRECATED: This parameter is deprecated in
- * favour of {@link Driver#session} that accepts an initial bookmark. Session will ensure that all nested
- * transactions are chained with bookmarks to guarantee causal consistency.
+ * @param {string|string[]} [bookmarkOrBookmarks=null] - reference or references to some previous transactions.
+ * DEPRECATED: This parameter is deprecated in favour of {@link Driver#session} that accepts an initial bookmark.
+ * Session will ensure that all nested transactions are chained with bookmarks to guarantee causal consistency.
* @returns {Transaction} - New Transaction
*/
- beginTransaction(bookmark) {
- return this._beginTransaction(this._mode, bookmark);
+ beginTransaction(bookmarkOrBookmarks) {
+ this._updateBookmark(new Bookmark(bookmarkOrBookmarks));
+ return this._beginTransaction(this._mode);
}
- _beginTransaction(accessMode, bookmark) {
- if (bookmark) {
- assertString(bookmark, 'Bookmark');
- this._updateBookmark(bookmark);
- }
-
+ _beginTransaction(accessMode) {
if (this._hasTx) {
throw newError('You cannot begin a transaction on a session with an open transaction; ' +
'either run from within the transaction or use a different session.');
@@ -128,10 +125,10 @@ class Session {
/**
* Return the bookmark received following the last completed {@link Transaction}.
*
- * @return a reference to a previous transaction
+ * @return {string|null} a reference to a previous transaction
*/
lastBookmark() {
- return this._lastBookmark;
+ return this._lastBookmark.maxBookmarkAsString();
}
/**
@@ -170,13 +167,18 @@ class Session {
_runTransaction(accessMode, transactionWork) {
return this._transactionExecutor.execute(
- () => this._beginTransaction(accessMode, this.lastBookmark()),
+ () => this._beginTransaction(accessMode),
transactionWork
);
}
+ /**
+ * Update value of the last bookmark.
+ * @param {Bookmark} newBookmark the new bookmark.
+ * @private
+ */
_updateBookmark(newBookmark) {
- if (newBookmark) {
+ if (newBookmark && !newBookmark.isEmpty()) {
this._lastBookmark = newBookmark;
}
}
diff --git a/src/v1/transaction.js b/src/v1/transaction.js
index c4c0fa7e4..1c1ac34b3 100644
--- a/src/v1/transaction.js
+++ b/src/v1/transaction.js
@@ -20,6 +20,7 @@ import StreamObserver from './internal/stream-observer';
import Result from './result';
import {assertString} from './internal/util';
import {EMPTY_CONNECTION_HOLDER} from './internal/connection-holder';
+import Bookmark from './internal/bookmark';
/**
* Represents a transaction in the Neo4j database.
@@ -31,27 +32,23 @@ class Transaction {
* @constructor
* @param {ConnectionHolder} connectionHolder - the connection holder to get connection from.
* @param {function()} onClose - Function to be called when transaction is committed or rolled back.
- * @param errorTransformer callback use to transform error
- * @param bookmark optional bookmark
- * @param onBookmark callback invoked when new bookmark is produced
+ * @param {function(error: Error): Error} errorTransformer callback use to transform error.
+ * @param {Bookmark} bookmark bookmark for transaction begin.
+ * @param {function(bookmark: Bookmark)} onBookmark callback invoked when new bookmark is produced.
*/
constructor(connectionHolder, onClose, errorTransformer, bookmark, onBookmark) {
this._connectionHolder = connectionHolder;
- let streamObserver = new _TransactionStreamObserver(this);
- let params = {};
- if (bookmark) {
- params = {bookmark: bookmark};
- }
+ const streamObserver = new _TransactionStreamObserver(this);
this._connectionHolder.getConnection(streamObserver).then(conn => {
- conn.run('BEGIN', params, streamObserver);
+ conn.run('BEGIN', bookmark.asBeginTransactionParameters(), streamObserver);
conn.pullAll(streamObserver);
}).catch(error => streamObserver.onError(error));
this._state = _states.ACTIVE;
this._onClose = onClose;
this._errorTransformer = errorTransformer;
- this._onBookmark = onBookmark || (() => {});
+ this._onBookmark = onBookmark;
}
/**
@@ -149,7 +146,7 @@ class _TransactionStreamObserver extends StreamObserver {
onCompleted(meta) {
super.onCompleted(meta);
- const bookmark = meta.bookmark;
+ const bookmark = new Bookmark(meta.bookmark);
this._tx._onBookmark(bookmark);
}
}
diff --git a/test/internal/bookmark.test.js b/test/internal/bookmark.test.js
new file mode 100644
index 000000000..7b406c65b
--- /dev/null
+++ b/test/internal/bookmark.test.js
@@ -0,0 +1,121 @@
+/**
+ * Copyright (c) 2002-2017 "Neo Technology,","
+ * Network Engine for Objects in Lund AB [http://neotechnology.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 Bookmark from '../../src/v1/internal/bookmark';
+
+describe('Bookmark', () => {
+
+ it('should be possible to construct bookmark from string', () => {
+ const bookmark = new Bookmark('neo4j:bookmark:v1:tx412');
+
+ expect(bookmark.isEmpty()).toBeFalsy();
+ expect(bookmark.maxBookmarkAsString()).toEqual('neo4j:bookmark:v1:tx412');
+ });
+
+ it('should be possible to construct bookmark from string array', () => {
+ const bookmark = new Bookmark(['neo4j:bookmark:v1:tx1', 'neo4j:bookmark:v1:tx2', 'neo4j:bookmark:v1:tx3']);
+
+ expect(bookmark.isEmpty()).toBeFalsy();
+ expect(bookmark.maxBookmarkAsString()).toEqual('neo4j:bookmark:v1:tx3');
+ });
+
+ it('should be possible to construct bookmark from null', () => {
+ const bookmark = new Bookmark(null);
+
+ expect(bookmark.isEmpty()).toBeTruthy();
+ expect(bookmark.maxBookmarkAsString()).toBeNull();
+ });
+
+ it('should be possible to construct bookmark from undefined', () => {
+ const bookmark = new Bookmark(undefined);
+
+ expect(bookmark.isEmpty()).toBeTruthy();
+ expect(bookmark.maxBookmarkAsString()).toBeNull();
+ });
+
+ it('should be possible to construct bookmark from an empty string', () => {
+ const bookmark = new Bookmark('');
+
+ expect(bookmark.isEmpty()).toBeTruthy();
+ expect(bookmark.maxBookmarkAsString()).toBeNull();
+ });
+
+ it('should be possible to construct bookmark from empty array', () => {
+ const bookmark = new Bookmark([]);
+
+ expect(bookmark.isEmpty()).toBeTruthy();
+ expect(bookmark.maxBookmarkAsString()).toBeNull();
+ });
+
+ it('should not be possible to construct bookmark from object', () => {
+ expect(() => new Bookmark({})).toThrowError(TypeError);
+ expect(() => new Bookmark({bookmark: 'neo4j:bookmark:v1:tx1'})).toThrowError(TypeError);
+ });
+
+ it('should not be possible to construct bookmark from number array', () => {
+ expect(() => new Bookmark([1, 2, 3])).toThrowError(TypeError);
+ });
+
+ it('should not be possible to construct bookmark from mixed array', () => {
+ expect(() => new Bookmark(['neo4j:bookmark:v1:tx1', 2, 'neo4j:bookmark:v1:tx3'])).toThrowError(TypeError);
+ });
+
+ it('should keep unparsable bookmark', () => {
+ const bookmark = new Bookmark('neo4j:bookmark:v1:txWrong');
+
+ expect(bookmark.isEmpty()).toBeFalsy();
+ expect(bookmark.maxBookmarkAsString()).toEqual('neo4j:bookmark:v1:txWrong');
+ });
+
+ it('should skip unparsable bookmarks', () => {
+ const bookmark = new Bookmark(['neo4j:bookmark:v1:tx42', 'neo4j:bookmark:v1:txWrong', 'neo4j:bookmark:v1:tx4242']);
+
+ expect(bookmark.isEmpty()).toBeFalsy();
+ expect(bookmark.maxBookmarkAsString()).toEqual('neo4j:bookmark:v1:tx4242');
+ });
+
+ it('should turn into empty transaction params when empty', () => {
+ const bookmark = new Bookmark(null);
+
+ expect(bookmark.isEmpty()).toBeTruthy();
+ expect(bookmark.asBeginTransactionParameters()).toEqual({});
+ });
+
+ it('should turn into transaction params when represents single bookmark', () => {
+ const bookmark = new Bookmark('neo4j:bookmark:v1:tx142');
+
+ expect(bookmark.isEmpty()).toBeFalsy();
+ expect(bookmark.asBeginTransactionParameters()).toEqual({
+ bookmark: 'neo4j:bookmark:v1:tx142',
+ bookmarks: ['neo4j:bookmark:v1:tx142']
+ });
+ });
+
+ it('should turn into transaction params when represents multiple bookmarks', () => {
+ const bookmark = new Bookmark(
+ ['neo4j:bookmark:v1:tx1', 'neo4j:bookmark:v1:tx3', 'neo4j:bookmark:v1:tx42', 'neo4j:bookmark:v1:tx5']
+ );
+
+ expect(bookmark.isEmpty()).toBeFalsy();
+ expect(bookmark.asBeginTransactionParameters()).toEqual({
+ bookmark: 'neo4j:bookmark:v1:tx42',
+ bookmarks: ['neo4j:bookmark:v1:tx1', 'neo4j:bookmark:v1:tx3', 'neo4j:bookmark:v1:tx42', 'neo4j:bookmark:v1:tx5']
+ });
+ });
+
+});
diff --git a/test/resources/boltkit/multiple_bookmarks.script b/test/resources/boltkit/multiple_bookmarks.script
new file mode 100644
index 000000000..5e7344103
--- /dev/null
+++ b/test/resources/boltkit/multiple_bookmarks.script
@@ -0,0 +1,16 @@
+!: AUTO INIT
+!: AUTO RESET
+!: AUTO PULL_ALL
+
+C: RUN "BEGIN" {"bookmark": "neo4j:bookmark:v1:tx94", "bookmarks": ["neo4j:bookmark:v1:tx5", "neo4j:bookmark:v1:tx29", "neo4j:bookmark:v1:tx94", "neo4j:bookmark:v1:tx56", "neo4j:bookmark:v1:tx16", "neo4j:bookmark:v1:tx68"]}
+ PULL_ALL
+S: SUCCESS {}
+ SUCCESS {}
+C: RUN "CREATE (n {name:'Bob'})" {}
+ PULL_ALL
+S: SUCCESS {}
+ SUCCESS {"bookmark": "neo4j:bookmark:v1:tx95"}
+C: RUN "COMMIT" {}
+ PULL_ALL
+S: SUCCESS {}
+ SUCCESS {}
diff --git a/test/resources/boltkit/read_tx_with_bookmarks.script b/test/resources/boltkit/read_tx_with_bookmarks.script
index a48ee65f9..cb22ffba3 100644
--- a/test/resources/boltkit/read_tx_with_bookmarks.script
+++ b/test/resources/boltkit/read_tx_with_bookmarks.script
@@ -2,7 +2,7 @@
!: AUTO RESET
!: AUTO PULL_ALL
-C: RUN "BEGIN" {"bookmark": "OldBookmark"}
+C: RUN "BEGIN" {"bookmark": "neo4j:bookmark:v1:tx42", "bookmarks": ["neo4j:bookmark:v1:tx42"]}
PULL_ALL
S: SUCCESS {}
SUCCESS {}
@@ -11,7 +11,7 @@ C: RUN "MATCH (n) RETURN n.name AS name" {}
S: SUCCESS {"fields": ["name"]}
RECORD ["Bob"]
RECORD ["Alice"]
- SUCCESS {"bookmark": "NewBookmark"}
+ SUCCESS {"bookmark": "neo4j:bookmark:v1:tx4242"}
C: RUN "COMMIT" {}
PULL_ALL
S: SUCCESS {}
diff --git a/test/resources/boltkit/write_read_tx_with_bookmark_override.script b/test/resources/boltkit/write_read_tx_with_bookmark_override.script
index 889b5d324..238d55b09 100644
--- a/test/resources/boltkit/write_read_tx_with_bookmark_override.script
+++ b/test/resources/boltkit/write_read_tx_with_bookmark_override.script
@@ -2,19 +2,19 @@
!: AUTO RESET
!: AUTO PULL_ALL
-C: RUN "BEGIN" {"bookmark": "BookmarkA"}
+C: RUN "BEGIN" {"bookmark": "neo4j:bookmark:v1:tx42", "bookmarks": ["neo4j:bookmark:v1:tx42"]}
PULL_ALL
S: SUCCESS {}
SUCCESS {}
C: RUN "CREATE (n {name:'Bob'})" {}
PULL_ALL
S: SUCCESS {}
- SUCCESS {"bookmark": "BookmarkB"}
+ SUCCESS {"bookmark": "neo4j:bookmark:v1:tx4242"}
C: RUN "COMMIT" {}
PULL_ALL
S: SUCCESS {}
SUCCESS {}
-C: RUN "BEGIN" {"bookmark": "BookmarkOverride"}
+C: RUN "BEGIN" {"bookmark": "neo4j:bookmark:v1:tx99", "bookmarks": ["neo4j:bookmark:v1:tx99"]}
PULL_ALL
S: SUCCESS {}
SUCCESS {}
@@ -22,7 +22,7 @@ C: RUN "MATCH (n) RETURN n.name AS name" {}
PULL_ALL
S: SUCCESS {"fields": ["name"]}
RECORD ["Bob"]
- SUCCESS {"bookmark": "BookmarkC"}
+ SUCCESS {"bookmark": "neo4j:bookmark:v1:tx424242"}
C: RUN "COMMIT" {}
PULL_ALL
S: SUCCESS {}
diff --git a/test/resources/boltkit/write_read_tx_with_bookmarks.script b/test/resources/boltkit/write_read_tx_with_bookmarks.script
index e8207f887..fc4a18e3a 100644
--- a/test/resources/boltkit/write_read_tx_with_bookmarks.script
+++ b/test/resources/boltkit/write_read_tx_with_bookmarks.script
@@ -2,19 +2,19 @@
!: AUTO RESET
!: AUTO PULL_ALL
-C: RUN "BEGIN" {"bookmark": "BookmarkA"}
+C: RUN "BEGIN" {"bookmark": "neo4j:bookmark:v1:tx42", "bookmarks": ["neo4j:bookmark:v1:tx42"]}
PULL_ALL
S: SUCCESS {}
SUCCESS {}
C: RUN "CREATE (n {name:'Bob'})" {}
PULL_ALL
S: SUCCESS {}
- SUCCESS {"bookmark": "BookmarkB"}
+ SUCCESS {"bookmark": "neo4j:bookmark:v1:tx4242"}
C: RUN "COMMIT" {}
PULL_ALL
S: SUCCESS {}
SUCCESS {}
-C: RUN "BEGIN" {"bookmark": "BookmarkB"}
+C: RUN "BEGIN" {"bookmark": "neo4j:bookmark:v1:tx4242", "bookmarks": ["neo4j:bookmark:v1:tx4242"]}
PULL_ALL
S: SUCCESS {}
SUCCESS {}
@@ -22,7 +22,7 @@ C: RUN "MATCH (n) RETURN n.name AS name" {}
PULL_ALL
S: SUCCESS {"fields": ["name"]}
RECORD ["Bob"]
- SUCCESS {"bookmark": "BookmarkC"}
+ SUCCESS {"bookmark": "neo4j:bookmark:v1:tx424242"}
C: RUN "COMMIT" {}
PULL_ALL
S: SUCCESS {}
diff --git a/test/resources/boltkit/write_tx_with_bookmarks.script b/test/resources/boltkit/write_tx_with_bookmarks.script
index 08247a9e2..82269cb37 100644
--- a/test/resources/boltkit/write_tx_with_bookmarks.script
+++ b/test/resources/boltkit/write_tx_with_bookmarks.script
@@ -2,14 +2,14 @@
!: AUTO RESET
!: AUTO PULL_ALL
-C: RUN "BEGIN" {"bookmark": "OldBookmark"}
+C: RUN "BEGIN" {"bookmark": "neo4j:bookmark:v1:tx42", "bookmarks": ["neo4j:bookmark:v1:tx42"]}
PULL_ALL
S: SUCCESS {}
SUCCESS {}
C: RUN "CREATE (n {name:'Bob'})" {}
PULL_ALL
S: SUCCESS {}
- SUCCESS {"bookmark": "NewBookmark"}
+ SUCCESS {"bookmark": "neo4j:bookmark:v1:tx4242"}
C: RUN "COMMIT" {}
PULL_ALL
S: SUCCESS {}
diff --git a/test/v1/direct.driver.boltkit.it.js b/test/v1/direct.driver.boltkit.it.js
index 781f78df3..ce03ba837 100644
--- a/test/v1/direct.driver.boltkit.it.js
+++ b/test/v1/direct.driver.boltkit.it.js
@@ -17,8 +17,8 @@
* limitations under the License.
*/
-import neo4j from '../../lib/v1';
-import {READ, WRITE} from '../../lib/v1/driver';
+import neo4j from '../../src/v1';
+import {READ, WRITE} from '../../src/v1/driver';
import boltkit from './boltkit';
import sharedNeo4j from '../internal/shared-neo4j';
@@ -62,7 +62,7 @@ describe('direct driver', () => {
kit.run(() => {
const driver = createDriver();
- const session = driver.session(READ, 'OldBookmark');
+ const session = driver.session(READ, 'neo4j:bookmark:v1:tx42');
const tx = session.beginTransaction();
tx.run('MATCH (n) RETURN n.name AS name').then(result => {
const records = result.records;
@@ -71,7 +71,7 @@ describe('direct driver', () => {
expect(records[1].get('name')).toEqual('Alice');
tx.commit().then(() => {
- expect(session.lastBookmark()).toEqual('NewBookmark');
+ expect(session.lastBookmark()).toEqual('neo4j:bookmark:v1:tx4242');
session.close(() => {
driver.close();
@@ -96,14 +96,14 @@ describe('direct driver', () => {
kit.run(() => {
const driver = createDriver();
- const session = driver.session(WRITE, 'OldBookmark');
+ const session = driver.session(WRITE, 'neo4j:bookmark:v1:tx42');
const tx = session.beginTransaction();
tx.run('CREATE (n {name:\'Bob\'})').then(result => {
const records = result.records;
expect(records.length).toEqual(0);
tx.commit().then(() => {
- expect(session.lastBookmark()).toEqual('NewBookmark');
+ expect(session.lastBookmark()).toEqual('neo4j:bookmark:v1:tx4242');
session.close(() => {
driver.close();
@@ -128,14 +128,14 @@ describe('direct driver', () => {
kit.run(() => {
const driver = createDriver();
- const session = driver.session(WRITE, 'BookmarkA');
+ const session = driver.session(WRITE, 'neo4j:bookmark:v1:tx42');
const writeTx = session.beginTransaction();
writeTx.run('CREATE (n {name:\'Bob\'})').then(result => {
const records = result.records;
expect(records.length).toEqual(0);
writeTx.commit().then(() => {
- expect(session.lastBookmark()).toEqual('BookmarkB');
+ expect(session.lastBookmark()).toEqual('neo4j:bookmark:v1:tx4242');
const readTx = session.beginTransaction();
readTx.run('MATCH (n) RETURN n.name AS name').then(result => {
@@ -144,7 +144,7 @@ describe('direct driver', () => {
expect(records[0].get('name')).toEqual('Bob');
readTx.commit().then(() => {
- expect(session.lastBookmark()).toEqual('BookmarkC');
+ expect(session.lastBookmark()).toEqual('neo4j:bookmark:v1:tx424242');
session.close(() => {
driver.close();
@@ -171,23 +171,23 @@ describe('direct driver', () => {
kit.run(() => {
const driver = createDriver();
- const session = driver.session(WRITE, 'BookmarkA');
+ const session = driver.session(WRITE, 'neo4j:bookmark:v1:tx42');
const writeTx = session.beginTransaction();
writeTx.run('CREATE (n {name:\'Bob\'})').then(result => {
const records = result.records;
expect(records.length).toEqual(0);
writeTx.commit().then(() => {
- expect(session.lastBookmark()).toEqual('BookmarkB');
+ expect(session.lastBookmark()).toEqual('neo4j:bookmark:v1:tx4242');
- const readTx = session.beginTransaction('BookmarkOverride');
+ const readTx = session.beginTransaction('neo4j:bookmark:v1:tx99');
readTx.run('MATCH (n) RETURN n.name AS name').then(result => {
const records = result.records;
expect(records.length).toEqual(1);
expect(records[0].get('name')).toEqual('Bob');
readTx.commit().then(() => {
- expect(session.lastBookmark()).toEqual('BookmarkC');
+ expect(session.lastBookmark()).toEqual('neo4j:bookmark:v1:tx424242');
session.close(() => {
driver.close();
@@ -215,14 +215,14 @@ describe('direct driver', () => {
kit.run(() => {
const driver = createDriver();
- const session = driver.session(WRITE, 'BookmarkA');
+ const session = driver.session(WRITE, 'neo4j:bookmark:v1:tx42');
const writeTx = session.beginTransaction();
writeTx.run('CREATE (n {name:\'Bob\'})').then(result => {
const records = result.records;
expect(records.length).toEqual(0);
writeTx.commit().then(() => {
- expect(session.lastBookmark()).toEqual('BookmarkB');
+ expect(session.lastBookmark()).toEqual('neo4j:bookmark:v1:tx4242');
const readTx = session.beginTransaction(null);
readTx.run('MATCH (n) RETURN n.name AS name').then(result => {
@@ -231,7 +231,7 @@ describe('direct driver', () => {
expect(records[0].get('name')).toEqual('Bob');
readTx.commit().then(() => {
- expect(session.lastBookmark()).toEqual('BookmarkC');
+ expect(session.lastBookmark()).toEqual('neo4j:bookmark:v1:tx424242');
session.close(() => {
driver.close();
diff --git a/test/v1/routing.driver.boltkit.it.js b/test/v1/routing.driver.boltkit.it.js
index ebc97eb15..769cb22a1 100644
--- a/test/v1/routing.driver.boltkit.it.js
+++ b/test/v1/routing.driver.boltkit.it.js
@@ -1072,10 +1072,10 @@ describe('routing driver', () => {
const driver = newDriver('bolt+routing://127.0.0.1:9001');
const session = driver.session();
- const tx = session.beginTransaction('OldBookmark');
+ const tx = session.beginTransaction('neo4j:bookmark:v1:tx42');
tx.run('CREATE (n {name:\'Bob\'})').then(() => {
tx.commit().then(() => {
- expect(session.lastBookmark()).toEqual('NewBookmark');
+ expect(session.lastBookmark()).toEqual('neo4j:bookmark:v1:tx4242');
session.close();
driver.close();
@@ -1093,11 +1093,11 @@ describe('routing driver', () => {
});
it('should send initial bookmark wihtout access mode', done => {
- testWriteSessionWithAccessModeAndBookmark(null, 'OldBookmark', done);
+ testWriteSessionWithAccessModeAndBookmark(null, 'neo4j:bookmark:v1:tx42', done);
});
it('should use write session mode and initial bookmark', done => {
- testWriteSessionWithAccessModeAndBookmark(WRITE, 'OldBookmark', done);
+ testWriteSessionWithAccessModeAndBookmark(WRITE, 'neo4j:bookmark:v1:tx42', done);
});
it('should use read session mode and initial bookmark', done => {
@@ -1113,7 +1113,7 @@ describe('routing driver', () => {
kit.run(() => {
const driver = newDriver('bolt+routing://127.0.0.1:9001');
- const session = driver.session(READ, 'OldBookmark');
+ const session = driver.session(READ, 'neo4j:bookmark:v1:tx42');
const tx = session.beginTransaction();
tx.run('MATCH (n) RETURN n.name AS name').then(result => {
const records = result.records;
@@ -1122,7 +1122,7 @@ describe('routing driver', () => {
expect(records[1].get('name')).toEqual('Alice');
tx.commit().then(() => {
- expect(session.lastBookmark()).toEqual('NewBookmark');
+ expect(session.lastBookmark()).toEqual('neo4j:bookmark:v1:tx4242');
session.close();
driver.close();
@@ -1152,11 +1152,11 @@ describe('routing driver', () => {
kit.run(() => {
const driver = newDriver('bolt+routing://127.0.0.1:9001');
- const session = driver.session(null, 'BookmarkA');
+ const session = driver.session(null, 'neo4j:bookmark:v1:tx42');
const writeTx = session.beginTransaction();
writeTx.run('CREATE (n {name:\'Bob\'})').then(() => {
writeTx.commit().then(() => {
- expect(session.lastBookmark()).toEqual('BookmarkB');
+ expect(session.lastBookmark()).toEqual('neo4j:bookmark:v1:tx4242');
const readTx = session.beginTransaction();
readTx.run('MATCH (n) RETURN n.name AS name').then(result => {
@@ -1165,7 +1165,7 @@ describe('routing driver', () => {
expect(records[0].get('name')).toEqual('Bob');
readTx.commit().then(() => {
- expect(session.lastBookmark()).toEqual('BookmarkC');
+ expect(session.lastBookmark()).toEqual('neo4j:bookmark:v1:tx424242');
session.close();
driver.close();
@@ -1858,6 +1858,38 @@ describe('routing driver', () => {
});
});
+ it('should send multiple bookmarks', done => {
+ const kit = new boltkit.BoltKit();
+ const router = kit.start('./test/resources/boltkit/acquire_endpoints.script', 9010);
+ const writer = kit.start('./test/resources/boltkit/multiple_bookmarks.script', 9007);
+
+ kit.run(() => {
+ const driver = newDriver('bolt+routing://127.0.0.1:9010');
+
+ const bookmarks = ['neo4j:bookmark:v1:tx5', 'neo4j:bookmark:v1:tx29', 'neo4j:bookmark:v1:tx94',
+ 'neo4j:bookmark:v1:tx56', 'neo4j:bookmark:v1:tx16', 'neo4j:bookmark:v1:tx68'];
+ const session = driver.session(WRITE, bookmarks);
+ const tx = session.beginTransaction();
+
+ tx.run(`CREATE (n {name:'Bob'})`).then(() => {
+ tx.commit().then(() => {
+ expect(session.lastBookmark()).toEqual('neo4j:bookmark:v1:tx95');
+
+ session.close();
+ driver.close();
+
+ router.exit(code1 => {
+ writer.exit(code2 => {
+ expect(code1).toEqual(0);
+ expect(code2).toEqual(0);
+ done();
+ });
+ });
+ });
+ });
+ });
+ });
+
function moveNextDateNow30SecondsForward() {
const currentTime = Date.now();
hijackNextDateNowCall(currentTime + 30 * 1000 + 1);
@@ -1880,7 +1912,7 @@ describe('routing driver', () => {
const tx = session.beginTransaction();
tx.run('CREATE (n {name:\'Bob\'})').then(() => {
tx.commit().then(() => {
- expect(session.lastBookmark()).toEqual('NewBookmark');
+ expect(session.lastBookmark()).toEqual('neo4j:bookmark:v1:tx4242');
session.close();
driver.close();
diff --git a/test/v1/session.test.js b/test/v1/session.test.js
index de4a2daf5..daf6a0dc2 100644
--- a/test/v1/session.test.js
+++ b/test/v1/session.test.js
@@ -24,6 +24,7 @@ import {READ} from '../../src/v1/driver';
import {SingleConnectionProvider} from '../../src/v1/internal/connection-providers';
import FakeConnection from '../internal/fake-connection';
import sharedNeo4j from '../internal/shared-neo4j';
+import _ from 'lodash';
describe('session', () => {
@@ -434,9 +435,9 @@ describe('session', () => {
it('should fail nicely for illegal bookmark', () => {
expect(() => session.beginTransaction({})).toThrowError(TypeError);
+ expect(() => session.beginTransaction({foo: 'bar'})).toThrowError(TypeError);
expect(() => session.beginTransaction(42)).toThrowError(TypeError);
- expect(() => session.beginTransaction([])).toThrowError(TypeError);
- expect(() => session.beginTransaction(['bookmark'])).toThrowError(TypeError);
+ expect(() => session.beginTransaction([42.0, 42.0])).toThrowError(TypeError);
});
it('should allow creation of a ' + neo4j.session.READ + ' session', done => {
@@ -943,6 +944,27 @@ describe('session', () => {
});
});
+ it('should send multiple bookmarks', done => {
+ if (!serverIs31OrLater(done)) {
+ return;
+ }
+
+ const nodeCount = 17;
+ const bookmarkPromises = _.range(nodeCount).map(() => runQueryAndGetBookmark(driver));
+
+ Promise.all(bookmarkPromises).then(bookmarks => {
+ expect(_.uniq(bookmarks).length > 1).toBeTruthy();
+ bookmarks.forEach(bookmark => expect(_.isString(bookmark)).toBeTruthy());
+
+ const session = driver.session(READ, bookmarks);
+ session.run('MATCH (n) RETURN count(n)').then(result => {
+ const count = result.records[0].get(0).toInt();
+ expect(count).toEqual(nodeCount);
+ session.close(() => done());
+ });
+ });
+ });
+
function serverIs31OrLater(done) {
// lazy way of checking the version number
// if server has been set we know it is at least 3.1
@@ -1004,4 +1026,21 @@ describe('session', () => {
});
});
}
+
+ function runQueryAndGetBookmark(driver) {
+ const session = driver.session();
+ const tx = session.beginTransaction();
+
+ return new Promise((resolve, reject) => {
+ tx.run('CREATE ()').then(() => {
+ tx.commit().then(() => {
+ const bookmark = session.lastBookmark();
+ session.close(() => {
+ resolve(bookmark);
+ });
+ }).catch(error => reject(error));
+ }).catch(error => reject(error));
+ });
+ }
+
});
diff --git a/test/v1/transaction.test.js b/test/v1/transaction.test.js
index c69d44ae7..baa5309b2 100644
--- a/test/v1/transaction.test.js
+++ b/test/v1/transaction.test.js
@@ -233,7 +233,7 @@ describe('transaction', () => {
}
const tx = session.beginTransaction();
- expect(session.lastBookmark()).not.toBeDefined();
+ expect(session.lastBookmark()).toBeNull();
tx.run("CREATE (:TXNode1)").then(() => {
tx.run("CREATE (:TXNode2)").then(() => {
tx.commit().then(() => {
@@ -249,7 +249,7 @@ describe('transaction', () => {
return;
}
- expect(session.lastBookmark()).not.toBeDefined();
+ expect(session.lastBookmark()).toBeNull();
const tx1 = session.beginTransaction();
tx1.run('CREATE ()').then(() => {
@@ -282,7 +282,7 @@ describe('transaction', () => {
return;
}
- expect(session.lastBookmark()).not.toBeDefined();
+ expect(session.lastBookmark()).toBeNull();
const tx1 = session.beginTransaction();
tx1.run('CREATE ()').then(() => {