diff --git a/django/db/models/expressions.py b/django/db/models/expressions.py index 667e9f93c6..88c5596c17 100644 --- a/django/db/models/expressions.py +++ b/django/db/models/expressions.py @@ -758,6 +758,8 @@ class CombinedExpression(SQLiteNumericMixin, Expression): rhs = self.rhs.resolve_expression( query, allow_joins, reuse, summarize, for_save ) + if isinstance(lhs, ColPairs) or isinstance(rhs, ColPairs): + raise ValueError("CompositePrimaryKey is not combinable.") if not isinstance(self, (DurationExpression, TemporalSubtraction)): try: lhs_type = lhs.output_field.get_internal_type() diff --git a/django/db/models/lookups.py b/django/db/models/lookups.py index 734f911f83..56dbdabac1 100644 --- a/django/db/models/lookups.py +++ b/django/db/models/lookups.py @@ -4,7 +4,7 @@ import warnings from django.core.exceptions import EmptyResultSet, FullResultSet from django.db.backends.base.operations import BaseDatabaseOperations -from django.db.models.expressions import Case, Expression, Func, Value, When +from django.db.models.expressions import Case, ColPairs, Expression, Func, Value, When from django.db.models.fields import ( BooleanField, CharField, @@ -119,6 +119,10 @@ class Lookup(Expression): value = value.resolve_expression(compiler.query) if hasattr(value, "as_sql"): sql, params = compiler.compile(value) + if isinstance(value, ColPairs): + raise ValueError( + "CompositePrimaryKey cannot be used as a lookup value." + ) # Ensure expression is wrapped in parentheses to respect operator # precedence but avoid double wrapping as it can be misinterpreted # on some backends (e.g. subqueries on SQLite). diff --git a/tests/composite_pk/test_filter.py b/tests/composite_pk/test_filter.py index 7e361c5925..a99a1602a0 100644 --- a/tests/composite_pk/test_filter.py +++ b/tests/composite_pk/test_filter.py @@ -1,3 +1,5 @@ +from django.db.models import F, TextField +from django.db.models.functions import Cast from django.test import TestCase from .models import Comment, Tenant, User @@ -54,6 +56,20 @@ class CompositePKFilterTests(TestCase): with self.subTest(lookup=lookup, count=count): self.assertEqual(User.objects.filter(**lookup).count(), count) + def test_rhs_pk(self): + msg = "CompositePrimaryKey cannot be used as a lookup value." + with self.assertRaisesMessage(ValueError, msg): + Comment.objects.filter(text__gt=F("pk")).count() + + def test_rhs_combinable(self): + msg = "CompositePrimaryKey is not combinable." + for expr in [F("pk") + (1, 1), (1, 1) + F("pk")]: + with ( + self.subTest(expression=expr), + self.assertRaisesMessage(ValueError, msg), + ): + Comment.objects.filter(text__gt=expr).count() + def test_order_comments_by_pk_asc(self): self.assertSequenceEqual( Comment.objects.order_by("pk"), @@ -410,3 +426,8 @@ class CompositePKFilterTests(TestCase): subquery = Comment.objects.filter(id=3).only("pk") queryset = User.objects.filter(comments__in=subquery) self.assertSequenceEqual(queryset, (self.user_2,)) + + def test_cannot_cast_pk(self): + msg = "Casting CompositePrimaryKey is not supported." + with self.assertRaisesMessage(ValueError, msg): + Comment.objects.filter(text__gt=Cast(F("pk"), TextField())).count()