Opened 3 months ago

Closed 2 months ago

#36445 closed Bug (wontfix)

Value(None, output_field=JSONField()) incorrectly saves as SQL NULL in bulk_update()

Reported by: Clifford Gama Owned by: Uddyan Goyal
Component: Database layer (models, ORM) Version: dev
Severity: Normal Keywords:
Cc: Simon Charette, Thomas 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 Clifford Gama)

When using bulk_update() with a Value(None, output_field=JSONField()) expression, the value is incorrectly saved as a SQL NULL rather than a JSON null. This behavior was temporarily fixed in 00c690efbc0b10f67924687f24a7b30397bf47d9 due to incomplete for_save propagation during expression resolution (bug in #36419), but reappears after the proper restoration of for_save handling in #36419.

  • tests/queries/test_bulk_update.py

    diff --git a/tests/queries/test_bulk_update.py b/tests/queries/test_bulk_update.py
    index 480fac6784..6c553421cb 100644
    a b from math import ceil  
    33
    44from django.core.exceptions import FieldDoesNotExist
    55from django.db import connection
    6 from django.db.models import F
     6from django.db.models import F, Value, JSONField
    77from django.db.models.functions import Coalesce, Lower
    88from django.db.utils import IntegrityError
    99from django.test import TestCase, override_settings, skipUnlessDBFeature
    class BulkUpdateTests(TestCase):  
    315315                sql_null_qs = JSONFieldNullable.objects.filter(json_field__isnull=True)
    316316                self.assertSequenceEqual(sql_null_qs, [obj])
    317317
     318    @skipUnlessDBFeature("supports_json_field")
     319    def test_json_field_json_null_value(self):
     320        obj = JSONFieldNullable.objects.create(json_field={})
     321        obj.json_field = Value(None, output_field=JSONField())
     322        JSONFieldNullable.objects.bulk_update([obj], fields=["json_field"])
     323        obj.refresh_from_db()
     324        json_null_qs = JSONFieldNullable.objects.filter(json_field=None)
     325        self.assertSequenceEqual(json_null_qs, [obj])
     326
    318327    def test_nullable_fk_after_related_save(self):
    319328        parent = RelatedObject.objects.create()
    320329        child = SingleObject()

Change History (13)

comment:1 by Sarah Boyce, 3 months ago

Triage Stage: UnreviewedAccepted

Thank you for the ticket

comment:2 by Clifford Gama, 3 months ago

Description: modified (diff)

comment:3 by GoyalRocks007, 3 months ago

Owner: set to Uddyan Goyal
Status: newassigned

comment:4 by Simon Charette, 3 months ago

Now that we know this isn't a regression should we consider biting the bullet and stop trying to make Value(None, JSONField()) work by introducing a proper JSONNull expression as described in ticket:35381#comment:3?

comment:5 by GoyalRocks007, 3 months ago

Has patch: set

Hello this ticket was unassigned so I assigned this to myself as mentioned in the documentation. I have raised a [PR https://github.com/django/django/pull/19546] for this bug. Please review. I have tried to follow all the instructions as mentioned in the documentation. Please let me know if I missed anything.
Thanks!

Last edited 3 months ago by GoyalRocks007 (previous) (diff)

comment:6 by Thomas, 3 months ago

@Simon Charette: It may not be a regression for the bulk_update case outlined here (of which I don't fully understand the chronology), but isn't it a regression in the case I outlined in #36453 (which has been marked as a duplicate of this issue)? Code that has been working a certain way for 1-2 years is broken in 5.2.3.

Last edited 3 months ago by Thomas (previous) (diff)

comment:7 by Simon Charette, 3 months ago

Cc: Simon Charette added

Now that we know this isn't a regression

I wasn't involved in the regression status determination, my comment was referring to this discussion and assuming it was the case. It is possible that the issue you reported in #36453 is a valid regression in Django 5.2.3 for a slightly different scenario as When / Case have a variation of expression resolving than Coalesce.

If you can write a regression test that passes for < 5.2 and fail for 5.2.3 then it is effectively a regression and closing #36453, and potentially categorizing this ticket as an issue predating 5.2, was likely a triage mistake.

Last edited 3 months ago by Simon Charette (previous) (diff)

comment:8 by Thomas, 3 months ago

I've made a regression test for the use case detailed in #36453.
https://github.com/thomas-mckay/django/commit/5ca2b19568b39c41248479ad4b19973fe622c438

After bisection, I've noticed that the test passes from 4.2 to 5.2.2. It breaks in 5.2.3 and before 4.2.
To recap:

I've checked Value.for_save, and it is indeed False in 5.2.2 and True in 5.2.3 for the Value in the When condition. So, I understand why #36453 may have been closed as a duplicate of this one: it sounds like the same change may be responsible. My only concern is that the bug in this ticket only covers one of the side-effects of that change (that may or may not be a regression), but not the one I'm experiencing (which I really hope is considered a regression), and that addressing this side-effect will not fix the behavior that's been broken in my case. Let me know if I'm taking this ticket off-topic and I'll re-open my ticket and continue there.

Last edited 3 months ago by Thomas (previous) (diff)

comment:9 by Thomas, 3 months ago

I've tested the patch proposed by @GoyalRocks007 in comment:5. With this patch the test passes again.

comment:10 by Thomas, 3 months ago

Cc: Thomas added

in reply to:  4 comment:11 by Clifford Gama, 3 months ago

Replying to Simon Charette:

Now that we know this isn't a regression should we consider biting the bullet and stop trying to make Value(None, JSONField()) work by introducing a proper JSONNull expression as described in ticket:35381#comment:3?

In light of recent reports around JSON null/SQL NULL, (the latest being #36453) I'd say yes. Would this mean marking this ticket as invalid in favour of a #35381 repurposed for the JSONNull new feature? Or perhaps a new ticket?

Last edited 3 months ago by Clifford Gama (previous) (diff)

comment:12 by Sarah Boyce, 2 months ago

Patch needs improvement: set

I see that there is a growing consensus to pursue JSONNull (ref ticket:36445#comment:4, ticket:35381#comment:10, ticket:36445#comment:11).
We should pursue this before bolting on more logic to trying to resolve the json null problem (note that I confirmed that this test in the PR has been failing since at least Django 4.1 and potentially has never worked)

comment:13 by Natalia Bidart, 2 months ago

Resolution: wontfix
Status: assignedclosed

#35381 was repurposed so this ticket is being closed as wontfix following previous conversations.

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