#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.

Change History (0)

Note: See TracTickets for help on using tickets.
Back to Top