diff --git a/src/django_mysql/cache.py b/src/django_mysql/cache.py index 0f8f1da9..f260b9c5 100644 --- a/src/django_mysql/cache.py +++ b/src/django_mysql/cache.py @@ -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 @@ -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"] @@ -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: @@ -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 @@ -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, @@ -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, @@ -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] @@ -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) @@ -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, "+") @@ -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) @@ -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: """ diff --git a/src/django_mysql/management/commands/cull_mysql_caches.py b/src/django_mysql/management/commands/cull_mysql_caches.py index a6af604b..cfc27e41 100644 --- a/src/django_mysql/management/commands/cull_mysql_caches.py +++ b/src/django_mysql/management/commands/cull_mysql_caches.py @@ -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 = "" - 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: diff --git a/src/django_mysql/management/commands/mysql_cache_migration.py b/src/django_mysql/management/commands/mysql_cache_migration.py index afe90fae..3133c24e 100644 --- a/src/django_mysql/management/commands/mysql_cache_migration.py +++ b/src/django_mysql/management/commands/mysql_cache_migration.py @@ -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 = "" - 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( diff --git a/src/django_mysql/models/expressions.py b/src/django_mysql/models/expressions.py index fea27d8f..f94a8adc 100644 --- a/src/django_mysql/models/expressions.py +++ b/src/django_mysql/models/expressions.py @@ -8,8 +8,6 @@ from django.db.models.expressions import BaseExpression from django.db.models.sql.compiler import SQLCompiler -from django_mysql.utils import collapse_spaces - class TwoSidedExpression(BaseExpression): def __init__(self, lhs: BaseExpression, rhs: BaseExpression) -> None: @@ -52,19 +50,19 @@ class AppendListF(TwoSidedExpression): # comma and 'value' # N.B. using MySQL side variables to avoid repeat calculation of # expression[s] - sql_expression = collapse_spaces( - """ - CONCAT_WS( - ',', - IF( - (@tmp_f:=%s) > '', - @tmp_f, - NULL - ), - %s - ) - """ + # fmt: off + sql_expression = ( + "CONCAT_WS(" + "',', " + "IF(" + "(@tmp_f:=%s) > '', " + "@tmp_f, " + "NULL" + "), " + "%s" + ")" ) + # fmt: on def as_sql( self, @@ -86,19 +84,19 @@ class AppendLeftListF(TwoSidedExpression): # comma and 'value' # N.B. using MySQL side variables to avoid repeat calculation of # expression[s] - sql_expression = collapse_spaces( - """ - CONCAT_WS( - ',', - %s, - IF( - (@tmp_f:=%s) > '', - @tmp_f, - NULL - ) - ) - """ + # fmt: off + sql_expression = ( + "CONCAT_WS(" + "',', " + "%s, " + "IF(" + "(@tmp_f:=%s) > '', " + "@tmp_f, " + "NULL" + ")" + ")" ) + # fmt: on def as_sql( self, @@ -115,23 +113,23 @@ def as_sql( class PopListF(BaseExpression): - sql_expression = collapse_spaces( - """ - SUBSTRING( - @tmp_f:=%s, - 1, - IF( - LOCATE(',', @tmp_f), - ( - CHAR_LENGTH(@tmp_f) - - CHAR_LENGTH(SUBSTRING_INDEX(@tmp_f, ',', -1)) - - 1 - ), - 0 - ) - ) - """ + # fmt: off + sql_expression = ( + "SUBSTRING(" + "@tmp_f:=%s, " + "1, " + "IF(" + "LOCATE(',', @tmp_f), " + "(" + "CHAR_LENGTH(@tmp_f) - " + "CHAR_LENGTH(SUBSTRING_INDEX(@tmp_f, ',', -1)) - " + "1" + "), " + "0" + ")" + ")" ) + # fmt: on def __init__(self, lhs: BaseExpression) -> None: super().__init__() @@ -155,15 +153,15 @@ def as_sql( class PopLeftListF(BaseExpression): - sql_expression = collapse_spaces( - """ - IF( - (@tmp_c:=LOCATE(',', @tmp_f:=%s)) > 0, - SUBSTRING(@tmp_f, @tmp_c + 1), - '' - ) - """ + # fmt: off + sql_expression = ( + "IF(" + "(@tmp_c:=LOCATE(',', @tmp_f:=%s)) > 0, " + "SUBSTRING(@tmp_f, @tmp_c + 1), " + "''" + ")" ) + # fmt: on def __init__(self, lhs: BaseExpression) -> None: super().__init__() @@ -207,19 +205,19 @@ class AddSetF(TwoSidedExpression): # comma and 'value' # N.B. using MySQL side variables to avoid repeat calculation of # expression[s] - sql_expression = collapse_spaces( - """ - IF( - FIND_IN_SET(@tmp_val:=%s, @tmp_f:=%s), - @tmp_f, - CONCAT_WS( - ',', - IF(CHAR_LENGTH(@tmp_f), @tmp_f, NULL), - @tmp_val - ) - ) - """ + # fmt: off + sql_expression = ( + "IF(" + "FIND_IN_SET(@tmp_val:=%s, @tmp_f:=%s), " + "@tmp_f, " + "CONCAT_WS(" + "',', " + "IF(CHAR_LENGTH(@tmp_f), @tmp_f, NULL), " + "@tmp_val" + ")" + ")" ) + # fmt: on def as_sql( self, @@ -241,38 +239,38 @@ class RemoveSetF(TwoSidedExpression): # that element. # There are some tricks going on - e.g. LEAST to evaluate a sub expression # but not use it in the output of CONCAT_WS - sql_expression = collapse_spaces( - """ - IF( - @tmp_pos:=FIND_IN_SET(%s, @tmp_f:=%s), - CONCAT_WS( - ',', - LEAST( - @tmp_len:=( - CHAR_LENGTH(@tmp_f) - - CHAR_LENGTH(REPLACE(@tmp_f, ',', '')) + - IF(CHAR_LENGTH(@tmp_f), 1, 0) - ), - NULL - ), - CASE WHEN - (@tmp_before:=SUBSTRING_INDEX(@tmp_f, ',', @tmp_pos - 1)) - = '' - THEN NULL - ELSE @tmp_before - END, - CASE WHEN - (@tmp_after:= - SUBSTRING_INDEX(@tmp_f, ',', - (@tmp_len - @tmp_pos))) - = '' - THEN NULL - ELSE @tmp_after - END - ), - @tmp_f - ) - """ + # fmt: off + sql_expression = ( + "IF(" + "@tmp_pos:=FIND_IN_SET(%s, @tmp_f:=%s), " + "CONCAT_WS(" + "',', " + "LEAST(" + "@tmp_len:=(" + "CHAR_LENGTH(@tmp_f) - " + "CHAR_LENGTH(REPLACE(@tmp_f, ',', '')) + " + "IF(CHAR_LENGTH(@tmp_f), 1, 0)" + "), " + "NULL" + "), " + "CASE WHEN " + "(@tmp_before:=SUBSTRING_INDEX(@tmp_f, ',', @tmp_pos - 1)) " + "= '' " + "THEN NULL " + "ELSE @tmp_before " + "END, " + "CASE WHEN " + "(@tmp_after:=" + "SUBSTRING_INDEX(@tmp_f, ',', - (@tmp_len - @tmp_pos))) " + "= '' " + "THEN NULL " + "ELSE @tmp_after " + "END" + "), " + "@tmp_f" + ")" ) + # fmt: on def as_sql( self, diff --git a/src/django_mysql/models/transforms.py b/src/django_mysql/models/transforms.py index 5796e601..8e38b283 100644 --- a/src/django_mysql/models/transforms.py +++ b/src/django_mysql/models/transforms.py @@ -7,23 +7,21 @@ from django.db.models import IntegerField, Transform from django.db.models.sql.compiler import SQLCompiler -from django_mysql.utils import collapse_spaces - class SetLength(Transform): lookup_name = "len" output_field = IntegerField() # No str.count equivalent in MySQL :( - expr = collapse_spaces( - """ - ( - CHAR_LENGTH(%s) - - CHAR_LENGTH(REPLACE(%s, ',', '')) + - IF(CHAR_LENGTH(%s), 1, 0) - ) - """ + # fmt: off + expr = ( + "(" + "CHAR_LENGTH(%s) - " + "CHAR_LENGTH(REPLACE(%s, ',', '')) + " + "IF(CHAR_LENGTH(%s), 1, 0)" + ")" ) + # fmt: on def as_sql( self, compiler: SQLCompiler, connection: BaseDatabaseWrapper diff --git a/src/django_mysql/utils.py b/src/django_mysql/utils.py index 1ab3c4f2..84b6e505 100644 --- a/src/django_mysql/utils.py +++ b/src/django_mysql/utils.py @@ -126,11 +126,6 @@ def settings_to_cmd_args(settings_dict: dict[str, Any]) -> list[str]: return args -def collapse_spaces(string: str) -> str: - bits = string.replace("\n", " ").split(" ") - return " ".join(filter(None, bits)) - - def index_name( model: type[Model], *field_names: str, using: str = DEFAULT_DB_ALIAS ) -> str: