Code

Ticket #5415: db_signal_r6979.patch

File db_signal_r6979.patch, 7.7 KB (added by gav, 6 years ago)

Changes to DB signals against r6979 in response to Malcolm's requests.

  • django/db/__init__.py

     
    11import os 
    22from django.conf import settings 
    3 from django.core import signals 
     3from django.core import signals as core_signals 
    44from django.core.exceptions import ImproperlyConfigured 
    55from django.dispatch import dispatcher 
    66from django.utils.functional import curry 
     
    5454 
    5555# Register an event that closes the database connection 
    5656# when a Django request is finished. 
    57 dispatcher.connect(connection.close, signal=signals.request_finished) 
     57dispatcher.connect(connection.close, signal=core_signals.request_finished) 
    5858 
    5959# Register an event that resets connection.queries 
    6060# when a Django request is started. 
    6161def reset_queries(): 
    6262    connection.queries = [] 
    63 dispatcher.connect(reset_queries, signal=signals.request_started) 
     63dispatcher.connect(reset_queries, signal=core_signals.request_started) 
    6464 
    6565# Register an event that rolls back the connection 
    6666# when a Django request has an exception. 
    6767def _rollback_on_exception(): 
    6868    from django.db import transaction 
    6969    transaction.rollback_unless_managed() 
    70 dispatcher.connect(_rollback_on_exception, signal=signals.got_request_exception) 
     70dispatcher.connect(_rollback_on_exception, signal=core_signals.got_request_exception) 
  • django/db/signals.py

     
     1query_execute = object() 
  • django/db/backends/util.py

     
    11import datetime 
     2from django.db import signals 
     3from django.dispatch import dispatcher 
    24import md5 
    35from time import time 
    46 
     
    4143        else: 
    4244            return getattr(self.cursor, attr) 
    4345 
     46    def __iter__(self): 
     47        return iter(self.cursor) 
     48 
     49 
     50class CursorSignalWrapper(object): 
     51    def __init__(self, cursor, db): 
     52        self.cursor = cursor 
     53        self.db = db # Instance of a BaseDatabaseWrapper subclass 
     54 
     55    def execute(self, sql, params=()): 
     56        start = time() 
     57        try: 
     58            return self.cursor.execute(sql, params) 
     59        finally: 
     60            stop = time() 
     61            dispatcher.send(signal=signals.query_execute, sender=self, sql=sql, params=params, time=stop-start) 
     62 
     63    def executemany(self, sql, param_list): 
     64        start = time() 
     65        try: 
     66            return self.cursor.executemany(sql, param_list) 
     67        finally: 
     68            stop = time() 
     69            # XXX: Should this be a special signal that only gets called once for efficiency? 
     70            for params in param_list: 
     71                dispatcher.send(signal=signals.query_execute, sender=self, sql=sql, params=params, time=stop-start) 
     72 
     73    def __getattr__(self, attr): 
     74        if attr in self.__dict__: 
     75            return self.__dict__[attr] 
     76        else: 
     77            return getattr(self.cursor, attr) 
     78 
     79    def __iter__(self): 
     80        return iter(self.cursor) 
     81 
     82 
    4483############################################### 
    4584# Converters from database (string) to Python # 
    4685############################################### 
  • django/db/backends/__init__.py

     
    1414        self.connection = None 
    1515        self.queries = [] 
    1616        self.options = kwargs 
     17        self.use_signal_cursor = None 
    1718 
    1819    def _commit(self): 
    1920        if self.connection is not None: 
     
    3233        from django.conf import settings 
    3334        cursor = self._cursor(settings) 
    3435        if settings.DEBUG: 
    35             return self.make_debug_cursor(cursor) 
     36            return self.make_signal_cursor(self.make_debug_cursor(cursor)) 
     37        elif self.use_signal_cursor: 
     38            return self.make_signal_cursor(cursor) 
    3639        return cursor 
    3740 
    3841    def make_debug_cursor(self, cursor): 
    3942        from django.db.backends import util 
    4043        return util.CursorDebugWrapper(cursor, self) 
     44     
     45    def make_signal_cursor(self, cursor): 
     46        from django.db.backends import util 
     47        return util.CursorSignalWrapper(cursor, self) 
    4148 
     49 
    4250class BaseDatabaseFeatures(object): 
    4351    allows_group_by_ordinal = True 
    4452    allows_unique_and_pk = True 
  • tests/modeltests/select_related/models.py

     
    77the select-related behavior will traverse. 
    88""" 
    99 
    10 from django.db import models 
     10from django.db import models, signals 
     11from django.dispatch import dispatcher 
    1112 
    1213# Who remembers high school biology? 
    1314 
     
    7576        obj.save() 
    7677        parent = obj 
    7778 
     79class QueryCounter(object): 
     80    counter = 0 
     81 
     82    @staticmethod 
     83    def add(): 
     84        QueryCounter.counter += 1 
     85         
     86    @staticmethod 
     87    def clear(): 
     88        QueryCounter.counter = 0 
     89 
     90 
     91def track_query(sender, *args, **kwargs): 
     92    QueryCounter.add() 
     93dispatcher.connect(track_query, signal=signals.query_execute, weak=False) 
     94 
    7895__test__ = {'API_TESTS':""" 
    7996 
    8097# Set up. 
     
    92109 
    93110# Normally, accessing FKs doesn't fill in related objects: 
    94111>>> db.reset_queries() 
     112>>> QueryCounter.clear() 
    95113>>> fly = Species.objects.get(name="melanogaster") 
    96114>>> fly.genus.family.order.klass.phylum.kingdom.domain 
    97115<Domain: Eukaryota> 
    98116>>> len(db.connection.queries) 
    991178 
     118>>> QueryCounter.counter 
     1198 
    100120 
    101121# However, a select_related() call will fill in those related objects without any extra queries: 
    102122>>> db.reset_queries() 
     123>>> QueryCounter.clear() 
    103124>>> person = Species.objects.select_related().get(name="sapiens") 
    104125>>> person.genus.family.order.klass.phylum.kingdom.domain 
    105126<Domain: Eukaryota> 
    106127>>> len(db.connection.queries) 
    1071281 
     129>>> QueryCounter.counter 
     1301 
    108131 
    109132# select_related() also of course applies to entire lists, not just items. 
    110133# Without select_related() 
    111134>>> db.reset_queries() 
     135>>> QueryCounter.clear() 
    112136>>> world = Species.objects.all() 
    113137>>> [o.genus.family for o in world] 
    114138[<Family: Drosophilidae>, <Family: Hominidae>, <Family: Fabaceae>, <Family: Amanitacae>] 
    115139>>> len(db.connection.queries) 
    1161409 
     141>>> QueryCounter.counter 
     1429 
    117143 
    118144# With select_related(): 
    119145>>> db.reset_queries() 
     146>>> QueryCounter.clear() 
    120147>>> world = Species.objects.all().select_related() 
    121148>>> [o.genus.family for o in world] 
    122149[<Family: Drosophilidae>, <Family: Hominidae>, <Family: Fabaceae>, <Family: Amanitacae>] 
    123150>>> len(db.connection.queries) 
    1241511 
     152>>> QueryCounter.counter 
     1531 
    125154 
    126155# The "depth" argument to select_related() will stop the descent at a particular level: 
    127156>>> db.reset_queries() 
     157>>> QueryCounter.clear() 
    128158>>> pea = Species.objects.select_related(depth=1).get(name="sativum") 
    129159>>> pea.genus.family.order.klass.phylum.kingdom.domain 
    130160<Domain: Eukaryota> 
     
    132162# Notice: one few query than above because of depth=1 
    133163>>> len(db.connection.queries) 
    1341647 
     165>>> QueryCounter.counter 
     1667 
    135167 
    136168>>> db.reset_queries() 
     169>>> QueryCounter.clear() 
    137170>>> pea = Species.objects.select_related(depth=5).get(name="sativum") 
    138171>>> pea.genus.family.order.klass.phylum.kingdom.domain 
    139172<Domain: Eukaryota> 
    140173>>> len(db.connection.queries) 
    1411743 
     175>>> QueryCounter.counter 
     1763 
    142177 
    143178>>> db.reset_queries() 
     179>>> QueryCounter.clear() 
    144180>>> world = Species.objects.all().select_related(depth=2) 
    145181>>> [o.genus.family.order for o in world] 
    146182[<Order: Diptera>, <Order: Primates>, <Order: Fabales>, <Order: Agaricales>] 
    147183>>> len(db.connection.queries) 
    1481845 
     185>>> QueryCounter.counter 
     1865 
    149187 
    150188# Reset DEBUG to where we found it. 
    151189>>> settings.DEBUG = False