Skip to content

Commit f121154

Browse files
authored
Introduce driver.execute_query (#833)
* Introduce driver.execute_query * TestKit backend can handle omitted query params * Add tests for query parameter precedence * Docs: be more precise about retires * Tested and improved examples in docstrings * API docs: explain auto-commit limitations * API docs: fix missing ref * Remove debug code * Emit experimental warning for Driver.execute_query * Emit experimental warning for Driver.query_bookmark_manager * Emit ExperimentalWarning for Result.to_eager_result
1 parent d8c475e commit f121154

File tree

21 files changed

+2327
-94
lines changed

21 files changed

+2327
-94
lines changed

docs/source/api.rst

Lines changed: 205 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -153,7 +153,196 @@ Closing a driver will immediately shut down all connections in the pool.
153153
query, use :meth:`neo4j.Driver.verify_connectivity`.
154154

155155
.. autoclass:: neo4j.Driver()
156-
:members: session, encrypted, close, verify_connectivity, get_server_info
156+
:members: session, query_bookmark_manager, encrypted, close,
157+
verify_connectivity, get_server_info
158+
159+
.. method:: execute_query(query, parameters_=None,routing_=neo4j.RoutingControl.WRITERS, database_=None, impersonated_user_=None, bookmark_manager_=self.query_bookmark_manager, result_transformer_=Result.to_eager_result, **kwargs)
160+
161+
Execute a query in a transaction function and return all results.
162+
163+
This method is a handy wrapper for lower-level driver APIs like
164+
sessions, transactions, and transaction functions. It is intended
165+
for simple use cases where there is no need for managing all possible
166+
options.
167+
168+
The internal usage of transaction functions provides a retry-mechanism
169+
for appropriate errors. Furthermore, this means that queries using
170+
``CALL {} IN TRANSACTIONS`` or the older ``USING PERIODIC COMMIT``
171+
will not work (use :meth:`Session.run` for these).
172+
173+
The method is roughly equivalent to::
174+
175+
def execute_query(
176+
query_, parameters_, routing_, database_, impersonated_user_,
177+
bookmark_manager_, result_transformer_, **kwargs
178+
):
179+
def work(tx):
180+
result = tx.run(query_, parameters_, **kwargs)
181+
return result_transformer_(result)
182+
183+
with driver.session(
184+
database=database_,
185+
impersonated_user=impersonated_user_,
186+
bookmark_manager=bookmark_manager_,
187+
) as session:
188+
if routing_ == RoutingControl.WRITERS:
189+
return session.execute_write(work)
190+
elif routing_ == RoutingControl.READERS:
191+
return session.execute_read(work)
192+
193+
Usage example::
194+
195+
from typing import List
196+
197+
import neo4j
198+
199+
200+
def example(driver: neo4j.Driver) -> List[str]:
201+
\"""Get the name of all 42 year-olds.\"""
202+
records, summary, keys = driver.execute_query(
203+
"MATCH (p:Person {age: $age}) RETURN p.name",
204+
{"age": 42},
205+
routing_=neo4j.RoutingControl.READERS, # or just "r"
206+
database_="neo4j",
207+
)
208+
assert keys == ["p.name"] # not needed, just for illustration
209+
# log_summary(summary) # log some metadata
210+
return [str(record["p.name"]) for record in records]
211+
# or: return [str(record[0]) for record in records]
212+
# or even: return list(map(lambda r: str(r[0]), records))
213+
214+
Another example::
215+
216+
import neo4j
217+
218+
219+
def example(driver: neo4j.Driver) -> int:
220+
\"""Call all young people "My dear" and get their count.\"""
221+
record = driver.execute_query(
222+
"MATCH (p:Person) WHERE p.age <= $age "
223+
"SET p.nickname = 'My dear' "
224+
"RETURN count(*)",
225+
# optional routing parameter, as write is default
226+
# routing_=neo4j.RoutingControl.WRITERS, # or just "w",
227+
database_="neo4j",
228+
result_transformer_=neo4j.Result.single,
229+
age=15,
230+
)
231+
assert record is not None # for typechecking and illustration
232+
count = record[0]
233+
assert isinstance(count, int)
234+
return count
235+
236+
:param query_: cypher query to execute
237+
:type query_: typing.Optional[str]
238+
:param parameters_: parameters to use in the query
239+
:type parameters_: typing.Optional[typing.Dict[str, typing.Any]]
240+
:param routing_:
241+
whether to route the query to a reader (follower/read replica) or
242+
a writer (leader) in the cluster. Default is to route to a writer.
243+
:type routing_: neo4j.RoutingControl
244+
:param database_:
245+
database to execute the query against.
246+
247+
None (default) uses the database configured on the server side.
248+
249+
.. Note::
250+
It is recommended to always specify the database explicitly
251+
when possible. This allows the driver to work more efficiently,
252+
as it will not have to resolve the default database first.
253+
254+
See also the Session config :ref:`database-ref`.
255+
:type database_: typing.Optional[str]
256+
:param impersonated_user_:
257+
Name of the user to impersonate.
258+
259+
This means that all query will be executed in the security context
260+
of the impersonated user. For this, the user for which the
261+
:class:`Driver` has been created needs to have the appropriate
262+
permissions.
263+
264+
See also the Session config :ref:`impersonated-user-ref`.
265+
:type impersonated_user_: typing.Optional[str]
266+
:param result_transformer_:
267+
A function that gets passed the :class:`neo4j.Result` object
268+
resulting from the query and converts it to a different type. The
269+
result of the transformer function is returned by this method.
270+
271+
.. warning::
272+
273+
The transformer function must **not** return the
274+
:class:`neo4j.Result` itself.
275+
276+
Example transformer that checks that exactly one record is in the
277+
result stream, then returns the record and the result summary::
278+
279+
from typing import Tuple
280+
281+
import neo4j
282+
283+
284+
def transformer(
285+
result: neo4j.Result
286+
) -> Tuple[neo4j.Record, neo4j.ResultSummary]:
287+
record = result.single(strict=True)
288+
summary = result.consume()
289+
return record, summary
290+
291+
Note that methods of :class:`neo4j.Result` that don't take
292+
mandatory arguments can be used directly as transformer functions.
293+
For example::
294+
295+
import neo4j
296+
297+
298+
def example(driver: neo4j.Driver) -> neo4j.Record::
299+
record = driver.execute_query(
300+
"SOME QUERY",
301+
result_transformer_=neo4j.Result.single
302+
)
303+
304+
305+
# is equivalent to:
306+
307+
308+
def transformer(result: neo4j.Result) -> neo4j.Record:
309+
return result.single()
310+
311+
312+
def example(driver: neo4j.Driver) -> neo4j.Record::
313+
record = driver.execute_query(
314+
"SOME QUERY",
315+
result_transformer_=transformer
316+
)
317+
318+
:type result_transformer_:
319+
typing.Callable[[neo4j.Result], typing.Union[T]]
320+
:param bookmark_manager_:
321+
Specify a bookmark manager to use.
322+
323+
If present, the bookmark manager is used to keep the query causally
324+
consistent with all work executed using the same bookmark manager.
325+
326+
Defaults to the driver's :attr:`.query_bookmark_manager`.
327+
328+
Pass :const:`None` to disable causal consistency.
329+
:type bookmark_manager_:
330+
typing.Union[neo4j.BookmarkManager, neo4j.BookmarkManager,
331+
None]
332+
:param kwargs: additional keyword parameters. None of these can end
333+
with a single underscore. This is to avoid collisions with the
334+
keyword configuration parameters of this method. If you need to
335+
pass such a parameter, use the ``parameters_`` parameter instead.
336+
These take precedence over parameters passed as ``parameters_``.
337+
:type kwargs: typing.Any
338+
339+
:returns: the result of the ``result_transformer``
340+
:rtype: T
341+
342+
**This is experimental.** (See :ref:`filter-warnings-ref`)
343+
It might be changed or removed any time even without prior notice.
344+
345+
.. versionadded:: 5.5
157346

158347

159348
.. _driver-configuration-ref:
@@ -974,11 +1163,22 @@ A :class:`neo4j.Result` is attached to an active connection, through a :class:`n
9741163

9751164
.. automethod:: to_df
9761165

1166+
.. automethod:: to_eager_result
1167+
9771168
.. automethod:: closed
9781169

9791170
See https://neo4j.com/docs/python-manual/current/cypher-workflow/#python-driver-type-mapping for more about type mapping.
9801171

9811172

1173+
***********
1174+
EagerResult
1175+
***********
1176+
1177+
.. autoclass:: neo4j.EagerResult
1178+
:show-inheritance:
1179+
:members:
1180+
1181+
9821182
Graph
9831183
=====
9841184

@@ -1370,15 +1570,17 @@ BookmarkManager
13701570
Constants, Enums, Helpers
13711571
*************************
13721572

1373-
.. autoclass:: neo4j.Address
1573+
.. autoclass:: neo4j.RoutingControl
13741574
:show-inheritance:
13751575
:members:
13761576

1577+
.. autoclass:: neo4j.Address
1578+
:show-inheritance:
1579+
:members:
13771580

13781581
.. autoclass:: neo4j.IPv4Address()
13791582
:show-inheritance:
13801583

1381-
13821584
.. autoclass:: neo4j.IPv6Address()
13831585
:show-inheritance:
13841586

0 commit comments

Comments
 (0)