Skip to content
This repository

HTTPS clone URL

Subversion checkout URL

You can clone with HTTPS or Subversion.

Download ZIP
Browse code

Fixed #3283 -- Added support for empty QuerySets via none() method. T…

…hanks for the patch, medhat

git-svn-id: http://code.djangoproject.com/svn/django/trunk@4394 bcc190cf-cafb-0310-a4f2-bffc1f526a37
  • Loading branch information...
commit 13280259a8f3ce8c462dc4b484efc922d60666de 1 parent 31bdd8d
Adrian Holovaty authored January 23, 2007
8  django/db/models/manager.py
... ...
@@ -1,4 +1,4 @@
1  
-from django.db.models.query import QuerySet
  1
+from django.db.models.query import QuerySet, EmptyQuerySet
2 2
 from django.dispatch import dispatcher
3 3
 from django.db.models import signals
4 4
 from django.db.models.fields import FieldDoesNotExist
@@ -41,12 +41,18 @@ def contribute_to_class(self, model, name):
41 41
     #######################
42 42
     # PROXIES TO QUERYSET #
43 43
     #######################
  44
+    
  45
+    def get_empty_query_set(self):
  46
+        return EmptyQuerySet(self.model)
44 47
 
45 48
     def get_query_set(self):
46 49
         """Returns a new QuerySet object.  Subclasses can override this method
47 50
         to easily customise the behaviour of the Manager.
48 51
         """
49 52
         return QuerySet(self.model)
  53
+    
  54
+    def none(self):
  55
+        return self.get_empty_query_set()
50 56
 
51 57
     def all(self):
52 58
         return self.get_query_set()
75  django/db/models/query.py
@@ -25,6 +25,9 @@
25 25
 # Larger values are slightly faster at the expense of more storage space.
26 26
 GET_ITERATOR_CHUNK_SIZE = 100
27 27
 
  28
+class EmptyResultSet(Exception):
  29
+    pass
  30
+
28 31
 ####################
29 32
 # HELPER FUNCTIONS #
30 33
 ####################
@@ -168,7 +171,12 @@ def iterator(self):
168 171
         extra_select = self._select.items()
169 172
 
170 173
         cursor = connection.cursor()
171  
-        select, sql, params = self._get_sql_clause()
  174
+        
  175
+        try:
  176
+            select, sql, params = self._get_sql_clause()
  177
+        except EmptyResultSet:
  178
+            raise StopIteration
  179
+            
172 180
         cursor.execute("SELECT " + (self._distinct and "DISTINCT " or "") + ",".join(select) + sql, params)
173 181
         fill_cache = self._select_related
174 182
         index_end = len(self.model._meta.fields)
@@ -192,7 +200,12 @@ def count(self):
192 200
         counter._offset = None
193 201
         counter._limit = None
194 202
         counter._select_related = False
195  
-        select, sql, params = counter._get_sql_clause()
  203
+        
  204
+        try:
  205
+            select, sql, params = counter._get_sql_clause()
  206
+        except EmptyResultSet:
  207
+            return 0
  208
+            
196 209
         cursor = connection.cursor()
197 210
         if self._distinct:
