Skip to content

Commit 744e9ab

Browse files
committed
Merge pull request #153 from boggle/1.0-consumption-fixes
Clarify semantics of statement result methods [DO NOT MERGE]
2 parents 32f7de9 + 3990383 commit 744e9ab

File tree

5 files changed

+176
-127
lines changed

5 files changed

+176
-127
lines changed

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

Lines changed: 47 additions & 87 deletions
Original file line numberDiff line numberDiff line change
@@ -42,7 +42,6 @@
4242
import org.neo4j.driver.v1.util.Function;
4343
import org.neo4j.driver.v1.util.Functions;
4444

45-
import static java.lang.String.format;
4645
import static java.util.Collections.emptyList;
4746

4847
public class InternalStatementResult implements StatementResult
@@ -55,7 +54,6 @@ public class InternalStatementResult implements StatementResult
5554
private List<String> keys = null;
5655
private ResultSummary summary = null;
5756

58-
private boolean open = true;
5957
private long position = -1;
6058
private boolean done = false;
6159

@@ -170,26 +168,17 @@ StreamCollector pullAllResponseCollector()
170168
@Override
171169
public List<String> keys()
172170
{
173-
tryFetching();
171+
if ( keys == null )
172+
{
173+
tryFetchNext();
174+
}
174175
return keys;
175176
}
176177

177178
@Override
178179
public boolean hasNext()
179180
{
180-
if (!recordBuffer.isEmpty())
181-
{
182-
return true;
183-
}
184-
else if (done)
185-
{
186-
return false;
187-
}
188-
else
189-
{
190-
tryFetching();
191-
return hasNext();
192-
}
181+
return tryFetchNext();
193182
}
194183

195184
@Override
@@ -201,67 +190,52 @@ public Record next()
201190
// in a way that makes the two equivalent in performance.
202191
// To get the intended benefit, we need to allocate Record in this method,
203192
// and have it copy out its fields from some lower level data structure.
204-
assertOpen();
205-
Record nextRecord = recordBuffer.poll();
206-
if ( nextRecord != null )
193+
if ( tryFetchNext() )
207194
{
208195
position += 1;
209-
return nextRecord;
210-
}
211-
else if ( done )
212-
{
213-
return null;
196+
return recordBuffer.poll();
214197
}
215198
else
216199
{
217-
tryFetching();
218-
return next();
200+
throw new NoSuchRecordException( "No more records" );
219201
}
220202
}
221203

222204
@Override
223205
public Record single()
224206
{
225-
if( position > 0 )
207+
if ( hasNext() )
226208
{
227-
throw new NoSuchRecordException(
228-
"Cannot retrieve the first record, because other operations have already used the first record. " +
229-
"Please ensure you are not calling `first` multiple times, or are mixing it with calls " +
230-
"to `next`, `single`, `list` or any other method that changes the position of the cursor." );
231-
}
209+
Record single = next();
210+
boolean hasMoreThanOne = hasNext();
232211

233-
if( !hasNext() )
234-
{
235-
throw new NoSuchRecordException( "Cannot retrieve the first record, because this result is empty." );
236-
}
212+
consume();
213+
214+
if ( hasMoreThanOne )
215+
{
216+
throw new NoSuchRecordException( "Expected a result with a single record, but this result contains at least one more. " +
217+
"Ensure your query returns only one record, or use `first` instead of `single` if " +
218+
"you do not care about the number of records in the result." );
219+
}
237220

238-
Record first = next();
239-
if( hasNext() )
221+
return single;
222+
}
223+
else
240224
{
241-
throw new NoSuchRecordException( "Expected a result with a single record, but this result contains at least one more. " +
242-
"Ensure your query returns only one record, or use `first` instead of `single` if " +
243-
"you do not care about the number of records in the result." );
225+
throw new NoSuchRecordException( "Cannot retrieve a single record, because this result is empty." );
244226
}
245-
return first;
246227
}
247228

248229
@Override
249230
public Record peek()
250231
{
251-
assertOpen();
252-
Record nextRecord = recordBuffer.peek();
253-
if ( nextRecord != null )
254-
{
255-
return nextRecord;
256-
}
257-
else if ( done )
232+
if ( tryFetchNext() )
258233
{
259-
return null;
234+
return recordBuffer.peek();
260235
}
261236
else
262237
{
263-
tryFetching();
264-
return peek();
238+
throw new NoSuchRecordException( "Cannot peek past the last record" );
265239
}
266240
}
267241

@@ -274,46 +248,41 @@ public List<Record> list()
274248
@Override
275249
public <T> List<T> list( Function<Record, T> mapFunction )
276250
{
277-
if ( isEmpty() )
278-
{
279-
assertOpen();
280-
return emptyList();
281-
}
282-
else if ( position == -1 && hasNext() )
251+
if ( hasNext() )
283252
{
284253
List<T> result = new ArrayList<>();
254+
285255
do
286256
{
287257
result.add( mapFunction.apply( next() ) );
288258
}
289259
while ( hasNext() );
290260

291-
consume();
292261
return result;
293262
}
294263
else
295264
{
296-
throw new ClientException(
297-
format( "Can't retain records when cursor is not pointing at the first record (currently at position %d)", position )
298-
);
265+
return emptyList();
299266
}
300267
}
301268

