Skip to content
Merged
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
169 changes: 84 additions & 85 deletions src/django_mysql/cache.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,6 @@
import zlib
from collections.abc import Iterable
from random import random
from textwrap import dedent
from time import time
from typing import Any, Callable, Literal, cast

Expand All @@ -15,7 +14,7 @@
from django.utils.encoding import force_bytes
from django.utils.module_loading import import_string

from django_mysql.utils import collapse_spaces, get_list_sql
from django_mysql.utils import get_list_sql

_EncodedKeyType = Literal["i", "p", "z"]

Expand Down Expand Up @@ -102,18 +101,18 @@ class MySQLCache(BaseDatabaseCache):
# 1970)
FOREVER_TIMEOUT = BIGINT_UNSIGNED_MAX >> 1

create_table_sql = dedent(
"""\
CREATE TABLE `{table_name}` (
cache_key varchar(255) CHARACTER SET utf8 COLLATE utf8_bin
NOT NULL PRIMARY KEY,
value longblob NOT NULL,
value_type char(1) CHARACTER SET latin1 COLLATE latin1_bin
NOT NULL DEFAULT 'p',
expires BIGINT UNSIGNED NOT NULL
);
"""
# fmt: off
create_table_sql = (
"CREATE TABLE `{table_name}` (\n"
" cache_key varchar(255) CHARACTER SET utf8 COLLATE utf8_bin\n"
" NOT NULL PRIMARY KEY,\n"
" value longblob NOT NULL,\n"
" value_type char(1) CHARACTER SET latin1 COLLATE latin1_bin\n"
" NOT NULL DEFAULT 'p',\n"
" expires BIGINT UNSIGNED NOT NULL\n"
");\n"
)
# fmt: on

@classmethod
def _now(cls) -> int:
Expand Down Expand Up @@ -162,14 +161,14 @@ def get(
value, value_type = row
return self.decode(value, value_type)

_get_query = collapse_spaces(
"""
SELECT value, value_type
FROM {table}
WHERE cache_key = %s AND
expires >= %s
"""
# fmt: off
_get_query = (
"SELECT value, value_type "
"FROM {table} "
"WHERE cache_key = %s AND "
"expires >= %s"
)
# fmt: on

def get_many(
self, keys: Iterable[str], version: int | None = None
Expand Down Expand Up @@ -199,14 +198,14 @@ def get_many(

return data

_get_many_query = collapse_spaces(
"""
SELECT cache_key, value, value_type
FROM {table}
WHERE cache_key IN {list_sql} AND
expires >= %s
"""
# fmt: off
_get_many_query = (
"SELECT cache_key, value, value_type "
"FROM {table} "
"WHERE cache_key IN {list_sql} AND "
"expires >= %s"
)
# fmt: on

def set(
self,
Expand Down Expand Up @@ -261,39 +260,39 @@ def _base_set(
insert_id = cursor.lastrowid
return insert_id != 444

_set_many_query = collapse_spaces(
"""
INSERT INTO {table} (cache_key, value, value_type, expires)
VALUES {{VALUES_CLAUSE}}
ON DUPLICATE KEY UPDATE
value=VALUES(value),
value_type=VALUES(value_type),
expires=VALUES(expires)
"""
# fmt: off
_set_many_query = (
"INSERT INTO {table} (cache_key, value, value_type, expires) "
"VALUES {{VALUES_CLAUSE}} "
"ON DUPLICATE KEY UPDATE "
"value=VALUES(value), "
"value_type=VALUES(value_type), "
"expires=VALUES(expires)"
)
# fmt: on

_set_query = _set_many_query.replace("{{VALUES_CLAUSE}}", "(%s, %s, %s, %s)")

# Uses the IFNULL / LEAST / LAST_INSERT_ID trick to communicate the special
# value of 444 back to the client (LAST_INSERT_ID is otherwise 0, since
# there is no AUTO_INCREMENT column)
_add_query = collapse_spaces(
"""
INSERT INTO {table} (cache_key, value, value_type, expires)
VALUES (%s, %s, %s, %s)
ON DUPLICATE KEY UPDATE
value=IF(expires > @tmp_now:=%s, value, VALUES(value)),
value_type=IF(expires > @tmp_now, value_type, VALUES(value_type)),
expires=IF(
expires > @tmp_now,
IFNULL(
LEAST(LAST_INSERT_ID(444), NULL),
expires
),
VALUES(expires)
)
"""
# fmt: off
_add_query = (
"INSERT INTO {table} (cache_key, value, value_type, expires) "
"VALUES (%s, %s, %s, %s) "
"ON DUPLICATE KEY UPDATE "
"value=IF(expires > @tmp_now:=%s, value, VALUES(value)), "
"value_type=IF(expires > @tmp_now, value_type, VALUES(value_type)), "
"expires=IF("
"expires > @tmp_now, "
"IFNULL("
"LEAST(LAST_INSERT_ID(444), NULL), "
"expires"
"), "
"VALUES(expires)"
")"
)
# fmt: on

def set_many(
self,
Expand Down Expand Up @@ -332,12 +331,12 @@ def delete(self, key: str, version: int | None = None) -> None:
with connections[db].cursor() as cursor:
cursor.execute(self._delete_query.format(table=table), (key,))

_delete_query = collapse_spaces(
"""
DELETE FROM {table}
WHERE cache_key = %s
"""
# fmt: off
_delete_query = (
"DELETE FROM {table} "
"WHERE cache_key = %s"
)
# fmt: on

def delete_many(self, keys: Iterable[str], version: int | None = None) -> None:
made_keys = [self.make_key(key, version=version) for key in keys]
Expand All @@ -355,12 +354,12 @@ def delete_many(self, keys: Iterable[str], version: int | None = None) -> None:
made_keys,
)

_delete_many_query = collapse_spaces(
"""
DELETE FROM {table}
WHERE cache_key IN {list_sql}
"""
# fmt: off
_delete_many_query = (
"DELETE FROM {table} "
"WHERE cache_key IN {list_sql}"
)
# fmt: on

def has_key(self, key: str, version: int | None = None) -> bool:
key = self.make_key(key, version=version)
Expand All @@ -373,12 +372,12 @@ def has_key(self, key: str, version: int | None = None) -> bool:
cursor.execute(self._has_key_query.format(table=table), (key, self._now()))
return cursor.fetchone() is not None

_has_key_query = collapse_spaces(
"""
SELECT 1 FROM {table}
WHERE cache_key = %s and expires > %s
"""
# fmt: off
_has_key_query = (
"SELECT 1 FROM {table} "
"WHERE cache_key = %s and expires > %s"
)
# fmt: on

def incr(self, key: str, delta: int = 1, version: int | None = None) -> int:
return self._base_delta(key, delta, version, "+")
Expand Down Expand Up @@ -412,18 +411,18 @@ def _base_delta(

# Looks a bit tangled to turn the blob back into an int for updating, but
# it works. Stores the new value for insert_id() with LAST_INSERT_ID
_delta_query = collapse_spaces(
"""
UPDATE {table}
SET value = LAST_INSERT_ID(
CAST(value AS SIGNED INTEGER)
{operation}
%s
)
WHERE cache_key = %s AND
value_type = 'i'
"""
# fmt: off
_delta_query = (
"UPDATE {table} "
"SET value = LAST_INSERT_ID("
"CAST(value AS SIGNED INTEGER) "
"{operation} "
"%s"
") "
"WHERE cache_key = %s AND "
"value_type = 'i'"
)
# fmt: on

def clear(self) -> None:
db = router.db_for_write(self.cache_model_class)
Expand All @@ -445,14 +444,14 @@ def touch(
)
return affected_rows > 0

_touch_query = collapse_spaces(
"""
UPDATE {table}
SET expires = %s
WHERE cache_key = %s AND
expires >= %s
"""
# fmt: off
_touch_query = (
"UPDATE {table} "
"SET expires = %s "
"WHERE cache_key = %s AND "
"expires >= %s"
)
# fmt: on

def validate_key(self, key: str) -> None:
"""
Expand Down
9 changes: 3 additions & 6 deletions src/django_mysql/management/commands/cull_mysql_caches.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,17 +8,14 @@
from django.core.management import BaseCommand, CommandError

from django_mysql.cache import MySQLCache
from django_mysql.utils import collapse_spaces


class Command(BaseCommand):
args = "<optional cache aliases>"

help = collapse_spaces(
"""
Runs cache.cull() on all your MySQLCache caches, or only those
specified aliases.
"""
help = (
"Runs cache.cull() on all your MySQLCache caches, or only those "
"specified aliases."
)

def add_arguments(self, parser: argparse.ArgumentParser) -> None:
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,17 +8,12 @@
from django.core.management import BaseCommand, CommandError

from django_mysql.cache import MySQLCache
from django_mysql.utils import collapse_spaces


class Command(BaseCommand):
args = "<app_name>"

help = collapse_spaces(
"""
Outputs a migration that will create a table.
"""
)
help = "Outputs a migration that will create a table."

def add_arguments(self, parser: argparse.ArgumentParser) -> None:
parser.add_argument(
Expand Down
Loading