Skip to content

Commit 8ee9aff

Browse files
committed
Merge pull request #118 from jakewins/1.0-single
single and first return record
2 parents f7b65a5 + 341e0a1 commit 8ee9aff

File tree

13 files changed

+111
-110
lines changed

13 files changed

+111
-110
lines changed

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

Lines changed: 30 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,7 @@
2828
import org.neo4j.driver.v1.ResultSummary;
2929
import org.neo4j.driver.v1.Value;
3030
import org.neo4j.driver.v1.exceptions.ClientException;
31-
import org.neo4j.driver.v1.exceptions.NoRecordException;
31+
import org.neo4j.driver.v1.exceptions.NoSuchRecordException;
3232

3333
import static java.lang.String.format;
3434
import static java.util.Collections.emptyList;
@@ -100,7 +100,7 @@ public Record record()
100100
}
101101
else
102102
{
103-
throw new NoRecordException(
103+
throw new NoSuchRecordException(
104104
"In order to access the fields of a record in a result, " +
105105
"you must first call next() to point the result to the next record in the result stream."
106106
);
@@ -176,16 +176,38 @@ else if ( records == 0) {
176176
}
177177

178178
@Override
179-
public boolean first()
179+
public Record first()
180180
{
181-
long pos = position();
182-
return pos < 0 ? next() : pos == 0;
181+
if( position() > 0 )
182+
{
183+
throw new NoSuchRecordException( "Cannot retrieve the first record, because this result cursor has been moved already. " +
184+
"Please ensure you are not calling `first` multiple times, or are mixing it with calls " +
185+
"to `next`, `single`, `list` or any other method that changes the position of the cursor." );
186+
}
187+
188+
if( position == 0 )
189+
{
190+
return record();
191+
}
192+
193+
if( !next() )
194+
{
195+
throw new NoSuchRecordException( "Cannot retrieve the first record, because this result is empty." );
196+
}
197+
return record();
183198
}
184199

185200
@Override
186-
public boolean single()
201+
public Record single()
187202
{
188-
return first() && atEnd();
203+
Record first = first();
204+
if( !iter.hasNext() )
205+
{
206+
throw new NoSuchRecordException( "Expected a result with a single record, but this result contains at least one more. " +
207+
"Ensure your query returns only one record, or use `first` instead of `single` if " +
208+
"you do not care about the number of records in the result." );
209+
}
210+
return first;
189211
}
190212

191213
@Override
@@ -208,7 +230,7 @@ public <T> List<T> list( Function<RecordAccessor, T> mapFunction )
208230
assertOpen();
209231
return emptyList();
210232
}
211-
else if ( first() )
233+
else if ( position == 0 || ( position == -1 && next() ) )
212234
{
213235
List<T> result = new ArrayList<>();
214236
do

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

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

2323
import org.neo4j.driver.internal.value.NullValue;
24-
import org.neo4j.driver.v1.exceptions.NoRecordException;
24+
import org.neo4j.driver.v1.exceptions.NoSuchRecordException;
2525

2626
/**
2727
* Access an underlying record (which is an ordered map of fields)
@@ -62,7 +62,7 @@ public interface RecordAccessor extends ListAccessor
6262
*
6363
* @param key the key of the property
6464
* @return the property's value or a {@link NullValue} if no such key exists
65-
* @throws NoRecordException if the associated underlying record is not available
65+
* @throws NoSuchRecordException if the associated underlying record is not available
6666
*/
6767
Value get( String key );
6868

@@ -78,12 +78,12 @@ public interface RecordAccessor extends ListAccessor
7878
* Retrieve all record fields
7979
*
8080
* @return all fields in key order
81-
* @throws NoRecordException if the associated underlying record is not available
81+
* @throws NoSuchRecordException if the associated underlying record is not available
8282
*/
8383
List<Pair<String, Value>> fields();
8484

8585
/**
86-
* @throws NoRecordException if the associated underlying record is not available
86+
* @throws NoSuchRecordException if the associated underlying record is not available
8787
* @return an immutable copy of the currently associated underlying record
8888
*/
8989
Record record();

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

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

2323
import org.neo4j.driver.v1.exceptions.ClientException;
24+
import org.neo4j.driver.v1.exceptions.NoSuchRecordException;
2425

2526

2627
/**
@@ -41,10 +42,10 @@ public interface ResultCursor extends RecordAccessor, Resource
4142
{
4243
/**
4344
* @return an immutable copy of the currently viewed record
44-
* @throws ClientException if no calls has been made to {@link #next()}, {@link #first()}, nor {@link #skip(long)}
45+
* @throws NoSuchRecordException if no calls has been made to {@link #next()}, {@link #first()}, nor {@link #skip(long)}
4546
*/
4647
@Override
47-
Record record();
48+
Record record() throws NoSuchRecordException;
4849

4950
/**
5051
* Retrieve the zero based position of the cursor in the stream of records.
@@ -90,26 +91,28 @@ public interface ResultCursor extends RecordAccessor, Resource
9091
long limit( long records );
9192

9293
/**
93-
* Move to the first record if possible, otherwise do nothing.
94+
* Return the first record in the stream. Fail with an exception if the stream is empty
95+
* or if this cursor has already been used to move "into" the stream.
96+
*
97+
* @return the first record in the stream
98+
* @throws NoSuchRecordException if there is no first record or the cursor has been used already
9499
*
95-
* @return <tt>true</tt> if the cursor is placed on the first record
96100
*/
97-
boolean first();
101+
Record first() throws NoSuchRecordException;
98102

99103
/**
100-
* Move to the first record if possible and verify that it is the only record.
104+
* Move to the first record and return an immutable copy of it, failing if there is not exactly
105+
* one record in the stream, or if this cursor has already been used to move "into" the stream.
101106
*
102-
* @return <tt>true</tt> if the cursor was successfully placed at the single first and only record
107+
* @return the first and only record in the stream
108+
* @throws NoSuchRecordException if there is not exactly one record in the stream, or if the cursor has been used already
103109
*/
104-
boolean single();
110+
Record single() throws NoSuchRecordException;
105111

106112
/**
107-
* Investigate the next upcoming record.
108-
*
109-
* The returned {@link RecordAccessor} is updated consistently whenever this associated cursor
110-
* is moved.
113+
* Investigate the next upcoming record without changing the position of this cursor.
111114
*
112-
* @return a view on the next record, or null if there is no next record
115+
* @return an immutable copy of the next record, or null if there is no next record
113116
*/
114117
Record peek();
115118

@@ -118,7 +121,8 @@ public interface ResultCursor extends RecordAccessor, Resource
118121
* This can be used if you want to iterate over the stream multiple times or to store the
119122
* whole result for later use.
120123
*
121-
* Calling this method exhausts the result cursor and moves it to the last record
124+
* Calling this method exhausts the result cursor and moves it to the last record.
125+
*
122126
* @throws ClientException if the cursor can't be positioned at the first record
123127
* @return list of all immutable records
124128
*/
@@ -129,7 +133,8 @@ public interface ResultCursor extends RecordAccessor, Resource
129133
* This can be used if you want to iterate over the stream multiple times or to store the
130134
* whole result for later use.
131135
*
132-
* Calling this method exhausts the result cursor and moves it to the last record
136+
* Calling this method exhausts the result cursor and moves it to the last record.
137+
*
133138
* @throws ClientException if the cursor can't be positioned at the first record
134139
* @param mapFunction a function to map from Value to T. See {@link Values} for some predefined functions, such
135140
* as {@link Values#valueAsBoolean()}, {@link Values#valueAsList(Function)}.

driver/src/main/java/org/neo4j/driver/v1/exceptions/NoRecordException.java renamed to driver/src/main/java/org/neo4j/driver/v1/exceptions/NoSuchRecordException.java

Lines changed: 2 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -18,17 +18,12 @@
1818
*/
1919
package org.neo4j.driver.v1.exceptions;
2020

21-
public class NoRecordException extends ClientException
21+
public class NoSuchRecordException extends ClientException
2222
{
2323
private static final long serialVersionUID = 9091962868264042491L;
2424

25-
public NoRecordException( String message )
25+
public NoSuchRecordException( String message )
2626
{
2727
super( message );
2828
}
29-
30-
public NoRecordException( String message, Throwable cause )
31-
{
32-
super( message, cause );
33-
}
3429
}

driver/src/main/java/org/neo4j/driver/v1/exceptions/value/Unrepresentable.java

Lines changed: 0 additions & 29 deletions
This file was deleted.

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

Lines changed: 32 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -36,10 +36,12 @@
3636
import org.neo4j.driver.v1.ResultCursor;
3737
import org.neo4j.driver.v1.Value;
3838
import org.neo4j.driver.v1.exceptions.ClientException;
39+
import org.neo4j.driver.v1.exceptions.NoSuchRecordException;
3940

4041
import static org.hamcrest.CoreMatchers.equalTo;
4142
import static org.hamcrest.collection.IsCollectionWithSize.hasSize;
4243
import static org.junit.Assert.assertFalse;
44+
import static org.junit.Assert.assertNotNull;
4345
import static org.junit.Assert.assertNull;
4446
import static org.junit.Assert.assertThat;
4547
import static org.junit.Assert.assertTrue;
@@ -59,13 +61,12 @@ public void iterationShouldWorksAsExpected()
5961
// WHEN
6062
assertThat( result.position(), equalTo( -1L ) );
6163
assertTrue( result.next() ); //-1 -> 0
62-
assertTrue( result.first() );
64+
assertNotNull( result.first() );
6365
assertFalse( result.atEnd() );
6466
assertThat( values( result.record() ), equalTo(Arrays.asList(value("v1-1"), value( "v2-1" ))));
6567

6668
assertThat( result.position(), equalTo( 0L ) );
6769
assertTrue( result.next() ); //0 -> 1
68-
assertFalse( result.first() );
6970
assertFalse( result.atEnd() );
7071
assertThat( values( result.record() ), equalTo(Arrays.asList(value("v1-2"), value( "v2-2" ))));
7172

@@ -75,15 +76,18 @@ public void iterationShouldWorksAsExpected()
7576
// THEN
7677
assertThat( result.position(), equalTo( 2L ) );
7778
assertTrue( result.atEnd() );
78-
assertFalse( result.first() );
7979
assertThat( values( result.record() ), equalTo(Arrays.asList(value("v1-3"), value( "v2-3" ))));
8080
assertFalse( result.next() );
8181
}
8282

8383
@Test
84-
public void firstFalseOnEmptyStream()
84+
public void firstThrowsOnEmptyStream()
8585
{
86-
assertFalse( createResult( 0 ).first() );
86+
// Expect
87+
expectedException.expect( NoSuchRecordException.class );
88+
89+
// When
90+
createResult( 0 ).first();
8791
}
8892

8993
@Test
@@ -94,18 +98,36 @@ public void firstMovesCursorOnce()
9498

9599
// WHEN
96100
assertThat( result.position(), equalTo( -1L ) );
97-
assertTrue( result.first() );
101+
assertNotNull( result.first() );
98102
assertThat( result.position(), equalTo( 0L ) );
99-
assertTrue( result.first() );
103+
assertNotNull( result.first() );
100104
assertThat( result.position(), equalTo( 0L ) );
101105
}
102106

103107
@Test
104108
public void singleShouldWorkAsExpected()
105109
{
106-
assertFalse( createResult( 42 ).single() );
107-
assertFalse( createResult( 0 ).single() );
108-
assertTrue( createResult( 1 ).single() );
110+
assertNotNull( createResult( 1 ).single() );
111+
}
112+
113+
@Test
114+
public void singleShouldThrowOnBigResult()
115+
{
116+
// Expect
117+
expectedException.expect( NoSuchRecordException.class );
118+
119+
// When
120+
createResult( 42 ).single();
121+
}
122+
123+
@Test
124+
public void singleShouldThrowOnEmptyResult()
125+
{
126+
// Expect
127+
expectedException.expect( NoSuchRecordException.class );
128+
129+
// When
130+
createResult( 0 ).single();
109131
}
110132

111133
@Test

driver/src/test/java/org/neo4j/driver/v1/DriverDocIT.java

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -31,7 +31,6 @@
3131
import static org.hamcrest.MatcherAssert.assertThat;
3232
import static org.hamcrest.Matchers.containsInAnyOrder;
3333
import static org.junit.Assert.assertEquals;
34-
import static org.junit.Assert.assertTrue;
3534

3635
@RunWith( DocTestRunner.class )
3736
public class DriverDocIT
@@ -53,8 +52,7 @@ public void exampleUsage( DocSnippet snippet )
5352

5453
// then it should've created a bunch of data
5554
ResultCursor result = session.run( "MATCH (n) RETURN count(n)" );
56-
assertTrue( result.single() );
57-
assertEquals( 3, result.get( 0 ).asInt() );
55+
assertEquals( 3, result.single().get( 0 ).asInt() );
5856
assertThat( (List<String>)snippet.get( "names" ), containsInAnyOrder( "Bob", "Alice", "Tina" ) );
5957
}
6058
}

