mirror of
				https://github.com/django/django.git
				synced 2025-10-31 01:25:32 +00:00 
			
		
		
		
	[3.2.x] Fixed #32645 -- Fixed QuerySet.update() crash when ordered by joined fields on MySQL/MariaDB.
Thanks Matt Westcott for the report. Regression in779e615e36. Backport ofca98729055from main
This commit is contained in:
		| @@ -1,4 +1,5 @@ | |||||||
| from django.core.exceptions import FieldError | from django.core.exceptions import FieldError | ||||||
|  | from django.db.models.expressions import Col | ||||||
| from django.db.models.sql import compiler | from django.db.models.sql import compiler | ||||||
|  |  | ||||||
|  |  | ||||||
| @@ -45,8 +46,16 @@ class SQLUpdateCompiler(compiler.SQLUpdateCompiler, SQLCompiler): | |||||||
|         if self.query.order_by: |         if self.query.order_by: | ||||||
|             order_by_sql = [] |             order_by_sql = [] | ||||||
|             order_by_params = [] |             order_by_params = [] | ||||||
|  |             db_table = self.query.get_meta().db_table | ||||||
|             try: |             try: | ||||||
|                 for _, (sql, params, _) in self.get_order_by(): |                 for resolved, (sql, params, _) in self.get_order_by(): | ||||||
|  |                     if ( | ||||||
|  |                         isinstance(resolved.expression, Col) and | ||||||
|  |                         resolved.expression.alias != db_table | ||||||
|  |                     ): | ||||||
|  |                         # Ignore ordering if it contains joined fields, because | ||||||
|  |                         # they cannot be used in the ORDER BY clause. | ||||||
|  |                         raise FieldError | ||||||
|                     order_by_sql.append(sql) |                     order_by_sql.append(sql) | ||||||
|                     order_by_params.extend(params) |                     order_by_params.extend(params) | ||||||
|                 update_query += ' ORDER BY ' + ', '.join(order_by_sql) |                 update_query += ' ORDER BY ' + ', '.join(order_by_sql) | ||||||
|   | |||||||
| @@ -2645,7 +2645,8 @@ unique field in the order that is specified without conflicts. For example:: | |||||||
|  |  | ||||||
| .. note:: | .. note:: | ||||||
|  |  | ||||||
|     If the ``order_by()`` clause contains annotations, it will be ignored. |     ``order_by()`` clause will be ignored if it contains annotations, inherited | ||||||
|  |     fields, or lookups spanning relations. | ||||||
|  |  | ||||||
| ``delete()`` | ``delete()`` | ||||||
| ~~~~~~~~~~~~ | ~~~~~~~~~~~~ | ||||||
|   | |||||||
| @@ -36,3 +36,7 @@ Bugfixes | |||||||
|  |  | ||||||
| * Fixed a regression in Django 3.2 that caused a crash when combining ``Q()`` | * Fixed a regression in Django 3.2 that caused a crash when combining ``Q()`` | ||||||
|   objects which contains boolean expressions (:ticket:`32548`). |   objects which contains boolean expressions (:ticket:`32548`). | ||||||
|  |  | ||||||
|  | * Fixed a regression in Django 3.2 that caused a crash of ``QuerySet.update()`` | ||||||
|  |   on a queryset ordered by inherited or joined fields on MySQL and MariaDB | ||||||
|  |   (:ticket:`32645`). | ||||||
|   | |||||||
| @@ -45,3 +45,7 @@ class Bar(models.Model): | |||||||
|  |  | ||||||
| class UniqueNumber(models.Model): | class UniqueNumber(models.Model): | ||||||
|     number = models.IntegerField(unique=True) |     number = models.IntegerField(unique=True) | ||||||
|  |  | ||||||
|  |  | ||||||
|  | class UniqueNumberChild(UniqueNumber): | ||||||
|  |     pass | ||||||
|   | |||||||
| @@ -7,7 +7,10 @@ from django.db.models.functions import Abs, Concat, Lower | |||||||
| from django.test import TestCase | from django.test import TestCase | ||||||
| from django.test.utils import register_lookup | from django.test.utils import register_lookup | ||||||
|  |  | ||||||
| from .models import A, B, Bar, D, DataPoint, Foo, RelatedPoint, UniqueNumber | from .models import ( | ||||||
|  |     A, B, Bar, D, DataPoint, Foo, RelatedPoint, UniqueNumber, | ||||||
|  |     UniqueNumberChild, | ||||||
|  | ) | ||||||
|  |  | ||||||
|  |  | ||||||
| class SimpleTest(TestCase): | class SimpleTest(TestCase): | ||||||
| @@ -249,3 +252,26 @@ class MySQLUpdateOrderByTest(TestCase): | |||||||
|             ).order_by('number_inverse').update( |             ).order_by('number_inverse').update( | ||||||
|                 number=F('number') + 1, |                 number=F('number') + 1, | ||||||
|             ) |             ) | ||||||
|  |  | ||||||
|  |     def test_order_by_update_on_parent_unique_constraint(self): | ||||||
|  |         # Ordering by inherited fields is omitted because joined fields cannot | ||||||
|  |         # be used in the ORDER BY clause. | ||||||
|  |         UniqueNumberChild.objects.create(number=3) | ||||||
|  |         UniqueNumberChild.objects.create(number=4) | ||||||
|  |         with self.assertRaises(IntegrityError): | ||||||
|  |             UniqueNumberChild.objects.order_by('number').update( | ||||||
|  |                 number=F('number') + 1, | ||||||
|  |             ) | ||||||
|  |  | ||||||
|  |     def test_order_by_update_on_related_field(self): | ||||||
|  |         # Ordering by related fields is omitted because joined fields cannot be | ||||||
|  |         # used in the ORDER BY clause. | ||||||
|  |         data = DataPoint.objects.create(name='d0', value='apple') | ||||||
|  |         related = RelatedPoint.objects.create(name='r0', data=data) | ||||||
|  |         with self.assertNumQueries(1) as ctx: | ||||||
|  |             updated = RelatedPoint.objects.order_by('data__name').update(name='new') | ||||||
|  |         sql = ctx.captured_queries[0]['sql'] | ||||||
|  |         self.assertNotIn('ORDER BY', sql) | ||||||
|  |         self.assertEqual(updated, 1) | ||||||
|  |         related.refresh_from_db() | ||||||
|  |         self.assertEqual(related.name, 'new') | ||||||
|   | |||||||
		Reference in New Issue
	
	Block a user