mirror of https://github.com/django/django.git
Fixed #23797 -- Fixed QuerySet.exclude() when rhs is a nullable column.
This commit is contained in:
parent
b7b7df5fbc
commit
512da9d585
1
AUTHORS
1
AUTHORS
|
@ -392,6 +392,7 @@ answer newbie questions, and generally made Django that much better:
|
||||||
Jacob Burch <jacobburch@gmail.com>
|
Jacob Burch <jacobburch@gmail.com>
|
||||||
Jacob Green
|
Jacob Green
|
||||||
Jacob Kaplan-Moss <jacob@jacobian.org>
|
Jacob Kaplan-Moss <jacob@jacobian.org>
|
||||||
|
Jacob Walls <http://www.jacobtylerwalls.com/>
|
||||||
Jakub Paczkowski <jakub@paczkowski.eu>
|
Jakub Paczkowski <jakub@paczkowski.eu>
|
||||||
Jakub Wilk <jwilk@jwilk.net>
|
Jakub Wilk <jwilk@jwilk.net>
|
||||||
Jakub Wiśniowski <restless.being@gmail.com>
|
Jakub Wiśniowski <restless.being@gmail.com>
|
||||||
|
|
|
@ -1324,9 +1324,7 @@ class Query(BaseExpression):
|
||||||
require_outer = lookup_type == 'isnull' and condition.rhs is True and not current_negated
|
require_outer = lookup_type == 'isnull' and condition.rhs is True and not current_negated
|
||||||
if current_negated and (lookup_type != 'isnull' or condition.rhs is False) and condition.rhs is not None:
|
if current_negated and (lookup_type != 'isnull' or condition.rhs is False) and condition.rhs is not None:
|
||||||
require_outer = True
|
require_outer = True
|
||||||
if (lookup_type != 'isnull' and (
|
if lookup_type != 'isnull':
|
||||||
self.is_nullable(targets[0]) or
|
|
||||||
self.alias_map[join_list[-1]].join_type == LOUTER)):
|
|
||||||
# The condition added here will be SQL like this:
|
# The condition added here will be SQL like this:
|
||||||
# NOT (col IS NOT NULL), where the first NOT is added in
|
# NOT (col IS NOT NULL), where the first NOT is added in
|
||||||
# upper layers of code. The reason for addition is that if col
|
# upper layers of code. The reason for addition is that if col
|
||||||
|
@ -1336,9 +1334,18 @@ class Query(BaseExpression):
|
||||||
# (col IS NULL OR col != someval)
|
# (col IS NULL OR col != someval)
|
||||||
# <=>
|
# <=>
|
||||||
# NOT (col IS NOT NULL AND col = someval).
|
# NOT (col IS NOT NULL AND col = someval).
|
||||||
|
if (
|
||||||
|
self.is_nullable(targets[0]) or
|
||||||
|
self.alias_map[join_list[-1]].join_type == LOUTER
|
||||||
|
):
|
||||||
lookup_class = targets[0].get_lookup('isnull')
|
lookup_class = targets[0].get_lookup('isnull')
|
||||||
col = self._get_col(targets[0], join_info.targets[0], alias)
|
col = self._get_col(targets[0], join_info.targets[0], alias)
|
||||||
clause.add(lookup_class(col, False), AND)
|
clause.add(lookup_class(col, False), AND)
|
||||||
|
# If someval is a nullable column, someval IS NOT NULL is
|
||||||
|
# added.
|
||||||
|
if isinstance(value, Col) and self.is_nullable(value.target):
|
||||||
|
lookup_class = value.target.get_lookup('isnull')
|
||||||
|
clause.add(lookup_class(value, False), AND)
|
||||||
return clause, used_joins if not require_outer else ()
|
return clause, used_joins if not require_outer else ()
|
||||||
|
|
||||||
def add_filter(self, filter_clause):
|
def add_filter(self, filter_clause):
|
||||||
|
|
|
@ -142,6 +142,7 @@ class Cover(models.Model):
|
||||||
class Number(models.Model):
|
class Number(models.Model):
|
||||||
num = models.IntegerField()
|
num = models.IntegerField()
|
||||||
other_num = models.IntegerField(null=True)
|
other_num = models.IntegerField(null=True)
|
||||||
|
another_num = models.IntegerField(null=True)
|
||||||
|
|
||||||
def __str__(self):
|
def __str__(self):
|
||||||
return str(self.num)
|
return str(self.num)
|
||||||
|
|
|
@ -2372,7 +2372,10 @@ class ValuesQuerysetTests(TestCase):
|
||||||
qs = Number.objects.extra(select={'num2': 'num+1'}).annotate(Count('id'))
|
qs = Number.objects.extra(select={'num2': 'num+1'}).annotate(Count('id'))
|
||||||
values = qs.values_list(named=True).first()
|
values = qs.values_list(named=True).first()
|
||||||
self.assertEqual(type(values).__name__, 'Row')
|
self.assertEqual(type(values).__name__, 'Row')
|
||||||
self.assertEqual(values._fields, ('num2', 'id', 'num', 'other_num', 'id__count'))
|
self.assertEqual(
|
||||||
|
values._fields,
|
||||||
|
('num2', 'id', 'num', 'other_num', 'another_num', 'id__count'),
|
||||||
|
)
|
||||||
self.assertEqual(values.num, 72)
|
self.assertEqual(values.num, 72)
|
||||||
self.assertEqual(values.num2, 73)
|
self.assertEqual(values.num2, 73)
|
||||||
self.assertEqual(values.id__count, 1)
|
self.assertEqual(values.id__count, 1)
|
||||||
|
@ -2855,6 +2858,18 @@ class ExcludeTests(TestCase):
|
||||||
self.r1.delete()
|
self.r1.delete()
|
||||||
self.assertFalse(qs.exists())
|
self.assertFalse(qs.exists())
|
||||||
|
|
||||||
|
def test_exclude_nullable_fields(self):
|
||||||
|
number = Number.objects.create(num=1, other_num=1)
|
||||||
|
Number.objects.create(num=2, other_num=2, another_num=2)
|
||||||
|
self.assertSequenceEqual(
|
||||||
|
Number.objects.exclude(other_num=F('another_num')),
|
||||||
|
[number],
|
||||||
|
)
|
||||||
|
self.assertSequenceEqual(
|
||||||
|
Number.objects.exclude(num=F('another_num')),
|
||||||
|
[number],
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
class ExcludeTest17600(TestCase):
|
class ExcludeTest17600(TestCase):
|
||||||
"""
|
"""
|
||||||
|
|
Loading…
Reference in New Issue