diff options
| author | Shai Berger <shai@platonix.com> | 2017-09-21 19:13:09 +0300 |
|---|---|---|
| committer | Tim Graham <timograham@gmail.com> | 2017-09-21 12:13:09 -0400 |
| commit | d612026c37f85769322a44c0284ca2d2be6b6e5b (patch) | |
| tree | 3b8e13157e72dba25878837d055706f135181ba1 /docs | |
| parent | 347551c2a1bc78b3a5886be6adde0c8151001acc (diff) | |
Refs #28595 -- Added a hook to add execute wrappers for database queries.
Thanks Adam Johnson, Carl Meyer, Anssi Kääriäinen, Mariusz Felisiak,
Michael Manfre, and Tim Graham for discussion and review.
Diffstat (limited to 'docs')
| -rw-r--r-- | docs/releases/2.0.txt | 5 | ||||
| -rw-r--r-- | docs/topics/db/index.txt | 1 | ||||
| -rw-r--r-- | docs/topics/db/instrumentation.txt | 114 |
3 files changed, 120 insertions, 0 deletions
diff --git a/docs/releases/2.0.txt b/docs/releases/2.0.txt index 71d41b664c..77eee0c206 100644 --- a/docs/releases/2.0.txt +++ b/docs/releases/2.0.txt @@ -339,6 +339,11 @@ Models parameters, if the backend supports this feature. Of Django's built-in backends, only Oracle supports it. +* The new :meth:`connection.execute_wrapper() + <django.db.backends.base.DatabaseWrapper.execute_wrapper>` method allows + :doc:`installing wrappers around execution of database queries + </topics/db/instrumentation>`. + * The new ``filter`` argument for built-in aggregates allows :ref:`adding different conditionals <conditional-aggregation>` to multiple aggregations over the same fields or relations. diff --git a/docs/topics/db/index.txt b/docs/topics/db/index.txt index 51f60a65d7..032ec30bb5 100644 --- a/docs/topics/db/index.txt +++ b/docs/topics/db/index.txt @@ -21,4 +21,5 @@ model maps to a single database table. multi-db tablespaces optimization + instrumentation examples/index diff --git a/docs/topics/db/instrumentation.txt b/docs/topics/db/instrumentation.txt new file mode 100644 index 0000000000..9578c1224b --- /dev/null +++ b/docs/topics/db/instrumentation.txt @@ -0,0 +1,114 @@ +======================== +Database instrumentation +======================== + +.. versionadded:: 2.0 + +To help you understand and control the queries issued by your code, Django +provides a hook for installing wrapper functions around the execution of +database queries. For example, wrappers can count queries, measure query +duration, log queries, or even prevent query execution (e.g. to make sure that +no queries are issued while rendering a template with prefetched data). + +The wrappers are modeled after :doc:`middleware </topics/http/middleware>` -- +they are callables which take another callable as one of their arguments. They +call that callable to invoke the (possibly wrapped) database query, and they +can do what they want around that call. They are, however, created and +installed by user code, and so don't need a separate factory like middleware do. + +Installing a wrapper is done in a context manager -- so the wrappers are +temporary and specific to some flow in your code. + +As mentioned above, an example of a wrapper is a query execution blocker. It +could look like this:: + + def blocker(*args): + raise Exception('No database access allowed here.') + +And it would be used in a view to block queries from the template like so:: + + from django.db import connection + from django.shortcuts import render + + def my_view(request): + context = {...} # Code to generate context with all data. + template_name = ... + with connection.execute_wrapper(blocker): + return render(request, template_name, context) + +The parameters sent to the wrappers are: + +* ``execute`` -- a callable, which should be invoked with the rest of the + parameters in order to execute the query. + +* ``sql`` -- a ``str``, the SQL query to be sent to the database. + +* ``params`` -- a list/tuple of parameter values for the SQL command, or a + list/tuple of lists/tuples if the wrapped call is ``executemany()``. + +* ``many`` -- a ``bool`` indicating whether the ultimately invoked call is + ``execute()`` or ``executemany()`` (and whether ``params`` is expected to be + a sequence of values, or a sequence of sequences of values). + +* ``context`` -- a dictionary with further data about the context of + invocation. This includes the connection and cursor. + +Using the parameters, a slightly more complex version of the blocker could +include the connection name in the error message:: + + def blocker(execute, sql, params, many, context): + alias = context['connection'].alias + raise Exception("Access to database '{}' blocked here".format(alias)) + +For a more complete example, a query logger could look like this:: + + import time + + class QueryLogger: + + def __init__(self): + self.queries = [] + + def __call__(self, execute, sql, params, many, context): + current_query = {'sql': sql, 'params': params, 'many': many} + start = time.time() + try: + result = execute(sql, params, many, context) + except Exception as e: + current_query['status'] = 'error' + current_query['exception'] = e + raise + else: + current_query['status'] = 'ok' + return result + finally: + duration = time.time() - start + current_query['duration'] = duration + self.queries.append(current_query) + +To use this, you would create a logger object and install it as a wrapper:: + + from django.db import connection + + ql = QueryLogger() + with connection.execute_wrapper(ql): + do_queries() + # Now we can print the log. + print(ql.queries) + +.. currentmodule:: django.db.backends.base.DatabaseWrapper + +``connection.execute_wrapper()`` +-------------------------------- + +.. method:: execute_wrapper(wrapper) + +Returns a context manager which, when entered, installs a wrapper around +database query executions, and when exited, removes the wrapper. The wrapper is +installed on the thread-local connection object. + +``wrapper`` is a callable taking five arguments. It is called for every query +execution in the scope of the context manager, with arguments ``execute``, +``sql``, ``params``, ``many``, and ``context`` as described above. It's +expected to call ``execute(sql, params, many, context)`` and return the return +value of that call. |
