diff options
| -rw-r--r-- | django/db/models/constraints.py | 8 | ||||
| -rw-r--r-- | django/db/models/query_utils.py | 8 | ||||
| -rw-r--r-- | tests/constraints/tests.py | 13 |
3 files changed, 22 insertions, 7 deletions
diff --git a/django/db/models/constraints.py b/django/db/models/constraints.py index 7dfeb3b649..a4e8ab8ab1 100644 --- a/django/db/models/constraints.py +++ b/django/db/models/constraints.py @@ -6,6 +6,8 @@ from django.core.exceptions import FieldDoesNotExist, ValidationError from django.db import connections from django.db.models.constants import LOOKUP_SEP from django.db.models.expressions import Exists, ExpressionList, F, RawSQL +from django.db.models.fields import BooleanField +from django.db.models.functions import Coalesce from django.db.models.indexes import IndexExpression from django.db.models.lookups import Exact, IsNull from django.db.models.query_utils import Q @@ -212,7 +214,11 @@ class CheckConstraint(BaseConstraint): # Ignore constraints with excluded fields in condition. if exclude and self._expression_refs_exclude(model, self.condition, exclude): return - if not Q(self.condition).check(against, using=using): + condition = self.condition + if connections[using].features.supports_comparing_boolean_expr: + # Checks constraints do not fail when they evaluate to UNKNOWN. + condition = Coalesce(condition, True, output_field=BooleanField()) + if not Q(condition).check(against, using=using): raise ValidationError( self.get_violation_error_message(), code=self.violation_error_code ) diff --git a/django/db/models/query_utils.py b/django/db/models/query_utils.py index c282c4f744..c37e6b7a49 100644 --- a/django/db/models/query_utils.py +++ b/django/db/models/query_utils.py @@ -165,8 +165,7 @@ class Q(tree.Node): matches against the expressions. """ # Avoid circular imports. - from django.db.models import BooleanField, Value - from django.db.models.functions import Coalesce + from django.db.models import Value from django.db.models.sql import Query from django.db.models.sql.constants import SINGLE @@ -178,10 +177,7 @@ class Q(tree.Node): query.add_annotation(Value(1), "_check") connection = connections[using] # This will raise a FieldError if a field is missing in "against". - if connection.features.supports_comparing_boolean_expr: - query.add_q(Q(Coalesce(self, True, output_field=BooleanField()))) - else: - query.add_q(self) + query.add_q(self) compiler = query.get_compiler(using=using) context_manager = ( transaction.atomic(using=using) diff --git a/tests/constraints/tests.py b/tests/constraints/tests.py index 39b170125f..0c10429c22 100644 --- a/tests/constraints/tests.py +++ b/tests/constraints/tests.py @@ -1297,6 +1297,19 @@ class UniqueConstraintTests(TestCase): with self.assertRaisesMessage(ValidationError, msg): constraint.validate(Product, Product(price=None)) + @skipUnlessDBFeature("supports_comparing_boolean_expr") + def test_validate_nullable_condition(self): + GeneratedFieldStoredProduct.objects.create(name="Product", price=42) + constraint = models.UniqueConstraint( + fields=["name"], + name="uniq_name_for_positive_price", + condition=models.Q(price__gt=0), + ) + constraint.validate( + GeneratedFieldStoredProduct, + GeneratedFieldStoredProduct(name="Product", price=None), + ) + def test_name(self): constraints = get_constraints(UniqueConstraintProduct._meta.db_table) expected_name = "name_color_uniq" |
