diff options
| author | Simon Charette <charette.s@gmail.com> | 2025-04-04 14:42:31 -0400 |
|---|---|---|
| committer | Mariusz Felisiak <felisiak.mariusz@gmail.com> | 2025-04-07 22:56:40 +0200 |
| commit | 71a19a0e475165dbc14c1fe02f552013ee670e4c (patch) | |
| tree | 3f0178e34807741aba98cbdf82e82c693c9e9940 | |
| parent | 8ad3e80e88201f4c557f6fa79fcfc0f8a0961830 (diff) | |
Fixed #36301 -- Fixed select_for_update(of) crash when using values()/values_list().
Regression in 65ad4ade74dc9208b9d686a451cd6045df0c9c3a which allowed for
annotations to be SELECT'ed before model field references through
values()/values_list() and broke assumptions the select_for_update(of)
table infererence logic had about model fields always being first.
Refs #28900.
Thanks OutOfFocus4 for the report and Sarah for the test.
| -rw-r--r-- | django/db/models/sql/compiler.py | 23 | ||||
| -rw-r--r-- | docs/releases/5.2.1.txt | 4 | ||||
| -rw-r--r-- | tests/select_for_update/tests.py | 11 |
3 files changed, 27 insertions, 11 deletions
diff --git a/django/db/models/sql/compiler.py b/django/db/models/sql/compiler.py index 56a759b1bd..b0b2ac5583 100644 --- a/django/db/models/sql/compiler.py +++ b/django/db/models/sql/compiler.py @@ -256,17 +256,8 @@ class SQLCompiler: # self.query.select is a special case. These columns never go to # any model. cols = self.query.select - if cols: - klass_info = { - "model": self.query.model, - "select_fields": list( - range( - len(self.query.extra_select), - len(self.query.extra_select) + len(cols), - ) - ), - } selected = [] + select_fields = None if self.query.selected is None: selected = [ *( @@ -276,18 +267,28 @@ class SQLCompiler: *((None, col) for col in cols), *self.query.annotation_select.items(), ] + select_fields = list( + range( + len(self.query.extra_select), + len(self.query.extra_select) + len(cols), + ) + ) else: - for alias, expression in self.query.selected.items(): + select_fields = [] + for index, (alias, expression) in enumerate(self.query.selected.items()): # Reference to an annotation. if isinstance(expression, str): expression = self.query.annotations[expression] # Reference to a column. elif isinstance(expression, int): + select_fields.append(index) expression = cols[expression] # ColPairs cannot be aliased. if isinstance(expression, ColPairs): alias = None selected.append((alias, expression)) + if select_fields: + klass_info = {"model": self.query.model, "select_fields": select_fields} for select_idx, (alias, expression) in enumerate(selected): if alias: diff --git a/docs/releases/5.2.1.txt b/docs/releases/5.2.1.txt index b2ff7c22d4..139ce32da1 100644 --- a/docs/releases/5.2.1.txt +++ b/docs/releases/5.2.1.txt @@ -28,3 +28,7 @@ Bugfixes ``allow_overwrite=True``, where leftover content from a previously larger file could remain after overwriting with a smaller one due to lack of truncation (:ticket:`36298`). + +* Fixed a regression in Django 5.2 that caused a crash when using + ``QuerySet.select_for_update(of=(…))`` with ``values()/values_list()`` + including expressions (:ticket:`36301`). diff --git a/tests/select_for_update/tests.py b/tests/select_for_update/tests.py index e8ba8f8b6e..1bc87113ba 100644 --- a/tests/select_for_update/tests.py +++ b/tests/select_for_update/tests.py @@ -13,6 +13,8 @@ from django.db import ( router, transaction, ) +from django.db.models import F, Value +from django.db.models.functions import Concat from django.test import ( TransactionTestCase, override_settings, @@ -150,6 +152,15 @@ class SelectForUpdateTests(TransactionTestCase): self.assertTrue(self.has_for_update_sql(ctx.captured_queries, of=expected)) @skipUnlessDBFeature("has_select_for_update_of") + def test_for_update_of_values_list(self): + queries = Person.objects.select_for_update( + of=("self",), + ).values_list(Concat(Value("Dr. "), F("name")), "born") + with transaction.atomic(): + values = queries.get(pk=self.person.pk) + self.assertSequenceEqual(values, ("Dr. Reinhardt", self.city1.pk)) + + @skipUnlessDBFeature("has_select_for_update_of") def test_for_update_sql_model_inheritance_generated_of(self): with transaction.atomic(), CaptureQueriesContext(connection) as ctx: list(EUCountry.objects.select_for_update(of=("self",))) |
