diff --git a/django/db/models/sql/query.py b/django/db/models/sql/query.py index 550e6e323c..e6d653a46e 100644 --- a/django/db/models/sql/query.py +++ b/django/db/models/sql/query.py @@ -1197,14 +1197,21 @@ class BaseQuery(object): col = target.column alias = join_list[-1] + if (lookup_type == 'isnull' and value is True and not negate and + len(join_list) > 1): + # If the comparison is against NULL, we may need to use some left + # outer joins when creating the join chain. This is only done when + # needed, as it's less efficient at the database level. + self.promote_alias_chain(join_list) + while final > 1: - # An optimization: if the final join is against the same column as - # we are comparing against, we can go back one step in the join - # chain and compare against the lhs of the join instead (and then - # repeat the optimization). The result, potentially, involves less - # table joins. + # An optimization: if the final join is an inner join against the + # same column as we are comparing against, we can go back one step + # in the join chain and compare against the lhs of the join instead + # (and then repeat the optimization). The result, potentially, + # involves less table joins. join = self.alias_map[alias] - if col != join[RHS_JOIN_COL]: + if col != join[RHS_JOIN_COL] or join[JOIN_TYPE] != self.INNER: break self.unref_alias(alias) alias = join[LHS_ALIAS] @@ -1214,14 +1221,6 @@ class BaseQuery(object): if final == penultimate: penultimate = last.pop() - if (lookup_type == 'isnull' and value is True and not negate and - final > 1): - # If the comparison is against NULL, we need to use a left outer - # join when connecting to the previous model. We make that - # adjustment here. We don't do this unless needed as it's less - # efficient at the database level. - self.promote_alias(join_list[penultimate]) - if connector == OR: # Some joins may need to be promoted when adding a new filter to a # disjunction. We walk the list of new joins and where it diverges diff --git a/tests/regressiontests/null_queries/models.py b/tests/regressiontests/null_queries/models.py index df04f14eba..7cde0aac61 100644 --- a/tests/regressiontests/null_queries/models.py +++ b/tests/regressiontests/null_queries/models.py @@ -13,6 +13,17 @@ class Choice(models.Model): def __unicode__(self): return u"Choice: %s in poll %s" % (self.choice, self.poll) +# A set of models with an inner one pointing to two outer ones. +class OuterA(models.Model): + pass + +class OuterB(models.Model): + data = models.CharField(max_length=10) + +class Inner(models.Model): + first = models.ForeignKey(OuterA) + second = models.ForeignKey(OuterB, null=True) + __test__ = {'API_TESTS':""" # Regression test for the use of None as a query value. None is interpreted as # an SQL NULL, but only in __exact queries. @@ -56,4 +67,16 @@ ValueError: Cannot use None as a query value >>> p2.choice_set.all() [] +# Querying across reverse relations and then another relation should insert +# outer joins correctly so as not to exclude results. +>>> obj = OuterA.objects.create() +>>> OuterA.objects.filter(inner__second=None) +[<OuterA: OuterA object>] +>>> OuterA.objects.filter(inner__second__data=None) +[<OuterA: OuterA object>] +>>> _ = Inner.objects.create(first=obj) +>>> Inner.objects.filter(first__inner__second=None) +[<Inner: Inner object>] + + """} diff --git a/tests/regressiontests/one_to_one_regress/models.py b/tests/regressiontests/one_to_one_regress/models.py index 66d00d7223..5c89c55f45 100644 --- a/tests/regressiontests/one_to_one_regress/models.py +++ b/tests/regressiontests/one_to_one_regress/models.py @@ -33,6 +33,15 @@ class Favorites(models.Model): def __unicode__(self): return u"Favorites for %s" % self.name +class Target(models.Model): + pass + +class Pointer(models.Model): + other = models.OneToOneField(Target, primary_key=True) + +class Pointer2(models.Model): + other = models.OneToOneField(Target) + __test__ = {'API_TESTS':""" # Regression test for #1064 and #1506: Check that we create models via the m2m # relation if the remote model has a OneToOneField. @@ -119,4 +128,17 @@ False >>> r.place == p True +# Regression test for #9968: filtering reverse one-to-one relations with +# primary_key=True was misbehaving. We test both (primary_key=True & False) +# cases here to prevent any reappearance of the problem. +>>> _ = Target.objects.create() +>>> Target.objects.filter(pointer=None) +[<Target: Target object>] +>>> Target.objects.exclude(pointer=None) +[] +>>> Target.objects.filter(pointer2=None) +[<Target: Target object>] +>>> Target.objects.exclude(pointer2=None) +[] + """}