Fixed #3283 -- Added support for empty QuerySets via none() method. Thanks for the patch, medhat

git-svn-id: http://code.djangoproject.com/svn/django/trunk@4394 bcc190cf-cafb-0310-a4f2-bffc1f526a37
This commit is contained in:
Adrian Holovaty 2007-01-23 02:11:08 +00:00
parent 31bdd8d720
commit 13280259a8
4 changed files with 97 additions and 16 deletions

View File

@ -1,4 +1,4 @@
from django.db.models.query import QuerySet from django.db.models.query import QuerySet, EmptyQuerySet
from django.dispatch import dispatcher from django.dispatch import dispatcher
from django.db.models import signals from django.db.models import signals
from django.db.models.fields import FieldDoesNotExist from django.db.models.fields import FieldDoesNotExist
@ -42,12 +42,18 @@ class Manager(object):
# PROXIES TO QUERYSET # # PROXIES TO QUERYSET #
####################### #######################
def get_empty_query_set(self):
return EmptyQuerySet(self.model)
def get_query_set(self): def get_query_set(self):
"""Returns a new QuerySet object. Subclasses can override this method """Returns a new QuerySet object. Subclasses can override this method
to easily customise the behaviour of the Manager. to easily customise the behaviour of the Manager.
""" """
return QuerySet(self.model) return QuerySet(self.model)
def none(self):
return self.get_empty_query_set()
def all(self): def all(self):
return self.get_query_set() return self.get_query_set()

View File

