Commits

Jason Moiron committed 66db10d

add support for subselect tables in django 1.2 without the requirement of patching django

Comments (0)

Files changed (3)

         for t in map(resolve, tables):
             backend.keyhandler.invalidate_table(t, db)
 
+def get_tables_for_query(query):
+    """Takes a Django 'query' object and returns all tables that will be used in
+    that query as a list.  Note that where clauses can have their own querysets
+    with their own dependent queries, etc."""
+    from django.db.models.sql.where import WhereNode
+    from django.db.models.query import QuerySet
+    tables = list(query.tables)
+    if query.where and query.where.children and isinstance(query.where.children[0], WhereNode):
+        where_node = query.where.children[0]
+        for child in where_node.children:
+            for item in child:
+                if isinstance(item, QuerySet):
+                    tables += get_tables_for_query(item.query)
+    return list(set(tables))
+
 # The KeyGen is used only to generate keys.  Some of these keys will be used
 # directly in the cache, while others are only general purpose functions to
 # generate hashes off of one or more values.
             key, val = None, None
             # check the blacklist for any of the involved tables;  if it's not
             # there, then look for the value in the cache.
-            if cls.query.tables and not blacklist_match(*cls.query.tables):
-                gen_key = self.keyhandler.get_generation(*cls.query.tables, **{'db':db})
+            tables = get_tables_for_query(cls.query)
+            if tables and not blacklist_match(*tables):
+                gen_key = self.keyhandler.get_generation(*tables, **{'db':db})
                 key = self.keyhandler.sql_key(gen_key, sql, params, cls.get_ordering(), result_type, db)
                 val = self.cache_backend.get(key, None, db)
 
             if val is not None:
-                signals.qc_hit.send(sender=cls, tables=cls.query.tables,
+                signals.qc_hit.send(sender=cls, tables=tables,
                         query=(sql, params, cls.query.ordering_aliases),
                         size=len(val), key=key)
                 return val
 
-            signals.qc_miss.send(sender=cls, tables=cls.query.tables,
+            signals.qc_miss.send(sender=cls, tables=tables,
                     query=(sql, params, cls.query.ordering_aliases),
                     key=key)
 

johnny/tests/base.py

     def _hit(self, *a, **k): self.q.put(True)
     def _miss(self, *a, **k): self.q.put(False)
 
+    def clear(self):
+        while not self.q.empty():
+            self.q.get_nowait()
     def get(self): return self.q.get()
     def get_nowait(self): return self.q.get_nowait()
     def qsize(self): return self.q.qsize()

johnny/tests/cache.py

         p1.books.clear()
         self.failUnless(b.authors.all().count() == 0)
 
+    def test_subselect_support(self):
+        """Test that subselects are handled properly."""
+        from django import db
+        db.reset_queries()
+        from testapp.models import Book, Person, PersonType
+        author_types = PersonType.objects.filter(title='Author')
+        author_people = Person.objects.filter(person_types__in=author_types)
+        written_books = Book.objects.filter(authors__in=author_people)
+        q = base.message_queue()
+        self.failUnless(len(db.connection.queries) == 0)
+        count = written_books.count()
+        self.failUnless(q.get() == False)
+        # execute the query again, this time it's cached
+        self.failUnless(written_books.count() == count)
+        self.failUnless(q.get() == True)
+        # change the person type of 'Author' to something else
+        pt = PersonType.objects.get(title='Author')
+        pt.title = 'NonAuthor'
+        pt.save()
+        self.failUnless(PersonType.objects.filter(title='Author').count() == 0)
+        q.clear()
+        db.reset_queries()
+        # now execute the same query;  the result should be diff and it should be
+        # a cache miss
+        new_count = written_books.count()
+        self.failUnless(new_count != count)
+        self.failUnless(q.get() == False)
+
 
 class TransactionSupportTest(TransactionQueryCacheBase):
     fixtures = base.johnny_fixtures