Skip to content

Commit f16f9c1

Browse files
committed
Merge pull request #44 from neo4j/1.0-consume
1.0 consume
2 parents 4d9e8f2 + f6bd50c commit f16f9c1

File tree

4 files changed

+48
-47
lines changed

4 files changed

+48
-47
lines changed

examples/test_examples.py

Lines changed: 7 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -98,7 +98,7 @@ def test_statement(self):
9898
# tag::statement[]
9999
result = session.run("CREATE (person:Person {name: {name}})", {"name": "Arthur"})
100100
# end::statement[]
101-
result.discard()
101+
result.consume()
102102
session.close()
103103

104104
def test_statement_without_parameters(self):
@@ -107,7 +107,7 @@ def test_statement_without_parameters(self):
107107
# tag::statement-without-parameters[]
108108
result = session.run("CREATE (person:Person {name: 'Arthur'})")
109109
# end::statement-without-parameters[]
110-
result.discard()
110+
result.consume()
111111
session.close()
112112

113113
def test_result_traversal(self):
@@ -181,9 +181,9 @@ def test_result_summary_query_profile(self):
181181
# tag::result-summary-query-profile[]
182182
result = session.run("PROFILE MATCH (p:Person {name: {name}}) "
183183
"RETURN id(p)", {"name": "Arthur"})
184-
list(result) # skip the records to get to the summary
185-
print(result.summary.statement_type)
186-
print(result.summary.profile)
184+
summary = result.consume()
185+
print(summary.statement_type)
186+
print(summary.profile)
187187
# end::result-summary-query-profile[]
188188
session.close()
189189

@@ -192,8 +192,8 @@ def test_result_summary_notifications(self):
192192
session = driver.session()
193193
# tag::result-summary-notifications[]
194194
result = session.run("EXPLAIN MATCH (king), (queen) RETURN king, queen")
195-
list(result) # skip the records to get to the summary
196-
for notification in result.summary.notifications:
195+
summary = result.consume()
196+
for notification in summary.notifications:
197197
print(notification)
198198
# end::result-summary-notifications[]
199199
session.close()

neo4j/v1/session.py

Lines changed: 22 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -158,10 +158,6 @@ class StatementResult(object):
158158
#: Dictionary of parameters passed with the statement.
159159
parameters = None
160160

161-
#: The result summary (only available after the result has
162-
#: been fully consumed)
163-
summary = None
164-
165161
def __init__(self, connection, run_response, pull_all_response):
166162
super(StatementResult, self).__init__()
167163

@@ -176,6 +172,10 @@ def __init__(self, connection, run_response, pull_all_response):
176172
# the result is used immediately, this buffer will be ignored.
177173
self._buffer = deque()
178174

175+
# The result summary (populated after the records have been
176+
# fully consumed).
177+
self._summary = None
178+
179179
# Flag to indicate whether the entire stream has been consumed
180180
# from the network (but not necessarily yielded).
181181
self._consumed = False
@@ -190,7 +190,7 @@ def on_record(values):
190190

191191
def on_footer(metadata):
192192
# Called on receipt of the result footer.
193-
self.summary = ResultSummary(self.statement, self.parameters, **metadata)
193+
self._summary = ResultSummary(self.statement, self.parameters, **metadata)
194194
self._consumed = True
195195

196196
def on_failure(metadata):
@@ -215,8 +215,9 @@ def __next__(self):
215215
elif self._consumed:
216216
raise StopIteration()
217217
else:
218+
fetch = self.connection.fetch
218219
while not self._buffer and not self._consumed:
219-
self.connection.fetch()
220+
fetch()
220221
return self.__next__()
221222

222223
def keys(self):
@@ -227,15 +228,16 @@ def keys(self):
227228
self.connection.fetch()
228229
return self._keys
229230

