Repository: django/django
File: django/core/cache/backends/db.py (Python)

Code before:
        self.db_table = table
        self.app_label = "django_cache"
        self.model_name = "cacheentry"
        self.verbose_name = "cache entry"
        self.verbose_name_plural = "cache entries"
        self.object_name = "CacheEntry"
        self.abstract = False
        self.managed = True
        self.proxy = False
        self.swapped = False


class BaseDatabaseCache(BaseCache):
    def __init__(self, table, params):
        super().__init__(params)
        self._table = table

        class CacheEntry:
            _meta = Options(table)

        self.cache_model_class = CacheEntry

    def __init_subclass__(cls, **kwargs):
        # If a subclass implements a specialized *_many method,
        # it should use that method for the async implementation,
        # vs the version inherited from BaseCache.
        super().__init_subclass__(**kwargs)
        if hasattr(cls, "get_many"):
            setattr(cls, "aget_many", aget_impl)
        if hasattr(cls, "set_many"):
            setattr(cls, "aset_many", aset_impl)
        if hasattr(cls, "delete_many"):
            setattr(cls, "adelete_many", adelete_impl)


class DatabaseCache(BaseDatabaseCache):
    # This class uses cursors provided by the database connection. This means
    # it reads expiration values as aware or naive datetimes, depending on the
    # value of USE_TZ and whether the database supports time zones. The ORM's
    # conversion and adaptation infrastructure is then used to avoid comparing
    # aware and naive datetimes accidentally.

    pickle_protocol = pickle.HIGHEST_PROTOCOL

    def get(self, key, default=None, version=None):
        return self.get_many([key], version).get(key, default)

    def get_many(self, keys, version=None):
        if not keys:
            return {}


Review comment (performance):
This should be defined on `BaseCache` and not `BaseDatabaseCache`. As pointed out in the ticket the memcached and redis backend are also affected.

Code after:
        super().__init__(params)
        self._table = table

        class CacheEntry:
            _meta = Options(table)

        self.cache_model_class = CacheEntry


class DatabaseCache(BaseDatabaseCache):
    # This class uses cursors provided by the database connection. This means
    # it reads expiration values as aware or naive datetimes, depending on the
    # value of USE_TZ and whether the database supports time zones. The ORM's
    # conversion and adaptation infrastructure is then used to avoid comparing
    # aware and naive datetimes accidentally.

    pickle_protocol = pickle.HIGHEST_PROTOCOL

    def get(self, key, default=None, version=None):
        return self.get_many([key], version).get(key, default)

    def get_many(self, keys, version=None):
        if not keys:
            return {}

        key_map = {
            self.make_and_validate_key(key, version=version): key for key in keys
        }

        db = router.db_for_read(self.cache_model_class)
        connection = connections[db]
        quote_name = connection.ops.quote_name
        table = quote_name(self._table)

        with connection.cursor() as cursor:
            cursor.execute(
                "SELECT %s, %s, %s FROM %s WHERE %s IN (%s)"
                % (
                    quote_name("cache_key"),
                    quote_name("value"),
                    quote_name("expires"),
                    table,
                    quote_name("cache_key"),
                    ", ".join(["%s"] * len(key_map)),
                ),
                list(key_map),
            )
            rows = cursor.fetchall()

        result = {}
        expired_keys = []