summaryrefslogtreecommitdiff
path: root/django
diff options
context:
space:
mode:
authorAnssi Kääriäinen <akaariai@gmail.com>2014-01-08 19:35:47 +0200
committerAnssi Kääriäinen <akaariai@gmail.com>2014-02-04 18:48:06 +0200
commit35cecb1ebd0ccda0be7a518d1b7273333d26fbae (patch)
tree80bf011330ecea76aaebaf4660e28bf9f8032098 /django
parent867f8fd6c7f204769f2c72d1684b5c8c220aa5b4 (diff)
Fixed #21748 -- join promotion for negated AND conditions
Made sure Django treats case .filter(NOT (a AND b)) the same way as .filter((NOT a OR NOT b)) for join promotion.
Diffstat (limited to 'django')
-rw-r--r--django/db/models/sql/query.py24
1 files changed, 16 insertions, 8 deletions
diff --git a/django/db/models/sql/query.py b/django/db/models/sql/query.py
index 838c2914d9..79a4459e2f 100644
--- a/django/db/models/sql/query.py
+++ b/django/db/models/sql/query.py
@@ -483,7 +483,7 @@ class Query(object):
# Base table must be present in the query - this is the same
# table on both sides.
self.get_initial_alias()
- joinpromoter = JoinPromoter(connector, 2)
+ joinpromoter = JoinPromoter(connector, 2, False)
joinpromoter.add_votes(
j for j in self.alias_map if self.alias_map[j].join_type == self.INNER)
rhs_votes = set()
@@ -1299,11 +1299,9 @@ class Query(object):
connector = q_object.connector
current_negated = current_negated ^ q_object.negated
branch_negated = branch_negated or q_object.negated
- # Note that if the connector happens to match what we have already in
- # the tree, the add will be a no-op.
target_clause = self.where_class(connector=connector,
negated=q_object.negated)
- joinpromoter = JoinPromoter(q_object.connector, len(q_object.children))
+ joinpromoter = JoinPromoter(q_object.connector, len(q_object.children), current_negated)
for child in q_object.children:
if isinstance(child, Node):
child_clause, needed_inner = self._add_q(
@@ -2013,8 +2011,16 @@ class JoinPromoter(object):
conditions.
"""
- def __init__(self, connector, num_children):
+ def __init__(self, connector, num_children, negated):
self.connector = connector
+ self.negated = negated
+ if self.negated:
+ if connector == AND:
+ self.effective_connector = OR
+ else:
+ self.effective_connector = AND
+ else:
+ self.effective_connector = self.connector
self.num_children = num_children
# Maps of table alias to how many times it is seen as required for
# inner and/or outer joins.
@@ -2038,6 +2044,8 @@ class JoinPromoter(object):
"""
to_promote = set()
to_demote = set()
+ # The effective_connector is used so that NOT (a AND b) is treated
+ # similarly to (a OR b) for join promotion.
for table, votes in self.inner_votes.items():
# We must use outer joins in OR case when the join isn't contained
# in all of the joins. Otherwise the INNER JOIN itself could remove
@@ -2049,7 +2057,7 @@ class JoinPromoter(object):
# to rel_a would remove a valid match from the query. So, we need
# to promote any existing INNER to LOUTER (it is possible this
# promotion in turn will be demoted later on).
- if self.connector == 'OR' and votes < self.num_children:
+ if self.effective_connector == 'OR' and votes < self.num_children:
to_promote.add(table)
# If connector is AND and there is a filter that can match only
# when there is a joinable row, then use INNER. For example, in
@@ -2061,8 +2069,8 @@ class JoinPromoter(object):
# (rel_a__col__icontains=Alex | rel_a__col__icontains=Russell)
# then if rel_a doesn't produce any rows, the whole condition
# can't match. Hence we can safely use INNER join.
- if self.connector == 'AND' or (self.connector == 'OR' and
- votes == self.num_children):
+ if self.effective_connector == 'AND' or (
+ self.effective_connector == 'OR' and votes == self.num_children):
to_demote.add(table)
# Finally, what happens in cases where we have:
# (rel_a__col=1|rel_b__col=2) & rel_a__col__gte=0