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.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)

View File

@ -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()