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