|
1 "Database cache backend." |
|
2 |
|
3 from django.core.cache.backends.base import BaseCache |
|
4 from django.db import connection, transaction, DatabaseError |
|
5 import base64, time |
|
6 from datetime import datetime |
|
7 try: |
|
8 import cPickle as pickle |
|
9 except ImportError: |
|
10 import pickle |
|
11 |
|
12 class CacheClass(BaseCache): |
|
13 def __init__(self, table, params): |
|
14 BaseCache.__init__(self, params) |
|
15 self._table = table |
|
16 max_entries = params.get('max_entries', 300) |
|
17 try: |
|
18 self._max_entries = int(max_entries) |
|
19 except (ValueError, TypeError): |
|
20 self._max_entries = 300 |
|
21 cull_frequency = params.get('cull_frequency', 3) |
|
22 try: |
|
23 self._cull_frequency = int(cull_frequency) |
|
24 except (ValueError, TypeError): |
|
25 self._cull_frequency = 3 |
|
26 |
|
27 def get(self, key, default=None): |
|
28 cursor = connection.cursor() |
|
29 cursor.execute("SELECT cache_key, value, expires FROM %s WHERE cache_key = %%s" % self._table, [key]) |
|
30 row = cursor.fetchone() |
|
31 if row is None: |
|
32 return default |
|
33 now = datetime.now() |
|
34 if row[2] < now: |
|
35 cursor.execute("DELETE FROM %s WHERE cache_key = %%s" % self._table, [key]) |
|
36 transaction.commit_unless_managed() |
|
37 return default |
|
38 return pickle.loads(base64.decodestring(row[1])) |
|
39 |
|
40 def set(self, key, value, timeout=None): |
|
41 return self._base_set('set', key, value, timeout) |
|
42 |
|
43 def add(self, key, value, timeout=None): |
|
44 return self._base_set('add', key, value, timeout) |
|
45 |
|
46 def _base_set(self, mode, key, value, timeout=None): |
|
47 if timeout is None: |
|
48 timeout = self.default_timeout |
|
49 cursor = connection.cursor() |
|
50 cursor.execute("SELECT COUNT(*) FROM %s" % self._table) |
|
51 num = cursor.fetchone()[0] |
|
52 now = datetime.now().replace(microsecond=0) |
|
53 exp = datetime.fromtimestamp(time.time() + timeout).replace(microsecond=0) |
|
54 if num > self._max_entries: |
|
55 self._cull(cursor, now) |
|
56 encoded = base64.encodestring(pickle.dumps(value, 2)).strip() |
|
57 cursor.execute("SELECT cache_key FROM %s WHERE cache_key = %%s" % self._table, [key]) |
|
58 try: |
|
59 if mode == 'set' and cursor.fetchone(): |
|
60 cursor.execute("UPDATE %s SET value = %%s, expires = %%s WHERE cache_key = %%s" % self._table, [encoded, str(exp), key]) |
|
61 else: |
|
62 cursor.execute("INSERT INTO %s (cache_key, value, expires) VALUES (%%s, %%s, %%s)" % self._table, [key, encoded, str(exp)]) |
|
63 except DatabaseError: |
|
64 # To be threadsafe, updates/inserts are allowed to fail silently |
|
65 pass |
|
66 else: |
|
67 transaction.commit_unless_managed() |
|
68 |
|
69 def delete(self, key): |
|
70 cursor = connection.cursor() |
|
71 cursor.execute("DELETE FROM %s WHERE cache_key = %%s" % self._table, [key]) |
|
72 transaction.commit_unless_managed() |
|
73 |
|
74 def has_key(self, key): |
|
75 cursor = connection.cursor() |
|
76 cursor.execute("SELECT cache_key FROM %s WHERE cache_key = %%s" % self._table, [key]) |
|
77 return cursor.fetchone() is not None |
|
78 |
|
79 def _cull(self, cursor, now): |
|
80 if self._cull_frequency == 0: |
|
81 cursor.execute("DELETE FROM %s" % self._table) |
|
82 else: |
|
83 cursor.execute("DELETE FROM %s WHERE expires < %%s" % self._table, [str(now)]) |
|
84 cursor.execute("SELECT COUNT(*) FROM %s" % self._table) |
|
85 num = cursor.fetchone()[0] |
|
86 if num > self._max_entries: |
|
87 cursor.execute("SELECT cache_key FROM %s ORDER BY cache_key LIMIT 1 OFFSET %%s" % self._table, [num / self._cull_frequency]) |
|
88 cursor.execute("DELETE FROM %s WHERE cache_key < %%s" % self._table, [cursor.fetchone()[0]]) |