Ticket #3283: empty_queryset_3.patch

File empty_queryset_3.patch, 7.9 KB (added by medhat, 18 years ago)

Yet another version of the patch that fixes a problem with QNot (with tests)

  • django/db/models/manager.py

     
    1 from django.db.models.query import QuerySet
     1from django.db.models.query import QuerySet, EmptyQuerySet
    22from django.dispatch import dispatcher
    33from django.db.models import signals
    44from django.db.models.fields import FieldDoesNotExist
     
    4141    #######################
    4242    # PROXIES TO QUERYSET #
    4343    #######################
     44   
     45    def get_empty_query_set(self):
     46        return EmptyQuerySet(self.model)
    4447
    4548    def get_query_set(self):
    4649        """Returns a new QuerySet object.  Subclasses can override this method
    4750        to easily customise the behaviour of the Manager.
    4851        """
    4952        return QuerySet(self.model)
     53   
     54    def none(self):
     55        return self.get_empty_query_set()
    5056
    5157    def all(self):
    5258        return self.get_query_set()
  • django/db/models/query.py

     
    2525# Larger values are slightly faster at the expense of more storage space.
    2626GET_ITERATOR_CHUNK_SIZE = 100
    2727
     28class EmptyResultSet(Exception):
     29    pass
     30
    2831####################
    2932# HELPER FUNCTIONS #
    3033####################
     
    168171        extra_select = self._select.items()
    169172
    170173        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           
    172180        cursor.execute("SELECT " + (self._distinct and "DISTINCT " or "") + ",".join(select) + sql, params)
    173181        fill_cache = self._select_related
    174182        index_end = len(self.model._meta.fields)
     
    192200        counter._offset = None
    193201        counter._limit = None
    194202        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           
    196209        cursor = connection.cursor()
    197210        if self._distinct:
    198211            id_col = "%s.%s" % (backend.quote_name(self.model._meta.db_table),
     
    523536            field_names = [f.attname for f in self.model._meta.fields]
    524537
    525538        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       
    527545        select = ['%s.%s' % (backend.quote_name(self.model._meta.db_table), backend.quote_name(c)) for c in columns]
    528546        cursor.execute("SELECT " + (self._distinct and "DISTINCT " or "") + ",".join(select) + sql, params)
    529547        while 1:
     
    545563        if self._field.null:
    546564            self._where.append('%s.%s IS NOT NULL' % \
    547565                (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       
    549572        sql = 'SELECT %s %s GROUP BY 1 ORDER BY 1 %s' % \
    550573            (backend.get_date_trunc_sql(self._kind, '%s.%s' % (backend.quote_name(self.model._meta.db_table),
    551574            backend.quote_name(self._field.column))), sql, self._order)
     
    562585        c._kind = self._kind
    563586        c._order = self._order
    564587        return c
     588   
     589class 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
    565602
     603    def _clone(self, klass=None, **kwargs):
     604        c = super(EmptyQuerySet, self)._clone(klass, **kwargs)
     605        c._result_cache = []
     606        return c
     607
    566608class QOperator(object):
    567609    "Base class for QAnd and QOr"
    568610    def __init__(self, *args):
     
    571613    def get_sql(self, opts):
    572614        joins, where, params = SortedDict(), [], []
    573615        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
    578624        if where:
    579625            return joins, ['(%s)' % self.operator.join(where)], params
    580626        return joins, [], params
     
    628674        self.q = q
    629675
    630676    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(), [], []
    633682        return joins, where2, params
    634683
    635684def get_where_clause(lookup_type, table_prefix, field_name, value):
     
    645694        if in_string:
    646695            return '%s%s IN (%s)' % (table_prefix, field_name, in_string)
    647696        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
    653698    elif lookup_type == 'range':
    654699        return '%s%s BETWEEN %%s AND %%s' % (table_prefix, field_name)
    655700    elif lookup_type in ('year', 'month', 'day'):
  • docs/db-api.txt

     
    525525    [datetime.datetime(2005, 3, 20), datetime.datetime(2005, 2, 20)]
    526526    >>> Entry.objects.filter(headline__contains='Lennon').dates('pub_date', 'day')
    527527    [datetime.datetime(2005, 3, 20)]
     528   
     529``none()``
     530~~~~~~~~~~
    528531
     532Returns an ``EmptyQuerySet`` -- a ``QuerySet`` that always evaluates to
     533an empty list. This can be used in cases where you know that you should
     534return an empty result set and your caller is expecting a ``QuerySet``
     535object (instead of returning an empty list, for example.)
     536
     537Examples::
     538   
     539    >>> Entry.objects.none()
     540    []
     541
    529542``select_related()``
    530543~~~~~~~~~~~~~~~~~~~~
    531544
  • tests/modeltests/lookup/models.py

     
    191191>>> Article.objects.filter(headline__contains='\\')
    192192[<Article: Article with \ backslash>]
    193193
     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()
     2000
     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
    194209"""}
Back to Top