Skip to content
This repository

HTTPS clone URL

Subversion checkout URL

You can clone with HTTPS or Subversion.

Download ZIP
Browse code

Fixed #19173 -- Made EmptyQuerySet a marker class only

The guarantee that no queries will be made when accessing results is
done by new EmptyWhere class which is used for query.where and having.

Thanks to Simon Charette for reviewing and valuable suggestions.
  • Loading branch information...
commit a2396a4c8f2ccd7f91adee6d8c2e9c31f13f0e3f 1 parent a843539
Anssi Kääriäinen authored October 24, 2012
4  django/contrib/auth/models.py
@@ -473,8 +473,8 @@ class AnonymousUser(object):
473 473
     is_staff = False
474 474
     is_active = False
475 475
     is_superuser = False
476  
-    _groups = EmptyManager()
477  
-    _user_permissions = EmptyManager()
  476
+    _groups = EmptyManager(Group)
  477
+    _user_permissions = EmptyManager(Permission)
478 478
 
479 479
     def __init__(self):
480 480
         pass
8  django/db/models/manager.py
... ...
@@ -1,6 +1,6 @@
1 1
 import copy
2 2
 from django.db import router
3  
-from django.db.models.query import QuerySet, EmptyQuerySet, insert_query, RawQuerySet
  3
+from django.db.models.query import QuerySet, insert_query, RawQuerySet
4 4
 from django.db.models import signals
5 5
 from django.db.models.fields import FieldDoesNotExist
6 6
 
@@ -113,7 +113,7 @@ def db(self):
113 113
     #######################
114 114
 
115 115
     def get_empty_query_set(self):
116  
-        return EmptyQuerySet(self.model, using=self._db)
  116
+        return QuerySet(self.model, using=self._db).none()
117 117
 
118 118
     def get_query_set(self):
119 119
         """Returns a new QuerySet object.  Subclasses can override this method
@@ -258,5 +258,9 @@ def __get__(self, instance, type=None):
258 258
 
259 259
 
260 260
 class EmptyManager(Manager):
  261
+    def __init__(self, model):
  262
+        super(EmptyManager, self).__init__()
  263
+        self.model = model
  264
+
261 265
     def get_query_set(self):
262 266
         return self.get_empty_query_set()
159  django/db/models/query.py
@@ -35,7 +35,6 @@ class QuerySet(object):
35 35
     """
36 36
     def __init__(self, model=None, query=None, using=None):
37 37
         self.model = model
38  
-        # EmptyQuerySet instantiates QuerySet with model as None
39 38
         self._db = using
40 39
         self.query = query or sql.Query(self.model)
41 40
         self._result_cache = None
@@ -217,7 +216,9 @@ def __getitem__(self, k):
217 216
     def __and__(self, other):
218 217
         self._merge_sanity_check(other)
219 218
         if isinstance(other, EmptyQuerySet):
220  
-            return other._clone()
  219
+            return other
  220
+        if isinstance(self, EmptyQuerySet):
  221
+            return self
221 222
         combined = self._clone()
222 223
         combined._merge_known_related_objects(other)
223 224
         combined.query.combine(other.query, sql.AND)
@@ -225,9 +226,11 @@ def __and__(self, other):
225 226
 
226 227
     def __or__(self, other):
227 228
         self._merge_sanity_check(other)
228  
-        combined = self._clone()
  229
+        if isinstance(self, EmptyQuerySet):
  230
+            return other
229 231
         if isinstance(other, EmptyQuerySet):
230  
-            return combined
  232
+            return self
  233
+        combined = self._clone()
231 234
         combined._merge_known_related_objects(other)
232 235
         combined.query.combine(other.query, sql.OR)
233 236
         return combined
@@ -632,7 +635,9 @@ def none(self):
632 635
         """
633 636
         Returns an empty QuerySet.
634 637
         """
635  
-        return self._clone(klass=EmptyQuerySet)
  638
+        clone = self._clone()
  639
+        clone.query.set_empty()
  640
+        return clone
636 641
 
637 642
     ##################################################################
638 643
     # PUBLIC METHODS THAT ALTER ATTRIBUTES AND RETURN A NEW QUERYSET #
