mirror of
				https://github.com/django/django.git
				synced 2025-10-30 17:16:10 +00:00 
			
		
		
		
	Fixed #35559 -- Avoided unnecessary query on sliced union of empty queries.
While refs #34125 focused on the SQL correctness of slicing of union of potentially empty queries it missed an optimization opportunity to avoid performing a query at all when all queries are empty. Thanks Lucidiot for the report.
This commit is contained in:
		
				
					committed by
					
						 Sarah Boyce
						Sarah Boyce
					
				
			
			
				
	
			
			
			
						parent
						
							6b3f55446f
						
					
				
				
					commit
					9cb8baa0c4
				
			| @@ -583,20 +583,49 @@ class SQLCompiler: | ||||
|                     raise DatabaseError( | ||||
|                         "ORDER BY not allowed in subqueries of compound statements." | ||||
|                     ) | ||||
|         elif self.query.is_sliced and combinator == "union": | ||||
|             for compiler in compilers: | ||||
|                 # A sliced union cannot have its parts elided as some of them | ||||
|                 # might be sliced as well and in the event where only a single | ||||
|                 # part produces a non-empty resultset it might be impossible to | ||||
|                 # generate valid SQL. | ||||
|                 compiler.elide_empty = False | ||||
|         parts = () | ||||
|         selected = self.query.selected | ||||
|         parts = [] | ||||
|         empty_compiler = None | ||||
|         for compiler in compilers: | ||||
|             try: | ||||
|                 parts.append(self._get_combinator_part_sql(compiler)) | ||||
|             except EmptyResultSet: | ||||
|                 # Omit the empty queryset with UNION and with DIFFERENCE if the | ||||
|                 # first queryset is nonempty. | ||||
|                 if combinator == "union" or (combinator == "difference" and parts): | ||||
|                     empty_compiler = compiler | ||||
|                     continue | ||||
|                 raise | ||||
|         if not parts: | ||||
|             raise EmptyResultSet | ||||
|         elif len(parts) == 1 and combinator == "union" and self.query.is_sliced: | ||||
|             # A sliced union cannot be composed of a single component because | ||||
|             # in the event the later is also sliced it might result in invalid | ||||
|             # SQL due to the usage of multiple LIMIT clauses. Prevent that from | ||||
|             # happening by always including an empty resultset query to force | ||||
|             # the creation of an union. | ||||
|             empty_compiler.elide_empty = False | ||||
|             parts.append(self._get_combinator_part_sql(empty_compiler)) | ||||
|         combinator_sql = self.connection.ops.set_operators[combinator] | ||||
|         if all and combinator == "union": | ||||
|             combinator_sql += " ALL" | ||||
|         braces = "{}" | ||||
|         if not self.query.subquery and features.supports_slicing_ordering_in_compound: | ||||
|             braces = "({})" | ||||
|         sql_parts, args_parts = zip( | ||||
|             *((braces.format(sql), args) for sql, args in parts) | ||||
|         ) | ||||
|         result = [" {} ".format(combinator_sql).join(sql_parts)] | ||||
|         params = [] | ||||
|         for part in args_parts: | ||||
|             params.extend(part) | ||||
|         return result, params | ||||
|  | ||||
|     def _get_combinator_part_sql(self, compiler): | ||||
|         features = self.connection.features | ||||
|         # If the columns list is limited, then all combined queries | ||||
|         # must have the same columns list. Set the selects defined on | ||||
|         # the query on all combined queries, if not already set. | ||||
|         selected = self.query.selected | ||||
|         if selected is not None and compiler.query.selected is None: | ||||
|             compiler.query = compiler.query.clone() | ||||
|             compiler.query.set_values(selected) | ||||
| @@ -613,34 +642,9 @@ class SQLCompiler: | ||||
|                 or not features.supports_slicing_ordering_in_compound | ||||
|             ): | ||||
|                 part_sql = "({})".format(part_sql) | ||||
|                 elif ( | ||||
|                     self.query.subquery | ||||
|                     and features.supports_slicing_ordering_in_compound | ||||
|                 ): | ||||
|         elif self.query.subquery and features.supports_slicing_ordering_in_compound: | ||||
|             part_sql = "({})".format(part_sql) | ||||
|                 parts += ((part_sql, part_args),) | ||||
|             except EmptyResultSet: | ||||
|                 # Omit the empty queryset with UNION and with DIFFERENCE if the | ||||
|                 # first queryset is nonempty. | ||||
|                 if combinator == "union" or (combinator == "difference" and parts): | ||||
|                     continue | ||||
|                 raise | ||||
|         if not parts: | ||||
|             raise EmptyResultSet | ||||
|         combinator_sql = self.connection.ops.set_operators[combinator] | ||||
|         if all and combinator == "union": | ||||
|             combinator_sql += " ALL" | ||||
|         braces = "{}" | ||||
|         if not self.query.subquery and features.supports_slicing_ordering_in_compound: | ||||
|             braces = "({})" | ||||
|         sql_parts, args_parts = zip( | ||||
|             *((braces.format(sql), args) for sql, args in parts) | ||||
|         ) | ||||
|         result = [" {} ".format(combinator_sql).join(sql_parts)] | ||||
|         params = [] | ||||
|         for part in args_parts: | ||||
|             params.extend(part) | ||||
|         return result, params | ||||
|         return part_sql, part_args | ||||
|  | ||||
|     def get_qualify_sql(self): | ||||
|         where_parts = [] | ||||
|   | ||||
| @@ -76,6 +76,12 @@ class QuerySetSetOperationTests(TestCase): | ||||
|         qs3 = qs1.union(qs2) | ||||
|         self.assertNumbersEqual(qs3[:1], [0]) | ||||
|  | ||||
|     def test_union_all_none_slice(self): | ||||
|         qs = Number.objects.filter(id__in=[]) | ||||
|         with self.assertNumQueries(0): | ||||
|             self.assertSequenceEqual(qs.union(qs), []) | ||||
|             self.assertSequenceEqual(qs.union(qs)[0:0], []) | ||||
|  | ||||
|     def test_union_empty_filter_slice(self): | ||||
|         qs1 = Number.objects.filter(num__lte=0) | ||||
|         qs2 = Number.objects.filter(pk__in=[]) | ||||
|   | ||||
		Reference in New Issue
	
	Block a user