mirror of
				https://github.com/django/django.git
				synced 2025-10-31 09:41:08 +00:00 
			
		
		
		
	Fixed #28454 -- Simplifed use of Query.setup_joins() by returning a named tuple.
This commit is contained in:
		
				
					committed by
					
						 Tim Graham
						Tim Graham
					
				
			
			
				
	
			
			
			
						parent
						
							8df7681d0e
						
					
				
				
					commit
					32d1bf2bdb
				
			| @@ -818,8 +818,8 @@ class SQLCompiler: | |||||||
|                 related_field_name = f.related_query_name() |                 related_field_name = f.related_query_name() | ||||||
|                 fields_found.add(related_field_name) |                 fields_found.add(related_field_name) | ||||||
|  |  | ||||||
|                 _, _, _, joins, _ = self.query.setup_joins([related_field_name], opts, root_alias) |                 join_info = self.query.setup_joins([related_field_name], opts, root_alias) | ||||||
|                 alias = joins[-1] |                 alias = join_info.joins[-1] | ||||||
|                 from_parent = issubclass(model, opts.model) and model is not opts.model |                 from_parent = issubclass(model, opts.model) and model is not opts.model | ||||||
|                 klass_info = { |                 klass_info = { | ||||||
|                     'model': model, |                     'model': model, | ||||||
|   | |||||||
| @@ -6,7 +6,7 @@ themselves do not have to (and could be backed by things other than SQL | |||||||
| databases). The abstraction barrier only works one way: this module has to know | databases). The abstraction barrier only works one way: this module has to know | ||||||
| all about the internals of models in order to get the information it needs. | all about the internals of models in order to get the information it needs. | ||||||
| """ | """ | ||||||
| from collections import Counter, Iterator, Mapping, OrderedDict | from collections import Counter, Iterator, Mapping, OrderedDict, namedtuple | ||||||
| from contextlib import suppress | from contextlib import suppress | ||||||
| from itertools import chain, count, product | from itertools import chain, count, product | ||||||
| from string import ascii_uppercase | from string import ascii_uppercase | ||||||
| @@ -44,6 +44,12 @@ def get_field_names_from_opts(opts): | |||||||
|     )) |     )) | ||||||
|  |  | ||||||
|  |  | ||||||
|  | JoinInfo = namedtuple( | ||||||
|  |     'JoinInfo', | ||||||
|  |     ('final_field', 'targets', 'opts', 'joins', 'path') | ||||||
|  | ) | ||||||
|  |  | ||||||
|  |  | ||||||
| class RawQuery: | class RawQuery: | ||||||
|     """A single raw SQL query.""" |     """A single raw SQL query.""" | ||||||
|  |  | ||||||
| @@ -935,10 +941,9 @@ class Query: | |||||||
|                 curr_opts = int_model._meta |                 curr_opts = int_model._meta | ||||||
|                 continue |                 continue | ||||||
|             link_field = curr_opts.get_ancestor_link(int_model) |             link_field = curr_opts.get_ancestor_link(int_model) | ||||||
|             _, _, _, joins, _ = self.setup_joins( |             join_info = self.setup_joins([link_field.name], curr_opts, alias) | ||||||
|                 [link_field.name], curr_opts, alias) |  | ||||||
|             curr_opts = int_model._meta |             curr_opts = int_model._meta | ||||||
|             alias = seen[int_model] = joins[-1] |             alias = seen[int_model] = join_info.joins[-1] | ||||||
|         return alias or seen[None] |         return alias or seen[None] | ||||||
|  |  | ||||||
|     def add_annotation(self, annotation, alias, is_summary=False): |     def add_annotation(self, annotation, alias, is_summary=False): | ||||||
| @@ -1146,39 +1151,38 @@ class Query: | |||||||
|         allow_many = not branch_negated or not split_subq |         allow_many = not branch_negated or not split_subq | ||||||
|  |  | ||||||
|         try: |         try: | ||||||
|             field, sources, opts, join_list, path = self.setup_joins( |             join_info = self.setup_joins(parts, opts, alias, can_reuse=can_reuse, allow_many=allow_many) | ||||||
|                 parts, opts, alias, can_reuse=can_reuse, allow_many=allow_many) |  | ||||||
|  |  | ||||||
|             # Prevent iterator from being consumed by check_related_objects() |             # Prevent iterator from being consumed by check_related_objects() | ||||||
|             if isinstance(value, Iterator): |             if isinstance(value, Iterator): | ||||||
|                 value = list(value) |                 value = list(value) | ||||||
|             self.check_related_objects(field, value, opts) |             self.check_related_objects(join_info.final_field, value, join_info.opts) | ||||||
|  |  | ||||||
|             # split_exclude() needs to know which joins were generated for the |             # split_exclude() needs to know which joins were generated for the | ||||||
|             # lookup parts |             # lookup parts | ||||||
|             self._lookup_joins = join_list |             self._lookup_joins = join_info.joins | ||||||
|         except MultiJoin as e: |         except MultiJoin as e: | ||||||
|             return self.split_exclude(filter_expr, LOOKUP_SEP.join(parts[:e.level]), |             return self.split_exclude(filter_expr, LOOKUP_SEP.join(parts[:e.level]), | ||||||
|                                       can_reuse, e.names_with_path) |                                       can_reuse, e.names_with_path) | ||||||
|  |  | ||||||
|         # Update used_joins before trimming since they are reused to determine |         # Update used_joins before trimming since they are reused to determine | ||||||
|         # which joins could be later promoted to INNER. |         # which joins could be later promoted to INNER. | ||||||
|         used_joins.update(join_list) |         used_joins.update(join_info.joins) | ||||||
|         targets, alias, join_list = self.trim_joins(sources, join_list, path) |         targets, alias, join_list = self.trim_joins(join_info.targets, join_info.joins, join_info.path) | ||||||
|         if can_reuse is not None: |         if can_reuse is not None: | ||||||
|             can_reuse.update(join_list) |             can_reuse.update(join_list) | ||||||
|  |  | ||||||
|         if field.is_relation: |         if join_info.final_field.is_relation: | ||||||
|             # No support for transforms for relational fields |             # No support for transforms for relational fields | ||||||
|             num_lookups = len(lookups) |             num_lookups = len(lookups) | ||||||
|             if num_lookups > 1: |             if num_lookups > 1: | ||||||
|                 raise FieldError('Related Field got invalid lookup: {}'.format(lookups[0])) |                 raise FieldError('Related Field got invalid lookup: {}'.format(lookups[0])) | ||||||
|             if len(targets) == 1: |             if len(targets) == 1: | ||||||
|                 col = targets[0].get_col(alias, field) |                 col = targets[0].get_col(alias, join_info.final_field) | ||||||
|             else: |             else: | ||||||
|                 col = MultiColSource(alias, targets, sources, field) |                 col = MultiColSource(alias, targets, join_info.targets, join_info.final_field) | ||||||
|         else: |         else: | ||||||
|             col = targets[0].get_col(alias, field) |             col = targets[0].get_col(alias, join_info.final_field) | ||||||
|  |  | ||||||
|         condition = self.build_lookup(lookups, col, value) |         condition = self.build_lookup(lookups, col, value) | ||||||
|         lookup_type = condition.lookup_name |         lookup_type = condition.lookup_name | ||||||
| @@ -1200,7 +1204,7 @@ class Query: | |||||||
|                 #   <=> |                 #   <=> | ||||||
|                 # NOT (col IS NOT NULL AND col = someval). |                 # NOT (col IS NOT NULL AND col = someval). | ||||||
|                 lookup_class = targets[0].get_lookup('isnull') |                 lookup_class = targets[0].get_lookup('isnull') | ||||||
|                 clause.add(lookup_class(targets[0].get_col(alias, sources[0]), False), AND) |                 clause.add(lookup_class(targets[0].get_col(alias, join_info.targets[0]), False), AND) | ||||||
|         return clause, used_joins if not require_outer else () |         return clause, used_joins if not require_outer else () | ||||||
|  |  | ||||||
|     def add_filter(self, filter_clause): |     def add_filter(self, filter_clause): | ||||||
| @@ -1383,7 +1387,7 @@ class Query: | |||||||
|             reuse = can_reuse if join.m2m else None |             reuse = can_reuse if join.m2m else None | ||||||
|             alias = self.join(connection, reuse=reuse) |             alias = self.join(connection, reuse=reuse) | ||||||
|             joins.append(alias) |             joins.append(alias) | ||||||
|         return final_field, targets, opts, joins, path |         return JoinInfo(final_field, targets, opts, joins, path) | ||||||
|  |  | ||||||
|     def trim_joins(self, targets, joins, path): |     def trim_joins(self, targets, joins, path): | ||||||
|         """ |         """ | ||||||
| @@ -1425,16 +1429,14 @@ class Query: | |||||||
|                 return self.annotation_select[name] |                 return self.annotation_select[name] | ||||||
|         else: |         else: | ||||||
|             field_list = name.split(LOOKUP_SEP) |             field_list = name.split(LOOKUP_SEP) | ||||||
|             field, sources, opts, join_list, path = self.setup_joins( |             join_info = self.setup_joins(field_list, self.get_meta(), self.get_initial_alias(), reuse) | ||||||
|                 field_list, self.get_meta(), |             targets, _, join_list = self.trim_joins(join_info.targets, join_info.joins, join_info.path) | ||||||
|                 self.get_initial_alias(), reuse) |  | ||||||
|             targets, _, join_list = self.trim_joins(sources, join_list, path) |  | ||||||
|             if len(targets) > 1: |             if len(targets) > 1: | ||||||
|                 raise FieldError("Referencing multicolumn fields with F() objects " |                 raise FieldError("Referencing multicolumn fields with F() objects " | ||||||
|                                  "isn't supported") |                                  "isn't supported") | ||||||
|             if reuse is not None: |             if reuse is not None: | ||||||
|                 reuse.update(join_list) |                 reuse.update(join_list) | ||||||
|             col = targets[0].get_col(join_list[-1], sources[0]) |             col = targets[0].get_col(join_list[-1], join_info.targets[0]) | ||||||
|             return col |             return col | ||||||
|  |  | ||||||
|     def split_exclude(self, filter_expr, prefix, can_reuse, names_with_path): |     def split_exclude(self, filter_expr, prefix, can_reuse, names_with_path): | ||||||
| @@ -1586,9 +1588,12 @@ class Query: | |||||||
|             for name in field_names: |             for name in field_names: | ||||||
|                 # Join promotion note - we must not remove any rows here, so |                 # Join promotion note - we must not remove any rows here, so | ||||||
|                 # if there is no existing joins, use outer join. |                 # if there is no existing joins, use outer join. | ||||||
|                 _, targets, _, joins, path = self.setup_joins( |                 join_info = self.setup_joins(name.split(LOOKUP_SEP), opts, alias, allow_many=allow_m2m) | ||||||
|                     name.split(LOOKUP_SEP), opts, alias, allow_many=allow_m2m) |                 targets, final_alias, joins = self.trim_joins( | ||||||
|                 targets, final_alias, joins = self.trim_joins(targets, joins, path) |                     join_info.targets, | ||||||
|  |                     join_info.joins, | ||||||
|  |                     join_info.path, | ||||||
|  |                 ) | ||||||
|                 for target in targets: |                 for target in targets: | ||||||
|                     cols.append(target.get_col(final_alias)) |                     cols.append(target.get_col(final_alias)) | ||||||
|             if cols: |             if cols: | ||||||
|   | |||||||
		Reference in New Issue
	
	Block a user