Skip to content

Commit f186165

Browse files
committed
Multiple bookmarks
Previously it was possible to only supply a single bookmark when creating a new session. However there is a use-case to be able to supply multiple bookmarks when multiple worker threads execute write queries and then reader should be able to observe all those writes. To achieve this driver now exposed session creation methods that take an iterable of bookmarks. It was chosen to add additional `Driver#session()` overloads with `Iterable<String>` instead of extending single-bookmark methods with varargs because: * this maintains backwards compatibility for pre-existing methods * addition of varargs would make API look strange because call or `Driver#session()` and call of `Driver#session(String... bookmarks)` without params is essentially the same thing * invocation of vararg methods with single `null` argument can be confusing * varargs are more suitable for manual params listing Driver will now send: ``` { bookmark: "max", bookmarks: ["one", "two", "max"] } ``` instead of simple: ``` { bookmark: "max" } ``` this is done to maintain backwards compatibility with databases that only support single bookmark. It forces driver to parse and compare bookmarks which violates the fact that bookmarks are opaque. This is done only to maintain backwards compatibility and should not be copied. Code doing this will eventually be removed.
1 parent ebb653d commit f186165

23 files changed

+563
-91
lines changed
Lines changed: 154 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,154 @@
1+
/*
2+
* Copyright (c) 2002-2017 "Neo Technology,"
3+
* Network Engine for Objects in Lund AB [http://neotechnology.com]
4+
*
5+
* This file is part of Neo4j.
6+
*
7+
* Licensed under the Apache License, Version 2.0 (the "License");
8+
* you may not use this file except in compliance with the License.
9+
* You may obtain a copy of the License at
10+
*
11+
* http://www.apache.org/licenses/LICENSE-2.0
12+
*
13+
* Unless required by applicable law or agreed to in writing, software
14+
* distributed under the License is distributed on an "AS IS" BASIS,
15+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
16+
* See the License for the specific language governing permissions and
17+
* limitations under the License.
18+
*/
19+
package org.neo4j.driver.internal;
20+
21+
import java.util.Collections;
22+
import java.util.HashMap;
23+
import java.util.Iterator;
24+
import java.util.Map;
25+
26+
import org.neo4j.driver.v1.Value;
27+
28+
import static java.util.Collections.emptyMap;
29+
import static java.util.Collections.singleton;
30+
import static org.neo4j.driver.v1.Values.value;
31+
32+
public final class Bookmark
33+
{
34+
private static final String BOOKMARK_KEY = "bookmark";
35+
private static final String BOOKMARKS_KEY = "bookmarks";
36+
private static final String BOOKMARK_PREFIX = "neo4j:bookmark:v1:tx";
37+
38+
private static final long UNKNOWN_BOOKMARK_VALUE = -1;
39+
40+
private static final Bookmark EMPTY = new Bookmark( Collections.<String>emptySet() );
41+
42+
private final Iterable<String> values;
43+
private final String maxValue;
44+
45+
private Bookmark( Iterable<String> values )
46+
{
47+
this.values = values;
48+
this.maxValue = maxBookmark( values );
49+
}
50+
51+
public static Bookmark empty()
52+
{
53+
return EMPTY;
54+
}
55+
56+
public static Bookmark from( String value )
57+
{
58+
if ( value == null )
59+
{
60+
return empty();
61+
}
62+
return from( singleton( value ) );
63+
}
64+
65+
public static Bookmark from( Iterable<String> values )
66+
{
67+
if ( values == null )
68+
{
69+
return empty();
70+
}
71+
return new Bookmark( values );
72+
}
73+
74+
public boolean isEmpty()
75+
{
76+
return maxValue == null;
77+
}
78+
79+
public String asString()
80+
{
81+
return maxValue;
82+
}
83+
84+
public Map<String,Value> asParameters()
85+
{
86+
if ( isEmpty() )
87+
{
88+
return emptyMap();
89+
}
90+
91+
// Driver sends {bookmark: "max", bookmarks: ["one", "two", "max"]} instead of simple
92+
// {bookmarks: ["one", "two", "max"]} for backwards compatibility reasons. Old servers can only accept single
93+
// bookmark that is why driver has to parse and compare given list of bookmarks. This functionality will
94+
// eventually be removed.
95+
Map<String,Value> parameters = new HashMap<>( 4 );
96+
parameters.put( BOOKMARK_KEY, value( maxValue ) );
97+
parameters.put( BOOKMARKS_KEY, value( values ) );
98+
return parameters;
99+
}
100+
101+
@Override
102+
public String toString()
103+
{
104+
return "Bookmark{values=" + values + "}";
105+
}
106+
107+
private static String maxBookmark( Iterable<String> bookmarks )
108+
{
109+
if ( bookmarks == null )
110+
{
111+
return null;
112+
}
113+
114+
Iterator<String> iterator = bookmarks.iterator();
115+
116+
if ( !iterator.hasNext() )
117+
{
118+
return null;
119+
}
120+
121+
String maxBookmark = iterator.next();
122+
long maxValue = bookmarkValue( maxBookmark );
123+
124+
while ( iterator.hasNext() )
125+
{
126+
String bookmark = iterator.next();
127+
long value = bookmarkValue( bookmark );
128+
129+
if ( value > maxValue )
130+
{
131+
maxBookmark = bookmark;
132+
maxValue = value;
133+
}
134+
}
135+
136+
return maxBookmark;
137+
}
138+
139+
private static long bookmarkValue( String value )
140+
{
141+
if ( value.startsWith( BOOKMARK_PREFIX ) )
142+
{
143+
try
144+
{
145+
return Long.parseLong( value.substring( BOOKMARK_PREFIX.length() ) );
146+
}
147+
catch ( NumberFormatException e )
148+
{
149+
return UNKNOWN_BOOKMARK_VALUE;
150+
}
151+
}
152+
return UNKNOWN_BOOKMARK_VALUE;
153+
}
154+
}

