Opened 5 weeks ago
Last modified 4 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: | Jason Hall |
---|---|---|---|
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: | no |
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 (14)
comment:1 by , 5 weeks ago
Description: | modified (diff) |
---|
comment:2 by , 5 weeks ago
Type: | Uncategorized → Bug |
---|
comment:3 by , 5 weeks 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 , 5 weeks 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 , 5 weeks 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 , 5 weeks 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 , 5 weeks 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 , 5 weeks 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 , 5 weeks 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 , 4 weeks 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 , 4 weeks ago
Owner: | set to |
---|---|
Status: | new → assigned |
by , 4 weeks ago
Attachment: | ticket_36550_bulk_create_composite_auto_now_add.patch added |
---|
comment:12 by , 4 weeks 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.
comment:13 by , 4 weeks ago
Has patch: | set |
---|
tests/composite_pk/test_create.py
Thank you, replicated the error with the above test