Browse Source

Fixed #33759 -- Avoided unnecessary subquery in QuerySet.delete() with self-referential subqueries if supported.

4the4ryushin 1 year ago
parent
commit
0b0998dc15

+ 3 - 0
django/db/backends/base/features.py

@@ -12,6 +12,9 @@ class BaseDatabaseFeatures:
     allows_group_by_select_index = True
     empty_fetchmany_value = []
     update_can_self_select = True
+    # Does the backend support self-reference subqueries in the DELETE
+    # statement?
+    delete_can_self_reference_subquery = True
 
     # Does the backend distinguish between '' and None?
     interprets_empty_strings_as_nulls = False

+ 1 - 0
django/db/backends/mysql/features.py

@@ -24,6 +24,7 @@ class DatabaseFeatures(BaseDatabaseFeatures):
     supports_slicing_ordering_in_compound = True
     supports_index_on_text_field = False
     supports_update_conflicts = True
+    delete_can_self_reference_subquery = False
     create_test_procedure_without_params_sql = """
         CREATE PROCEDURE test_procedure ()
         BEGIN

+ 4 - 1
django/db/models/sql/compiler.py

@@ -1890,7 +1890,10 @@ class SQLDeleteCompiler(SQLCompiler):
         Create the SQL for this query. Return the SQL string and list of
         parameters.
         """
-        if self.single_alias and not self.contains_self_reference_subquery:
+        if self.single_alias and (
+            self.connection.features.delete_can_self_reference_subquery
+            or not self.contains_self_reference_subquery
+        ):
             return self._as_sql(self.query)
         innerq = self.query.clone()
         innerq.__class__ = Query

+ 13 - 8
tests/delete_regress/tests.py

@@ -379,15 +379,20 @@ class DeleteTests(TestCase):
         child = Child.objects.create(name="Juan")
         Book.objects.create(pagecount=500, owner=child)
         PlayedWith.objects.create(child=child, toy=toy, date=datetime.date.today())
-        Book.objects.filter(
-            Exists(
-                Book.objects.filter(
-                    pk=OuterRef("pk"),
-                    owner__toys=toy.pk,
-                ),
-            )
-        ).delete()
+        with self.assertNumQueries(1) as ctx:
+            Book.objects.filter(
+                Exists(
+                    Book.objects.filter(
+                        pk=OuterRef("pk"),
+                        owner__toys=toy.pk,
+                    ),
+                )
+            ).delete()
+
         self.assertIs(Book.objects.exists(), False)
+        sql = ctx.captured_queries[0]["sql"].lower()
+        if connection.features.delete_can_self_reference_subquery:
+            self.assertEqual(sql.count("select"), 1)
 
 
 class DeleteDistinct(SimpleTestCase):