Loading django/db/models/fields/related.py +1 −0 Original line number Diff line number Diff line Loading @@ -1767,6 +1767,7 @@ class ForeignObject(RelatedField): root_constraint.add(lookup_class(targets[0].get_col(alias, sources[0]), value), AND) elif lookup_type == 'in': values = [get_normalized_value(value) for value in raw_value] root_constraint.connector = OR for value in values: value_constraint = constraint_class() for source, target, val in zip(sources, targets, value): Loading django/db/models/sql/query.py +9 −19 Original line number Diff line number Diff line Loading @@ -24,8 +24,8 @@ from django.db.models.sql.constants import (QUERY_TERMS, ORDER_DIR, SINGLE, ORDER_PATTERN, INNER, LOUTER) from django.db.models.sql.datastructures import ( EmptyResultSet, Empty, MultiJoin, Join, BaseTable) from django.db.models.sql.where import (WhereNode, EverythingNode, ExtraWhere, AND, OR, NothingNode) from django.db.models.sql.where import (WhereNode, ExtraWhere, AND, OR, NothingNode) from django.utils import six from django.utils.deprecation import RemovedInDjango20Warning from django.utils.encoding import force_text Loading Loading @@ -526,20 +526,8 @@ class Query(object): # Now relabel a copy of the rhs where-clause and add it to the current # one. if rhs.where: w = rhs.where.clone() w.relabel_aliases(change_map) if not self.where: # Since 'self' matches everything, add an explicit "include # everything" where-constraint so that connections between the # where clauses won't exclude valid results. self.where.add(EverythingNode(), AND) elif self.where: # rhs has an empty where clause. w = self.where_class() w.add(EverythingNode(), AND) else: w = self.where_class() self.where.add(w, connector) # Selection columns and extra extensions are those provided by 'rhs'. Loading Loading @@ -1207,7 +1195,8 @@ class Query(object): # So, demotion is OK. existing_inner = set( (a for a in self.alias_map if self.alias_map[a].join_type == INNER)) clause, require_inner = self._add_q(q_object, self.used_aliases) clause, _ = self._add_q(q_object, self.used_aliases) if clause: self.where.add(clause, AND) self.demote_joins(existing_inner) Loading @@ -1233,6 +1222,7 @@ class Query(object): child, can_reuse=used_aliases, branch_negated=branch_negated, current_negated=current_negated, connector=connector, allow_joins=allow_joins) joinpromoter.add_votes(needed_inner) if child_clause: target_clause.add(child_clause, connector) needed_inner = joinpromoter.update_join_types(self) return target_clause, needed_inner Loading django/db/models/sql/where.py +17 −43 Original line number Diff line number Diff line Loading @@ -28,22 +28,22 @@ class WhereNode(tree.Node): the correct SQL). A child is usually an expression producing boolean values. Most likely the expression is a Lookup instance, but other types of objects fulfilling the required API could be used too (for example, sql.where.EverythingNode). expression is a Lookup instance. However, a child could also be any class with as_sql() and either relabeled_clone() method or relabel_aliases() and clone() methods. The second alternative should be used if the alias is not the only mutable variable. relabeled_clone() method or relabel_aliases() and clone() methods and contains_aggregate attribute. """ default = AND def split_having(self, negated=False): """ Returns two possibly None nodes: one for those parts of self that should be pushed to having and one for those parts of self that should be in where. should be included in the WHERE clause and one for those parts of self that must be included in the HAVING clause. """ if not self.contains_aggregate: return self, None in_negated = negated ^ self.negated # If the effective connector is OR and this node contains an aggregate, # then we need to push the whole branch to HAVING clause. Loading @@ -57,9 +57,9 @@ class WhereNode(tree.Node): for c in self.children: if hasattr(c, 'split_having'): where_part, having_part = c.split_having(in_negated) if where_part: if where_part is not None: where_parts.append(where_part) if having_part: if having_part is not None: having_parts.append(having_part) elif c.contains_aggregate: having_parts.append(c) Loading @@ -76,55 +76,39 @@ class WhereNode(tree.Node): None, [] if this node is empty, and raises EmptyResultSet if this node can't match anything. """ # Note that the logic here is made slightly more complex than # necessary because there are two kind of empty nodes: Nodes # containing 0 children, and nodes that are known to match everything. # A match-everything node is different than empty node (which also # technically matches everything) for backwards compatibility reasons. # Refs #5261. result = [] result_params = [] everything_childs, nothing_childs = 0, 0 non_empty_childs = len(self.children) if self.connector == AND: full_needed, empty_needed = len(self.children), 1 else: full_needed, empty_needed = 1, len(self.children) for child in self.children: try: sql, params = compiler.compile(child) except EmptyResultSet: nothing_childs += 1 empty_needed -= 1 else: if sql: result.append(sql) result_params.extend(params) else: if sql is None: # Skip empty childs totally. non_empty_childs -= 1 continue everything_childs += 1 full_needed -= 1 # Check if this node matches nothing or everything. # First check the amount of full nodes and empty nodes # to make this node empty/full. if self.connector == AND: full_needed, empty_needed = non_empty_childs, 1 else: full_needed, empty_needed = 1, non_empty_childs # Now, check if this node is full/empty using the # counts. if empty_needed - nothing_childs <= 0: if empty_needed == 0: if self.negated: return '', [] else: raise EmptyResultSet if full_needed - everything_childs <= 0: if full_needed == 0: if self.negated: raise EmptyResultSet else: return '', [] if non_empty_childs == 0: # All the child nodes were empty, so this one is empty, too. return None, [] conn = ' %s ' % self.connector sql_string = conn.join(result) if sql_string: Loading Loading @@ -186,16 +170,6 @@ class WhereNode(tree.Node): return self._contains_aggregate(self) class EverythingNode(object): """ A node that matches everything. """ contains_aggregate = False def as_sql(self, compiler=None, connection=None): return '', [] class NothingNode(object): """ A node that matches nothing. Loading tests/queries/tests.py +11 −28 Original line number Diff line number Diff line Loading @@ -9,7 +9,7 @@ import unittest from django.core.exceptions import FieldError from django.db import connection, DEFAULT_DB_ALIAS from django.db.models import Count, F, Q from django.db.models.sql.where import WhereNode, EverythingNode, NothingNode from django.db.models.sql.where import WhereNode, NothingNode from django.db.models.sql.constants import LOUTER from django.db.models.sql.datastructures import EmptyResultSet from django.test import TestCase, skipUnlessDBFeature Loading Loading @@ -2851,20 +2851,10 @@ class WhereNodeTest(TestCase): def test_empty_full_handling_conjunction(self): compiler = WhereNodeTest.MockCompiler() w = WhereNode(children=[EverythingNode()]) self.assertEqual(w.as_sql(compiler, connection), ('', [])) w.negate() self.assertRaises(EmptyResultSet, w.as_sql, compiler, connection) w = WhereNode(children=[NothingNode()]) self.assertRaises(EmptyResultSet, w.as_sql, compiler, connection) w.negate() self.assertEqual(w.as_sql(compiler, connection), ('', [])) w = WhereNode(children=[EverythingNode(), EverythingNode()]) self.assertEqual(w.as_sql(compiler, connection), ('', [])) w.negate() self.assertRaises(EmptyResultSet, w.as_sql, compiler, connection) w = WhereNode(children=[EverythingNode(), self.DummyNode()]) self.assertEqual(w.as_sql(compiler, connection), ('dummy', [])) w = WhereNode(children=[self.DummyNode(), self.DummyNode()]) self.assertEqual(w.as_sql(compiler, connection), ('(dummy AND dummy)', [])) w.negate() Loading @@ -2876,22 +2866,10 @@ class WhereNodeTest(TestCase): def test_empty_full_handling_disjunction(self): compiler = WhereNodeTest.MockCompiler() w = WhereNode(children=[EverythingNode()], connector='OR') self.assertEqual(w.as_sql(compiler, connection), ('', [])) w.negate() self.assertRaises(EmptyResultSet, w.as_sql, compiler, connection) w = WhereNode(children=[NothingNode()], connector='OR') self.assertRaises(EmptyResultSet, w.as_sql, compiler, connection) w.negate() self.assertEqual(w.as_sql(compiler, connection), ('', [])) w = WhereNode(children=[EverythingNode(), EverythingNode()], connector='OR') self.assertEqual(w.as_sql(compiler, connection), ('', [])) w.negate() self.assertRaises(EmptyResultSet, w.as_sql, compiler, connection) w = WhereNode(children=[EverythingNode(), self.DummyNode()], connector='OR') self.assertEqual(w.as_sql(compiler, connection), ('', [])) w.negate() self.assertRaises(EmptyResultSet, w.as_sql, compiler, connection) w = WhereNode(children=[self.DummyNode(), self.DummyNode()], connector='OR') self.assertEqual(w.as_sql(compiler, connection), ('(dummy OR dummy)', [])) w.negate() Loading @@ -2905,15 +2883,20 @@ class WhereNodeTest(TestCase): compiler = WhereNodeTest.MockCompiler() empty_w = WhereNode() w = WhereNode(children=[empty_w, empty_w]) self.assertEqual(w.as_sql(compiler, connection), (None, [])) self.assertEqual(w.as_sql(compiler, connection), ('', [])) w.negate() self.assertEqual(w.as_sql(compiler, connection), (None, [])) with self.assertRaises(EmptyResultSet): w.as_sql(compiler, connection) w.connector = 'OR' self.assertEqual(w.as_sql(compiler, connection), (None, [])) with self.assertRaises(EmptyResultSet): w.as_sql(compiler, connection) w.negate() self.assertEqual(w.as_sql(compiler, connection), (None, [])) self.assertEqual(w.as_sql(compiler, connection), ('', [])) w = WhereNode(children=[empty_w, NothingNode()], connector='OR') self.assertRaises(EmptyResultSet, w.as_sql, compiler, connection) self.assertEqual(w.as_sql(compiler, connection), ('', [])) w = WhereNode(children=[empty_w, NothingNode()], connector='AND') with self.assertRaises(EmptyResultSet): w.as_sql(compiler, connection) class IteratorExceptionsTest(TestCase): Loading Loading
django/db/models/fields/related.py +1 −0 Original line number Diff line number Diff line Loading @@ -1767,6 +1767,7 @@ class ForeignObject(RelatedField): root_constraint.add(lookup_class(targets[0].get_col(alias, sources[0]), value), AND) elif lookup_type == 'in': values = [get_normalized_value(value) for value in raw_value] root_constraint.connector = OR for value in values: value_constraint = constraint_class() for source, target, val in zip(sources, targets, value): Loading
django/db/models/sql/query.py +9 −19 Original line number Diff line number Diff line Loading @@ -24,8 +24,8 @@ from django.db.models.sql.constants import (QUERY_TERMS, ORDER_DIR, SINGLE, ORDER_PATTERN, INNER, LOUTER) from django.db.models.sql.datastructures import ( EmptyResultSet, Empty, MultiJoin, Join, BaseTable) from django.db.models.sql.where import (WhereNode, EverythingNode, ExtraWhere, AND, OR, NothingNode) from django.db.models.sql.where import (WhereNode, ExtraWhere, AND, OR, NothingNode) from django.utils import six from django.utils.deprecation import RemovedInDjango20Warning from django.utils.encoding import force_text Loading Loading @@ -526,20 +526,8 @@ class Query(object): # Now relabel a copy of the rhs where-clause and add it to the current # one. if rhs.where: w = rhs.where.clone() w.relabel_aliases(change_map) if not self.where: # Since 'self' matches everything, add an explicit "include # everything" where-constraint so that connections between the # where clauses won't exclude valid results. self.where.add(EverythingNode(), AND) elif self.where: # rhs has an empty where clause. w = self.where_class() w.add(EverythingNode(), AND) else: w = self.where_class() self.where.add(w, connector) # Selection columns and extra extensions are those provided by 'rhs'. Loading Loading @@ -1207,7 +1195,8 @@ class Query(object): # So, demotion is OK. existing_inner = set( (a for a in self.alias_map if self.alias_map[a].join_type == INNER)) clause, require_inner = self._add_q(q_object, self.used_aliases) clause, _ = self._add_q(q_object, self.used_aliases) if clause: self.where.add(clause, AND) self.demote_joins(existing_inner) Loading @@ -1233,6 +1222,7 @@ class Query(object): child, can_reuse=used_aliases, branch_negated=branch_negated, current_negated=current_negated, connector=connector, allow_joins=allow_joins) joinpromoter.add_votes(needed_inner) if child_clause: target_clause.add(child_clause, connector) needed_inner = joinpromoter.update_join_types(self) return target_clause, needed_inner Loading
django/db/models/sql/where.py +17 −43 Original line number Diff line number Diff line Loading @@ -28,22 +28,22 @@ class WhereNode(tree.Node): the correct SQL). A child is usually an expression producing boolean values. Most likely the expression is a Lookup instance, but other types of objects fulfilling the required API could be used too (for example, sql.where.EverythingNode). expression is a Lookup instance. However, a child could also be any class with as_sql() and either relabeled_clone() method or relabel_aliases() and clone() methods. The second alternative should be used if the alias is not the only mutable variable. relabeled_clone() method or relabel_aliases() and clone() methods and contains_aggregate attribute. """ default = AND def split_having(self, negated=False): """ Returns two possibly None nodes: one for those parts of self that should be pushed to having and one for those parts of self that should be in where. should be included in the WHERE clause and one for those parts of self that must be included in the HAVING clause. """ if not self.contains_aggregate: return self, None in_negated = negated ^ self.negated # If the effective connector is OR and this node contains an aggregate, # then we need to push the whole branch to HAVING clause. Loading @@ -57,9 +57,9 @@ class WhereNode(tree.Node): for c in self.children: if hasattr(c, 'split_having'): where_part, having_part = c.split_having(in_negated) if where_part: if where_part is not None: where_parts.append(where_part) if having_part: if having_part is not None: having_parts.append(having_part) elif c.contains_aggregate: having_parts.append(c) Loading @@ -76,55 +76,39 @@ class WhereNode(tree.Node): None, [] if this node is empty, and raises EmptyResultSet if this node can't match anything. """ # Note that the logic here is made slightly more complex than # necessary because there are two kind of empty nodes: Nodes # containing 0 children, and nodes that are known to match everything. # A match-everything node is different than empty node (which also # technically matches everything) for backwards compatibility reasons. # Refs #5261. result = [] result_params = [] everything_childs, nothing_childs = 0, 0 non_empty_childs = len(self.children) if self.connector == AND: full_needed, empty_needed = len(self.children), 1 else: full_needed, empty_needed = 1, len(self.children) for child in self.children: try: sql, params = compiler.compile(child) except EmptyResultSet: nothing_childs += 1 empty_needed -= 1 else: if sql: result.append(sql) result_params.extend(params) else: if sql is None: # Skip empty childs totally. non_empty_childs -= 1 continue everything_childs += 1 full_needed -= 1 # Check if this node matches nothing or everything. # First check the amount of full nodes and empty nodes # to make this node empty/full. if self.connector == AND: full_needed, empty_needed = non_empty_childs, 1 else: full_needed, empty_needed = 1, non_empty_childs # Now, check if this node is full/empty using the # counts. if empty_needed - nothing_childs <= 0: if empty_needed == 0: if self.negated: return '', [] else: raise EmptyResultSet if full_needed - everything_childs <= 0: if full_needed == 0: if self.negated: raise EmptyResultSet else: return '', [] if non_empty_childs == 0: # All the child nodes were empty, so this one is empty, too. return None, [] conn = ' %s ' % self.connector sql_string = conn.join(result) if sql_string: Loading Loading @@ -186,16 +170,6 @@ class WhereNode(tree.Node): return self._contains_aggregate(self) class EverythingNode(object): """ A node that matches everything. """ contains_aggregate = False def as_sql(self, compiler=None, connection=None): return '', [] class NothingNode(object): """ A node that matches nothing. Loading
tests/queries/tests.py +11 −28 Original line number Diff line number Diff line Loading @@ -9,7 +9,7 @@ import unittest from django.core.exceptions import FieldError from django.db import connection, DEFAULT_DB_ALIAS from django.db.models import Count, F, Q from django.db.models.sql.where import WhereNode, EverythingNode, NothingNode from django.db.models.sql.where import WhereNode, NothingNode from django.db.models.sql.constants import LOUTER from django.db.models.sql.datastructures import EmptyResultSet from django.test import TestCase, skipUnlessDBFeature Loading Loading @@ -2851,20 +2851,10 @@ class WhereNodeTest(TestCase): def test_empty_full_handling_conjunction(self): compiler = WhereNodeTest.MockCompiler() w = WhereNode(children=[EverythingNode()]) self.assertEqual(w.as_sql(compiler, connection), ('', [])) w.negate() self.assertRaises(EmptyResultSet, w.as_sql, compiler, connection) w = WhereNode(children=[NothingNode()]) self.assertRaises(EmptyResultSet, w.as_sql, compiler, connection) w.negate() self.assertEqual(w.as_sql(compiler, connection), ('', [])) w = WhereNode(children=[EverythingNode(), EverythingNode()]) self.assertEqual(w.as_sql(compiler, connection), ('', [])) w.negate() self.assertRaises(EmptyResultSet, w.as_sql, compiler, connection) w = WhereNode(children=[EverythingNode(), self.DummyNode()]) self.assertEqual(w.as_sql(compiler, connection), ('dummy', [])) w = WhereNode(children=[self.DummyNode(), self.DummyNode()]) self.assertEqual(w.as_sql(compiler, connection), ('(dummy AND dummy)', [])) w.negate() Loading @@ -2876,22 +2866,10 @@ class WhereNodeTest(TestCase): def test_empty_full_handling_disjunction(self): compiler = WhereNodeTest.MockCompiler() w = WhereNode(children=[EverythingNode()], connector='OR') self.assertEqual(w.as_sql(compiler, connection), ('', [])) w.negate() self.assertRaises(EmptyResultSet, w.as_sql, compiler, connection) w = WhereNode(children=[NothingNode()], connector='OR') self.assertRaises(EmptyResultSet, w.as_sql, compiler, connection) w.negate() self.assertEqual(w.as_sql(compiler, connection), ('', [])) w = WhereNode(children=[EverythingNode(), EverythingNode()], connector='OR') self.assertEqual(w.as_sql(compiler, connection), ('', [])) w.negate() self.assertRaises(EmptyResultSet, w.as_sql, compiler, connection) w = WhereNode(children=[EverythingNode(), self.DummyNode()], connector='OR') self.assertEqual(w.as_sql(compiler, connection), ('', [])) w.negate() self.assertRaises(EmptyResultSet, w.as_sql, compiler, connection) w = WhereNode(children=[self.DummyNode(), self.DummyNode()], connector='OR') self.assertEqual(w.as_sql(compiler, connection), ('(dummy OR dummy)', [])) w.negate() Loading @@ -2905,15 +2883,20 @@ class WhereNodeTest(TestCase): compiler = WhereNodeTest.MockCompiler() empty_w = WhereNode() w = WhereNode(children=[empty_w, empty_w]) self.assertEqual(w.as_sql(compiler, connection), (None, [])) self.assertEqual(w.as_sql(compiler, connection), ('', [])) w.negate() self.assertEqual(w.as_sql(compiler, connection), (None, [])) with self.assertRaises(EmptyResultSet): w.as_sql(compiler, connection) w.connector = 'OR' self.assertEqual(w.as_sql(compiler, connection), (None, [])) with self.assertRaises(EmptyResultSet): w.as_sql(compiler, connection) w.negate() self.assertEqual(w.as_sql(compiler, connection), (None, [])) self.assertEqual(w.as_sql(compiler, connection), ('', [])) w = WhereNode(children=[empty_w, NothingNode()], connector='OR') self.assertRaises(EmptyResultSet, w.as_sql, compiler, connection) self.assertEqual(w.as_sql(compiler, connection), ('', [])) w = WhereNode(children=[empty_w, NothingNode()], connector='AND') with self.assertRaises(EmptyResultSet): w.as_sql(compiler, connection) class IteratorExceptionsTest(TestCase): Loading