driver/src/main/java/org/neo4j/driver/internal/BookmarkCollector.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -30,7 +30,7 @@ class BookmarkCollector extends NoOperationCollector
3030
}
3131

3232
@Override
33-
public void bookmark( String bookmark )
33+
public void bookmark( Bookmark bookmark )
3434
{
3535
transaction.setBookmark( bookmark );
3636
}

driver/src/main/java/org/neo4j/driver/internal/ExplicitTransaction.java

Lines changed: 14 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -34,8 +34,6 @@
3434
import org.neo4j.driver.v1.exceptions.Neo4jException;
3535
import org.neo4j.driver.v1.types.TypeSystem;
3636

37-
import static java.util.Collections.emptyMap;
38-
import static java.util.Collections.singletonMap;
3937
import static org.neo4j.driver.v1.Values.ofValue;
4038
import static org.neo4j.driver.v1.Values.value;
4139

@@ -68,19 +66,19 @@ private enum State
6866
private final SessionResourcesHandler resourcesHandler;
6967
private final Connection conn;
7068

71-
private String bookmark = null;
69+
private Bookmark bookmark = Bookmark.empty();
7270
private State state = State.ACTIVE;
7371

7472
public ExplicitTransaction( Connection conn, SessionResourcesHandler resourcesHandler )
7573
{
76-
this( conn, resourcesHandler, null );
74+
this( conn, resourcesHandler, Bookmark.empty() );
7775
}
7876

79-
ExplicitTransaction( Connection conn, SessionResourcesHandler resourcesHandler, String bookmark )
77+
ExplicitTransaction( Connection conn, SessionResourcesHandler resourcesHandler, Bookmark initialBookmark )
8078
{
8179
this.conn = conn;
8280
this.resourcesHandler = resourcesHandler;
83-
runBeginStatement( conn, bookmark );
81+
runBeginStatement( conn, initialBookmark );
8482
}
8583

