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:
parent
7f4c9222df
commit
51297a9232
|
@ -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()
|
||||||
|
|
|
@ -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):
|
||||||
|
|
|
@ -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()
|
||||||
|
|
||||||
|
|
|
@ -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
|
||||||
|
|
Loading…
Reference in New Issue