1
0
mirror of https://github.com/django/django.git synced 2025-10-23 21:59:11 +00:00

Fixed #17886 -- Fixed join promotion in ORed nullable queries

The ORM generated a query with INNER JOIN instead of LEFT OUTER JOIN
in a somewhat complicated case. The main issue was that there was a
chain of nullable FK -> non-nullble FK, and the join promotion logic
didn't see the need to promote the non-nullable FK even if the
previous nullable FK was already promoted to LOUTER JOIN. This resulted
in a query like a LOUTER b INNER c, which incorrectly prunes results.
This commit is contained in:
Anssi Kääriäinen
2012-08-21 18:43:08 +03:00
parent fd58d6c258
commit a193372753
3 changed files with 45 additions and 2 deletions

View File

@@ -23,7 +23,7 @@ from .models import (Annotation, Article, Author, Celebrity, Child, Cover,
Ranking, Related, Report, ReservedName, Tag, TvChef, Valid, X, Food, Eaten,
Node, ObjectA, ObjectB, ObjectC, CategoryItem, SimpleCategory,
SpecialCategory, OneToOneCategory, NullableName, ProxyCategory,
SingleObject, RelatedObject)
SingleObject, RelatedObject, ModelA, ModelD)
class BaseQuerysetTest(TestCase):
@@ -2105,3 +2105,26 @@ class WhereNodeTest(TestCase):
self.assertEqual(w.as_sql(qn, connection), (None, []))
w = WhereNode(children=[empty_w, NothingNode()], connector='OR')
self.assertRaises(EmptyResultSet, w.as_sql, qn, connection)
class NullJoinPromotionOrTest(TestCase):
def setUp(self):
d = ModelD.objects.create(name='foo')
ModelA.objects.create(name='bar', d=d)
def test_ticket_17886(self):
# The first Q-object is generating the match, the rest of the filters
# should not remove the match even if they do not match anything. The
# problem here was that b__name generates a LOUTER JOIN, then
# b__c__name generates join to c, which the ORM tried to promote but
# failed as that join isn't nullable.
q_obj = (
Q(d__name='foo')|
Q(b__name='foo')|
Q(b__c__name='foo')
)
qset = ModelA.objects.filter(q_obj)
self.assertEqual(len(qset), 1)
# We generate one INNER JOIN to D. The join is direct and not nullable
# so we can use INNER JOIN for it. However, we can NOT use INNER JOIN
# for the b->c join, as a->b is nullable.
self.assertEqual(str(qset.query).count('INNER JOIN'), 1)