Permalink
Browse files

caching for queryset.count()

  • Loading branch information...
jbalogh committed Feb 18, 2010
1 parent d4c17ff commit c1e871f4d7360e759596d9bb620a48016651c301
Showing with 51 additions and 5 deletions.
  1. +14 −2 caching/base.py
  2. +9 −2 docs/index.rst
  3. +1 −1 fabfile.py
  4. +1 −0 requirements.txt
  5. +26 −0 tests/test_cache.py
View
@@ -143,15 +143,27 @@ def add_to_flush_list(flush_keys, new_key):
class CachingQuerySet(models.query.QuerySet):
def query_key(self):
sql, params = self.query.get_compiler(using=self.db).as_sql()
return sql % params
def iterator(self):
# Work-around for Django #12717.
sql, params = self.query.get_compiler(using=self.db).as_sql()
query_string = sql % params
query_string = self.query_key()
iterator = super(CachingQuerySet, self).iterator
for obj in CacheMachine(query_string, iterator):
yield obj
raise StopIteration
def count(self):
timeout = getattr(settings, 'CACHE_COUNT_TIMEOUT', None)
super_count = super(CachingQuerySet, self).count
query_string = 'count:%s' % self.query_key()
if timeout is None:
return super_count()
else:
return cached(super_count, query_string, timeout)
def flush_key(obj):
"""We put flush lists in the flush: namespace."""
View
@@ -36,6 +36,14 @@ If you want to set a prefix for all keys in Cache Machine, define
CACHE_PREFIX = 'weee:'
Calls to ``QuerySet.count()`` can be cached, but they cannot be reliably
invalidated. Cache Machine would have to do a full select to figure out the
object keys, which is probably much more data than you want to pull. I
recommend a short cache timeout; long enough to avoid repetitive queries, but
short enough that stale counts won't be a big deal. ::
CACHE_COUNT_TIMEOUT = 60 # seconds, not too long.
Cache Manager
-------------
@@ -71,8 +79,7 @@ once iteration is done.
Caching is supported for normal :class:`QuerySets <django.db.models.QuerySet>` and
for :meth:`django.db.models.Manager.raw`. At this time, caching has not been
implemented for ``QuerySet.count``, ``QuerySet.values``, or
``QuerySet.values_list``.
implemented for ``QuerySet.values`` or ``QuerySet.values_list``.
To support easy cache invalidation, we use "flush lists" to mark the cached
queries an object belongs to. That way, all queries where an object was found
View
@@ -28,7 +28,7 @@ def doc(kind='html'):
def test():
local('django-admin.py test')
local('django-admin.py test -s')
def updoc():
View
@@ -1,5 +1,6 @@
# These are the reqs to build docs and run tests.
sphinx
mock
-e git://github.com/jbalogh/django-nose.git@6f060d49ee193a05734704820f3fea92ee1759d2#egg=django-nose
-e svn+http://code.djangoproject.com/svn/django/trunk@12335#egg=Django
python-memcached
View
@@ -1,5 +1,7 @@
from django.conf import settings
from django.core.cache import cache
import mock
from nose.tools import eq_
from test_utils import ExtraAppTestCase
@@ -14,6 +16,10 @@ class CachingTestCase(ExtraAppTestCase):
def setUp(self):
cache.clear()
self.old_timeout = getattr(settings, 'CACHE_COUNT_TIMEOUT', None)
def tearDown(self):
settings.CACHE_COUNT_TIMEOUT = self.old_timeout
def test_flush_key(self):
"""flush_key should work for objects or strings."""
@@ -97,3 +103,23 @@ def test_raw_cache_params(self):
raw2 = list(Addon.objects.raw(sql, [2]))[0]
eq_(raw2.id, 2)
@mock.patch('caching.base.cache')
def test_count_cache(self, cache_mock):
settings.CACHE_COUNT_TIMEOUT = 60
cache_mock.scheme = 'memcached'
cache_mock.get.return_value = None
q = Addon.objects.all()
count = q.count()
args, kwargs = cache_mock.set.call_args
key, value, timeout = args
eq_(value, 2)
eq_(timeout, 60)
@mock.patch('caching.base.cached')
def test_count_none_timeout(self, cached_mock):
settings.CACHE_COUNT_TIMEOUT = None
Addon.objects.count()
eq_(cached_mock.call_count, 0)

0 comments on commit c1e871f

Please sign in to comment.