Commit a2396a4c authored by Anssi Kääriäinen's avatar Anssi Kääriäinen
Browse files

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.
parent a843539a
Loading
Loading
Loading
Loading
+2 −2
Original line number Diff line number Diff line
@@ -473,8 +473,8 @@ class AnonymousUser(object):
    is_staff = False
    is_active = False
    is_superuser = False
    _groups = EmptyManager()
    _user_permissions = EmptyManager()
    _groups = EmptyManager(Group)
    _user_permissions = EmptyManager(Permission)

    def __init__(self):
        pass
+6 −2
Original line number Diff line number Diff line
import copy
from django.db import router
from django.db.models.query import QuerySet, EmptyQuerySet, insert_query, RawQuerySet
from django.db.models.query import QuerySet, insert_query, RawQuerySet
from django.db.models import signals
from django.db.models.fields import FieldDoesNotExist

@@ -113,7 +113,7 @@ class Manager(object):
    #######################

    def get_empty_query_set(self):
        return EmptyQuerySet(self.model, using=self._db)
        return QuerySet(self.model, using=self._db).none()

    def get_query_set(self):
        """Returns a new QuerySet object.  Subclasses can override this method
@@ -258,5 +258,9 @@ class SwappedManagerDescriptor(object):


class EmptyManager(Manager):
    def __init__(self, model):
        super(EmptyManager, self).__init__()
        self.model = model

    def get_query_set(self):
        return self.get_empty_query_set()
+22 −137
Original line number Diff line number Diff line
@@ -35,7 +35,6 @@ class QuerySet(object):
    """
    def __init__(self, model=None, query=None, using=None):
        self.model = model
        # EmptyQuerySet instantiates QuerySet with model as None
        self._db = using
        self.query = query or sql.Query(self.model)
        self._result_cache = None
@@ -217,7 +216,9 @@ class QuerySet(object):
    def __and__(self, other):
        self._merge_sanity_check(other)
        if isinstance(other, EmptyQuerySet):
            return other._clone()
            return other
        if isinstance(self, EmptyQuerySet):
            return self
        combined = self._clone()
        combined._merge_known_related_objects(other)
        combined.query.combine(other.query, sql.AND)
@@ -225,9 +226,11 @@ class QuerySet(object):

    def __or__(self, other):
        self._merge_sanity_check(other)
        combined = self._clone()
        if isinstance(self, EmptyQuerySet):
            return other
        if isinstance(other, EmptyQuerySet):
            return combined
            return self
        combined = self._clone()
        combined._merge_known_related_objects(other)
        combined.query.combine(other.query, sql.OR)
        return combined
@@ -632,7 +635,9 @@ class QuerySet(object):
        """
        Returns an empty QuerySet.
        """
        return self._clone(klass=EmptyQuerySet)
        clone = self._clone()
        clone.query.set_empty()
        return clone

    ##################################################################
    # PUBLIC METHODS THAT ALTER ATTRIBUTES AND RETURN A NEW QUERYSET #
@@ -981,6 +986,18 @@ class QuerySet(object):
    # empty" result.
    value_annotation = True

class InstanceCheckMeta(type):
    def __instancecheck__(self, instance):
        return instance.query.is_empty()

class EmptyQuerySet(six.with_metaclass(InstanceCheckMeta), object):
    """
    Marker class usable for checking if a queryset is empty by .none():
        isinstance(qs.none(), EmptyQuerySet) -> True
    """

    def __init__(self, *args, **kwargs):
        raise TypeError("EmptyQuerySet can't be instantiated")

class ValuesQuerySet(QuerySet):
    def __init__(self, *args, **kwargs):
@@ -1180,138 +1197,6 @@ class DateQuerySet(QuerySet):
        return c


