Skip to content

Commit 6cef322

Browse files
committed
Add ResultCursor.peek() and ResultCursor.hasRecord()
o Also ensure inner iterator is set to null as soon as possible to free resources early
1 parent 5ed268e commit 6cef322

File tree

9 files changed

+433
-42
lines changed

9 files changed

+433
-42
lines changed

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

Lines changed: 18 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -48,18 +48,6 @@ public InternalRecord( List<String> keys, Map<String, Integer> keyIndexLookup, V
4848
this.values = values;
4949
}
5050

51-
@Override
52-
public Value value( int index )
53-
{
54-
return index >= 0 && index < values.length ? values[index] : Values.NULL;
55-
}
56-
57-
@Override
58-
public int size()
59-
{
60-
return values.length;
61-
}
62-
6351
@Override
6452
public List<String> keys()
6553
{
@@ -101,6 +89,24 @@ public Value value( String key )
10189
}
10290
}
10391

92+
@Override
93+
public Value value( int index )
94+
{
95+
return index >= 0 && index < values.length ? values[index] : Values.NULL;
96+
}
97+
98+
@Override
99+
public int size()
100+
{
101+
return values.length;
102+
}
103+
104+
@Override
105+
public boolean hasRecord()
106+
{
107+
return true;
108+
}
109+
104110
@Override
105111
public Record record()
106112
{

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

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

2121
import java.util.ArrayList;
22-
import java.util.Iterator;
2322
import java.util.List;
2423

24+
import org.neo4j.driver.v1.Entry;
2525
import org.neo4j.driver.v1.Function;
2626
import org.neo4j.driver.v1.Record;
2727
import org.neo4j.driver.v1.RecordAccessor;
2828
import org.neo4j.driver.v1.ResultCursor;
2929
import org.neo4j.driver.v1.ResultSummary;
3030
import org.neo4j.driver.v1.Value;
3131
import org.neo4j.driver.v1.exceptions.ClientException;
32+
import org.neo4j.driver.v1.exceptions.NoRecordException;
3233

34+
import static java.lang.String.format;
3335
import static java.util.Collections.emptyList;
3436

3537
import static org.neo4j.driver.v1.Records.recordAsIs;
3638

3739
public class InternalResultCursor extends InternalRecordAccessor implements ResultCursor
3840
{
3941
private final List<String> keys;
40-
private final Iterator<Record> iter;
42+
private final PeekingIterator<Record> iter;
4143
private final ResultSummary summary;
4244

4345
private boolean open = true;
@@ -47,7 +49,7 @@ public class InternalResultCursor extends InternalRecordAccessor implements Resu
4749
public InternalResultCursor( List<String> keys, List<Record> body, ResultSummary summary )
4850
{
4951
this.keys = keys;
50-
this.iter = body.iterator();
52+
this.iter = new PeekingIterator<>( body.iterator() );
5153
this.summary = summary;
5254
}
5355

@@ -90,23 +92,27 @@ public int size()
9092
return keys.size();
9193
}
9294

93-
private Value throwNoRecord()
95+
@Override
96+
public boolean hasRecord()
9497
{
95-
throw new ClientException(
96-
"In order to access fields of a record in a result, " +
97-
"you must first call next() to point the result to the next record in the result stream."
98-
);
98+
assertOpen();
99+
return current != null && current.hasRecord();
99100
}
100101

101102
@Override
102103
public Record record()
103104
{
104-
assertOpen();
105-
if ( current == null )
105+
if ( hasRecord() )
106106
{
107-
throwNoRecord();
107+
return current;
108+
}
109+
else
110+
{
111+
throw new NoRecordException(
112+
"In order to access the fields of a record in a result, " +
113+
"you must first call next() to point the result to the next record in the result stream."
114+
);
108115
}
109-
return current;
110116
}
111117

112118
@Override
@@ -144,7 +150,7 @@ public long skip( long elements )
144150
{
145151
if ( elements < 0 )
146152
{
147-
throw new IllegalArgumentException( "Cannot skip negative number of elements" );
153+
throw new ClientException( "Cannot skip negative number of elements" );
148154
}
149155
else
150156
{
@@ -170,6 +176,12 @@ public boolean single()
170176
return first() && atEnd();
171177
}
172178

179+
@Override
180+
public RecordAccessor peek()
181+
{
182+
return new PeekingRecordAccessor();
183+
}
184+
173185
@Override
174186
public List<Record> retain()
175187
{
@@ -192,14 +204,14 @@ else if ( first() )
192204
result.add( mapFunction.apply( this ) );
193205
}
194206
while ( next() );
207+
discard();
195208
return result;
196209
}
197210
else
198211
{
199-
throw new
200-
ClientException( String.format(
201-
"Can't retain records when cursor is not pointing at the first record (currently at position %d)",
202-
position ) );
212+
throw new ClientException(
213+
format( "Can't retain records when cursor is not pointing at the first record (currently at position %d)", position )
214+
);
203215
}
204216
}
205217

