summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--django/db/models/expressions.py4
-rw-r--r--django/db/models/sql/compiler.py3
-rw-r--r--django/db/models/sql/query.py8
-rw-r--r--tests/expressions/tests.py22
4 files changed, 29 insertions, 8 deletions
diff --git a/django/db/models/expressions.py b/django/db/models/expressions.py
index 5b5a0ae4aa..a9768919a2 100644
--- a/django/db/models/expressions.py
+++ b/django/db/models/expressions.py
@@ -1145,11 +1145,9 @@ class Exists(Subquery):
output_field = fields.BooleanField()
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
super().__init__(queryset, **kwargs)
+ self.query = self.query.exists()
def __invert__(self):
clone = self.copy()
diff --git a/django/db/models/sql/compiler.py b/django/db/models/sql/compiler.py
index fc7a7aafae..b321762303 100644
--- a/django/db/models/sql/compiler.py
+++ b/django/db/models/sql/compiler.py
@@ -1127,9 +1127,6 @@ class SQLCompiler:
Backends (e.g. NoSQL) can override this in order to use optimized
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))
def execute_sql(self, result_type=MULTI, chunked_fetch=False, chunk_size=GET_ITERATOR_CHUNK_SIZE):
diff --git a/django/db/models/sql/query.py b/django/db/models/sql/query.py
index 659fa87314..4648daf395 100644
--- a/django/db/models/sql/query.py
+++ b/django/db/models/sql/query.py
@@ -522,7 +522,7 @@ class Query(BaseExpression):
def has_filters(self):
return self.where
- def has_results(self, using):
+ def exists(self):
q = self.clone()
if not q.distinct:
if q.group_by is True:
@@ -533,6 +533,12 @@ class Query(BaseExpression):
q.clear_select_clause()
q.clear_ordering(True)
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)
return compiler.has_results()
diff --git a/tests/expressions/tests.py b/tests/expressions/tests.py
index 2f392a51e6..c15204ce33 100644
--- a/tests/expressions/tests.py
+++ b/tests/expressions/tests.py
@@ -22,7 +22,7 @@ from django.db.models.functions import (
from django.db.models.sql import constants
from django.db.models.sql.datastructures import Join
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 .models import (
@@ -1738,6 +1738,26 @@ class ValueTests(TestCase):
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):
@classmethod