﻿id	summary	reporter	owner	description	type	status	component	version	severity	resolution	keywords	cc	stage	has_patch	needs_docs	needs_tests	needs_better_patch	easy	ui_ux
36522	Tuple lookups cannot handle right-hand sides mixing literal values and expressions	Jacob Walls	Simon Charette	"For this model in the test suite:
{{{#!py
class Comment(models.Model):
    pk = models.CompositePrimaryKey(""tenant"", ""id"")
    tenant = models.ForeignKey(
        Tenant,
        on_delete=models.CASCADE,
        related_name=""comments"",
    )
    id = models.SmallIntegerField(unique=True, db_column=""comment_id"")
    ...
}}}

This fails:
{{{#!diff
diff --git a/tests/composite_pk/test_update.py b/tests/composite_pk/test_update.py
index 697383b007..e76210e75e 100644
--- a/tests/composite_pk/test_update.py
+++ b/tests/composite_pk/test_update.py
@@ -191,3 +191,7 @@ class CompositePKUpdateTests(TestCase):
         )
         with self.assertRaisesMessage(FieldError, msg):
             Comment.objects.update(text=F(""pk""))
+
+    def test_update_fields_expression(self):
+        self.comment_1.id = F(""id"") + 100
+        self.comment_1.save()
}}}
{{{#!py
======================================================================
ERROR: test_update_fields_expression (composite_pk.test_update.CompositePKUpdateTests.test_update_fields_expression)
----------------------------------------------------------------------
Traceback (most recent call last):
  File ""/Users/jwalls/django/django/db/models/fields/__init__.py"", line 2130, in get_prep_value
    return int(value)
TypeError: int() argument must be a string, a bytes-like object or a real number, not 'CombinedExpression'

The above exception was the direct cause of the following exception:

Traceback (most recent call last):
  File ""/Users/jwalls/django/tests/composite_pk/test_update.py"", line 197, in test_update_fields_expression
    self.comment_1.save()
    ~~~~~~~~~~~~~~~~~~~^^
  File ""/Users/jwalls/django/django/db/models/base.py"", line 874, in save
    self.save_base(
    ~~~~~~~~~~~~~~^
        using=using,
        ^^^^^^^^^^^^
    ...<2 lines>...
        update_fields=update_fields,
        ^^^^^^^^^^^^^^^^^^^^^^^^^^^^
    )
    ^
  File ""/Users/jwalls/django/django/db/models/base.py"", line 966, in save_base
    updated = self._save_table(
        raw,
    ...<4 lines>...
        update_fields,
    )
  File ""/Users/jwalls/django/django/db/models/base.py"", line 1097, in _save_table
    updated = self._do_update(
        base_qs, using, pk_val, values, update_fields, forced_update
    )
  File ""/Users/jwalls/django/django/db/models/base.py"", line 1165, in _do_update
    return filtered._update(values) > 0
           ~~~~~~~~~~~~~~~~^^^^^^^^
  File ""/Users/jwalls/django/django/db/models/query.py"", line 1319, in _update
    return query.get_compiler(self.db).execute_sql(ROW_COUNT)
           ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~^^^^^^^^^^^
  File ""/Users/jwalls/django/django/db/models/sql/compiler.py"", line 2103, in execute_sql
    row_count = super().execute_sql(result_type)
  File ""/Users/jwalls/django/django/db/models/sql/compiler.py"", line 1611, in execute_sql
    sql, params = self.as_sql()
                  ~~~~~~~~~~~^^
  File ""/Users/jwalls/django/django/db/models/sql/compiler.py"", line 2089, in as_sql
    where, params = self.compile(self.query.where)
                    ~~~~~~~~~~~~^^^^^^^^^^^^^^^^^^
  File ""/Users/jwalls/django/django/db/models/sql/compiler.py"", line 578, in compile
    sql, params = node.as_sql(self, self.connection)
                  ~~~~~~~~~~~^^^^^^^^^^^^^^^^^^^^^^^
  File ""/Users/jwalls/django/django/db/models/sql/where.py"", line 151, in as_sql
    sql, params = compiler.compile(child)
                  ~~~~~~~~~~~~~~~~^^^^^^^
  File ""/Users/jwalls/django/django/db/models/sql/compiler.py"", line 578, in compile
    sql, params = node.as_sql(self, self.connection)
                  ~~~~~~~~~~~^^^^^^^^^^^^^^^^^^^^^^^
  File ""/Users/jwalls/django/django/db/models/fields/tuple_lookups.py"", line 130, in as_sql
    return super().as_sql(compiler, connection)
           ~~~~~~~~~~~~~~^^^^^^^^^^^^^^^^^^^^^^
  File ""/Users/jwalls/django/django/db/models/lookups.py"", line 407, in as_sql
    return super().as_sql(compiler, connection)
           ~~~~~~~~~~~~~~^^^^^^^^^^^^^^^^^^^^^^
  File ""/Users/jwalls/django/django/db/models/lookups.py"", line 239, in as_sql
    rhs_sql, rhs_params = self.process_rhs(compiler, connection)
                          ~~~~~~~~~~~~~~~~^^^^^^^^^^^^^^^^^^^^^^
  File ""/Users/jwalls/django/django/db/models/fields/tuple_lookups.py"", line 109, in process_rhs
    return compiler.compile(Tuple(*args))
           ~~~~~~~~~~~~~~~~^^^^^^^^^^^^^^
  File ""/Users/jwalls/django/django/db/models/sql/compiler.py"", line 576, in compile
    sql, params = vendor_impl(self, self.connection)
                  ~~~~~~~~~~~^^^^^^^^^^^^^^^^^^^^^^^
  File ""/Users/jwalls/django/django/db/models/fields/tuple_lookups.py"", line 46, in as_sqlite
    return self.as_sql(compiler, connection)
           ~~~~~~~~~~~^^^^^^^^^^^^^^^^^^^^^^
  File ""/Users/jwalls/django/django/db/models/expressions.py"", line 1107, in as_sql
    arg_sql, arg_params = compiler.compile(arg)
                          ~~~~~~~~~~~~~~~~^^^^^
  File ""/Users/jwalls/django/django/db/models/sql/compiler.py"", line 576, in compile
    sql, params = vendor_impl(self, self.connection)
                  ~~~~~~~~~~~^^^^^^^^^^^^^^^^^^^^^^^
  File ""/Users/jwalls/django/django/db/models/expressions.py"", line 29, in as_sqlite
    sql, params = self.as_sql(compiler, connection, **extra_context)
                  ~~~~~~~~~~~^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File ""/Users/jwalls/django/django/db/models/expressions.py"", line 1175, in as_sql
    val = output_field.get_db_prep_value(val, connection=connection)
  File ""/Users/jwalls/django/django/db/models/fields/__init__.py"", line 2137, in get_db_prep_value
    value = super().get_db_prep_value(value, connection, prepared)
  File ""/Users/jwalls/django/django/db/models/fields/__init__.py"", line 1006, in get_db_prep_value
    value = self.get_prep_value(value)
  File ""/Users/jwalls/django/django/db/models/fields/__init__.py"", line 2132, in get_prep_value
    raise e.__class__(
        ""Field '%s' expected a number but got %r."" % (self.name, value),
    ) from e
TypeError: Field 'id' expected a number but got <CombinedExpression: Col(composite_pk_comment, composite_pk.Comment.id) + Value(100)>.
}}}
----
The `+ 100` is not necessary to reproduce, just there for realism. Error without is the same: ""expected a number but got Col(...""

If support is difficult, a `FieldError` is perhaps better, see `test_update_value_not_composite()`."	Bug	closed	Database layer (models, ORM)	5.2	Release blocker	fixed	compositeprimarykey, F, composite primary key, rhs	Simon Charette	Ready for checkin	1	0	0	0	0	0