198 211
             id_col = "%s.%s" % (backend.quote_name(self.model._meta.db_table),
@@ -523,7 +536,12 @@ def iterator(self):
523 536
             field_names = [f.attname for f in self.model._meta.fields]
524 537
 
525 538
         cursor = connection.cursor()
526  
-        select, sql, params = self._get_sql_clause()
  539
+        
  540
+        try:
  541
+            select, sql, params = self._get_sql_clause()
  542
+        except EmptyResultSet:
  543
+            raise StopIteration
  544
+        
527 545
         select = ['%s.%s' % (backend.quote_name(self.model._meta.db_table), backend.quote_name(c)) for c in columns]
528 546
         cursor.execute("SELECT " + (self._distinct and "DISTINCT " or "") + ",".join(select) + sql, params)
529 547
         while 1:
@@ -545,7 +563,12 @@ def iterator(self):
545 563
         if self._field.null:
546 564
             self._where.append('%s.%s IS NOT NULL' % \
547 565
                 (backend.quote_name(self.model._meta.db_table), backend.quote_name(self._field.column)))
548  
-        select, sql, params = self._get_sql_clause()
  566
+                
  567
+        try:
  568
+            select, sql, params = self._get_sql_clause()
  569
+        except EmptyResultSet:
  570
+            raise StopIteration
  571
+        
549 572
         sql = 'SELECT %s %s GROUP BY 1 ORDER BY 1 %s' % \
550 573
             (backend.get_date_trunc_sql(self._kind, '%s.%s' % (backend.quote_name(self.model._meta.db_table),
551 574
             backend.quote_name(self._field.column))), sql, self._order)
@@ -562,6 +585,25 @@ def _clone(self, klass=None, **kwargs):
562 585
         c._kind = self._kind
563 586
         c._order = self._order
564 587
         return c
  588
+    
  589
+class EmptyQuerySet(QuerySet):
  590
+    def __init__(self, model=None):
  591
+        super(EmptyQuerySet, self).__init__(model)
  592
+        self._result_cache = []
  593
+        
  594
+    def iterator(self):
  595
+        raise StopIteration
  596
+        
  597
+    def count(self):
  598
+        return 0
  599
+        
  600
+    def delete(self):
  601
+        pass
  602
+
  603
+    def _clone(self, klass=None, **kwargs):
  604
+        c = super(EmptyQuerySet, self)._clone(klass, **kwargs)
  605
+        c._result_cache = []
  606
+        return c
565 607
 
566 608
 class QOperator(object):
567 609
     "Base class for QAnd and QOr"
@@ -571,10 +613,14 @@ def __init__(self, *args):
571 613
     def get_sql(self, opts):
572 614
         joins, where, params = SortedDict(), [], []
573 615
         for val in self.args:
574  
-            joins2, where2, params2 = val.get_sql(opts)
575  
-            joins.update(joins2)
576  
-            where.extend(where2)
577  
-            params.extend(params2)
  616
+            try:
  617
+                joins2, where2, params2 = val.get_sql(opts)
  618
+                joins.update(joins2)
  619
+                where.extend(where2)
  620
+                params.extend(params2)
  621
+            except EmptyResultSet:
  622
+                if not isinstance(self, QOr):
  623
+                    raise EmptyResultSet
578 624
         if where:
579 625
             return joins, ['(%s)' % self.operator.join(where)], params
580 626
         return joins, [], params
@@ -628,8 +674,11 @@ def __init__(self, q):
628 674
         self.q = q
629 675
 
630 676
     def get_sql(self, opts):
631  
-        joins, where, params = self.q.get_sql(opts)
632  
-        where2 = ['(NOT (%s))' % " AND ".join(where)]
  677
+        try:
  678
+            joins, where, params = self.q.get_sql(opts)
  679
+            where2 = ['(NOT (%s))' % " AND ".join(where)]
  680
+        except EmptyResultSet:
  681
+            return SortedDict(), [], []
633 682
         return joins, where2, params
634 683
 
635 684
 def get_where_clause(lookup_type, table_prefix, field_name, value):
@@ -645,11 +694,7 @@ def get_where_clause(lookup_type, table_prefix, field_name, value):
645 694
         if in_string:
646 695
             return '%s%s IN (%s)' % (table_prefix, field_name, in_string)
647 696
         else:
648  
-            # Most backends do not accept an empty string inside the IN
649  
-            # expression, i.e. cannot do "WHERE ... IN ()".  Since there are
650  
-            # also some backends that do not accept "WHERE false", we instead
651  
-            # use an expression that always evaluates to False.
652  
-            return '0=1'
  697
+            raise EmptyResultSet
653 698
     elif lookup_type == 'range':
654 699
         return '%s%s BETWEEN %%s AND %%s' % (table_prefix, field_name)
655 700
     elif lookup_type in ('year', 'month', 'day'):
15  docs/db-api.txt
@@ -525,6 +525,21 @@ Examples::
525 525
     [datetime.datetime(2005, 3, 20), datetime.datetime(2005, 2, 20)]
526 526
     >>> Entry.objects.filter(headline__contains='Lennon').dates('pub_date', 'day')
527 527
     [datetime.datetime(2005, 3, 20)]
  528
+    
  529
+``none()``
  530
+~~~~~~~~~~
  531
+
  532
+**New in Django development version**
  533
+
  534
+Returns an ``EmptyQuerySet`` -- a ``QuerySet`` that always evaluates to 
  535
+an empty list. This can be used in cases where you know that you should
  536
+return an empty result set and your caller is expecting a ``QuerySet``
  537
+object (instead of returning an empty list, for example.)
  538
+
  539
+Examples::
  540
+    
  541
+    >>> Entry.objects.none()
  542
+    []
528 543
 
529 544
 ``select_related()``
530 545
 ~~~~~~~~~~~~~~~~~~~~
15  tests/modeltests/lookup/models.py
@@ -191,4 +191,19 @@ def __str__(self):
191 191
 >>> Article.objects.filter(headline__contains='\\')
192 192
 [<Article: Article with \ backslash>]
193 193
 
  194
+# none() returns an EmptyQuerySet that behaves like any other QuerySet object
  195
+>>> Article.objects.none()
  196
+[]
  197
+>>> Article.objects.none().filter(headline__startswith='Article')
  198
+[]
  199
+>>> Article.objects.none().count()
  200
+0
  201
+
  202
+# using __in with an empty list should return an empty query set
  203
+>>> Article.objects.filter(id__in=[])
  204
+[]
  205
+
  206
+>>> Article.objects.exclude(id__in=[])
  207
+[<Article: Article with \ backslash>, <Article: Article% with percent sign>, <Article: Article_ with underscore>, <Article: Article 5>, <Article: Article 6>, <Article: Article 4>, <Article: Article 2>, <Article: Article 3>, <Article: Article 7>, <Article: Article 1>]
  208
+
194 209
 """}

0 notes on commit 1328025

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