2
0
Эх сурвалжийг харах

Fixed #32691 -- Made Exact lookup on BooleanFields compare directly to a boolean value on MySQL.

Performance regression in 37e6c5b79bd0529a3c85b8c478e4002fd33a2a1d.

Thanks Todor Velichkov for the report.

Co-authored-by: Mariusz Felisiak <felisiak.mariusz@gmail.com>
Roman 3 жил өмнө
parent
commit
407fe95cb1

+ 12 - 0
django/db/backends/mysql/operations.py

@@ -2,6 +2,7 @@ import uuid
 
 from django.conf import settings
 from django.db.backends.base.operations import BaseDatabaseOperations
+from django.db.models import Exists, ExpressionWrapper, Lookup
 from django.utils import timezone
 from django.utils.encoding import force_str
 
@@ -378,3 +379,14 @@ class DatabaseOperations(BaseDatabaseOperations):
             ):
                 lookup = 'JSON_UNQUOTE(%s)'
         return lookup
+
+    def conditional_expression_supported_in_where_clause(self, expression):
+        # MySQL ignores indexes with boolean fields unless they're compared
+        # directly to a boolean value.
+        if isinstance(expression, (Exists, Lookup)):
+            return True
+        if isinstance(expression, ExpressionWrapper) and expression.conditional:
+            return self.conditional_expression_supported_in_where_clause(expression.expression)
+        if getattr(expression, 'conditional', False):
+            return False
+        return super().conditional_expression_supported_in_where_clause(expression)

+ 39 - 0
tests/lookup/tests.py

@@ -2,6 +2,7 @@ import collections.abc
 from datetime import datetime
 from math import ceil
 from operator import attrgetter
+from unittest import skipUnless
 
 from django.core.exceptions import FieldError
 from django.db import connection, models
@@ -927,6 +928,44 @@ class LookupTests(TestCase):
         with self.assertRaisesMessage(ValueError, msg):
             list(Article.objects.filter(author=Author.objects.all()[1:]))
 
+    @skipUnless(connection.vendor == 'mysql', 'MySQL-specific workaround.')
+    def test_exact_booleanfield(self):
+        # MySQL ignores indexes with boolean fields unless they're compared
+        # directly to a boolean value.
+        product = Product.objects.create(name='Paper', qty_target=5000)
+        Stock.objects.create(product=product, short=False, qty_available=5100)
+        stock_1 = Stock.objects.create(product=product, short=True, qty_available=180)
+        qs = Stock.objects.filter(short=True)
+        self.assertSequenceEqual(qs, [stock_1])
+        self.assertIn(
+            '%s = True' % connection.ops.quote_name('short'),
+            str(qs.query),
+        )
+
+    @skipUnless(connection.vendor == 'mysql', 'MySQL-specific workaround.')
+    def test_exact_booleanfield_annotation(self):
+        # MySQL ignores indexes with boolean fields unless they're compared
+        # directly to a boolean value.
+        qs = Author.objects.annotate(case=Case(
+            When(alias='a1', then=True),
+            default=False,
+            output_field=BooleanField(),
+        )).filter(case=True)
+        self.assertSequenceEqual(qs, [self.au1])
+        self.assertIn(' = True', str(qs.query))
+
+        qs = Author.objects.annotate(
+            wrapped=ExpressionWrapper(Q(alias='a1'), output_field=BooleanField()),
+        ).filter(wrapped=True)
+        self.assertSequenceEqual(qs, [self.au1])
+        self.assertIn(' = True', str(qs.query))
+        # EXISTS(...) shouldn't be compared to a boolean value.
+        qs = Author.objects.annotate(
+            exists=Exists(Author.objects.filter(alias='a1', pk=OuterRef('pk'))),
+        ).filter(exists=True)
+        self.assertSequenceEqual(qs, [self.au1])
+        self.assertNotIn(' = True', str(qs.query))
+
     def test_custom_field_none_rhs(self):
         """
         __exact=value is transformed to __isnull=True if Field.get_prep_value()