#26522: Bug in django.db.models.sql.Query.combine
----------------------------------------------+--------------------
     Reporter:  OleLaursen                    |      Owner:  nobody
         Type:  Bug                           |     Status:  new
    Component:  Database layer (models, ORM)  |    Version:  1.9
     Severity:  Normal                        |   Keywords:
 Triage Stage:  Unreviewed                    |  Has patch:  0
Easy pickings:  0                             |      UI/UX:  0
----------------------------------------------+--------------------
 Hi,

 There's a logical error somewhere in Query.combine or friends.

 I have a pretty complicated query with many-to-many self-joins and ORs,
 and after the upgrade to Django 1.9 and Python 3 combine() sometimes, but
 not always, crashes with the assertion error
 {{{
 assert set(change_map.keys()).intersection(set(change_map.values())) ==
 set()
 }}}

 Inspecting the change_map, it does indeed contain a circular reference
 (BTW shouldn't you use "not" to test for an empty set rather than
 comparing with set()?).

 At first, I didn't understand how it could crash sometimes and sometimes
 not - we're talking about the same query being executed through a view.
 But when I examine change_map, its content does indeed change from reload
 to reload - I suppose this may have something to do with the order of
 dicts/sets the combine() algorithm is using.

 Here comes some code, I cut out a bunch of unused stuff, but otherwise
 it's the same:

 {{{
 class Invoice(models.Model):
     customer = models.ForeignKey(Customer)

     date_created = models.DateField(default=datetime.date.today,
 db_index=True)
     date_sent = models.DateField(null=True, blank=True)
     date_due = models.DateField(null=True, blank=True)
     date_paid = models.DateField(null=True, blank=True)
     date_credited = models.DateField(null=True, blank=True)
     date_collect = models.DateField(null=True, blank=True)

     invoice_type = models.CharField(default="invoice", max_length=32)

     reminders = models.ManyToManyField("Invoice",
 related_name="reminded_set", blank=True)
     reminder_counter = models.IntegerField(null=True, blank=True)
 }}}

 That's the model, and in the view:

 {{{
     import datetime
     from django.db.models import Q

     date = datetime.datetime.now()

     invoices = Invoice.objects.filter(
         Q(date_created__lte=date),
         Q(date_paid__gt=date) | Q(date_paid=None),
         Q(date_credited__gt=date) | Q(date_credited=None),
         customer=1,
     )

     filtered_invoices = Invoice.objects.none()

     not_due = Q(date_due__gte=date) | Q(date_due=None)
     not_reminded_yet = ~Q(reminders__date_created__lte=date)
     not_collected = Q(date_collect__gt=date) | Q(date_collect=None)

     filtered_invoices |= invoices.filter(not_due, not_collected,
 date_sent__lte=date, invoice_type="invoice")

     filtered_invoices |= invoices.filter(not_collected, not_reminded_yet,
 date_sent__lte=date, date_due__lt=date, invoice_type="invoice")

     for r in [1, 2, 3]:
         qs = invoices.filter(not_collected,
 reminders__date_created__lte=date, reminders__reminder_counter=r,
 invoice_type="invoice")
         for i in range(r + 1, 3 + 1):
             qs = qs.filter(~Q(reminders__reminder_counter=i) |
 Q(reminders__reminder_counter=i, reminders__date_created__gt=date))
         filtered_invoices |= qs
 }}}

 I realize it's pretty complicated but I'm not sure what's essential and
 what's not. I hope someone knowledgeable of how combine() works can figure
 out why ordering in some cases matters.

--
Ticket URL: <https://code.djangoproject.com/ticket/26522>
Django <https://code.djangoproject.com/>
The Web framework for perfectionists with deadlines.

-- 
You received this message because you are subscribed to the Google Groups 
"Django updates" group.
To unsubscribe from this group and stop receiving emails from it, send an email 
to django-updates+unsubscr...@googlegroups.com.
To post to this group, send email to django-updates@googlegroups.com.
To view this discussion on the web visit 
https://groups.google.com/d/msgid/django-updates/053.e26f982bf27f4f832b4918a50d863d01%40djangoproject.com.
For more options, visit https://groups.google.com/d/optout.

Reply via email to