Skip to content

Commit 54d1c60

Browse files
author
Zhen
committed
Added notifications into result summary
1 parent 42a169b commit 54d1c60

File tree

2 files changed

+74
-4
lines changed

2 files changed

+74
-4
lines changed

neo4j/session.py

Lines changed: 47 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -140,7 +140,8 @@ def on_footer(self, metadata):
140140
self.complete = True
141141
self.summary = ResultSummary(self.statement, self.parameters,
142142
metadata.get("type"), metadata.get("stats"),
143-
metadata.get("plan"), metadata.get("profile"))
143+
metadata.get("plan"), metadata.get("profile"),
144+
metadata.get("notifications", []))
144145
if self.bench_test:
145146
self.bench_test.end_recv = perf_counter()
146147

@@ -188,7 +189,12 @@ class ResultSummary(object):
188189
#: A :class:`.ProfiledPlan` instance
189190
profile = None
190191

191-
def __init__(self, statement, parameters, statement_type, statistics, plan, profile):
192+
#: Notifications provide extra information for a user executing a statement.
193+
#: They can be warnings about problematic queries or other valuable information that can be presented in a client.
194+
#: Unlike failures or errors, notifications do not affect the execution of a statement.
195+
notifications = None
196+
197+
def __init__(self, statement, parameters, statement_type, statistics, plan, profile, notifications):
192198
self.statement = statement
193199
self.parameters = parameters
194200
self.statement_type = statement_type
@@ -198,6 +204,7 @@ def __init__(self, statement, parameters, statement_type, statistics, plan, prof
198204
if profile is not None:
199205
self.profile = ProfiledPlan(profile)
200206
self.plan = self.profile
207+
self.notifications = list(map(Notification, notifications))
201208

202209

203210
class StatementStatistics(object):
@@ -283,11 +290,47 @@ class ProfiledPlan(Plan):
283290
rows = 0
284291

285292
def __init__(self, profile):
286-
self.db_hits = profile.get("dbHits", 0);
287-
self.rows = profile.get("rows", 0);
293+
self.db_hits = profile.get("dbHits", 0)
294+
self.rows = profile.get("rows", 0)
288295
super(ProfiledPlan, self).__init__(profile)
289296

290297

298+
class Notification(object):
299+
""" Representation for notifications found when executing a statement.
300+
A notification can be visualized in a client pinpointing problems or other information about the statement.
301+
"""
302+
303+
#: A notification code for the discovered issue.
304+
code = None
305+
306+
#: A short summary of the notification
307+
title = None
308+
309+
#: A long description of the notification
310+
description = None
311+
312+
#: The position in the statement where this notification points to, if relevant. This is a namedtuple
313+
#: consisting of offset, line and column:
314+
#:
315+
#: - offset - the character offset referred to by this position; offset numbers start at 0
316+
#:
317+
#: - line - the line number referred to by the position; line numbers start at 1
318+
#:
319+
#: - column - the column number referred to by the position; column numbers start at 1
320+
position = None
321+
322+
def __init__(self, notification):
323+
self.code = notification["code"]
324+
self.title = notification["title"]
325+
self.description = notification["description"]
326+
position = notification.get("position")
327+
if position is not None:
328+
self.position = Position(position["offset"], position["line"], position["column"])
329+
330+
331+
Position = namedtuple('Position', ['offset', 'line', 'column'])
332+
333+
291334
class Session(object):
292335
""" Logical session carried out over an established TCP connection.
293336
Sessions should generally be constructed using the :meth:`.Driver.session`

test/session_test.py

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -198,6 +198,33 @@ def test_can_obtain_profile_info(self):
198198
"runtime": "INTERPRETED", "Rows": 1, "DbHits": 0}
199199
assert len(profile.children) == 1
200200

201+
def test_no_notification_info(self):
202+
with GraphDatabase.driver("bolt://localhost").session() as session:
203+
result = session.run("CREATE (n) RETURN n")
204+
notifications = result.summarize().notifications
205+
assert notifications == []
206+
207+
def test_can_obtain_notification_info(self):
208+
with GraphDatabase.driver("bolt://localhost").session() as session:
209+
result = session.run("EXPLAIN MATCH (n), (m) RETURN n, m")
210+
notifications = result.summarize().notifications
211+
212+
assert len(notifications) == 1
213+
notification = notifications[0]
214+
assert notification.code == "Neo.ClientNotification.Statement.CartesianProduct"
215+
assert notification.title == "This query builds a cartesian product between disconnected patterns."
216+
assert notification.description == \
217+
"If a part of a query contains multiple disconnected patterns, " \
218+
"this will build a cartesian product between all those parts. " \
219+
"This may produce a large amount of data and slow down query processing. " \
220+
"While occasionally intended, it may often be possible to reformulate the query " \
221+
"that avoids the use of this cross product, perhaps by adding a relationship between " \
222+
"the different parts or by using OPTIONAL MATCH (identifier is: (m))"
223+
position = notification.position
224+
assert position.offset == 0
225+
assert position.line == 1
226+
assert position.column == 1
227+
201228

202229
class TransactionTestCase(TestCase):
203230
def test_can_commit_transaction(self):

0 commit comments

Comments
 (0)