Opened 42 minutes ago
#37438 assigned Bug
Negated XOR conditions incorrectly use INNER JOIN for nullable relations
| Reported by: | Pravin | Owned by: | Pravin |
|---|---|---|---|
| Component: | Database layer (models, ORM) | Version: | dev |
| Severity: | Normal | Keywords: | XOR, JoinPromoter |
| Cc: | Pravin | Triage Stage: | Unreviewed |
| Has patch: | no | Needs documentation: | no |
| Needs tests: | no | Patch needs improvement: | no |
| Easy pickings: | no | UI/UX: | no |
Description
Given the following models:
class Detail(models.Model):
first = models.CharField(max_length=20)
class Item(models.Model):
detail = models.ForeignKey(
Detail,
models.CASCADE,
null=True,
)
The following query should return both items:
from django.db.models import Q
detail = Detail.objects.create(first="match")
with_detail = Item.objects.create(detail=detail)
without_detail = Item.objects.create()
items = Item.objects.filter(
~(
Q(detail__first__isnull=True)
^ Q(pk=without_detail.pk)
)
).order_by("pk")
print(list(items.values_list("pk", flat=True)))
For with_detail, detail__first__isnull and
pk=without_detail.pk are both false.
For without_detail, the missing detail produces a NULL
value and its primary key matches without_detail.pk, so both conditions
are true.
A negated XOR matches when both conditions have the same result. Therefore,
both items should be returned.
Expected result:
[with_detail.pk, without_detail.pk]
Actual result:
[with_detail.pk]
JoinPromoter doesn't handle XOR separately. For join promotion,
a negated XOR is treated as an AND, so an INNER JOIN is considered
safe. The inner join removes without_detail before the conditions are
evaluated.