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 symptoms 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 symptom 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.

Version 2, edited 3 months ago by Thomas (previous) (next) (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, 3 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