Opened 9 years ago

Closed 7 years ago

#3288 closed defect (fixed)

ForeignKey to 'self' and select_related() = infinite recursion

Reported by: Max Derkachev <mderk@…> Owned by: PhiR
Component: Database layer (models, ORM) Version: master
Severity: major Keywords: qs-rf-fixed
Cc: Triage Stage: Accepted
Has patch: yes Needs documentation: no
Needs tests: no Patch needs improvement: yes
Easy pickings: UI/UX:

Description

Maybe it's a variant of #3045 bug.
The [simplified] case is

class Foo(models.Model):
    parent = models.ForeignKey('self')
    
    class Meta:
       db_table = 'foos'

foo = Foo.objects.select_related().get(pk=1)

That leads to infinite recursion in django.db.models.query.fill_table_cache. The bottom of the stack trace is:

  ........ many many lines ............
  File "/home/maxim/dproject/django/db/models/query.py", line 684, in fill_table_cache
    fill_table_cache(f.rel.to._meta, select, tables, where, db_table, cache_tables_seen)
  File "/home/maxim/dproject/django/db/models/query.py", line 684, in fill_table_cache
    fill_table_cache(f.rel.to._meta, select, tables, where, db_table, cache_tables_seen)
  File "/home/maxim/dproject/django/db/models/query.py", line 681, in fill_table_cache
    where.append('%s.%s = %s.%s' % \
  File "/home/maxim/dproject/django/db/models/fields/related.py", line 737, in get_related_field
    return self.to._meta.get_field(self.field_name)
  File "/home/maxim/dproject/django/db/models/options.py", line 98, in get_field
    if f.name == name:
RuntimeError: maximum recursion depth exceeded in cmp

Attachments (2)

3288.diff (3.4 KB) - added by PhiR 8 years ago.
fix and tests
3288_96.diff (3.5 KB) - added by PhiR 8 years ago.
fix and tests, backported to 0.96

Download all attachments as: .zip

Change History (16)

comment:1 Changed 9 years ago by mir@…

  • Triage Stage changed from Unreviewed to Accepted

I could reproduce the bug. The db_table setting is not necessary. Funny, it really works (as reported) without any data in the table for foo.

I'm closing #3045 in favour of this ticket since it appears to be the same bug, and #3288 is a lot clearer. But if we have a patch for #3288, please test it also for #3045! The conditions for #3045 are a bit different and more complicated.

comment:2 Changed 9 years ago by mir@…

this is a special case of bug #2885, which I've marked as duplicate of this one.

comment:3 Changed 8 years ago by PhiR

  • Has patch set
  • Keywords qs-rf added
  • Owner changed from nobody to PhiR
  • Status changed from new to assigned
  • Version set to SVN

comment:4 Changed 8 years ago by PhiR

although this is marked qs-rf, it is a very simple patch and it can also be applied to 96! The regression test should go in the trunk so qs-rf will be tested against it whenever it is merged.

comment:5 Changed 8 years ago by PhiR

special case of #2549.

Changed 8 years ago by PhiR

fix and tests

Changed 8 years ago by PhiR

fix and tests, backported to 0.96

comment:6 Changed 8 years ago by PhiR

  • Triage Stage changed from Accepted to Ready for checkin

It should be noted that the attached patches also fix #2549 and #3045 at the same time. The regression tests cover both their cases (FK cycle) and ours (FK to self). I'm adding patches even though this is marked qs-rf because this is a nasty bug and 0.96 users might want to have it. Also we might as well put the reg tests in trunk so this issue isn't replicated by qs-rf.

comment:7 Changed 8 years ago by pkenjora

  • Patch needs improvement set

I applied 3288_96.diff with no effect. My model is:

class ProjectImage(models.Model):

project = models.ForeignKey("Project", null=True, edit_inline=True, core=True)
title = models.CharField(maxlength=50, blank=True)
description = models.TextField(blank=True)
image = models.ImageField(upload_to='project', height_field="100", width_field="100", blank=False)
faqs = models.ManyToManyField(Faq, blank=True)
categories = models.ManyToManyField(Category)

class Project(models.Model):

title = models.CharField(maxlength=50)
description = models.TextField()
pub_date = models.DateTimeField(auto_now_add=True)
icon = models.ForeignKey(ProjectImage, edit_inline=True, core=True, related_name='icon')
parts = models.ManyToManyField(Part)
cars = models.ManyToManyField(Car)
faqs = models.ManyToManyField(Faq, blank=True)

The patch does not break any functionality but I still encounter the same error.

Exception Type: RuntimeError
Exception Value: maximum recursion depth exceeded in cmp
Exception Location: /home/patrickm/lib/python2.4/django/db/models/fields/init.py in get_follow, line 328

comment:8 Changed 8 years ago by pkenjora

Adding edit_inline=True seems to break the patch.

In above example make error go away by changing:

project = models.ForeignKey("Project", null=True, edit_inline=True, core=True)

to:

project = models.ForeignKey("Project", null=True, core=True)

That resolved the issue if it persists after patch is applied.

comment:9 Changed 8 years ago by mtredinnick

(In [6521]) queryset-refactor: Fixed a possibility of shooting oneself in the foot and
creating infinite recursion with select_related(). Refs #3045, #3288.

comment:10 Changed 8 years ago by mtredinnick

  • Keywords qs-rf-fixed added; qs-rf removed
  • Triage Stage changed from Ready for checkin to Accepted

comment:11 Changed 8 years ago by alfred.einstein@…

Dunno if the same bug but I got something similar with this:

class Test(models.Model):
	recursion_loop = models.ForeignKey('self',edit_inline=models.TABULAR)

this is the output

  File "/home/visi/djtrunk/django/db/models/related.py", line 99, in get_follow
    return self.opts.get_follow(over)
  File "/home/visi/djtrunk/django/db/models/options.py", line 177, in get_follow
    fol = f.get_follow(child_override)
  File "/home/visi/djtrunk/django/db/models/related.py", line 99, in get_follow
    return self.opts.get_follow(over)
  File "/home/visi/djtrunk/django/db/models/options.py", line 177, in get_follow
    fol = f.get_follow(child_override)
  File "/home/visi/djtrunk/django/db/models/related.py", line 99, in get_follow
    return self.opts.get_follow(over)
  File "/home/visi/djtrunk/django/db/models/options.py", line 177, in get_follow
    fol = f.get_follow(child_override)
  File "/home/visi/djtrunk/django/db/models/related.py", line 99, in get_follow
    return self.opts.get_follow(over)
  File "/home/visi/djtrunk/django/db/models/options.py", line 177, in get_follow
    fol = f.get_follow(child_override)
  File "/home/visi/djtrunk/django/db/models/fields/__init__.py", line 372, in get_follow
    if override != None:
RuntimeError: maximum recursion depth exceeded in cmp

comment:12 Changed 8 years ago by ramiro

Marked #6250 as duplicate

comment:13 Changed 7 years ago by ljernejcic@…

I wanted to note that this error popups for OneToOneFields: location_category = models.OneToOneField('self', blank=True)

comment:14 Changed 7 years ago by mtredinnick

  • Resolution set to fixed
  • Status changed from assigned to closed

(In [7477]) Merged the queryset-refactor branch into trunk.

This is a big internal change, but mostly backwards compatible with existing
code. Also adds a couple of new features.

Fixed #245, #1050, #1656, #1801, #2076, #2091, #2150, #2253, #2306, #2400, #2430, #2482, #2496, #2676, #2737, #2874, #2902, #2939, #3037, #3141, #3288, #3440, #3592, #3739, #4088, #4260, #4289, #4306, #4358, #4464, #4510, #4858, #5012, #5020, #5261, #5295, #5321, #5324, #5325, #5555, #5707, #5796, #5817, #5987, #6018, #6074, #6088, #6154, #6177, #6180, #6203, #6658

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