Skip to content

Commit ff60660

Browse files
committed
Fix endless loop in Result.peek with fetch_size=1
Implement `ResultSingle` and `ResultPeek` in TestKit backend. `ResultSingle` will be deactivated for now as TestKit expects the driver to raise an error if there is not exactly 1 records in the result stream. Currently, the driver only warns if there are more and returns None if there are none. This (breaking) fix will be introduced in 5.0.
1 parent 752bcd3 commit ff60660

File tree

3 files changed

+46
-12
lines changed

3 files changed

+46
-12
lines changed

neo4j/work/result.py

Lines changed: 30 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,7 @@
2929
SessionExpired,
3030
)
3131
from neo4j.work.summary import ResultSummary
32+
from neo4j.exceptions import ResultConsumedError
3233

3334

3435
class _ConnectionErrorHandler:
@@ -233,20 +234,37 @@ def __iter__(self):
233234
self._closed = True
234235

235236
def _attach(self):
236-
"""Sets the Result object in an attached state by fetching messages from the connection to the buffer.
237+
"""Sets the Result object in an attached state by fetching messages from
238+
the connection to the buffer.
237239
"""
238240
if self._closed is False:
239241
while self._attached is False:
240242
self._connection.fetch_message()
241243

242-
def _buffer_all(self):
243-
"""Sets the Result object in an detached state by fetching all records from the connection to the buffer.
244+
def _buffer(self, n=None):
245+
"""Try to fill `self_record_buffer` with n records.
246+
247+
Might end up with more records in the buffer if the fetch size makes it
248+
overshoot.
249+
Might ent up with fewer records in the buffer if there are not enough
250+
records available.
244251
"""
245252
record_buffer = deque()
246253
for record in self:
247254
record_buffer.append(record)
255+
if n is not None and len(record_buffer) >= n:
256+
break
248257
self._closed = False
249-
self._record_buffer = record_buffer
258+
if n is None:
259+
self._record_buffer = record_buffer
260+
else:
261+
self._record_buffer.extend(record_buffer)
262+
263+
def _buffer_all(self):
264+
"""Sets the Result object in an detached state by fetching all records
265+
from the connection to the buffer.
266+
"""
267+
self._buffer()
250268

251269
def _obtain_summary(self):
252270
"""Obtain the summary of this result, buffering any remaining records.
@@ -319,6 +337,13 @@ def single(self):
319337
:returns: the next :class:`neo4j.Record` or :const:`None` if none remain
320338
:warns: if more than one record is available
321339
"""
340+
# TODO in 5.0 replace with this code that raises an error if there's not
341+
# exactly one record in the left result stream.
342+
# self._buffer(2).
343+
# if len(self._record_buffer) != 1:
344+
# raise SomeError("Expected exactly 1 record, found %i"
345+
# % len(self._record_buffer))
346+
# return self._record_buffer.popleft()
322347
records = list(self) # TODO: exhausts the result with self.consume if there are more records.
323348
size = len(records)
324349
if size == 0:
@@ -333,16 +358,9 @@ def peek(self):
333358
334359
:returns: the next :class:`.Record` or :const:`None` if none remain
335360
"""
361+
self._buffer(1)
336362
if self._record_buffer:
337363
return self._record_buffer[0]
338-
if not self._attached:
339-
return None
340-
while self._attached:
341-
self._connection.fetch_message()
342-
if self._record_buffer:
343-
return self._record_buffer[0]
344-
345-
return None
346364

347365
def graph(self):
348366
"""Return a :class:`neo4j.graph.Graph` instance containing all the graph objects

testkitbackend/requests.py

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -301,6 +301,20 @@ def ResultNext(backend, data):
301301
backend.send_response("Record", totestkit.record(record))
302302

303303

304+
def ResultSingle(backend, data):
305+
result = backend.results[data["resultId"]]
306+
backend.send_response("Record", totestkit.record(result.single()))
307+
308+
309+
def ResultPeek(backend, data):
310+
result = backend.results[data["resultId"]]
311+
record = result.peek()
312+
if record is not None:
313+
backend.send_response("Record", totestkit.record(record))
314+
else:
315+
backend.send_response("NullRecord", {})
316+
317+
304318
def ResultConsume(backend, data):
305319
result = backend.results[data["resultId"]]
306320
summary = result.consume()

testkitbackend/test_config.json

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,8 @@
3232
"TLSv1.1 and below are disabled in the driver"
3333
},
3434
"features": {
35+
"Feature:API:Result.Single": "Does not raise error when not exactly one record is available. To be fixed in 5.0",
36+
"Feature:API:Result.Peek": true,
3537
"AuthorizationExpiredTreatment": true,
3638
"Optimization:ImplicitDefaultArguments": true,
3739
"Optimization:MinimalResets": true,

0 commit comments

Comments
 (0)