summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorSimon Charette <charette.s@gmail.com>2023-08-02 20:47:49 -0400
committerMariusz Felisiak <felisiak.mariusz@gmail.com>2023-08-04 10:58:53 +0200
commit3a1863319c1ed4bc81fe270b0d7d25c7f03fdb48 (patch)
tree3ce97bbbb29699150096c4935ad54626311e86e3
parent951dcbb2e6c4d1686706f86c955509ac0ced788c (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.py5
-rw-r--r--docs/releases/4.2.5.txt4
-rw-r--r--tests/constraints/models.py7
-rw-r--r--tests/constraints/tests.py20
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