Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Introduce driver.execute_query #833

Merged
merged 17 commits into from
Jan 26, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
208 changes: 205 additions & 3 deletions docs/source/api.rst
Original file line number Diff line number Diff line change
Expand Up @@ -153,7 +153,196 @@ Closing a driver will immediately shut down all connections in the pool.
query, use :meth:`neo4j.Driver.verify_connectivity`.

.. autoclass:: neo4j.Driver()
:members: session, encrypted, close, verify_connectivity, get_server_info
:members: session, query_bookmark_manager, encrypted, close,
verify_connectivity, get_server_info

.. 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)

Execute a query in a transaction function and return all results.

This method is a handy wrapper for lower-level driver APIs like
sessions, transactions, and transaction functions. It is intended
for simple use cases where there is no need for managing all possible
options.

The internal usage of transaction functions provides a retry-mechanism
for appropriate errors. Furthermore, this means that queries using
``CALL {} IN TRANSACTIONS`` or the older ``USING PERIODIC COMMIT``
will not work (use :meth:`Session.run` for these).

The method is roughly equivalent to::

def execute_query(
query_, parameters_, routing_, database_, impersonated_user_,
bookmark_manager_, result_transformer_, **kwargs
):
def work(tx):
result = tx.run(query_, parameters_, **kwargs)
return result_transformer_(result)

with driver.session(
database=database_,
impersonated_user=impersonated_user_,
bookmark_manager=bookmark_manager_,
) as session:
if routing_ == RoutingControl.WRITERS:
return session.execute_write(work)
elif routing_ == RoutingControl.READERS:
return session.execute_read(work)

Usage example::

from typing import List

import neo4j


def example(driver: neo4j.Driver) -> List[str]:
\"""Get the name of all 42 year-olds.\"""
records, summary, keys = driver.execute_query(
"MATCH (p:Person {age: $age}) RETURN p.name",
{"age": 42},
routing_=neo4j.RoutingControl.READERS, # or just "r"
database_="neo4j",
)
assert keys == ["p.name"] # not needed, just for illustration
# log_summary(summary) # log some metadata
return [str(record["p.name"]) for record in records]
# or: return [str(record[0]) for record in records]
# or even: return list(map(lambda r: str(r[0]), records))

Another example::

import neo4j


def example(driver: neo4j.Driver) -> int:
\"""Call all young people "My dear" and get their count.\"""
record = driver.execute_query(
"MATCH (p:Person) WHERE p.age <= $age "
"SET p.nickname = 'My dear' "
"RETURN count(*)",
# optional routing parameter, as write is default
# routing_=neo4j.RoutingControl.WRITERS, # or just "w",
database_="neo4j",
result_transformer_=neo4j.Result.single,
age=15,
)
assert record is not None # for typechecking and illustration
count = record[0]
assert isinstance(count, int)
return count

:param query_: cypher query to execute
:type query_: typing.Optional[str]
:param parameters_: parameters to use in the query
:type parameters_: typing.Optional[typing.Dict[str, typing.Any]]
:param routing_:
whether to route the query to a reader (follower/read replica) or
a writer (leader) in the cluster. Default is to route to a writer.
:type routing_: neo4j.RoutingControl
:param database_:
database to execute the query against.

None (default) uses the database configured on the server side.

.. Note::
It is recommended to always specify the database explicitly
when possible. This allows the driver to work more efficiently,
as it will not have to resolve the default database first.

See also the Session config :ref:`database-ref`.
:type database_: typing.Optional[str]
:param impersonated_user_:
Name of the user to impersonate.

This means that all query will be executed in the security context
of the impersonated user. For this, the user for which the
:class:`Driver` has been created needs to have the appropriate
permissions.

See also the Session config :ref:`impersonated-user-ref`.
:type impersonated_user_: typing.Optional[str]
:param result_transformer_:
A function that gets passed the :class:`neo4j.Result` object
resulting from the query and converts it to a different type. The
result of the transformer function is returned by this method.

.. warning::

The transformer function must **not** return the
:class:`neo4j.Result` itself.

Example transformer that checks that exactly one record is in the
result stream, then returns the record and the result summary::

from typing import Tuple

import neo4j


def transformer(
result: neo4j.Result
) -> Tuple[neo4j.Record, neo4j.ResultSummary]:
record = result.single(strict=True)
summary = result.consume()
return record, summary

Note that methods of :class:`neo4j.Result` that don't take
mandatory arguments can be used directly as transformer functions.
For example::

import neo4j


def example(driver: neo4j.Driver) -> neo4j.Record::
record = driver.execute_query(
"SOME QUERY",
result_transformer_=neo4j.Result.single
)


# is equivalent to:


def transformer(result: neo4j.Result) -> neo4j.Record:
return result.single()


def example(driver: neo4j.Driver) -> neo4j.Record::
record = driver.execute_query(
"SOME QUERY",
result_transformer_=transformer
)

:type result_transformer_:
typing.Callable[[neo4j.Result], typing.Union[T]]
:param bookmark_manager_:
Specify a bookmark manager to use.

If present, the bookmark manager is used to keep the query causally
consistent with all work executed using the same bookmark manager.

Defaults to the driver's :attr:`.query_bookmark_manager`.

Pass :const:`None` to disable causal consistency.
:type bookmark_manager_:
typing.Union[neo4j.BookmarkManager, neo4j.BookmarkManager,
None]
:param kwargs: additional keyword parameters. None of these can end
with a single underscore. This is to avoid collisions with the
keyword configuration parameters of this method. If you need to
pass such a parameter, use the ``parameters_`` parameter instead.
These take precedence over parameters passed as ``parameters_``.
:type kwargs: typing.Any

:returns: the result of the ``result_transformer``
:rtype: T

**This is experimental.** (See :ref:`filter-warnings-ref`)
It might be changed or removed any time even without prior notice.

.. versionadded:: 5.5


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

.. automethod:: to_df

.. automethod:: to_eager_result

.. automethod:: closed

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


***********
EagerResult
***********

.. autoclass:: neo4j.EagerResult
:show-inheritance:
:members:


Graph
=====

Expand Down Expand Up @@ -1370,15 +1570,17 @@ BookmarkManager
Constants, Enums, Helpers
*************************

.. autoclass:: neo4j.Address
.. autoclass:: neo4j.RoutingControl
:show-inheritance:
:members:

.. autoclass:: neo4j.Address
:show-inheritance:
:members:

.. autoclass:: neo4j.IPv4Address()
:show-inheritance:


.. autoclass:: neo4j.IPv6Address()
:show-inheritance:

Expand Down
Loading