@ -25,6 +25,9 @@ QUERY_TERMS = (
# Larger values are slightly faster at the expense of more storage space. # Larger values are slightly faster at the expense of more storage space.
GET_ITERATOR_CHUNK_SIZE = 100 GET_ITERATOR_CHUNK_SIZE = 100
class EmptyResultSet(Exception):
pass
#################### ####################
# HELPER FUNCTIONS # # HELPER FUNCTIONS #
#################### ####################
@ -168,7 +171,12 @@ class QuerySet(object):
extra_select = self._select.items() extra_select = self._select.items()
cursor = connection.cursor() cursor = connection.cursor()
select, sql, params = self._get_sql_clause()
try:
select, sql, params = self._get_sql_clause()
except EmptyResultSet:
raise StopIteration
cursor.execute("SELECT " + (self._distinct and "DISTINCT " or "") + ",".join(select) + sql, params) cursor.execute("SELECT " + (self._distinct and "DISTINCT " or "") + ",".join(select) + sql, params)
fill_cache = self._select_related fill_cache = self._select_related
index_end = len(self.model._meta.fields) index_end = len(self.model._meta.fields)
@ -192,7 +200,12 @@ class QuerySet(object):
counter._offset = None counter._offset = None
counter._limit = None counter._limit = None
counter._select_related = False counter._select_related = False
select, sql, params = counter._get_sql_clause()
try:
select, sql, params = counter._get_sql_clause()
except EmptyResultSet:
return 0
cursor = connection.cursor() cursor = connection.cursor()
if self._distinct: if self._distinct:
id_col = "%s.%s" % (backend.quote_name(self.model._meta.db_table), id_col = "%s.%s" % (backend.quote_name(self.model._meta.db_table),
@ -523,7 +536,12 @@ class ValuesQuerySet(QuerySet):
field_names = [f.attname for f in self.model._meta.fields] field_names = [f.attname for f in self.model._meta.fields]
cursor = connection.cursor() cursor = connection.cursor()
select, sql, params = self._get_sql_clause()
try:
select, sql, params = self._get_sql_clause()
except EmptyResultSet:
raise StopIteration
select = ['%s.%s' % (backend.quote_name(self.model._meta.db_table), backend.quote_name(c)) for c in columns] select = ['%s.%s' % (backend.quote_name(self.model._meta.db_table), backend.quote_name(c)) for c in columns]
cursor.execute("SELECT " + (self._distinct and "DISTINCT " or "") + ",".join(select) + sql, params) cursor.execute("SELECT " + (self._distinct and "DISTINCT " or "") + ",".join(select) + sql, params)
while 1: while 1:
@ -545,7 +563,12 @@ class DateQuerySet(QuerySet):
if self._field.null: if self._field.null:
self._where.append('%s.%s IS NOT NULL' % \ self._where.append('%s.%s IS NOT NULL' % \
(backend.quote_name(self.model._meta.db_table), backend.quote_name(self._field.column))) (backend.quote_name(self.model._meta.db_table), backend.quote_name(self._field.column)))
select, sql, params = self._get_sql_clause()
try:
select, sql, params = self._get_sql_clause()
except EmptyResultSet:
raise StopIteration
sql = 'SELECT %s %s GROUP BY 1 ORDER BY 1 %s' % \ sql = 'SELECT %s %s GROUP BY 1 ORDER BY 1 %s' % \
(backend.get_date_trunc_sql(self._kind, '%s.%s' % (backend.quote_name(self.model._meta.db_table), (backend.get_date_trunc_sql(self._kind, '%s.%s' % (backend.quote_name(self.model._meta.db_table),
backend.quote_name(self._field.column))), sql, self._order) backend.quote_name(self._field.column))), sql, self._order)
@ -563,6 +586,25 @@ class DateQuerySet(QuerySet):
c._order = self._order c._order = self._order
return c return c
class EmptyQuerySet(QuerySet):
def __init__(self, model=None):
super(EmptyQuerySet, self).__init__(model)
self._result_cache = []
def iterator(self):
raise StopIteration
def count(self):
return 0
def delete(self):
pass
def _clone(self, klass=None, **kwargs):
c = super(EmptyQuerySet, self)._clone(klass, **kwargs)
c._result_cache = []
return c
class QOperator(object): class QOperator(object):
"Base class for QAnd and QOr" "Base class for QAnd and QOr"
def __init__(self, *args): def __init__(self, *args):
@ -571,10 +613,14 @@ class QOperator(object):
def get_sql(self, opts): def get_sql(self, opts):
joins, where, params = SortedDict(), [], [] joins, where, params = SortedDict(), [], []
for val in self.args: for val in self.args:
joins2, where2, params2 = val.get_sql(opts) try:
joins.update(joins2) joins2, where2, params2 = val.get_sql(opts)
where.extend(where2) joins.update(joins2)
params.extend(params2) where.extend(where2)
params.extend(params2)
except EmptyResultSet:
if not isinstance(self, QOr):
raise EmptyResultSet
if where: if where:
return joins, ['(%s)' % self.operator.join(where)], params return joins, ['(%s)' % self.operator.join(where)], params
return joins, [], params return joins, [], params
@ -628,8 +674,11 @@ class QNot(Q):
self.q = q self.q = q
def get_sql(self, opts): def get_sql(self, opts):
joins, where, params = self.q.get_sql(opts) try:
where2 = ['(NOT (%s))' % " AND ".join(where)] joins, where, params = self.q.get_sql(opts)
where2 = ['(NOT (%s))' % " AND ".join(where)]
except EmptyResultSet:
return SortedDict(), [], []
return joins, where2, params return joins, where2, params
def get_where_clause(lookup_type, table_prefix, field_name, value): 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):
if in_string: if in_string:
return '%s%s IN (%s)' % (table_prefix, field_name, in_string) return '%s%s IN (%s)' % (table_prefix, field_name, in_string)
else: else:
# Most backends do not accept an empty string inside the IN raise EmptyResultSet
# expression, i.e. cannot do "WHERE ... IN ()". Since there are
# also some backends that do not accept "WHERE false", we instead
# use an expression that always evaluates to False.
return '0=1'
elif lookup_type == 'range': elif lookup_type == 'range':
return '%s%s BETWEEN %%s AND %%s' % (table_prefix, field_name) return '%s%s BETWEEN %%s AND %%s' % (table_prefix, field_name)
elif lookup_type in ('year', 'month', 'day'): elif lookup_type in ('year', 'month', 'day'):

View File

@ -526,6 +526,21 @@ Examples::
>>> Entry.objects.filter(headline__contains='Lennon').dates('pub_date', 'day') >>> Entry.objects.filter(headline__contains='Lennon').dates('pub_date', 'day')
[datetime.datetime(2005, 3, 20)] [datetime.datetime(2005, 3, 20)]
``none()``
~~~~~~~~~~
**New in Django development version**
Returns an ``EmptyQuerySet`` -- a ``QuerySet`` that always evaluates to
an empty list. This can be used in cases where you know that you should
return an empty result set and your caller is expecting a ``QuerySet``
object (instead of returning an empty list, for example.)
Examples::
>>> Entry.objects.none()
[]
``select_related()`` ``select_related()``
~~~~~~~~~~~~~~~~~~~~ ~~~~~~~~~~~~~~~~~~~~

View File

@ -191,4 +191,19 @@ DoesNotExist: Article matching query does not exist.
>>> Article.objects.filter(headline__contains='\\') >>> Article.objects.filter(headline__contains='\\')
[<Article: Article with \ backslash>] [<Article: Article with \ backslash>]
# none() returns an EmptyQuerySet that behaves like any other QuerySet object
>>> Article.objects.none()
[]
>>> Article.objects.none().filter(headline__startswith='Article')
[]
>>> Article.objects.none().count()
0
# using __in with an empty list should return an empty query set
>>> Article.objects.filter(id__in=[])
[]
>>> Article.objects.exclude(id__in=[])
[<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>]
"""} """}