@@ -981,6 +986,18 @@ def _as_sql(self, connection):
981 986
     # empty" result.
982 987
     value_annotation = True
983 988
 
  989
+class InstanceCheckMeta(type):
  990
+    def __instancecheck__(self, instance):
  991
+        return instance.query.is_empty()
  992
+
  993
+class EmptyQuerySet(six.with_metaclass(InstanceCheckMeta), object):
  994
+    """
  995
+    Marker class usable for checking if a queryset is empty by .none():
  996
+        isinstance(qs.none(), EmptyQuerySet) -> True
  997
+    """
  998
+
  999
+    def __init__(self, *args, **kwargs):
  1000
+        raise TypeError("EmptyQuerySet can't be instantiated")
984 1001
 
985 1002
 class ValuesQuerySet(QuerySet):
986 1003
     def __init__(self, *args, **kwargs):
@@ -1180,138 +1197,6 @@ def _clone(self, klass=None, setup=False, **kwargs):
1180 1197
         return c
1181 1198
 
1182 1199
 
1183  
-class EmptyQuerySet(QuerySet):
1184  
-    def __init__(self, model=None, query=None, using=None):
1185  
-        super(EmptyQuerySet, self).__init__(model, query, using)
1186  
-        self._result_cache = []
1187  
-
1188  
-    def __and__(self, other):
1189  
-        return self._clone()
1190  
-
1191  
-    def __or__(self, other):
1192  
-        return other._clone()
1193  
-
1194  
-    def count(self):
1195  
-        return 0
1196  
-
1197  
-    def delete(self):
1198  
-        pass
1199  
-
1200  
-    def _clone(self, klass=None, setup=False, **kwargs):
1201  
-        c = super(EmptyQuerySet, self)._clone(klass, setup=setup, **kwargs)
1202  
-        c._result_cache = []
1203  
-        return c
1204  
-
1205  
-    def iterator(self):
1206  
-        # This slightly odd construction is because we need an empty generator
1207  
-        # (it raises StopIteration immediately).
1208  
-        yield next(iter([]))
1209  
-
1210  
-    def all(self):
1211  
-        """
1212  
-        Always returns EmptyQuerySet.
1213  
-        """
1214  
-        return self
1215  
-
1216  
-    def filter(self, *args, **kwargs):
1217  
-        """
1218  
-        Always returns EmptyQuerySet.
1219  
-        """
1220  
-        return self
1221  
-
1222  
-    def exclude(self, *args, **kwargs):
1223  
-        """
1224  
-        Always returns EmptyQuerySet.
1225  
-        """
1226  
-        return self
1227  
-
1228  
-    def complex_filter(self, filter_obj):
1229  
-        """
1230  
-        Always returns EmptyQuerySet.
1231  
-        """
1232  
-        return self
1233  
-
1234  
-    def select_related(self, *fields, **kwargs):
1235  
-        """
1236  
-        Always returns EmptyQuerySet.
1237  
-        """
1238  
-        return self
1239  
-
1240  
-    def annotate(self, *args, **kwargs):
1241  
-        """
1242  
-        Always returns EmptyQuerySet.
1243  
-        """
1244  
-        return self
1245  
-
1246  
-    def order_by(self, *field_names):
1247  
-        """
1248  
-        Always returns EmptyQuerySet.
1249  
-        """
1250  
-        return self
1251  
-
1252  
-    def distinct(self, fields=None):
1253  
-        """
1254  
-        Always returns EmptyQuerySet.
1255  
-        """
1256  
-        return self
1257  
-
1258  
-    def extra(self, select=None, where=None, params=None, tables=None,
1259  
-              order_by=None, select_params=None):
1260  
-        """
1261  
-        Always returns EmptyQuerySet.
1262  
-        """
1263  
-        assert self.query.can_filter(), \
1264  
-                "Cannot change a query once a slice has been taken"
1265  
-        return self
1266  
-
1267  
-    def reverse(self):
1268  
-        """
1269  
-        Always returns EmptyQuerySet.
1270  
-        """
1271  
-        return self
1272  
-
1273  
-    def defer(self, *fields):
1274  
-        """
1275  
-        Always returns EmptyQuerySet.
1276  
-        """
1277  
-        return self
1278  
-
1279  
-    def only(self, *fields):
1280  
-        """
1281  
-        Always returns EmptyQuerySet.
1282  
-        """
1283  
-        return self
1284  
-
1285  
-    def update(self, **kwargs):
1286  
-        """
1287  
-        Don't update anything.
1288  
-        """
1289  
-        return 0
1290  
-
1291  
-    def aggregate(self, *args, **kwargs):
1292  
-        """
1293  
-        Return a dict mapping the aggregate names to None
1294  
-        """
1295  
-        for arg in args:
1296  
-            kwargs[arg.default_alias] = arg
1297  
-        return dict([(key, None) for key in kwargs])
1298  
-
1299  
-    def values(self, *fields):
1300  
-        """
1301  
-        Always returns EmptyQuerySet.
1302  
-        """
1303  
-        return self
1304  
-
1305  
-    def values_list(self, *fields, **kwargs):
1306  
-        """
1307  
-        Always returns EmptyQuerySet.
1308  
-        """
1309  
-        return self
1310  
-
1311  
-    # EmptyQuerySet is always an empty result in where-clauses (and similar
1312  
-    # situations).
1313  
-    value_annotation = False
1314  
-
1315 1200
 def get_klass_info(klass, max_depth=0, cur_depth=0, requested=None,
1316 1201
                    only_load=None, from_parent=None):
