2016-07-21 21:59:11 +00:00
|
|
|
# coding: utf-8
|
2014-09-26 14:53:44 +00:00
|
|
|
|
|
|
|
|
from __future__ import unicode_literals
|
2014-10-19 18:15:57 +00:00
|
|
|
from collections import Iterable
|
2014-11-24 11:05:00 +00:00
|
|
|
from time import time
|
2014-09-27 23:44:43 +00:00
|
|
|
|
2015-04-10 23:57:13 +00:00
|
|
|
from django.db.backends.utils import CursorWrapper
|
2017-06-03 20:17:22 +00:00
|
|
|
from django.db.models.query import EmptyResultSet
|
2015-04-10 23:57:13 +00:00
|
|
|
from django.db.models.signals import post_migrate
|
2014-09-26 14:53:44 +00:00
|
|
|
from django.db.models.sql.compiler import (
|
2016-09-29 17:11:21 +00:00
|
|
|
SQLCompiler, SQLInsertCompiler, SQLUpdateCompiler, SQLDeleteCompiler,
|
|
|
|
|
)
|
2014-10-20 21:43:10 +00:00
|
|
|
from django.db.transaction import Atomic, get_connection
|
2017-06-07 08:17:58 +00:00
|
|
|
from django.utils.six import binary_type, wraps
|
2014-10-04 23:25:48 +00:00
|
|
|
|
2015-10-05 20:31:47 +00:00
|
|
|
from .api import invalidate
|
2014-10-19 18:15:57 +00:00
|
|
|
from .cache import cachalot_caches
|
2017-06-04 16:37:39 +00:00
|
|
|
from .settings import cachalot_settings, ITERABLES
|
2014-10-19 18:15:57 +00:00
|
|
|
from .utils import (
|
2017-06-04 16:37:39 +00:00
|
|
|
_get_table_cache_keys, _get_tables_from_sql,
|
|
|
|
|
UncachableQuery, is_cachable, filter_cachable,
|
2016-09-29 17:11:21 +00:00
|
|
|
)
|
2014-09-26 14:53:44 +00:00
|
|
|
|
2016-07-21 21:59:11 +00:00
|
|
|
|
2014-09-26 14:53:44 +00:00
|
|
|
WRITE_COMPILERS = (SQLInsertCompiler, SQLUpdateCompiler, SQLDeleteCompiler)
|
|
|
|
|
|
|
|
|
|
|
2014-11-04 00:17:35 +00:00
|
|
|
def _unset_raw_connection(original):
|
|
|
|
|
def inner(compiler, *args, **kwargs):
|
|
|
|
|
compiler.connection.raw = False
|
2018-05-04 14:24:04 +00:00
|
|
|
try:
|
|
|
|
|
return original(compiler, *args, **kwargs)
|
|
|
|
|
finally:
|
|
|
|
|
compiler.connection.raw = True
|
2014-11-04 00:17:35 +00:00
|
|
|
return inner
|
2014-09-26 14:53:44 +00:00
|
|
|
|
|
|
|
|
|
2015-10-26 01:42:13 +00:00
|
|
|
def _get_result_or_execute_query(execute_query_func, cache,
|
|
|
|
|
cache_key, table_cache_keys):
|
2014-12-07 06:04:26 +00:00
|
|
|
data = cache.get_many(table_cache_keys + [cache_key])
|
|
|
|
|
|
2015-01-03 05:38:16 +00:00
|
|
|
new_table_cache_keys = set(table_cache_keys)
|
|
|
|
|
new_table_cache_keys.difference_update(data)
|
2014-12-07 06:04:26 +00:00
|
|
|
|
2018-07-27 15:07:46 +00:00
|
|
|
if not new_table_cache_keys:
|
|
|
|
|
try:
|
|
|
|
|
timestamp, result = data.pop(cache_key)
|
|
|
|
|
if timestamp >= max(data.values()):
|
|
|
|
|
return result
|
|
|
|
|
except (KeyError, TypeError, ValueError):
|
|
|
|
|
# In case `cache_key` is not in `data` or contains bad data,
|
|
|
|
|
# we simply run the query and cache again the results.
|
|
|
|
|
pass
|
2014-12-07 06:04:26 +00:00
|
|
|
|
|
|
|
|
result = execute_query_func()
|
2017-06-04 16:37:39 +00:00
|
|
|
if result.__class__ not in ITERABLES and isinstance(result, Iterable):
|
2014-12-07 06:04:26 +00:00
|
|
|
result = list(result)
|
|
|
|
|
|
2017-06-07 06:59:35 +00:00
|
|
|
now = time()
|
|
|
|
|
to_be_set = {k: now for k in new_table_cache_keys}
|
|
|
|
|
to_be_set[cache_key] = (now, result)
|
|
|
|
|
cache.set_many(to_be_set, cachalot_settings.CACHALOT_TIMEOUT)
|
2014-12-07 06:04:26 +00:00
|
|
|
|
|
|
|
|
return result
|
|
|
|
|
|
|
|
|
|
|
2014-11-04 00:17:35 +00:00
|
|
|
def _patch_compiler(original):
|
|
|
|
|
@wraps(original)
|
|
|
|
|
@_unset_raw_connection
|
|
|
|
|
def inner(compiler, *args, **kwargs):
|
2014-12-07 06:04:26 +00:00
|
|
|
execute_query_func = lambda: original(compiler, *args, **kwargs)
|
2017-06-04 16:37:39 +00:00
|
|
|
db_alias = compiler.using
|
2017-06-07 06:59:35 +00:00
|
|
|
if db_alias not in cachalot_settings.CACHALOT_DATABASES \
|
2015-06-19 16:06:20 +00:00
|
|
|
or isinstance(compiler, WRITE_COMPILERS):
|
2014-12-07 06:04:26 +00:00
|
|
|
return execute_query_func()
|
2014-11-04 00:17:35 +00:00
|
|
|
|
|
|
|
|
try:
|
2017-06-04 16:37:39 +00:00
|
|
|
cache_key = cachalot_settings.CACHALOT_QUERY_KEYGEN(compiler)
|
2015-05-24 17:29:50 +00:00
|
|
|
table_cache_keys = _get_table_cache_keys(compiler)
|
2015-06-17 19:24:49 +00:00
|
|
|
except (EmptyResultSet, UncachableQuery):
|
2014-12-07 06:04:26 +00:00
|
|
|
return execute_query_func()
|
2014-11-04 00:17:35 +00:00
|
|
|
|
2014-12-07 06:04:26 +00:00
|
|
|
return _get_result_or_execute_query(
|
2015-10-26 01:42:13 +00:00
|
|
|
execute_query_func,
|
2017-06-04 16:37:39 +00:00
|
|
|
cachalot_caches.get_cache(db_alias=db_alias),
|
2015-10-26 01:42:13 +00:00
|
|
|
cache_key, table_cache_keys)
|
2014-11-04 00:17:35 +00:00
|
|
|
|
|
|
|
|
return inner
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def _patch_write_compiler(original):
|
|
|
|
|
@wraps(original)
|
2015-10-28 10:37:58 +00:00
|
|
|
@_unset_raw_connection
|
2015-05-24 17:34:24 +00:00
|
|
|
def inner(write_compiler, *args, **kwargs):
|
2015-10-26 01:42:13 +00:00
|
|
|
db_alias = write_compiler.using
|
|
|
|
|
table = write_compiler.query.get_meta().db_table
|
2016-09-06 22:44:51 +00:00
|
|
|
if is_cachable(table):
|
2016-09-29 17:11:21 +00:00
|
|
|
invalidate(table, db_alias=db_alias,
|
|
|
|
|
cache_alias=cachalot_settings.CACHALOT_CACHE)
|
2015-05-24 17:34:24 +00:00
|
|
|
return original(write_compiler, *args, **kwargs)
|
2014-11-04 00:17:35 +00:00
|
|
|
|
|
|
|
|
return inner
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def _patch_orm():
|
2017-06-07 06:59:35 +00:00
|
|
|
if cachalot_settings.CACHALOT_ENABLED:
|
|
|
|
|
SQLCompiler.execute_sql = _patch_compiler(SQLCompiler.execute_sql)
|
2014-11-04 00:17:35 +00:00
|
|
|
for compiler in WRITE_COMPILERS:
|
|
|
|
|
compiler.execute_sql = _patch_write_compiler(compiler.execute_sql)
|
|
|
|
|
|
|
|
|
|
|
2017-06-07 06:59:35 +00:00
|
|
|
def _unpatch_orm():
|
|
|
|
|
if hasattr(SQLCompiler.execute_sql, '__wrapped__'):
|
|
|
|
|
SQLCompiler.execute_sql = SQLCompiler.execute_sql.__wrapped__
|
|
|
|
|
for compiler in WRITE_COMPILERS:
|
|
|
|
|
compiler.execute_sql = compiler.execute_sql.__wrapped__
|
|
|
|
|
|
|
|
|
|
|
2014-11-04 00:17:35 +00:00
|
|
|
def _patch_cursor():
|
|
|
|
|
def _patch_cursor_execute(original):
|
|
|
|
|
@wraps(original)
|
2014-12-07 01:44:50 +00:00
|
|
|
def inner(cursor, sql, *args, **kwargs):
|
2018-05-04 14:24:04 +00:00
|
|
|
try:
|
|
|
|
|
return original(cursor, sql, *args, **kwargs)
|
|
|
|
|
finally:
|
|
|
|
|
connection = cursor.db
|
|
|
|
|
if getattr(connection, 'raw', True):
|
|
|
|
|
if isinstance(sql, binary_type):
|
|
|
|
|
sql = sql.decode('utf-8')
|
|
|
|
|
sql = sql.lower()
|
|
|
|
|
if 'update' in sql or 'insert' in sql or 'delete' in sql \
|
|
|
|
|
or 'alter' in sql or 'create' in sql \
|
|
|
|
|
or 'drop' in sql:
|
|
|
|
|
tables = filter_cachable(
|
|
|
|
|
_get_tables_from_sql(connection, sql))
|
|
|
|
|
if tables:
|
|
|
|
|
invalidate(
|
|
|
|
|
*tables, db_alias=connection.alias,
|
|
|
|
|
cache_alias=cachalot_settings.CACHALOT_CACHE)
|
2014-11-04 00:17:35 +00:00
|
|
|
|
2014-09-26 14:53:44 +00:00
|
|
|
return inner
|
|
|
|
|
|
2017-06-07 06:59:35 +00:00
|
|
|
if cachalot_settings.CACHALOT_INVALIDATE_RAW:
|
|
|
|
|
CursorWrapper.execute = _patch_cursor_execute(CursorWrapper.execute)
|
|
|
|
|
CursorWrapper.executemany = \
|
|
|
|
|
_patch_cursor_execute(CursorWrapper.executemany)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def _unpatch_cursor():
|
|
|
|
|
if hasattr(CursorWrapper.execute, '__wrapped__'):
|
|
|
|
|
CursorWrapper.execute = CursorWrapper.execute.__wrapped__
|
|
|
|
|
CursorWrapper.executemany = CursorWrapper.executemany.__wrapped__
|
2014-09-26 14:53:44 +00:00
|
|
|
|
|
|
|
|
|
2014-09-29 17:16:53 +00:00
|
|
|
def _patch_atomic():
|
|
|
|
|
def patch_enter(original):
|
2014-10-05 22:58:39 +00:00
|
|
|
@wraps(original)
|
2014-09-29 17:16:53 +00:00
|
|
|
def inner(self):
|
2015-10-26 01:42:13 +00:00
|
|
|
cachalot_caches.enter_atomic(self.using)
|
2014-09-29 17:16:53 +00:00
|
|
|
original(self)
|
|
|
|
|
|
|
|
|
|
return inner
|
|
|
|
|
|
|
|
|
|
def patch_exit(original):
|
2014-10-05 22:58:39 +00:00
|
|
|
@wraps(original)
|
2014-09-29 17:16:53 +00:00
|
|
|
def inner(self, exc_type, exc_value, traceback):
|
2014-10-20 21:43:10 +00:00
|
|
|
needs_rollback = get_connection(self.using).needs_rollback
|
2018-05-04 14:24:04 +00:00
|
|
|
try:
|
|
|
|
|
original(self, exc_type, exc_value, traceback)
|
|
|
|
|
finally:
|
|
|
|
|
cachalot_caches.exit_atomic(
|
|
|
|
|
self.using, exc_type is None and not needs_rollback)
|
2014-09-29 17:16:53 +00:00
|
|
|
|
|
|
|
|
return inner
|
|
|
|
|
|
|
|
|
|
Atomic.__enter__ = patch_enter(Atomic.__enter__)
|
|
|
|
|
Atomic.__exit__ = patch_exit(Atomic.__exit__)
|
|
|
|
|
|
|
|
|
|
|
2017-06-07 06:59:35 +00:00
|
|
|
def _unpatch_atomic():
|
|
|
|
|
Atomic.__enter__ = Atomic.__enter__.__wrapped__
|
|
|
|
|
Atomic.__exit__ = Atomic.__exit__.__wrapped__
|
|
|
|
|
|
|
|
|
|
|
2014-10-21 18:29:02 +00:00
|
|
|
def _invalidate_on_migration(sender, **kwargs):
|
2016-09-29 17:11:21 +00:00
|
|
|
invalidate(*sender.get_models(), db_alias=kwargs['using'],
|
|
|
|
|
cache_alias=cachalot_settings.CACHALOT_CACHE)
|
2014-09-29 20:03:05 +00:00
|
|
|
|
|
|
|
|
|
2014-09-29 17:16:53 +00:00
|
|
|
def patch():
|
2014-10-21 18:29:02 +00:00
|
|
|
post_migrate.connect(_invalidate_on_migration)
|
|
|
|
|
|
2014-11-04 00:17:35 +00:00
|
|
|
_patch_cursor()
|
2014-10-21 19:47:50 +00:00
|
|
|
_patch_atomic()
|
2014-11-04 00:17:35 +00:00
|
|
|
_patch_orm()
|
2017-06-07 06:59:35 +00:00
|
|
|
|
|
|
|
|
|
|
|
|
|
def unpatch():
|
|
|
|
|
post_migrate.disconnect(_invalidate_on_migration)
|
|
|
|
|
|
|
|
|
|
_unpatch_cursor()
|
|
|
|
|
_unpatch_atomic()
|
|
|
|
|
_unpatch_orm()
|