1. ========================
    
  2. Database instrumentation
    
  3. ========================
    
  4. 
    
  5. To help you understand and control the queries issued by your code, Django
    
  6. provides a hook for installing wrapper functions around the execution of
    
  7. database queries. For example, wrappers can count queries, measure query
    
  8. duration, log queries, or even prevent query execution (e.g. to make sure that
    
  9. no queries are issued while rendering a template with prefetched data).
    
  10. 
    
  11. The wrappers are modeled after :doc:`middleware </topics/http/middleware>` --
    
  12. they are callables which take another callable as one of their arguments. They
    
  13. call that callable to invoke the (possibly wrapped) database query, and they
    
  14. can do what they want around that call. They are, however, created and
    
  15. installed by user code, and so don't need a separate factory like middleware do.
    
  16. 
    
  17. Installing a wrapper is done in a context manager -- so the wrappers are
    
  18. temporary and specific to some flow in your code.
    
  19. 
    
  20. As mentioned above, an example of a wrapper is a query execution blocker. It
    
  21. could look like this::
    
  22. 
    
  23.     def blocker(*args):
    
  24.         raise Exception('No database access allowed here.')
    
  25. 
    
  26. And it would be used in a view to block queries from the template like so::
    
  27. 
    
  28.     from django.db import connection
    
  29.     from django.shortcuts import render
    
  30. 
    
  31.     def my_view(request):
    
  32.         context = {...}  # Code to generate context with all data.
    
  33.         template_name = ...
    
  34.         with connection.execute_wrapper(blocker):
    
  35.             return render(request, template_name, context)
    
  36. 
    
  37. The parameters sent to the wrappers are:
    
  38. 
    
  39. * ``execute`` -- a callable, which should be invoked with the rest of the
    
  40.   parameters in order to execute the query.
    
  41. 
    
  42. * ``sql`` -- a ``str``, the SQL query to be sent to the database.
    
  43. 
    
  44. * ``params`` -- a list/tuple of parameter values for the SQL command, or a
    
  45.   list/tuple of lists/tuples if the wrapped call is ``executemany()``.
    
  46. 
    
  47. * ``many`` -- a ``bool`` indicating whether the ultimately invoked call is
    
  48.   ``execute()`` or ``executemany()`` (and whether ``params`` is expected to be
    
  49.   a sequence of values, or a sequence of sequences of values).
    
  50. 
    
  51. * ``context`` -- a dictionary with further data about the context of
    
  52.   invocation. This includes the connection and cursor.
    
  53. 
    
  54. Using the parameters, a slightly more complex version of the blocker could
    
  55. include the connection name in the error message::
    
  56. 
    
  57.     def blocker(execute, sql, params, many, context):
    
  58.         alias = context['connection'].alias
    
  59.         raise Exception("Access to database '{}' blocked here".format(alias))
    
  60. 
    
  61. For a more complete example, a query logger could look like this::
    
  62. 
    
  63.     import time
    
  64. 
    
  65.     class QueryLogger:
    
  66. 
    
  67.         def __init__(self):
    
  68.             self.queries = []
    
  69. 
    
  70.         def __call__(self, execute, sql, params, many, context):
    
  71.             current_query = {'sql': sql, 'params': params, 'many': many}
    
  72.             start = time.monotonic()
    
  73.             try:
    
  74.                 result = execute(sql, params, many, context)
    
  75.             except Exception as e:
    
  76.                 current_query['status'] = 'error'
    
  77.                 current_query['exception'] = e
    
  78.                 raise
    
  79.             else:
    
  80.                 current_query['status'] = 'ok'
    
  81.                 return result
    
  82.             finally:
    
  83.                 duration = time.monotonic() - start
    
  84.                 current_query['duration'] = duration
    
  85.                 self.queries.append(current_query)
    
  86. 
    
  87. To use this, you would create a logger object and install it as a wrapper::
    
  88. 
    
  89.     from django.db import connection
    
  90. 
    
  91.     ql = QueryLogger()
    
  92.     with connection.execute_wrapper(ql):
    
  93.         do_queries()
    
  94.     # Now we can print the log.
    
  95.     print(ql.queries)
    
  96. 
    
  97. .. currentmodule:: django.db.backends.base.DatabaseWrapper
    
  98. 
    
  99. ``connection.execute_wrapper()``
    
  100. --------------------------------
    
  101. 
    
  102. .. method:: execute_wrapper(wrapper)
    
  103. 
    
  104. Returns a context manager which, when entered, installs a wrapper around
    
  105. database query executions, and when exited, removes the wrapper. The wrapper is
    
  106. installed on the thread-local connection object.
    
  107. 
    
  108. ``wrapper`` is a callable taking five arguments.  It is called for every query
    
  109. execution in the scope of the context manager, with arguments ``execute``,
    
  110. ``sql``, ``params``, ``many``, and ``context`` as described above. It's
    
  111. expected to call ``execute(sql, params, many, context)`` and return the return
    
  112. value of that call.