1317 1202
     """
9  django/db/models/sql/query.py
@@ -25,7 +25,7 @@
25 25
 from django.db.models.sql.datastructures import EmptyResultSet, Empty, MultiJoin
26 26
 from django.db.models.sql.expressions import SQLEvaluator
27 27
 from django.db.models.sql.where import (WhereNode, Constraint, EverythingNode,
28  
-    ExtraWhere, AND, OR)
  28
+    ExtraWhere, AND, OR, EmptyWhere)
29 29
 from django.core.exceptions import FieldError
30 30
 
31 31
 __all__ = ['Query', 'RawQuery']
@@ -1511,6 +1511,13 @@ def split_exclude(self, filter_expr, prefix, can_reuse):
1511 1511
             self.add_filter(('%s__isnull' % trimmed_prefix, False), negate=True,
1512 1512
                     can_reuse=can_reuse)
1513 1513
 
  1514
+    def set_empty(self):
  1515
+        self.where = EmptyWhere()
  1516
+        self.having = EmptyWhere()
  1517
+
  1518
+    def is_empty(self):
  1519
+        return isinstance(self.where, EmptyWhere) or isinstance(self.having, EmptyWhere)
  1520
+
1514 1521
     def set_limits(self, low=None, high=None):
1515 1522
         """
1516 1523
         Adjusts the limits on the rows retrieved. We use low/high to set these,
8  django/db/models/sql/where.py
@@ -272,6 +272,14 @@ def relabel_aliases(self, change_map, node=None):
272 272
                 if hasattr(child[3], 'relabel_aliases'):
273 273
                     child[3].relabel_aliases(change_map)
274 274
 
  275
+class EmptyWhere(WhereNode):
  276
+
  277
+    def add(self, data, connector):
  278
+        return
  279
+
  280
+    def as_sql(self, qn=None, connection=None):
  281
+        raise EmptyResultSet
  282
+
275 283
 class EverythingNode(object):