@@ -208,6 +220,7 @@ else if ( first() )
208220
public ResultSummary summarize()
209221
{
210222
while ( next() ) ;
223+
discard();
211224
return summary;
212225
}
213226

@@ -216,11 +229,20 @@ public void close()
216229
{
217230
if ( open )
218231
{
232+
discard();
219233
open = false;
220234
}
221235
else
222236
{
223-
throw new IllegalStateException( "Already closed" );
237+
throw new ClientException( "Already closed" );
238+
}
239+
}
240+
241+
private void assertOpen()
242+
{
243+
if ( !open )
244+
{
245+
throw new ClientException( "Cursor already closed" );
224246
}
225247
}
226248

@@ -229,11 +251,73 @@ private boolean isEmpty()
229251
return position == -1 && !iter.hasNext();
230252
}
231253

232-
private void assertOpen()
254+
private void discard()
233255
{
234-
if ( !open )
256+
iter.discard();
257+
}
258+
259+
private class PeekingRecordAccessor implements RecordAccessor
260+
{
261+
@Override
262+
public List<String> keys()
263+
{
264+
return InternalResultCursor.this.keys();
265+
}
266+
267+
@Override
268+
public boolean containsKey( String key )
269+
{
270+
return InternalResultCursor.this.containsKey( key );
271+
}
272+
273+
@Override
274+
public int index( String key )
275+
{
276+
return InternalResultCursor.this.index( key );
277+
}
278+
279+
@Override
280+
public Value value( String key )
281+
{
282+
return record().value( key );
283+
}
284+
285+
@Override
286+
public int size()
287+
{
288+
return InternalResultCursor.this.size();
289+
}
290+
291+
@Override
292+
public List<Entry<Value>> fields()
293+
{
294+
return record().fields();
295+
}
296+
297+
@Override
298+
public boolean hasRecord()
299+
{
300+
return iter.hasNext();
301+
}
302+
303+
@Override
304+
public Record record()
305+
{
306+
Record record = iter.peek();
307+
if ( record == null )
308+
{
309+
throw new NoRecordException( "Cannot peek past last record" );
310+
}
311+
else
312+
{
313+
return record;
314+
}
315+
}
316+
317+
@Override
318+
public Value value( int index )
235319
{
236-
throw new IllegalStateException( "Cursor already closed" );
320+
return record().value( index );
237321
}
238322
}
239323
}
Lines changed: 87 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,87 @@
1+
/**
2+
* Copyright (c) 2002-2015 "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.Iterator;
22+
import java.util.NoSuchElementException;
23+
24+
public class PeekingIterator<T> implements Iterator<T>
25+
{
26+
private Iterator<T> iterator;
27+
private T cached;
28+
29+
public PeekingIterator( Iterator<T> iterator )
30+
{
31+
this.iterator = iterator.hasNext() ? iterator : null;
32+
33+
}
34+
35+
public T peek()
36+
{
37+
return cacheNext() ? cached : null;
38+
}
39+
40+
public boolean hasNext()
41+
{
42+
return cacheNext();
43+
}
44+
45+
public T next()
46+
{
47+
if ( cacheNext() )
48+
{
49+
T result = cached;
50+
cached = null;
51+
return result;
52+
}
53+
else
54+
{
55+
throw new NoSuchElementException();
56+
}
57+
}
58+
59+
private boolean cacheNext()
60+
{
61+
if ( cached == null )
62+
{
63+
if ( iterator == null )
64+
{
65+
return false;
66+
}
67+
else
68+
{
69+
cached = iterator.next();
70+
if (! iterator.hasNext()) {
71+
this.iterator = null;
72+
}
73+
return true;
74+
}
75+
}
76+
else
77+
{
78+
return true;
79+
}
80+
}
81+
82+
public void discard()
83+
{
84+
this.cached = null;
85+
this.iterator = null;
86+
}
87+
}

0 commit comments

Comments
 (0)