Code

Ticket #10790: ticket10790.diff

File ticket10790.diff, 5.9 KB (added by lrekucki, 2 years ago)

Rebased patch.

Line 
1diff --git a/django/db/models/sql/compiler.py b/django/db/models/sql/compiler.py
2index 72948f9..d940c97 100644
3--- a/django/db/models/sql/compiler.py
4+++ b/django/db/models/sql/compiler.py
5@@ -456,7 +456,7 @@ class SQLCompiler(object):
6         """
7         if not alias:
8             alias = self.query.get_initial_alias()
9-        field, target, opts, joins, _, _ = self.query.setup_joins(pieces,
10+        field, target, opts, joins, _, _, _ = self.query.setup_joins(pieces,
11                 opts, alias, False)
12         alias = joins[-1]
13         col = target.column
14diff --git a/django/db/models/sql/expressions.py b/django/db/models/sql/expressions.py
15index 1bbf742..af1ed63 100644
16--- a/django/db/models/sql/expressions.py
17+++ b/django/db/models/sql/expressions.py
18@@ -44,7 +44,7 @@ class SQLEvaluator(object):
19             self.cols[node] = query.aggregate_select[node.name]
20         else:
21             try:
22-                field, source, opts, join_list, last, _ = query.setup_joins(
23+                field, source, opts, join_list, last, _, _ = query.setup_joins(
24                     field_list, query.get_meta(),
25                     query.get_initial_alias(), False)
26                 col, _, join_list = query.trim_joins(source, join_list, last, False)
27diff --git a/django/db/models/sql/query.py b/django/db/models/sql/query.py
28index a78df34..630a270 100644
29--- a/django/db/models/sql/query.py
30+++ b/django/db/models/sql/query.py
31@@ -700,6 +700,14 @@ class Query(object):
32             return True
33         return False
34 
35+    def demote_alias(self, alias):
36+        """
37+        Demotes the join type of an alias to an inner join.
38+        """
39+        data = list(self.alias_map[alias])
40+        data[JOIN_TYPE] = self.INNER
41+        self.alias_map[alias] = tuple(data)
42+
43     def promote_alias_chain(self, chain, must_promote=False):
44         """
45         Walks along a chain of aliases, promoting the first nullable join and
46@@ -1007,7 +1015,7 @@ class Query(object):
47             #   - this is an annotation over a model field
48             # then we need to explore the joins that are required.
49 
50-            field, source, opts, join_list, last, _ = self.setup_joins(
51+            field, source, opts, join_list, last, _, _ = self.setup_joins(
52                 field_list, opts, self.get_initial_alias(), False)
53 
54             # Process the join chain to see if it can be trimmed
55@@ -1119,7 +1127,7 @@ class Query(object):
56         allow_many = trim or not negate
57 
58         try:
59-            field, target, opts, join_list, last, extra_filters = self.setup_joins(
60+            field, target, opts, join_list, last, extra_filters, allow_trim_join = self.setup_joins(
61                     parts, opts, alias, True, allow_many, allow_explicit_fk=True,
62                     can_reuse=can_reuse, negate=negate,
63                     process_extras=process_extras)
64@@ -1139,6 +1147,13 @@ class Query(object):
65             self.promote_alias_chain(join_list)
66             join_promote = True
67 
68+        # If we have a one2one or many2one field, we can trim the left outer
69+        # join from the end of a list of joins.
70+        # In order to do this, we convert alias join type back to INNER and
71+        # trim_joins later will do the strip for us.
72+        if allow_trim_join and field.rel:
73+            self.demote_alias(join_list[-1])
74+
75         # Process the join list to see if we can remove any inner joins from
76         # the far end (fewer tables in a query is better).
77         nonnull_comparison = (lookup_type == 'isnull' and value is False)
78@@ -1295,6 +1310,7 @@ class Query(object):
79         dupe_set = set()
80         exclusions = set()
81         extra_filters = []
82+        allow_trim_join = True
83         int_alias = None
84         for pos, name in enumerate(names):
85             if int_alias is not None:
86@@ -1318,6 +1334,11 @@ class Query(object):
87                     raise FieldError("Cannot resolve keyword %r into field. "
88                             "Choices are: %s" % (name, ", ".join(names)))
89 
90+            # presence of indirect field in the filter requires
91+            # left outer join for isnull
92+            if not direct and allow_trim_join:
93+                allow_trim_join = False
94+
95             if not allow_many and (m2m or not direct):
96                 for alias in joins:
97                     self.unref_alias(alias)
98@@ -1359,6 +1380,8 @@ class Query(object):
99                 extra_filters.extend(field.extra_filters(names, pos, negate))
100             if direct:
101                 if m2m:
102+                    # null query on m2mfield requires outer join
103+                    allow_trim_join = False
104                     # Many-to-many field defined on the current model.
105                     if cached_data:
106                         (table1, from_col1, to_col1, table2, from_col2,
107@@ -1479,7 +1502,7 @@ class Query(object):
108             else:
109                 raise FieldError("Join on field %r not permitted." % name)
110 
111-        return field, target, opts, joins, last, extra_filters
112+        return field, target, opts, joins, last, extra_filters, allow_trim_join
113 
114     def trim_joins(self, target, join_list, last, trim, nonnull_check=False):
115         """
116@@ -1648,7 +1671,7 @@ class Query(object):
117 
118         try:
119             for name in field_names:
120-                field, target, u2, joins, u3, u4 = self.setup_joins(
121+                field, target, u2, joins, u3, u4, allow_trim_join = self.setup_joins(
122                         name.split(LOOKUP_SEP), opts, alias, False, allow_m2m,
123                         True)
124                 final_alias = joins[-1]
125@@ -1930,7 +1953,7 @@ class Query(object):
126         """
127         opts = self.model._meta
128         alias = self.get_initial_alias()
129-        field, col, opts, joins, last, extra = self.setup_joins(
130+        field, col, opts, joins, last, extra, allow_trim_join = self.setup_joins(
131                 start.split(LOOKUP_SEP), opts, alias, False)
132         select_col = self.alias_map[joins[1]][LHS_JOIN_COL]
133         select_alias = alias