276 284
     """
277 285
     A node that matches everything.
10  docs/ref/models/querysets.txt
@@ -593,15 +593,17 @@ none
593 593
 
594 594
 .. method:: none()
595 595
 
596  
-Returns an ``EmptyQuerySet`` — a ``QuerySet`` subclass that always evaluates to
597  
-an empty list. This can be used in cases where you know that you should return
598  
-an empty result set and your caller is expecting a ``QuerySet`` object (instead
599  
-of returning an empty list, for example.)
  596
+Calling none() will create a queryset that never returns any objects and no
  597
+query will be executed when accessing the results. A qs.none() queryset
  598
+is an instance of ``EmptyQuerySet``.
600 599
 
601 600
 Examples::
602 601
 
603 602
     >>> Entry.objects.none()
604 603
     []
  604
+    >>> from django.db.models.query import EmptyQuerySet
  605
+    >>> isinstance(Entry.objects.none(), EmptyQuerySet)
  606
+    True
605 607
 
606 608
 all
607 609
 ~~~
5  docs/releases/1.6.txt
@@ -31,6 +31,11 @@ Minor features
31 31
 Backwards incompatible changes in 1.6
32 32
 =====================================
33 33
 
  34
+* The ``django.db.models.query.EmptyQuerySet`` can't be instantiated any more -
  35
+  it is only usable as a marker class for checking if
  36
+  :meth:`~django.db.models.query.QuerySet.none` has been called:
  37
+  ``isinstance(qs.none(), EmptyQuerySet)``
  38
+
34 39
 .. warning::
35 40
 
36 41
     In addition to the changes outlined in this section, be sure to review the
7  tests/modeltests/basic/tests.py
@@ -4,6 +4,7 @@
4 4
 
5 5
 from django.core.exceptions import ObjectDoesNotExist, MultipleObjectsReturned
6 6
 from django.db.models.fields import Field, FieldDoesNotExist
  7
+from django.db.models.query import EmptyQuerySet
7 8
 from django.test import TestCase, skipIfDBFeature, skipUnlessDBFeature
8 9
 from django.utils import six
9 10
 from django.utils.translation import ugettext_lazy
@@ -639,3 +640,9 @@ def test_create_relation_with_ugettext_lazy(self):
639 640
         Article.objects.bulk_create([Article(headline=lazy, pub_date=datetime.now())])
640 641
         article = Article.objects.get()
641 642
         self.assertEqual(article.headline, notlazy)
  643
+
  644
+    def test_emptyqs(self):
  645
+        # Can't be instantiated
  646
+        with self.assertRaises(TypeError):
  647
+            EmptyQuerySet()
  648
+        self.assertTrue(isinstance(Article.objects.none(), EmptyQuerySet))
2  tests/modeltests/get_object_or_404/tests.py
@@ -53,7 +53,7 @@ def test_get_object_or_404(self):
53 53
             get_object_or_404, Author.objects.all()
54 54
         )
55 55
 
56  
-        # Using an EmptyQuerySet raises a Http404 error.
  56
+        # Using an empty QuerySet raises a Http404 error.
57 57
         self.assertRaises(Http404,
58 58
             get_object_or_404, Article.objects.none(), title__contains="Run"
59 59
         )
2  tests/modeltests/lookup/tests.py
@@ -436,7 +436,7 @@ def test_exclude(self):
436 436
             ])
437 437
 
438 438
     def test_none(self):
439  
-       # none() returns an EmptyQuerySet that behaves like any other QuerySet object
  439
+       # none() returns a QuerySet that behaves like any other QuerySet object
440 440
         self.assertQuerysetEqual(Article.objects.none(), [])
441 441
         self.assertQuerysetEqual(
442 442
             Article.objects.none().filter(headline__startswith='Article'), [])
59  tests/regressiontests/queries/tests.py
@@ -9,7 +9,7 @@
9 9
 from django.core.exceptions import FieldError
10 10
 from django.db import DatabaseError, connection, connections, DEFAULT_DB_ALIAS
11 11
 from django.db.models import Count, F, Q
12  
-from django.db.models.query import ITER_CHUNK_SIZE, EmptyQuerySet
  12
+from django.db.models.query import ITER_CHUNK_SIZE
13 13
 from django.db.models.sql.where import WhereNode, EverythingNode, NothingNode
14 14
 from django.db.models.sql.datastructures import EmptyResultSet
15 15
 from django.test import TestCase, skipUnlessDBFeature
@@ -663,31 +663,32 @@ def test_tickets_7448_7707(self):
663 663
             Item.objects.filter(created__in=[self.time1, self.time2]),
664 664
             ['<Item: one>', '<Item: two>']
665 665
         )
666  
-
667 666
     def test_ticket7235(self):
668 667
         # An EmptyQuerySet should not raise exceptions if it is filtered.
669  
-        q = EmptyQuerySet()
670  
-        self.assertQuerysetEqual(q.all(), [])
671  
-        self.assertQuerysetEqual(q.filter(x=10), [])
672  
-        self.assertQuerysetEqual(q.exclude(y=3), [])
673  
-        self.assertQuerysetEqual(q.complex_filter({'pk': 1}), [])
674  
-        self.assertQuerysetEqual(q.select_related('spam', 'eggs'), [])
675  
-        self.assertQuerysetEqual(q.annotate(Count('eggs')), [])
676  
-        self.assertQuerysetEqual(q.order_by('-pub_date', 'headline'), [])
677  
-        self.assertQuerysetEqual(q.distinct(), [])
678  
-        self.assertQuerysetEqual(
679  
-            q.extra(select={'is_recent': "pub_date > '2006-01-01'"}),
680  
-            []
681  
-        )
682  
-        q.query.low_mark = 1
683  
-        self.assertRaisesMessage(
684  
-            AssertionError,
685  
-            'Cannot change a query once a slice has been taken',
686  
-            q.extra, select={'is_recent': "pub_date > '2006-01-01'"}
687  
-        )
688  
-        self.assertQuerysetEqual(q.reverse(), [])
689  
-        self.assertQuerysetEqual(q.defer('spam', 'eggs'), [])
690  
-        self.assertQuerysetEqual(q.only('spam', 'eggs'), [])
  668
+        Eaten.objects.create(meal='m')
  669
+        q = Eaten.objects.none()
  670
+        with self.assertNumQueries(0):
  671
+            self.assertQuerysetEqual(q.all(), [])
  672
+            self.assertQuerysetEqual(q.filter(meal='m'), [])
  673
+            self.assertQuerysetEqual(q.exclude(meal='m'), [])
  674
+            self.assertQuerysetEqual(q.complex_filter({'pk': 1}), [])
  675
+            self.assertQuerysetEqual(q.select_related('food'), [])
  676
+            self.assertQuerysetEqual(q.annotate(Count('food')), [])
  677
+            self.assertQuerysetEqual(q.order_by('meal', 'food'), [])
  678
+            self.assertQuerysetEqual(q.distinct(), [])
  679
+            self.assertQuerysetEqual(
  680
+                q.extra(select={'foo': "1"}),
  681
+                []
  682
+            )
  683
+            q.query.low_mark = 1
  684
+            self.assertRaisesMessage(
  685
+                AssertionError,
  686
+                'Cannot change a query once a slice has been taken',
  687
+                q.extra, select={'foo': "1"}
  688
+            )
  689
+            self.assertQuerysetEqual(q.reverse(), [])
  690
+            self.assertQuerysetEqual(q.defer('meal'), [])
  691
+            self.assertQuerysetEqual(q.only('meal'), [])
691 692
 
692 693
     def test_ticket7791(self):
693 694
         # There were "issues" when ordering and distinct-ing on fields related
@@ -1935,8 +1936,8 @@ def test_evaluated_queryset_as_argument(self):
1935 1936
 
1936 1937
 class EmptyQuerySetTests(TestCase):
1937 1938
     def test_emptyqueryset_values(self):
1938  
-        # #14366 -- Calling .values() on an EmptyQuerySet and then cloning that
1939  
-        # should not cause an error"
  1939
+        # #14366 -- Calling .values() on an empty QuerySet and then cloning
  1940
+        # that should not cause an error
1940 1941
         self.assertQuerysetEqual(
1941 1942
             Number.objects.none().values('num').order_by('num'), []
1942 1943
         )
@@ -1952,9 +1953,9 @@ def test_values_subquery(self):
1952 1953
         )
1953 1954
 
1954 1955
     def test_ticket_19151(self):
1955  
-        # #19151 -- Calling .values() or .values_list() on an EmptyQuerySet
1956  
-        # should return EmptyQuerySet and not cause an error.
1957  
-        q = EmptyQuerySet()
  1956
+        # #19151 -- Calling .values() or .values_list() on an empty QuerySet
  1957
+        # should return an empty QuerySet and not cause an error.
  1958
+        q = Author.objects.none()
1958 1959
         self.assertQuerysetEqual(q.values(), [])
1959 1960
         self.assertQuerysetEqual(q.values_list(), [])
1960 1961
 

0 notes on commit a2396a4

Please sign in to comment.
Something went wrong with that request. Please try again.