driver/src/test/java/org/neo4j/driver/v1/TransactionDocIT.java

Lines changed: 2 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,6 @@
2626
import org.neo4j.driver.v1.util.TestNeo4jSession;
2727

2828
import static org.junit.Assert.assertEquals;
29-
import static org.junit.Assert.assertTrue;
3029

3130
@RunWith( DocTestRunner.class )
3231
public class TransactionDocIT
@@ -45,8 +44,7 @@ public void classDoc( DocSnippet snippet )
4544

4645
// Then a node should've been created
4746
ResultCursor cursor = session.run( "MATCH (n) RETURN count(n)" );
48-
assertTrue( cursor.single() );
49-
assertEquals( 1, cursor.get( "count(n)" ).asInt() );
47+
assertEquals( 1, cursor.single().get( "count(n)" ).asInt() );
5048
}
5149

5250
/** @see Transaction#failure() */
@@ -60,7 +58,6 @@ public void failure( DocSnippet snippet )
6058

6159
// Then a node should've been created
6260
ResultCursor cursor = session.run( "MATCH (n) RETURN count(n)" );
63-
assertTrue( cursor.single() );
64-
assertEquals( 0, cursor.get( "count(n)" ).asInt() );
61+
assertEquals( 0, cursor.single().get( "count(n)" ).asInt() );
6562
}
6663
}

0 commit comments

Comments
 (0)