I'm working on the django-predicate library, which defines in-memory 
evaluation semantics for Q objects. The eventual goal is to precisely match 
the behavior of the ORM on the subset of supported lookup types. Yesterday, 
I noticed a bug in that library, where there was a mismatch with the 
behavior of the ORM.

De Morgan's law is a sort of distributive property for Boolean algebras, 
stating that (A ∧ B) ⇔ ¬(¬A ∨ ¬B). It has an equivalent statement for sets 
and logical predicates. However, it seems Django's Q object does *not* obey 
De Morgan's law, which was somewhat surprising.

I wanted to verify that this intended behavior. If so, does anyone know 
where this is documented or Trac tickets that discuss how & and ~ interact 
when turned into SQL queries?  

If it is not intended behavior, I'll file a Trac issue about it and we can 
continue discussion there.

Setup of example models.py

class Base(models.Model):
    class Meta:
        abstract = True
    char_value = models.CharField(max_length=100, default='')
    int_value = models.IntegerField(default=0)
    date_value = models.DateField(default=datetime.date.today)
    datetime_value = models.DateTimeField(default=datetime.datetime.now)


class TestObj(Base):
    m2ms = models.ManyToManyField(
        'testapp.M2MModel', related_name='test_objs')

class M2MModel(Base):
    pass

Example
test_obj = TestObj.objects.create()
test_obj.m2ms.create(int_value=10, char_value='foo')
test_obj.m2ms.create(int_value=20, char_value='bar')

assert test_obj not in TestObj.objects.filter(Q(m2ms__int_value=10) & 
Q(m2ms__char_value='bar'))
assert test_obj in TestObj.objects.filter(~(~Q(m2ms__int_value=10) | 
~Q(m2ms__char_value='bar')))

If De Morgan's law were obeyed, the two queries would evaluate to the same 
result.  The generated SQL is recorded in my pull request adding a test 
cases reproducing the failure in django-predicate: 
https://github.com/ptone/django-predicate/blob/30d23330e71ecc6a8f01743a43a56b406fe764ee/tests/testapp/tests.py#L215-L242

The key issue here seems to be crossing an m2m relation, which causes the 
negated disjuncts to get evaluated as subqueries.

Relevant Trac Issues

https://code.djangoproject.com/ticket/21956 Same issue, but when the fields 
occur on the same table that's being queried. This issue was apparently 
fixed in Django 1.5.
https://code.djangoproject.com/ticket/13099

Thanks,
Lucas

-- 
You received this message because you are subscribed to the Google Groups 
"Django users" group.
To unsubscribe from this group and stop receiving emails from it, send an email 
to [email protected].
To post to this group, send email to [email protected].
Visit this group at https://groups.google.com/group/django-users.
To view this discussion on the web visit 
https://groups.google.com/d/msgid/django-users/d2f97578-020d-4b7c-9d03-8e3079a18f2f%40googlegroups.com.
For more options, visit https://groups.google.com/d/optout.

Reply via email to