Opened 3 weeks ago

Closed 10 days ago

Last modified 4 days ago

#36416 closed Bug (fixed)

id_list argument to in_bulk() does not account for composite primary keys when batching

Reported by: Jacob Walls Owned by: Jacob Walls
Component: Database layer (models, ORM) Version: 5.2
Severity: Release blocker Keywords:
Cc: Simon Charette, Csirmaz Bendegúz Triage Stage: Ready for checkin
Has patch: yes Needs documentation: no
Needs tests: no Patch needs improvement: no
Easy pickings: no UI/UX: no

Description (last modified by Jacob Walls)

The id_list argument to in_bulk() divides large lists into batches, but similar to #36118, it did not account for composite primary keys, leading to errors like this on SQLite:

django.db.utils.OperationalError: too many SQL variables

#36118 mentioned that other uses like this remained to be audited.

The id_list argument is tested, but the batching was not covered.

Failing test (I may adjust this in the PR to run faster by mocking a lower query param limit, but this shows the OperationalError):

  • tests/composite_pk/tests.py

    diff --git a/tests/composite_pk/tests.py b/tests/composite_pk/tests.py
    index 91cbee0635..ba290a796f 100644
    a b class CompositePKTests(TestCase):  
    147147        result = Comment.objects.in_bulk([self.comment.pk])
    148148        self.assertEqual(result, {self.comment.pk: self.comment})
    149149
     150    def test_in_bulk_batching(self):
     151        num_requiring_batching = (connection.features.max_query_params // 2) + 1
     152        comments = [
     153            Comment(id=i, tenant=self.tenant)
     154            for i in range(2, num_requiring_batching + 1)
     155        ]
     156        Comment.objects.bulk_create(comments)
     157        id_list = list(Comment.objects.values_list("pk", flat=True))
     158        with self.assertNumQueries(2):
     159            comment_dict = Comment.objects.in_bulk(id_list=id_list)
     160        self.assertQuerySetEqual(comment_dict, id_list)
     161
    150162    def test_iterator(self):
    151163        """
    152164        Test the .iterator() method of composite_pk models.

Change History (10)

comment:1 by Jacob Walls, 3 weeks ago

Has patch: set

comment:2 by Jacob Walls, 3 weeks ago

Description: modified (diff)

comment:3 by Natalia Bidart, 3 weeks ago

Cc: Simon Charette added

Hello Jacob, thank you for the report and for providing a test! I'm having a bit of an issue when running the test, it seems to hang and never finishes. I've waited over 15 minutes. I have printed the values used in the tests and I have:

connection.features.max_query_params=250000
num_requiring_batching=125001

while high, these do not seem impossible. Specifically, the line that hangs is:

Comment.objects.in_bulk(id_list=id_list)

I would like to understand more about what/why this is happening before accepting. Is this failing for you on main without your proposed fix? Or is it hanging as well?

comment:4 by Jacob Walls, 3 weeks ago

I left a note here that I believe the slowness is simply due to the unfixed #36380 (and I added mocking in order to avoid creating such a large batch either way). The draft test finishes for me when I apply the fix for #36380 -- sorry I didn't pull these pieces together into the ticket description.

comment:5 by Jacob Walls, 3 weeks ago

(And yes, the draft test provided with the ticket is hanging on main for me: when CTRL-C'ing out of the hang, you can see from the trace that the issue is #36380.)

The draft test on the ticket fails on 5.2.1 if you make adjust it to run:

  • provide user=self.user in the Comment(... instantiation
  • on SQLite: adjust num_requiring_batching to circa 999 to see OperationalError because below Django 6.0 max_query_params is too protective (#36143)

in reply to:  4 comment:6 by Natalia Bidart, 3 weeks ago

Cc: Csirmaz Bendegúz added
Triage Stage: UnreviewedAccepted

Replying to Jacob Walls:

I left a note here that I believe the slowness is simply due to the unfixed #36380 (and I added mocking in order to avoid creating such a large batch either way). The draft test finishes for me when I apply the fix for #36380 -- sorry I didn't pull these pieces together into the ticket description.

Thank you, I missed this. When running the given test with the fixes for #36380, I also get the django.db.utils.OperationalError: too many SQL variables.

comment:7 by Sarah Boyce, 10 days ago

Triage Stage: AcceptedReady for checkin

comment:8 by Sarah Boyce <42296566+sarahboyce@…>, 10 days ago

Resolution: fixed
Status: assignedclosed

In 26313bc:

Fixed #36416 -- Made QuerySet.in_bulk() account for composite pks in id_list.

comment:9 by Sarah Boyce <42296566+sarahboyce@…>, 10 days ago

In 2bf4c5b9:

[5.2.x] Fixed #36416 -- Made QuerySet.in_bulk() account for composite pks in id_list.

Backport of 26313bc21932d0d3af278ab387549d63b1f64575 from main.

comment:10 by nessita <124304+nessita@…>, 4 days ago

In a68e8565:

Refs #34378, #36143, #36416 -- Fixed isolation of LookupTests.test_in_bulk_preserve_ordering_with_batch_size().

max_query_params is a property, so it must be patched on the class.

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