230-
def discard(self):
231-
""" Consume the remainder of this result and detach the connection
232-
from this result.
231+
def consume(self):
232+
""" Consume the remainder of this result and return the
233+
summary.
233234
"""
234235
if self.connection and not self.connection.closed:
235236
fetch = self.connection.fetch
236237
while not self._consumed:
237238
fetch()
238239
self.connection = None
240+
return self._summary
239241

240242

241243
class ResultSummary(object):
@@ -270,7 +272,7 @@ def __init__(self, statement, parameters, **metadata):
270272
self.statement = statement
271273
self.parameters = parameters
272274
self.statement_type = metadata.get("type")
273-
self.counters = Counters(metadata.get("stats", {}))
275+
self.counters = SummaryCounters(metadata.get("stats", {}))
274276
if "plan" in metadata:
275277
self.plan = make_plan(metadata["plan"])
276278
if "profile" in metadata:
@@ -285,13 +287,10 @@ def __init__(self, statement, parameters, **metadata):
285287
notification["description"], notification["severity"], position))
286288

287289

288-
class Counters(object):
290+
class SummaryCounters(object):
289291
""" Set of statistics from a Cypher statement execution.
290292
"""
291293

292-
#:
293-
contains_updates = False
294-
295294
#:
296295
nodes_created = 0
297296

@@ -333,6 +332,14 @@ def __init__(self, statistics):
333332
def __repr__(self):
334333
return repr(vars(self))
335334

335+
@property
336+
def contains_updates(self):
337+
return self.nodes_created or self.nodes_deleted or \
338+
self.relationships_created or self.relationships_deleted or \
339+
self.properties_set or self.labels_added or self.labels_removed or \
340+
self.indexes_added or self.indexes_removed or \
341+
self.constraints_added or self.constraints_removed
342+
336343

337344
#: A plan describes how the database will execute your statement.
338345
#:
@@ -473,7 +480,7 @@ def close(self):
473480
""" Recycle this session through the driver it came from.
474481
"""
475482
if self.last_result:
476-
self.last_result.discard()
483+
self.last_result.consume()
477484
self.driver.recycle(self)
478485

479486
def begin_transaction(self):

neokit

test/test_session.py

Lines changed: 18 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -156,12 +156,12 @@ def test_can_run_simple_statement_with_params(self):
156156
def test_fails_on_bad_syntax(self):
157157
session = GraphDatabase.driver("bolt://localhost", auth=auth_token).session()
158158
with self.assertRaises(CypherError):
159-
session.run("X").discard()
159+
session.run("X").consume()
160160

161161
def test_fails_on_missing_parameter(self):
162162
session = GraphDatabase.driver("bolt://localhost", auth=auth_token).session()
163163
with self.assertRaises(CypherError):
164-
session.run("RETURN {x}").discard()
164+
session.run("RETURN {x}").consume()
165165

166166
def test_can_run_simple_statement_from_bytes_string(self):
167167
session = GraphDatabase.driver("bolt://localhost", auth=auth_token).session()
@@ -227,7 +227,7 @@ def test_can_return_path(self):
227227
def test_can_handle_cypher_error(self):
228228
with GraphDatabase.driver("bolt://localhost", auth=auth_token).session() as session:
229229
with self.assertRaises(CypherError):
230-
session.run("X").discard()
230+
session.run("X").consume()
231231

232232
def test_keys_are_available_before_and_after_stream(self):
233233
with GraphDatabase.driver("bolt://localhost", auth=auth_token).session() as session:
@@ -248,30 +248,24 @@ class SummaryTestCase(ServerTestCase):
248248
def test_can_obtain_summary_after_consuming_result(self):
249249
with GraphDatabase.driver("bolt://localhost", auth=auth_token).session() as session:
250250
result = session.run("CREATE (n) RETURN n")
251-
list(result)
252-
summary = result.summary
251+
summary = result.consume()
253252
assert summary.statement == "CREATE (n) RETURN n"
254253
assert summary.parameters == {}
255254
assert summary.statement_type == "rw"
256255
assert summary.counters.nodes_created == 1
257256

258-
def test_cannot_obtain_summary_without_consuming_result(self):
259-
with GraphDatabase.driver("bolt://localhost", auth=auth_token).session() as session:
260-
result = session.run("CREATE (n) RETURN n")
261-
assert result.summary is None
262-
263257
def test_no_plan_info(self):
264258
with GraphDatabase.driver("bolt://localhost", auth=auth_token).session() as session:
265259
result = session.run("CREATE (n) RETURN n")
266-
list(result) # consume the result
267-
assert result.summary.plan is None
268-
assert result.summary.profile is None
260+
summary = result.consume()
261+
assert summary.plan is None
262+
assert summary.profile is None
269263

270264
def test_can_obtain_plan_info(self):
271265
with GraphDatabase.driver("bolt://localhost", auth=auth_token).session() as session:
272266
result = session.run("EXPLAIN CREATE (n) RETURN n")
273-
list(result) # consume the result
274-
plan = result.summary.plan
267+
summary = result.consume()
268+
plan = summary.plan
275269
assert plan.operator_type == "ProduceResults"
276270
assert plan.identifiers == ["n"]
277271
assert plan.arguments == {"planner": "COST", "EstimatedRows": 1.0, "version": "CYPHER 3.0",
@@ -282,8 +276,8 @@ def test_can_obtain_plan_info(self):
282276
def test_can_obtain_profile_info(self):
283277
with GraphDatabase.driver("bolt://localhost", auth=auth_token).session() as session:
284278
result = session.run("PROFILE CREATE (n) RETURN n")
285-
list(result) # consume the result
286-
profile = result.summary.profile
279+
summary = result.consume()
280+
profile = summary.profile
287281
assert profile.db_hits == 0
288282
assert profile.rows == 1
289283
assert profile.operator_type == "ProduceResults"
@@ -296,19 +290,19 @@ def test_can_obtain_profile_info(self):
296290
def test_no_notification_info(self):
297291
with GraphDatabase.driver("bolt://localhost", auth=auth_token).session() as session:
298292
result = session.run("CREATE (n) RETURN n")
299-
list(result) # consume the result
300-
notifications = result.summary.notifications
293+
summary = result.consume()
294+
notifications = summary.notifications
301295
assert notifications == []
302296

303297
def test_can_obtain_notification_info(self):
304298
with GraphDatabase.driver("bolt://localhost", auth=auth_token).session() as session:
305299
result = session.run("EXPLAIN MATCH (n), (m) RETURN n, m")
306-
list(result) # consume the result
307-
notifications = result.summary.notifications
300+
summary = result.consume()
301+
notifications = summary.notifications
308302

309303
assert len(notifications) == 1
310304
notification = notifications[0]
311-
assert notification.code == "Neo.ClientNotification.Statement.CartesianProduct"
305+
assert notification.code == "Neo.ClientNotification.Statement.CartesianProductWarning"
312306
assert notification.title == "This query builds a cartesian product between " \
313307
"disconnected patterns."
314308
assert notification.severity == "WARNING"
@@ -333,7 +327,7 @@ class ResetTestCase(ServerTestCase):
333327
def test_automatic_reset_after_failure(self):
334328
with GraphDatabase.driver("bolt://localhost", auth=auth_token).session() as session:
335329
try:
336-
session.run("X").discard()
330+
session.run("X").consume()
337331
except CypherError:
338332
result = session.run("RETURN 1")
339333
record = next(result)
@@ -347,7 +341,7 @@ def test_defunct(self):
347341
assert not session.connection.defunct
348342
with patch.object(ChunkChannel, "chunk_reader", side_effect=ProtocolError()):
349343
with self.assertRaises(ProtocolError):
350-
session.run("RETURN 1").discard()
344+
session.run("RETURN 1").consume()
351345
assert session.connection.defunct
352346
assert session.connection.closed
353347

0 commit comments

Comments
 (0)