Opened 5 years ago

Closed 15 months ago

#30129 closed New feature (fixed)

Allow creating models with fields values as a Subquery() with F() expressions

Reported by: Charlie McBride Owned by: Sarah Boyce
Component: Database layer (models, ORM) Version: 2.1
Severity: Normal Keywords: Subquery, F, Query Expressions
Cc: 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 Charlie McBride)

I understand why F() expressions are generally disallowed inside insert statements, the columns you are referencing don't yet exist, so it wouldn't make any sense to do so. However, if you are performing an insert with a Subquery (as in the example below), it's possible to have otherwise valid statements rejected because of the blanket blacklist of F() expressions during inserts.

For example:

given the following models:

class Item(models.Model):
    pass

class ItemVersion(models.Model):
    item = models.ForeignKey(Item, related_name='versions')
    version_number = models.IntegerField(default=0)

    class Meta:
        unique_together = ('item', 'version_number',)

I would like to be able to do the following operation to mitigate race conditions as described here (https://docs.djangoproject.com/en/2.1/ref/models/expressions/#avoiding-race-conditions-using-f)

    item = Item.objects.create()
    # arbitrary number of other items created/destroyed etc
    item_version_2 = ItemVersion.objects.create(
        item=item,
        version_number=Subquery(
            item.versions.order_by('-version_number').annotate(
                max_version_number=Coalesce(Max('version_number'), 0)
            ).annotate(
                new_version_number=F('max_version_number) + 1
            ).values('new_version_number')
         )
     )

As written, I would expect the F() in the inner expression to always be resolvable, because it is in a Subquery (and not the result of an insert). However this query is blocked by the compiler because "F() expressions can only be used to update, not to insert." Would it be possible to allow F expressions in a Subquery even if it is being used in an insert? Is there an edge case that I'm missing that caused the team to not consider this?

Change History (9)

comment:1 by Tim Graham, 5 years ago

Easy pickings: unset
Summary: Request to Allow F() Expressions in Subquery() on InsertAllow creating models with fields values as a Subquery() with F() expressions

comment:2 by Charlie McBride, 5 years ago

Description: modified (diff)

comment:3 by Tim Graham, 5 years ago

Triage Stage: UnreviewedAccepted

It seems okay, if it's feasible to implement.

comment:6 by Sarah Boyce, 15 months ago

I think this is resolved (I'm not sure when), raised a PR just to show that I think it's been fixed https://github.com/django/django/pull/16451 (but I will close the PR later).
I feel like I'm not supposed to close tickets so I'll let a DSF team member check I haven't missed anything and close the ticket.

comment:7 by Sarah Boyce, 15 months ago

Has patch: set
Owner: set to Sarah Boyce
Status: newassigned

PR to add a test for this.

Last edited 15 months ago by Tim Graham (previous) (diff)

comment:8 by Tim Graham, 15 months ago

I did a bisect and confirmed this was fixed in Django 3.0 by 35431298226165986ad07e91f9d3aca721ff38ec.

comment:9 by Sarah Boyce, 15 months ago

Thank you Tim! Updated the commit message to credit 👍

comment:10 by Mariusz Felisiak <felisiak.mariusz@…>, 15 months ago

In 05bcd5ba:

Refs #30129 -- Added test for create() with F() expression in Subquery.

Fixed in 35431298226165986ad07e91f9d3aca721ff38ec.

comment:11 by Mariusz Felisiak, 15 months ago

Resolution: fixed
Status: assignedclosed
Note: See TracTickets for help on using tickets.
Back to Top