1
0
mirror of https://github.com/django/django.git synced 2024-12-22 17:16:24 +00:00

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

Performance regression in 37e6c5b79b.

Thanks Todor Velichkov for the report.

Co-authored-by: Mariusz Felisiak <felisiak.mariusz@gmail.com>
This commit is contained in:
Roman 2021-10-11 23:53:53 +03:00 committed by Mariusz Felisiak
parent aaf9b55858
commit 407fe95cb1
2 changed files with 51 additions and 0 deletions

View File

@ -2,6 +2,7 @@ import uuid
from django.conf import settings from django.conf import settings
from django.db.backends.base.operations import BaseDatabaseOperations from django.db.backends.base.operations import BaseDatabaseOperations
from django.db.models import Exists, ExpressionWrapper, Lookup
from django.utils import timezone from django.utils import timezone
from django.utils.encoding import force_str from django.utils.encoding import force_str
@ -378,3 +379,14 @@ class DatabaseOperations(BaseDatabaseOperations):
): ):
lookup = 'JSON_UNQUOTE(%s)' lookup = 'JSON_UNQUOTE(%s)'
return lookup 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)

View File

@ -2,6 +2,7 @@ import collections.abc
from datetime import datetime from datetime import datetime
from math import ceil from math import ceil
from operator import attrgetter from operator import attrgetter
from unittest import skipUnless
from django.core.exceptions import FieldError from django.core.exceptions import FieldError
from django.db import connection, models from django.db import connection, models
@ -927,6 +928,44 @@ class LookupTests(TestCase):
with self.assertRaisesMessage(ValueError, msg): with self.assertRaisesMessage(ValueError, msg):
list(Article.objects.filter(author=Author.objects.all()[1:])) 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): def test_custom_field_none_rhs(self):
""" """
__exact=value is transformed to __isnull=True if Field.get_prep_value() __exact=value is transformed to __isnull=True if Field.get_prep_value()