Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Don't return the wrong cached value if parameters happen to be prefixes #50

Open
wants to merge 2 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
30 changes: 25 additions & 5 deletions johnny/cache.py
Original file line number Diff line number Diff line change
Expand Up @@ -176,24 +176,44 @@ def gen_multi_key(self, values, db='default'):
db = db[0:68] + self.gen_key(db[68:])
return '%s_%s_multi_%s' % (self.prefix, db, self.gen_key(*values))

@staticmethod
def _add_separator(iterable, separator):
"""Given an iterable, return a generator where every value is
separated by the separator given.

>>> list(KeyGen._add_separator([1, 2, 3], 0))
[1, 0, 2, 0, 3]

"""
it = iter(iterable)
yield next(it)
for item in it:
yield separator
yield item

@staticmethod
def _convert(x):
if isinstance(x, unicode):
return x.encode('utf-8')
return str(x)

@staticmethod
def _recursive_convert(x, key):
for item in x:
def _flatten(nested_list):
"""Return a generator where nested lists or tuples are flattened."""
for item in nested_list:
if isinstance(item, (tuple, list)):
KeyGen._recursive_convert(item, key)
for subitem in KeyGen._flatten(item):
yield subitem
else:
key.update(KeyGen._convert(item))
yield item

def gen_key(self, *values):
"""Generate a key from one or more values."""
key = md5()
KeyGen._recursive_convert(values, key)

for item in KeyGen._add_separator(KeyGen._flatten(values), "S"):
key.update(KeyGen._convert(item))

return key.hexdigest()


Expand Down
21 changes: 20 additions & 1 deletion johnny/tests/cache.py
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,9 @@ def any(iterable):
return False

# put tests in here to be included in the testing suite
__all__ = ['MultiDbTest', 'SingleModelTest', 'MultiModelTest', 'TransactionSupportTest', 'BlackListTest', 'TransactionManagerTestCase']
__all__ = ['MultiDbTest', 'SingleModelTest', 'MultiModelTest',
'TransactionSupportTest', 'BlackListTest',
'TransactionManagerTestCase', 'PrefixParamsTestCase']

def _pre_setup(self):
self.saved_DISABLE_SETTING = getattr(johnny_settings, 'DISABLE_QUERYSET_CACHE', False)
Expand Down Expand Up @@ -961,3 +963,20 @@ def test_savepoint_localstore_flush(self):
tm._commit_all_savepoints()
# And this checks if it actually happened.
self.failUnless(table_key in tm.local)


class PrefixParamsTestCase(QueryCacheBase):
def test_params_prefixes(self):
"""Ensure we don't return incorrect results when queryset parameters
happen to concatenate to the correct value.

"""
from testapp.models import User

User.objects.create(first_name="foo", last_name="bar")
User.objects.create(first_name="foob", last_name="ar")

u1 = User.objects.get(first_name="foo", last_name="bar")
u2 = User.objects.get(first_name="foob", last_name="ar")

self.assertNotEqual(u1, u2)