diff options
| author | Simon Charette <charette.s@gmail.com> | 2023-08-02 20:47:49 -0400 |
|---|---|---|
| committer | Mariusz Felisiak <felisiak.mariusz@gmail.com> | 2023-08-04 10:58:53 +0200 |
| commit | 3a1863319c1ed4bc81fe270b0d7d25c7f03fdb48 (patch) | |
| tree | 3ce97bbbb29699150096c4935ad54626311e86e3 | |
| parent | 951dcbb2e6c4d1686706f86c955509ac0ced788c (diff) | |
[4.2.x] Fixed #34754 -- Fixed JSONField check constraints validation on NULL values.
The __isnull lookup of JSONField must special case
Value(None, JSONField()) left-hand-side in order to be coherent with
its convoluted null handling.
Since psycopg>=3 offers no way to pass a NULL::jsonb the issue is
resolved by optimizing IsNull(Value(None), True | False) to
True | False.
Regression in 5c23d9f0c32f166c81ecb6f3f01d5077a6084318.
Thanks Alexandre Collet for the report.
Backport of 3434dbd39d373df7193ad006b970c09c1a909ea3 from main
| -rw-r--r-- | django/db/models/lookups.py | 5 | ||||
| -rw-r--r-- | docs/releases/4.2.5.txt | 4 | ||||
| -rw-r--r-- | tests/constraints/models.py | 7 | ||||
| -rw-r--r-- | tests/constraints/tests.py | 20 |
4 files changed, 35 insertions, 1 deletions
diff --git a/django/db/models/lookups.py b/django/db/models/lookups.py index 9e2d9373e6..8065498576 100644 --- a/django/db/models/lookups.py +++ b/django/db/models/lookups.py @@ -568,6 +568,11 @@ class IsNull(BuiltinLookup): raise ValueError( "The QuerySet value for an isnull lookup must be True or False." ) + if isinstance(self.lhs, Value) and self.lhs.value is None: + if self.rhs: + raise FullResultSet + else: + raise EmptyResultSet sql, params = self.process_lhs(compiler, connection) if self.rhs: return "%s IS NULL" % sql, params diff --git a/docs/releases/4.2.5.txt b/docs/releases/4.2.5.txt index a4e2471158..23ba728da5 100644 --- a/docs/releases/4.2.5.txt +++ b/docs/releases/4.2.5.txt @@ -9,4 +9,6 @@ Django 4.2.5 fixes several bugs in 4.2.4. Bugfixes ======== -* ... +* Fixed a regression in Django 4.2 that caused an incorrect validation of + ``CheckConstraints`` on ``__isnull`` lookups against ``JSONField`` + (:ticket:`34754`). diff --git a/tests/constraints/models.py b/tests/constraints/models.py index 3b349d204e..939efe0b83 100644 --- a/tests/constraints/models.py +++ b/tests/constraints/models.py @@ -115,3 +115,10 @@ class AbstractModel(models.Model): class ChildModel(AbstractModel): pass + + +class JSONFieldModel(models.Model): + data = models.JSONField(null=True) + + class Meta: + required_db_features = {"supports_json_field"} diff --git a/tests/constraints/tests.py b/tests/constraints/tests.py index e52d15233c..5f59b3a47e 100644 --- a/tests/constraints/tests.py +++ b/tests/constraints/tests.py @@ -11,6 +11,7 @@ from django.test import SimpleTestCase, TestCase, skipIfDBFeature, skipUnlessDBF from .models import ( ChildModel, ChildUniqueConstraintProduct, + JSONFieldModel, Product, UniqueConstraintConditionProduct, UniqueConstraintDeferrable, @@ -251,6 +252,25 @@ class CheckConstraintTests(TestCase): ) constraint.validate(Product, Product()) + @skipUnlessDBFeature("supports_json_field") + def test_validate_nullable_jsonfield(self): + is_null_constraint = models.CheckConstraint( + check=models.Q(data__isnull=True), + name="nullable_data", + ) + is_not_null_constraint = models.CheckConstraint( + check=models.Q(data__isnull=False), + name="nullable_data", + ) + is_null_constraint.validate(JSONFieldModel, JSONFieldModel(data=None)) + msg = f"Constraint “{is_null_constraint.name}” is violated." + with self.assertRaisesMessage(ValidationError, msg): + is_null_constraint.validate(JSONFieldModel, JSONFieldModel(data={})) + msg = f"Constraint “{is_not_null_constraint.name}” is violated." + with self.assertRaisesMessage(ValidationError, msg): + is_not_null_constraint.validate(JSONFieldModel, JSONFieldModel(data=None)) + is_not_null_constraint.validate(JSONFieldModel, JSONFieldModel(data={})) + class UniqueConstraintTests(TestCase): @classmethod |
