diff --git a/django/db/models/sql/compiler.py b/django/db/models/sql/compiler.py index 1c6e80b538..c42149e45a 100644 --- a/django/db/models/sql/compiler.py +++ b/django/db/models/sql/compiler.py @@ -391,7 +391,7 @@ class SQLCompiler(object): if not distinct or elt in select_aliases: result.append('%s %s' % (elt, order)) group_by.append((elt, [])) - elif get_order_dir(field)[0] not in self.query.extra: + elif not self.query._extra or get_order_dir(field)[0] not in self.query._extra: # 'col' is of the form 'field' or 'field1__field2' or # '-field1__field2__field', etc. for table, cols, order in self.find_ordering_name(field, @@ -987,7 +987,7 @@ class SQLUpdateCompiler(SQLCompiler): # We need to use a sub-select in the where clause to filter on things # from other tables. query = self.query.clone(klass=Query) - query.extra = {} + query._extra = {} query.select = [] query.add_fields([query.get_meta().pk.name]) # Recheck the count - it is possible that fiddling with the select diff --git a/django/db/models/sql/query.py b/django/db/models/sql/query.py index 93a0b52330..9eea55e98e 100644 --- a/django/db/models/sql/query.py +++ b/django/db/models/sql/query.py @@ -143,7 +143,10 @@ class Query(object): self.select_related = False # SQL aggregate-related attributes - self.aggregates = OrderedDict() # Maps alias -> SQL aggregate function + # The _aggregates will be an OrderedDict when used. Due to the cost + # of creating OrderedDict this attribute is created lazily (in + # self.aggregates property). + self._aggregates = None # Maps alias -> SQL aggregate function self.aggregate_select_mask = None self._aggregate_select_cache = None @@ -153,7 +156,9 @@ class Query(object): # These are for extensions. The contents are more or less appended # verbatim to the appropriate clause. - self.extra = OrderedDict() # Maps col_alias -> (col_sql, params). + # The _extra attribute is an OrderedDict, lazily created similarly to + # .aggregates + self._extra = None # Maps col_alias -> (col_sql, params). self.extra_select_mask = None self._extra_select_cache = None @@ -165,6 +170,18 @@ class Query(object): # load. self.deferred_loading = (set(), True) + @property + def extra(self): + if self._extra is None: + self._extra = OrderedDict() + return self._extra + + @property + def aggregates(self): + if self._aggregates is None: + self._aggregates = OrderedDict() + return self._aggregates + def __str__(self): """ Returns the query as a string of SQL with the parameter values @@ -245,7 +262,7 @@ class Query(object): obj.select_for_update_nowait = self.select_for_update_nowait obj.select_related = self.select_related obj.related_select_cols = [] - obj.aggregates = self.aggregates.copy() + obj._aggregates = self._aggregates.copy() if self._aggregates is not None else None if self.aggregate_select_mask is None: obj.aggregate_select_mask = None else: @@ -257,7 +274,7 @@ class Query(object): # used. obj._aggregate_select_cache = None obj.max_depth = self.max_depth - obj.extra = self.extra.copy() + obj._extra = self._extra.copy() if self._extra is not None else None if self.extra_select_mask is None: obj.extra_select_mask = None else: @@ -344,7 +361,7 @@ class Query(object): # and move them to the outer AggregateQuery. for alias, aggregate in self.aggregate_select.items(): if aggregate.is_summary: - query.aggregate_select[alias] = aggregate.relabeled_clone(relabels) + query.aggregates[alias] = aggregate.relabeled_clone(relabels) del obj.aggregate_select[alias] try: @@ -358,7 +375,7 @@ class Query(object): query = self self.select = [] self.default_cols = False - self.extra = {} + self._extra = {} self.remove_inherited_models() query.clear_ordering(True) @@ -527,7 +544,7 @@ class Query(object): # It would be nice to be able to handle this, but the queries don't # really make sense (or return consistent value sets). Not worth # the extra complexity when you can write a real query instead. - if self.extra and rhs.extra: + if self._extra and rhs._extra: raise ValueError("When merging querysets using 'or', you " "cannot have extra(select=...) on both sides.") self.extra.update(rhs.extra) @@ -756,8 +773,9 @@ class Query(object): self.group_by = [relabel_column(col) for col in self.group_by] self.select = [SelectInfo(relabel_column(s.col), s.field) for s in self.select] - self.aggregates = OrderedDict( - (key, relabel_column(col)) for key, col in self.aggregates.items()) + if self._aggregates: + self._aggregates = OrderedDict( + (key, relabel_column(col)) for key, col in self._aggregates.items()) # 2. Rename the alias in the internal table/alias datastructures. for ident, aliases in self.join_map.items(): @@ -967,7 +985,7 @@ class Query(object): """ opts = model._meta field_list = aggregate.lookup.split(LOOKUP_SEP) - if len(field_list) == 1 and aggregate.lookup in self.aggregates: + if len(field_list) == 1 and self._aggregates and aggregate.lookup in self.aggregates: # Aggregate is over an annotation field_name = field_list[0] col = field_name @@ -1049,7 +1067,7 @@ class Query(object): lookup_parts = lookup.split(LOOKUP_SEP) num_parts = len(lookup_parts) if (len(lookup_parts) > 1 and lookup_parts[-1] in self.query_terms - and lookup not in self.aggregates): + and (not self._aggregates or lookup not in self._aggregates)): # Traverse the lookup query to distinguish related fields from # lookup types. lookup_model = self.model @@ -1108,10 +1126,11 @@ class Query(object): value, lookup_type = self.prepare_lookup_value(value, lookup_type, can_reuse) clause = self.where_class() - for alias, aggregate in self.aggregates.items(): - if alias in (parts[0], LOOKUP_SEP.join(parts)): - clause.add((aggregate, lookup_type, value), AND) - return clause + if self._aggregates: + for alias, aggregate in self.aggregates.items(): + if alias in (parts[0], LOOKUP_SEP.join(parts)): + clause.add((aggregate, lookup_type, value), AND) + return clause opts = self.get_meta() alias = self.get_initial_alias() @@ -1170,6 +1189,8 @@ class Query(object): Returns whether or not all elements of this q_object need to be put together in the HAVING clause. """ + if not self._aggregates: + return False if not isinstance(obj, Node): return (refs_aggregate(obj[0].split(LOOKUP_SEP), self.aggregates) or (hasattr(obj[1], 'contains_aggregate') @@ -1632,7 +1653,7 @@ class Query(object): # Set only aggregate to be the count column. # Clear out the select cache to reflect the new unmasked aggregates. - self.aggregates = {None: count} + self._aggregates = {None: count} self.set_aggregate_mask(None) self.group_by = None @@ -1781,7 +1802,8 @@ class Query(object): self.extra_select_mask = set(names) self._extra_select_cache = None - def _aggregate_select(self): + @property + def aggregate_select(self): """The OrderedDict of aggregate columns that are not masked, and should be used in the SELECT clause. @@ -1789,6 +1811,8 @@ class Query(object): """ if self._aggregate_select_cache is not None: return self._aggregate_select_cache + elif not self._aggregates: + return {} elif self.aggregate_select_mask is not None: self._aggregate_select_cache = OrderedDict( (k, v) for k, v in self.aggregates.items() @@ -1797,11 +1821,13 @@ class Query(object): return self._aggregate_select_cache else: return self.aggregates - aggregate_select = property(_aggregate_select) - def _extra_select(self): + @property + def extra_select(self): if self._extra_select_cache is not None: return self._extra_select_cache + if not self._extra: + return {} elif self.extra_select_mask is not None: self._extra_select_cache = OrderedDict( (k, v) for k, v in self.extra.items() @@ -1810,7 +1836,6 @@ class Query(object): return self._extra_select_cache else: return self.extra - extra_select = property(_extra_select) def trim_start(self, names_with_path): """