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

Fixed #31792 -- Made Exists() reuse QuerySet.exists() optimizations.

The latter is already optimized to limit the number of results, avoid
selecting unnecessary fields, and drop ordering if possible without
altering the semantic of the query.
This commit is contained in:
Simon Charette 2020-08-12 23:16:22 -04:00 committed by Mariusz Felisiak
parent 7f4c9222df
commit 51297a9232
4 changed files with 29 additions and 8 deletions

View File

@ -1145,11 +1145,9 @@ class Exists(Subquery):
output_field = fields.BooleanField() output_field = fields.BooleanField()
def __init__(self, queryset, negated=False, **kwargs): def __init__(self, queryset, negated=False, **kwargs):
# As a performance optimization, remove ordering since EXISTS doesn't
# care about it, just whether or not a row matches.
queryset = queryset.order_by()
self.negated = negated self.negated = negated
super().__init__(queryset, **kwargs) super().__init__(queryset, **kwargs)
self.query = self.query.exists()
def __invert__(self): def __invert__(self):
clone = self.copy() clone = self.copy()

View File

@ -1127,9 +1127,6 @@ class SQLCompiler:
Backends (e.g. NoSQL) can override this in order to use optimized Backends (e.g. NoSQL) can override this in order to use optimized
versions of "query has any results." versions of "query has any results."
""" """
# This is always executed on a query clone, so we can modify self.query
self.query.add_extra({'a': 1}, None, None, None, None, None)
self.query.set_extra_mask(['a'])
return bool(self.execute_sql(SINGLE)) return bool(self.execute_sql(SINGLE))
def execute_sql(self, result_type=MULTI, chunked_fetch=False, chunk_size=GET_ITERATOR_CHUNK_SIZE): def execute_sql(self, result_type=MULTI, chunked_fetch=False, chunk_size=GET_ITERATOR_CHUNK_SIZE):

View File

@ -522,7 +522,7 @@ class Query(BaseExpression):
def has_filters(self): def has_filters(self):
return self.where return self.where
def has_results(self, using): def exists(self):
q = self.clone() q = self.clone()
if not q.distinct: if not q.distinct:
if q.group_by is True: if q.group_by is True:
@ -533,6 +533,12 @@ class Query(BaseExpression):
q.clear_select_clause() q.clear_select_clause()
q.clear_ordering(True) q.clear_ordering(True)
q.set_limits(high=1) q.set_limits(high=1)
q.add_extra({'a': 1}, None, None, None, None, None)
q.set_extra_mask(['a'])
return q
def has_results(self, using):
q = self.exists()
compiler = q.get_compiler(using=using) compiler = q.get_compiler(using=using)
return compiler.has_results() return compiler.has_results()

View File

@ -22,7 +22,7 @@ from django.db.models.functions import (
from django.db.models.sql import constants from django.db.models.sql import constants
from django.db.models.sql.datastructures import Join from django.db.models.sql.datastructures import Join
from django.test import SimpleTestCase, TestCase, skipUnlessDBFeature from django.test import SimpleTestCase, TestCase, skipUnlessDBFeature
from django.test.utils import Approximate, isolate_apps from django.test.utils import Approximate, CaptureQueriesContext, isolate_apps
from django.utils.functional import SimpleLazyObject from django.utils.functional import SimpleLazyObject
from .models import ( from .models import (
@ -1738,6 +1738,26 @@ class ValueTests(TestCase):
Value(object()).output_field Value(object()).output_field
class ExistsTests(TestCase):
def test_optimizations(self):
with CaptureQueriesContext(connection) as context:
list(Experiment.objects.values(exists=Exists(
Experiment.objects.order_by('pk'),
)).order_by())
captured_queries = context.captured_queries
self.assertEqual(len(captured_queries), 1)
captured_sql = captured_queries[0]['sql']
self.assertNotIn(
connection.ops.quote_name(Experiment._meta.pk.column),
captured_sql,
)
self.assertIn(
connection.ops.limit_offset_sql(None, 1),
captured_sql,
)
self.assertNotIn('ORDER BY', captured_sql)
class FieldTransformTests(TestCase): class FieldTransformTests(TestCase):
@classmethod @classmethod