Skip to content

Commit 77c554d

Browse files
committed
Clarify semantics of single
1 parent 7c098cf commit 77c554d

File tree

3 files changed

+66
-25
lines changed

3 files changed

+66
-25
lines changed

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

Lines changed: 9 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -222,27 +222,24 @@ else if ( done )
222222
@Override
223223
public Record single()
224224
{
225-
if( position > 0 )
226-
{
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-
}
232-
233225
if( !hasNext() )
234226
{
235-
throw new NoSuchRecordException( "Cannot retrieve the first record, because this result is empty." );
227+
throw new NoSuchRecordException( "Cannot retrieve a single record, because this result is empty." );
236228
}
237229

238-
Record first = next();
239-
if( hasNext() )
230+
Record single = next();
231+
boolean hasMany = hasNext();
232+
233+
consume();
234+
235+
if( hasMany )
240236
{
241237
throw new NoSuchRecordException( "Expected a result with a single record, but this result contains at least one more. " +
242238
"Ensure your query returns only one record, or use `first` instead of `single` if " +
243239
"you do not care about the number of records in the result." );
244240
}
245-
return first;
241+
242+
return single;
246243
}
247244

248245
@Override

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

Lines changed: 7 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -76,10 +76,12 @@ public interface StatementResult extends Iterator<Record>
7676

7777
/**
7878
* 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.
79+
* one record left in the stream
80+
*
81+
* Calling this method exhausts the result, even when failing.
8082
*
8183
* @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
84+
* @throws NoSuchRecordException if there is not exactly one record left in the stream
8385
*/
8486
Record single() throws NoSuchRecordException;
8587

@@ -103,7 +105,7 @@ public interface StatementResult extends Iterator<Record>
103105
* Calling this method exhausts the result.
104106
*
105107
* @throws ClientException if the result has already been used
106-
* @return list of all immutable records
108+
* @return list of all remaining immutable records
107109
*/
108110
List<Record> list();
109111

@@ -123,7 +125,7 @@ public interface StatementResult extends Iterator<Record>
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

@@ -141,4 +143,4 @@ public interface StatementResult extends Iterator<Record>
141143
* @return a summary for the whole query
142144
*/
143145
ResultSummary consume();
144-
}
146+
}

driver/src/test/java/org/neo4j/driver/internal/InternalStatementResultTest.java

Lines changed: 50 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -19,27 +19,27 @@
1919
package org.neo4j.driver.internal;
2020

2121

22+
import java.util.ArrayList;
23+
import java.util.Arrays;
24+
import java.util.LinkedList;
25+
import java.util.List;
26+
2227
import org.junit.Rule;
2328
import org.junit.Test;
2429
import org.junit.rules.ExpectedException;
2530
import org.mockito.invocation.InvocationOnMock;
2631
import org.mockito.stubbing.Answer;
2732

28-
import java.util.ArrayList;
29-
import java.util.Arrays;
30-
import java.util.LinkedList;
31-
import java.util.List;
32-
3333
import org.neo4j.driver.internal.spi.Connection;
3434
import org.neo4j.driver.internal.value.NullValue;
35-
import org.neo4j.driver.v1.Statement;
36-
import org.neo4j.driver.v1.StatementResult;
37-
import org.neo4j.driver.v1.util.Pair;
3835
import org.neo4j.driver.v1.Record;
3936
import org.neo4j.driver.v1.Records;
37+
import org.neo4j.driver.v1.Statement;
38+
import org.neo4j.driver.v1.StatementResult;
4039
import org.neo4j.driver.v1.Value;
4140
import org.neo4j.driver.v1.exceptions.ClientException;
4241
import org.neo4j.driver.v1.exceptions.NoSuchRecordException;
42+
import org.neo4j.driver.v1.util.Pair;
4343

4444
import static org.hamcrest.CoreMatchers.equalTo;
4545
import static org.hamcrest.collection.IsCollectionWithSize.hasSize;
@@ -48,8 +48,10 @@
4848
import static org.junit.Assert.assertNull;
4949
import static org.junit.Assert.assertThat;
5050
import static org.junit.Assert.assertTrue;
51+
import static org.junit.Assert.fail;
5152
import static org.mockito.Mockito.doAnswer;
5253
import static org.mockito.Mockito.mock;
54+
5355
import static org.neo4j.driver.v1.Values.value;
5456

5557
public class InternalStatementResultTest
@@ -192,6 +194,46 @@ public void singleShouldThrowOnEmptyResult()
192194
createResult( 0 ).single();
193195
}
194196

197+
@Test
198+
public void singleShouldThrowOnConsumedResult()
199+
{
200+
// Expect
201+
expectedException.expect( NoSuchRecordException.class );
202+
203+
// When
204+
StatementResult result = createResult( 2 );
205+
result.consume();
206+
result.single();
207+
}
208+
209+
@Test
210+
public void singleShouldNotThrowOnPartiallyConsumedResult()
211+
{
212+
// Given
213+
StatementResult result = createResult( 2 );
214+
result.next();
215+
216+
// When + Then
217+
assertNotNull( result.single() );
218+
}
219+
220+
@Test
221+
public void singleShouldConsumeIfFailing()
222+
{
223+
// Given
224+
StatementResult result = createResult( 2 );
225+
226+
try
227+
{
228+
result.single();
229+
fail( "Exception expected" );
230+
}
231+
catch ( NoSuchRecordException e )
232+
{
233+
assertFalse( result.hasNext() );
234+
}
235+
}
236+
195237
@Test
196238
public void retainShouldWorkAsExpected()
197239
{

0 commit comments

Comments
 (0)