302-
@SuppressWarnings("StatementWithEmptyBody")
303269
@Override
304270
public ResultSummary consume()
305271
{
306-
if(!open)
272+
if ( done )
307273
{
308-
return summary;
274+
recordBuffer.clear();
309275
}
310-
311-
while ( !done )
276+
else
312277
{
313-
connection.receiveOne();
278+
do
279+
{
280+
connection.receiveOne();
281+
recordBuffer.clear();
282+
}
283+
while ( !done );
314284
}
315-
recordBuffer.clear();
316-
open = false;
285+
317286
return summary;
318287
}
319288

@@ -323,26 +292,17 @@ public void remove()
323292
throw new ClientException( "Removing records from a result is not supported." );
324293
}
325294

326-
private void assertOpen()
295+
private boolean tryFetchNext()
327296
{
328-
if ( !open )
329-
{
330-
throw new ClientException( "Result has been closed" );
331-
}
332-
}
333-
334-
private boolean isEmpty()
335-
{
336-
tryFetching();
337-
return position == -1 && recordBuffer.isEmpty() && done;
338-
}
339-
340-
private void tryFetching()
341-
{
342-
while ( recordBuffer.isEmpty() && !done )
297+
while ( recordBuffer.isEmpty() )
343298
{
299+
if ( done )
300+
{
301+
return false;
302+
}
344303
connection.receiveOne();
345304
}
346-
}
347305

306+
return true;
307+
}
348308
}

driver/src/main/java/org/neo4j/driver/v1/StatementResult.java

Lines changed: 12 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,6 @@
2121
import java.util.Iterator;
2222
import java.util.List;
2323

24-
import org.neo4j.driver.v1.exceptions.ClientException;
2524
import org.neo4j.driver.v1.exceptions.NoSuchRecordException;
2625
import org.neo4j.driver.v1.summary.ResultSummary;
2726
import org.neo4j.driver.v1.util.Function;
@@ -70,23 +69,28 @@ public interface StatementResult extends Iterator<Record>
7069

7170
/**
7271
* Navigate to and retrieve the next {@link Record} in this result.
72+
*
73+
* @throws NoSuchRecordException if there is no record left in the stream
7374
* @return the next record
7475
*/
7576
@Override Record next();
7677

7778
/**
7879
* Return the first record in the result, failing if there is not exactly
79-
* one record, or if this result has already been used to move past the first record.
80+
* one record left in the stream
81+
*
82+
* Calling this method always exhausts the result, even when {@link NoSuchRecordException} is thrown.
8083
*
8184
* @return the first and only record in the stream
82-
* @throws NoSuchRecordException if there is not exactly one record in the stream, or if the cursor has been used already
85+
* @throws NoSuchRecordException if there is not exactly one record left in the stream
8386
*/
8487
Record single() throws NoSuchRecordException;
8588

8689
/**
8790
* Investigate the next upcoming record without moving forward in the result.
8891
*
89-
* @return the next record, or null if there is no next record
92+
* @throws NoSuchRecordException if there is no record left in the stream
93+
* @return the next record
9094
*/
9195
Record peek();
9296

@@ -102,8 +106,7 @@ public interface StatementResult extends Iterator<Record>
102106
*
103107
* Calling this method exhausts the result.
104108
*
105-
* @throws ClientException if the result has already been used
106-
* @return list of all immutable records
109+
* @return list of all remaining immutable records
107110
*/
108111
List<Record> list();
109112

@@ -119,11 +122,10 @@ public interface StatementResult extends Iterator<Record>
119122
*
120123
* Calling this method exhausts the result.
121124
*
122-
* @throws ClientException if the result has already been used
123125
* @param mapFunction a function to map from Value to T. See {@link Values} for some predefined functions, such
124126
* as {@link Values#ofBoolean()}, {@link Values#ofList(Function)}.
125127
* @param <T> the type of result list elements
126-
* @return list of all mapped immutable records
128+
* @return list of all mapped remaining immutable records
127129
*/
128130
<T> List<T> list( Function<Record, T> mapFunction );
129131

@@ -138,7 +140,7 @@ public interface StatementResult extends Iterator<Record>
138140
* }
139141
* </pre>
140142
*
141-
* @return a summary for the whole query
143+
* @return a summary for the whole query result
142144
*/
143145
ResultSummary consume();
144-
}
146+
}

driver/src/main/java/org/neo4j/driver/v1/exceptions/Neo4jException.java

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,8 @@
1919
package org.neo4j.driver.v1.exceptions;
2020

2121
/**
22-
* This is the base class for all Neo4j exceptions.
22+
* This is the base class for all exceptions caused as part of communication with the remote Neo4j server.
23+
*
2324
* @since 1.0
2425
*/
2526
public abstract class Neo4jException extends RuntimeException

driver/src/main/java/org/neo4j/driver/v1/exceptions/NoSuchRecordException.java

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

21+
import java.util.NoSuchElementException;
22+
2123
/**
24+
* Thrown whenever a client expected to read a record that was not available (i.e. because it wasn't returned by the server).
25+
*
26+
* This usually indicates an expectation mismatch between client code and database application logic.
27+
*
2228
* @since 1.0
2329
*/
24-
public class NoSuchRecordException extends ClientException
30+
public class NoSuchRecordException extends NoSuchElementException
2531
{
2632
private static final long serialVersionUID = 9091962868264042491L;
2733

0 commit comments

Comments
 (0)