diff --git a/django/db/models/sql/compiler.py b/django/db/models/sql/compiler.py index 0e483dc4f649..14603d57733e 100644 --- a/django/db/models/sql/compiler.py +++ b/django/db/models/sql/compiler.py @@ -228,6 +228,13 @@ def collapse_group_by(self, expressions, having): ] return expressions + @classmethod + def get_select_from_parent(cls, klass_info): + for ki in klass_info["related_klass_infos"]: + if ki["from_parent"]: + ki["select_fields"] = klass_info["select_fields"] + ki["select_fields"] + cls.get_select_from_parent(ki) + def get_select(self, with_col_aliases=False): """ Return three values: @@ -300,15 +307,7 @@ def get_select(self, with_col_aliases=False): related_klass_infos = self.get_related_selections(select, select_mask) klass_info["related_klass_infos"] = related_klass_infos - def get_select_from_parent(klass_info): - for ki in klass_info["related_klass_infos"]: - if ki["from_parent"]: - ki["select_fields"] = ( - klass_info["select_fields"] + ki["select_fields"] - ) - get_select_from_parent(ki) - - get_select_from_parent(klass_info) + self.get_select_from_parent(klass_info) ret = [] col_idx = 1 diff --git a/tests/bulk_create/tests.py b/tests/bulk_create/tests.py index 8ab918fff57d..7b77ffed3d0c 100644 --- a/tests/bulk_create/tests.py +++ b/tests/bulk_create/tests.py @@ -889,19 +889,27 @@ def test_db_default_primary_key(self): class BulkCreateTransactionTests(TransactionTestCase): available_apps = ["bulk_create"] + def get_unused_country_id(self): + # Find a serial ID that hasn't been used already and has enough of a + # buffer for the following `bulk_create` call without an explicit pk + # not to conflict. + return getattr(Country.objects.last(), "id", 10) + 100 + def test_no_unnecessary_transaction(self): + unused_id = self.get_unused_country_id() with self.assertNumQueries(1): Country.objects.bulk_create( - [Country(id=1, name="France", iso_two_letter="FR")] + [Country(id=unused_id, name="France", iso_two_letter="FR")] ) with self.assertNumQueries(1): Country.objects.bulk_create([Country(name="Canada", iso_two_letter="CA")]) def test_objs_with_and_without_pk(self): + unused_id = self.get_unused_country_id() with self.assertNumQueries(4): Country.objects.bulk_create( [ - Country(id=10, name="France", iso_two_letter="FR"), + Country(id=unused_id, name="France", iso_two_letter="FR"), Country(name="Canada", iso_two_letter="CA"), ] ) diff --git a/tests/select_related/tests.py b/tests/select_related/tests.py index 41ed350cf3da..59d1270aa069 100644 --- a/tests/select_related/tests.py +++ b/tests/select_related/tests.py @@ -1,6 +1,9 @@ +import gc + from django.core.exceptions import FieldError from django.db.models import FETCH_PEERS from django.test import SimpleTestCase, TestCase +from django.test.utils import garbage_collect from .models import ( Bookmark, @@ -57,6 +60,17 @@ def setUpTestData(cls): "Amanita muscaria" ) + def setup_gc_debug(self): + self.addCleanup(gc.set_debug, 0) + self.addCleanup(gc.enable) + gc.disable() + garbage_collect() + gc.set_debug(gc.DEBUG_SAVEALL) + + def assert_no_memory_leaks(self): + garbage_collect() + self.assertEqual(gc.garbage, []) + def test_access_fks_without_select_related(self): """ Normally, accessing FKs doesn't fill in related objects @@ -128,6 +142,11 @@ def test_select_related_with_extra(self): ) self.assertEqual(s.id + 10, s.a) + def test_select_related_memory_leak(self): + self.setup_gc_debug() + list(Species.objects.select_related("genus")) + self.assert_no_memory_leaks() + def test_certain_fields(self): """ The optional fields passed to select_related() control which related