8684
@Override
@@ -238,32 +236,28 @@ public synchronized void markToClose()
238236
state = State.FAILED;
239237
}
240238

241-
public String bookmark()
239+
public Bookmark bookmark()
242240
{
243241
return bookmark;
244242
}
245243

246-
void setBookmark( String bookmark )
244+
void setBookmark( Bookmark bookmark )
247245
{
248-
this.bookmark = bookmark;
246+
if ( bookmark != null && !bookmark.isEmpty() )
247+
{
248+
this.bookmark = bookmark;
249+
}
249250
}
250251

251-
private static void runBeginStatement( Connection connection, String bookmark )
252+
private static void runBeginStatement( Connection connection, Bookmark bookmark )
252253
{
253-
Map<String,Value> parameters;
254-
if ( bookmark != null )
255-
{
256-
parameters = singletonMap( "bookmark", value( bookmark ) );
257-
}
258-
else
259-
{
260-
parameters = emptyMap();
261-
}
254+
Bookmark initialBookmark = bookmark == null ? Bookmark.empty() : bookmark;
255+
Map<String,Value> parameters = initialBookmark.asParameters();
262256

263257
connection.run( "BEGIN", parameters, Collector.NO_OP );
264258
connection.pullAll( Collector.NO_OP );
265259

266-
if ( bookmark != null )
260+
if ( !initialBookmark.isEmpty() )
267261
{
268262
connection.sync();
269263
}

driver/src/main/java/org/neo4j/driver/internal/InternalDriver.java

Lines changed: 19 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@
1818
*/
1919
package org.neo4j.driver.internal;
2020

21+
import java.util.Collection;
2122
import java.util.concurrent.atomic.AtomicBoolean;
2223

2324
import org.neo4j.driver.internal.security.SecurityPlan;
@@ -62,7 +63,7 @@ public final Session session()
6263
@Override
6364
public final Session session( AccessMode mode )
6465
{
65-
return session( mode, null );
66+
return newSession( mode, Bookmark.empty() );
6667
}
6768

6869
@Override
@@ -73,6 +74,23 @@ public final Session session( String bookmark )
7374

7475
@Override
7576
public final Session session( AccessMode mode, String bookmark )
77+
{
78+
return newSession( mode, Bookmark.from( bookmark ) );
79+
}
80+
81+
@Override
82+
public Session session( Iterable<String> bookmarks )
83+
{
84+
return session( AccessMode.WRITE, bookmarks );
85+
}
86+
87+
@Override
88+
public Session session( AccessMode mode, Iterable<String> bookmarks )
89+
{
90+
return newSession( mode, Bookmark.from( bookmarks ) );
91+
}
92+
93+
private Session newSession( AccessMode mode, Bookmark bookmark )
7694
{
7795
assertOpen();
7896
Session session = sessionFactory.newInstance( mode, bookmark );

driver/src/main/java/org/neo4j/driver/internal/InternalStatementResult.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -138,7 +138,7 @@ public void notifications( List<Notification> notifications )
138138
}
139139

140140
@Override
141-
public void bookmark( String bookmark )
141+
public void bookmark( Bookmark bookmark )
142142
{
143143
if ( transaction != null )
144144
{

driver/src/main/java/org/neo4j/driver/internal/NetworkSession.java

Lines changed: 5 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -53,7 +53,7 @@ public class NetworkSession implements Session, SessionResourcesHandler
5353
private final RetryLogic retryLogic;
5454
protected final Logger logger;
5555

56-
private String bookmark;
56+
private Bookmark bookmark = Bookmark.empty();
5757
private PooledConnection currentConnection;
5858
private ExplicitTransaction currentTransaction;
5959

@@ -179,7 +179,7 @@ public synchronized Transaction beginTransaction()
179179
@Override
180180
public synchronized Transaction beginTransaction( String bookmark )
181181
{
182-
setBookmark( bookmark );
182+
setBookmark( Bookmark.from( bookmark ) );
183183
return beginTransaction();
184184
}
185185

@@ -195,12 +195,9 @@ public <T> T writeTransaction( TransactionWork<T> work )
195195
return transaction( AccessMode.WRITE, work );
196196
}
197197

198-
// Internal method for setting the bookmark explicitly, mainly for testing.
199-
// This method does not prevent setting the bookmark to null since that
200-
// is a valid requirement for some test scenarios.
201-
void setBookmark( String bookmark )
198+
void setBookmark( Bookmark bookmark )
202199
{
203-
if( bookmark != null )
200+
if ( bookmark != null && !bookmark.isEmpty() )
204201
{
205202
this.bookmark = bookmark;
206203
}
@@ -209,7 +206,7 @@ void setBookmark( String bookmark )
209206
@Override
210207
public String lastBookmark()
211208
{
212-
return bookmark;
209+
return bookmark == null ? null : bookmark.asString();
213210
}
214211

215212
@Override

driver/src/main/java/org/neo4j/driver/internal/SessionFactory.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -23,5 +23,5 @@
2323

2424
public interface SessionFactory extends AutoCloseable
2525
{
26-
Session newInstance( AccessMode mode, String bookmark );
26+
Session newInstance( AccessMode mode, Bookmark bookmark );
2727
}

driver/src/main/java/org/neo4j/driver/internal/SessionFactoryImpl.java

Lines changed: 16 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -27,10 +27,10 @@
2727

2828
public class SessionFactoryImpl implements SessionFactory
2929
{
30-
protected final ConnectionProvider connectionProvider;
31-
protected final RetryLogic retryLogic;
32-
protected final Logging logging;
33-
protected final boolean leakedSessionsLoggingEnabled;
30+
private final ConnectionProvider connectionProvider;
31+
private final RetryLogic retryLogic;
32+
private final Logging logging;
33+
private final boolean leakedSessionsLoggingEnabled;
3434

3535
SessionFactoryImpl( ConnectionProvider connectionProvider, RetryLogic retryLogic, Config config )
3636
{
@@ -41,23 +41,23 @@ public class SessionFactoryImpl implements SessionFactory
4141
}
4242

4343
@Override
44-
public Session newInstance( AccessMode mode, String bookmark )
44+
public final Session newInstance( AccessMode mode, Bookmark bookmark )
4545
{
46-
NetworkSession session;
47-
if ( leakedSessionsLoggingEnabled )
48-
{
49-
session = new LeakLoggingNetworkSession( connectionProvider, mode, retryLogic, logging );
50-
}
51-
else
52-
{
53-
session = new NetworkSession( connectionProvider, mode, retryLogic, logging );
54-
}
46+
NetworkSession session = createSession( connectionProvider, retryLogic, mode, logging );
5547
session.setBookmark( bookmark );
5648
return session;
5749
}
5850

51+
protected NetworkSession createSession( ConnectionProvider connectionProvider, RetryLogic retryLogic,
52+
AccessMode mode, Logging logging )
53+
{
54+
return leakedSessionsLoggingEnabled ?
55+
new LeakLoggingNetworkSession( connectionProvider, mode, retryLogic, logging ) :
56+
new NetworkSession( connectionProvider, mode, retryLogic, logging );
57+
}
58+
5959
@Override
60-
public void close() throws Exception
60+
public final void close() throws Exception
6161
{
6262
connectionProvider.close();
6363
}
@@ -69,7 +69,7 @@ public void close() throws Exception
6969
*
7070
* @return the connection provider used by this factory.
7171
*/
72-
public ConnectionProvider getConnectionProvider()
72+
public final ConnectionProvider getConnectionProvider()
7373
{
7474
return connectionProvider;
7575
}

0 commit comments

Comments
 (0)