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