Repository: pulp/pulpcore

Issue:
Update touch() to not have to use raw sql (!!)
**Version**
main

**Describe the bug**
In response to "why doesn't select_for_update() do what we expect", this response from Django team:

https://code.djangoproject.com/ticket/33516#comment:1

showed The Way to invoke it successfully.

Let's get rid of raw-sql-execution in the touch() method.

**Additional context**
https://bugzilla.redhat.com/show_bug.cgi?id=2021406
https://github.com/pulp/pulpcore/issues/2157



Discussion:

Resolving patch:
diff --git a/pulpcore/app/models/content.py b/pulpcore/app/models/content.py
--- a/pulpcore/app/models/content.py
+++ b/pulpcore/app/models/content.py
@@ -12,12 +12,11 @@
 import gnupg
 
 from itertools import chain
-from psycopg2 import sql
 
 from django.conf import settings
 from django.core import validators
 from django.core.files.storage import default_storage
-from django.db import IntegrityError, connection, models, transaction
+from django.db import IntegrityError, models, transaction
 from django.forms.models import model_to_dict
 from django.utils.timezone import now
 from django_lifecycle import BEFORE_UPDATE, BEFORE_SAVE, hook
@@ -109,37 +108,9 @@ def touch(self):
         that updating that row's timestamp-of-interest is the responsibility of whoever currently
         owns it, this results in correct data, while closing the window on deadlocks.
         """
-        # Build the list of ids we need to work on, since we're going to be building a
-        # SQL-query "by hand" in a moment.
-        pulp_ids = [f"'{uuid}'" for uuid in self.values_list("pk", flat=True)]
-        if not pulp_ids:
-            return None
-        ids_str = ",".join(pulp_ids)
-        # timestamp_of_interest exists on core_content and core_artifact, not on the Detail tables
-        # If we are an instance-of Content or its subclasses, we want to update the Content table.
-        # Otherwise, use the table associated w/ the query.
-        db_table = (
-            Content._meta.db_table if issubclass(self.model, Content) else self.model._meta.db_table
-        )
-        cursor = connection.cursor()
         with transaction.atomic():
-            # SQL-sanitizing the table-name here is certainly overkill - sql-injection here would
-            # require code calling touch() on a Model whose table-name-str was carefully chosen to
-            # be Bad - but, good habits...
-            stmt = sql.SQL(
-                "UPDATE {table_name} "
-                "   SET timestamp_of_interest = NOW() "
-                " WHERE pulp_id IN ("
-                "    SELECT pulp_id "
-                "      FROM {table_name} "
-                "     WHERE pulp_id in ({ids}) "
-                "     ORDER BY pulp_id "
-                "     FOR UPDATE "
-                "     SKIP LOCKED)".format(table_name=sql.Identifier(db_table).string, ids=ids_str)
-            )
-            rslt = cursor.execute(stmt)
-        cursor.close()
-        return rslt
+            sub_q = self.order_by("pk").select_for_update(skip_locked=True)
+            return self.filter(pk__in=sub_q).update(timestamp_of_interest=now())
 
 
 class QueryMixin:


Test patch: