mirror of
				https://github.com/django/django.git
				synced 2025-11-03 21:25:09 +00:00 
			
		
		
		
	Fixed #29542 -- Fixed invalid SQL if a Subquery from the HAVING clause is used in the GROUP BY clause.
Thanks Tim Graham for the review.
This commit is contained in:
		
							parent
							
								
									312eb5cb11
								
							
						
					
					
						commit
						dd3b470719
					
				
					 2 changed files with 20 additions and 2 deletions
				
			
		| 
						 | 
					@ -6,7 +6,7 @@ from itertools import chain
 | 
				
			||||||
 | 
					
 | 
				
			||||||
from django.core.exceptions import EmptyResultSet, FieldError
 | 
					from django.core.exceptions import EmptyResultSet, FieldError
 | 
				
			||||||
from django.db.models.constants import LOOKUP_SEP
 | 
					from django.db.models.constants import LOOKUP_SEP
 | 
				
			||||||
from django.db.models.expressions import OrderBy, Random, RawSQL, Ref
 | 
					from django.db.models.expressions import OrderBy, Random, RawSQL, Ref, Subquery
 | 
				
			||||||
from django.db.models.query_utils import QueryWrapper, select_related_descend
 | 
					from django.db.models.query_utils import QueryWrapper, select_related_descend
 | 
				
			||||||
from django.db.models.sql.constants import (
 | 
					from django.db.models.sql.constants import (
 | 
				
			||||||
    CURSOR, GET_ITERATOR_CHUNK_SIZE, MULTI, NO_RESULTS, ORDER_DIR, SINGLE,
 | 
					    CURSOR, GET_ITERATOR_CHUNK_SIZE, MULTI, NO_RESULTS, ORDER_DIR, SINGLE,
 | 
				
			||||||
| 
						 | 
					@ -127,6 +127,11 @@ class SQLCompiler:
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        for expr in expressions:
 | 
					        for expr in expressions:
 | 
				
			||||||
            sql, params = self.compile(expr)
 | 
					            sql, params = self.compile(expr)
 | 
				
			||||||
 | 
					            if isinstance(expr, Subquery) and not sql.startswith('('):
 | 
				
			||||||
 | 
					                # Subquery expression from HAVING clause may not contain
 | 
				
			||||||
 | 
					                # wrapping () because they could be removed when a subquery is
 | 
				
			||||||
 | 
					                # the "rhs" in an expression (see Subquery._prepare()).
 | 
				
			||||||
 | 
					                sql = '(%s)' % sql
 | 
				
			||||||
            if (sql, tuple(params)) not in seen:
 | 
					            if (sql, tuple(params)) not in seen:
 | 
				
			||||||
                result.append((sql, params))
 | 
					                result.append((sql, params))
 | 
				
			||||||
                seen.add((sql, tuple(params)))
 | 
					                seen.add((sql, tuple(params)))
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -4,7 +4,7 @@ from decimal import Decimal
 | 
				
			||||||
from django.core.exceptions import FieldDoesNotExist, FieldError
 | 
					from django.core.exceptions import FieldDoesNotExist, FieldError
 | 
				
			||||||
from django.db.models import (
 | 
					from django.db.models import (
 | 
				
			||||||
    BooleanField, CharField, Count, DateTimeField, ExpressionWrapper, F, Func,
 | 
					    BooleanField, CharField, Count, DateTimeField, ExpressionWrapper, F, Func,
 | 
				
			||||||
    IntegerField, NullBooleanField, Q, Sum, Value,
 | 
					    IntegerField, NullBooleanField, OuterRef, Q, Subquery, Sum, Value,
 | 
				
			||||||
)
 | 
					)
 | 
				
			||||||
from django.db.models.expressions import RawSQL
 | 
					from django.db.models.expressions import RawSQL
 | 
				
			||||||
from django.db.models.functions import Length, Lower
 | 
					from django.db.models.functions import Length, Lower
 | 
				
			||||||
| 
						 | 
					@ -585,3 +585,16 @@ class NonAggregateAnnotationTestCase(TestCase):
 | 
				
			||||||
            qs,
 | 
					            qs,
 | 
				
			||||||
            [{'jacob_name': 'Jacob Kaplan-Moss', 'james_name': 'James Bennett'}],
 | 
					            [{'jacob_name': 'Jacob Kaplan-Moss', 'james_name': 'James Bennett'}],
 | 
				
			||||||
        )
 | 
					        )
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    @skipUnlessDBFeature('supports_subqueries_in_group_by')
 | 
				
			||||||
 | 
					    def test_annotation_filter_with_subquery(self):
 | 
				
			||||||
 | 
					        long_books_qs = Book.objects.filter(
 | 
				
			||||||
 | 
					            publisher=OuterRef('pk'),
 | 
				
			||||||
 | 
					            pages__gt=400,
 | 
				
			||||||
 | 
					        ).values('publisher').annotate(count=Count('pk')).values('count')
 | 
				
			||||||
 | 
					        publisher_books_qs = Publisher.objects.annotate(
 | 
				
			||||||
 | 
					            total_books=Count('book'),
 | 
				
			||||||
 | 
					        ).filter(
 | 
				
			||||||
 | 
					            total_books=Subquery(long_books_qs, output_field=IntegerField()),
 | 
				
			||||||
 | 
					        ).values('name')
 | 
				
			||||||
 | 
					        self.assertCountEqual(publisher_books_qs, [{'name': 'Sams'}, {'name': 'Morgan Kaufmann'}])
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
		Loading…
	
	Add table
		Add a link
		
	
		Reference in a new issue