Opened 3 months ago
Last modified 6 weeks ago
#36550 assigned Bug
AssertionError raised when bulk creation model with primary key set via pre_save (e.g. auto_now_add) on databases with can_return_columns_from_insert
| Reported by: | Will-Ruddick | Owned by: | JaeHyuckSa |
|---|---|---|---|
| Component: | Database layer (models, ORM) | Version: | dev |
| Severity: | Normal | Keywords: | |
| Cc: | Simon Charette | Triage Stage: | Accepted |
| Has patch: | yes | Needs documentation: | no |
| Needs tests: | no | Patch needs improvement: | yes |
| Easy pickings: | no | UI/UX: | no |
Description (last modified by )
I have a model that looks something like this
class CompositeModel(models.Model):
pk = models.CompositePrimaryKey("other_model", "the_time", "created_at")
other_model = models.ForeignKey("test.OtherModel", on_delete=models.CASCADE)
the_time = models.DateTimeField()
created_at = models.DateTimeField(auto_now_add=True)
And when I try and bulk create it in the code I get the following exception.
CompositeModel.objects.bulk_create(new_items)
File "django/db/models/manager.py", line 87, in manager_method
return getattr(self.get_queryset(), name)(*args, **kwargs)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
File "django/db/models/query.py", line 836, in bulk_create
assert len(returned_columns) == len(objs_without_pk)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
AssertionError
Where returned_columns is an empty list and objs_without_pk is a list of the objects I'm trying to create.
If I use ignore_conflicts=True then everything works fine and the objects are created properly in the DB. I think this is a bug, but it could be user error.
This is happening on version 5.2.5 with a postgres DB
Attachments (1)
Change History (18)
comment:1 by , 3 months ago
| Description: | modified (diff) |
|---|
comment:2 by , 3 months ago
| Type: | Uncategorized → Bug |
|---|
comment:3 by , 3 months ago
| Severity: | Normal → Release blocker |
|---|---|
| Summary: | AssertionError raised when bulk creation model with composite primary key → AssertionError raised when bulk creation model with composite primary key with auto_now_add field |
| Triage Stage: | Unreviewed → Accepted |
comment:4 by , 3 months ago
Attached 36550.patch. The existing regression test for bulk_create() with a composite PK containing an auto_now_add field now passes with this change. This seems to fix the issue, but I’m not sure if this is the preferred approach.
comment:5 by , 3 months ago
| Resolution: | → wontfix |
|---|---|
| Severity: | Release blocker → Normal |
| Status: | new → closed |
| Summary: | AssertionError raised when bulk creation model with composite primary key with auto_now_add field → AssertionError raised when bulk creation model with primary key set via pre_save (e.g. auto_now_add) |
Been looking into this more today
This actually doesn't have anything to do with CompositePrimaryKey and we can have the same error doing TimeStamped.objects.bulk_create([TimeStamped()]) when TimeStamped is:
class TimeStamped(models.Model):
created = models.DateTimeField(auto_now_add=True, primary_key=True)
I believe the route of this is documented (https://docs.djangoproject.com/en/5.2/ref/models/querysets/#bulk-create):
The model’s save() method will not be called, and the pre_save and post_save signals will not be sent.
And the value for created is defined on the field's pre_save method
To support bulk_create to support primary_keys that are set of pre_save would be a new feature. We _might_ be able to detect this better and have a nicer error message
But in short, I do think this is roughly expected behavior with some documentation and so will close
comment:6 by , 3 months ago
Wanted to mention that there's a difference between the model pre_save signal and the Field.pre_save() method. Looks like bulk_create() has always used the pre_save() value, so that part is not a new feature. It might be worth evaluating this a little further, as from the docs I don't see an admonition about this not working (happy to be corrected on this).
comment:7 by , 3 months ago
| Resolution: | wontfix |
|---|---|
| Status: | closed → new |
| Summary: | AssertionError raised when bulk creation model with primary key set via pre_save (e.g. auto_now_add) → AssertionError raised when bulk creation model with primary key set via pre_save (e.g. auto_now_add) on databases with can_return_columns_from_insert |
| Version: | 5.2 → dev |
Looked into it more and found this is only an issue for databases with can_return_columns_from_insert set to True (so is allowed on MySQL for example)
comment:8 by , 3 months ago
Thanks. Jason, given Sarah's investigation, it looks like inserting logic to set the value is not necessary, since this already works on some databases.
comment:9 by , 3 months ago
Then would we want to modify the test logic and add @skipUnlessDBFeature("can_return_columns_from_insert")?
As far as a fix I was thinking we could ensure Field.pre_save() is applied before partitioning objects into with_pk and without_pk, adjust the classification to check completeness of all PK fields (including composite keys), and only request returning_fields when at least one object is missing a pk (skip requesting PKs when they're all known locally). Would that be acceptable?
comment:10 by , 3 months ago
| Cc: | added |
|---|
Then would we want to modify the test logic and add @skipUnlessDBFeature("can_return_columns_from_insert")?
Adding the skipUnlessDBFeature shouldn't be necessary as we expect it to work for all backends, regardless of whether can_return_columns_from_insert is True or False
I don't know what the fix should be but I recently reviewed #36490 and Simon had a few ideas around improving bulk_create, so I would be interested to hear his thoughts here
comment:11 by , 3 months ago
| Owner: | set to |
|---|---|
| Status: | new → assigned |
by , 3 months ago
| Attachment: | ticket_36550_bulk_create_composite_auto_now_add.patch added |
|---|
comment:12 by , 3 months ago
Here is what I came up with this morning...
This patch updates QuerySet._prepare_for_bulk_create() to correctly classify objects when using composite primary keys and auto_now_add fields. Instead of relying only on _is_pk_set(), it checks that all PK fields are populated (non-None and not DatabaseDefault) before treating an object as "with PK".
Also, I modifed the original regression test from previous comments. This is now backend agnostic and it ensures that objects bulk-created with a composite PK and auto_now_add values still have their primary key set correctly both in memory and in the database.
Looking forward to reading what you all think of this. Also, this test was failing before the code updates, and is now passing. I can prepare a PR for this, but wanted to wait for some expert input first.
comment:13 by , 3 months ago
| Has patch: | set |
|---|
comment:14 by , 7 weeks ago
| Owner: | removed |
|---|---|
| Status: | assigned → new |
comment:15 by , 7 weeks ago
| Has patch: | unset |
|---|
comment:16 by , 7 weeks ago
| Has patch: | set |
|---|---|
| Patch needs improvement: | set |
comment:17 by , 6 weeks ago
| Owner: | set to |
|---|---|
| Status: | new → assigned |
tests/composite_pk/test_create.py
Thank you, replicated the error with the above test