class EmptyQuerySet(QuerySet):
    def __init__(self, model=None, query=None, using=None):
        super(EmptyQuerySet, self).__init__(model, query, using)
        self._result_cache = []

    def __and__(self, other):
        return self._clone()

    def __or__(self, other):
        return other._clone()

    def count(self):
        return 0

    def delete(self):
        pass

    def _clone(self, klass=None, setup=False, **kwargs):
        c = super(EmptyQuerySet, self)._clone(klass, setup=setup, **kwargs)
        c._result_cache = []
        return c

    def iterator(self):
        # This slightly odd construction is because we need an empty generator
        # (it raises StopIteration immediately).
        yield next(iter([]))

    def all(self):
        """
        Always returns EmptyQuerySet.
        """
        return self

    def filter(self, *args, **kwargs):
        """
        Always returns EmptyQuerySet.
        """
        return self

    def exclude(self, *args, **kwargs):
        """
        Always returns EmptyQuerySet.
        """
        return self

    def complex_filter(self, filter_obj):
        """
        Always returns EmptyQuerySet.
        """
        return self

    def select_related(self, *fields, **kwargs):
        """
        Always returns EmptyQuerySet.
        """
        return self

    def annotate(self, *args, **kwargs):
        """
        Always returns EmptyQuerySet.
        """
        return self

    def order_by(self, *field_names):
        """
        Always returns EmptyQuerySet.
        """
        return self

    def distinct(self, fields=None):
        """
        Always returns EmptyQuerySet.
        """
        return self

    def extra(self, select=None, where=None, params=None, tables=None,
              order_by=None, select_params=None):
        """
        Always returns EmptyQuerySet.
        """
        assert self.query.can_filter(), \
                "Cannot change a query once a slice has been taken"
        return self

    def reverse(self):
        """
        Always returns EmptyQuerySet.
        """
        return self

    def defer(self, *fields):
        """
        Always returns EmptyQuerySet.
        """
        return self

    def only(self, *fields):
        """
        Always returns EmptyQuerySet.
        """
        return self

    def update(self, **kwargs):
        """
        Don't update anything.
        """
        return 0

    def aggregate(self, *args, **kwargs):
        """
        Return a dict mapping the aggregate names to None
        """
        for arg in args:
            kwargs[arg.default_alias] = arg
        return dict([(key, None) for key in kwargs])

    def values(self, *fields):
        """
        Always returns EmptyQuerySet.
        """
        return self

    def values_list(self, *fields, **kwargs):
        """
        Always returns EmptyQuerySet.
        """
        return self

    # EmptyQuerySet is always an empty result in where-clauses (and similar
    # situations).
    value_annotation = False

def get_klass_info(klass, max_depth=0, cur_depth=0, requested=None,
                   only_load=None, from_parent=None):
    """
+8 −1
Original line number Diff line number Diff line
@@ -25,7 +25,7 @@ from django.db.models.sql.constants import (QUERY_TERMS, ORDER_DIR, SINGLE,
from django.db.models.sql.datastructures import EmptyResultSet, Empty, MultiJoin
from django.db.models.sql.expressions import SQLEvaluator
from django.db.models.sql.where import (WhereNode, Constraint, EverythingNode,
    ExtraWhere, AND, OR)
    ExtraWhere, AND, OR, EmptyWhere)
from django.core.exceptions import FieldError

__all__ = ['Query', 'RawQuery']
@@ -1511,6 +1511,13 @@ class Query(object):
            self.add_filter(('%s__isnull' % trimmed_prefix, False), negate=True,
                    can_reuse=can_reuse)

    def set_empty(self):
        self.where = EmptyWhere()
        self.having = EmptyWhere()

    def is_empty(self):
        return isinstance(self.where, EmptyWhere) or isinstance(self.having, EmptyWhere)

    def set_limits(self, low=None, high=None):
        """
        Adjusts the limits on the rows retrieved. We use low/high to set these,
+8 −0
Original line number Diff line number Diff line
@@ -272,6 +272,14 @@ class WhereNode(tree.Node):
                if hasattr(child[3], 'relabel_aliases'):
                    child[3].relabel_aliases(change_map)

class EmptyWhere(WhereNode):

    def add(self, data, connector):
        return

    def as_sql(self, qn=None, connection=None):
        raise EmptyResultSet

class EverythingNode(object):
    """
    A node that matches everything.
Loading