Refs #27624 -- Made many attributes of Query immutable.

This commit is contained in:
Adam Johnson 2017-03-08 06:25:44 -08:00 committed by Tim Graham
parent 5db465d5a6
commit af121b08e8
2 changed files with 50 additions and 58 deletions

View File

@ -6,7 +6,6 @@ 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.
""" """
import copy
from collections import Counter, Iterator, Mapping, OrderedDict from collections import Counter, Iterator, Mapping, OrderedDict
from itertools import chain, count, product from itertools import chain, count, product
from string import ascii_uppercase from string import ascii_uppercase
@ -145,21 +144,21 @@ class Query:
# The select is used for cases where we want to set up the select # The select is used for cases where we want to set up the select
# clause to contain other than default fields (values(), subqueries...) # clause to contain other than default fields (values(), subqueries...)
# Note that annotations go to annotations dictionary. # Note that annotations go to annotations dictionary.
self.select = [] self.select = ()
self.tables = [] # Aliases in the order they are created. self.tables = () # Aliases in the order they are created.
self.where = where() self.where = where()
self.where_class = where self.where_class = where
# The group_by attribute can have one of the following forms: # The group_by attribute can have one of the following forms:
# - None: no group by at all in the query # - None: no group by at all in the query
# - A list of expressions: group by (at least) those expressions. # - A tuple of expressions: group by (at least) those expressions.
# String refs are also allowed for now. # String refs are also allowed for now.
# - True: group by all select fields of the model # - True: group by all select fields of the model
# See compiler.get_group_by() for details. # See compiler.get_group_by() for details.
self.group_by = None self.group_by = None
self.order_by = [] self.order_by = ()
self.low_mark, self.high_mark = 0, None # Used for offset/limit self.low_mark, self.high_mark = 0, None # Used for offset/limit
self.distinct = False self.distinct = False
self.distinct_fields = [] self.distinct_fields = ()
self.select_for_update = False self.select_for_update = False
self.select_for_update_nowait = False self.select_for_update_nowait = False
self.select_for_update_skip_locked = False self.select_for_update_skip_locked = False
@ -170,7 +169,7 @@ class Query:
# Holds the selects defined by a call to values() or values_list() # Holds the selects defined by a call to values() or values_list()
# excluding annotation_select and extra_select. # excluding annotation_select and extra_select.
self.values_select = [] self.values_select = ()
# SQL annotation-related attributes # SQL annotation-related attributes
# The _annotations will be an OrderedDict when used. Due to the cost # The _annotations will be an OrderedDict when used. Due to the cost
@ -199,7 +198,7 @@ class Query:
# A tuple that is a set of model field names and either True, if these # A tuple that is a set of model field names and either True, if these
# are the fields to defer, or False if these are the only fields to # are the fields to defer, or False if these are the only fields to
# load. # load.
self.deferred_loading = (set(), True) self.deferred_loading = (frozenset(), True)
self.context = {} self.context = {}
@ -271,25 +270,20 @@ class Query:
obj.default_cols = self.default_cols obj.default_cols = self.default_cols
obj.default_ordering = self.default_ordering obj.default_ordering = self.default_ordering
obj.standard_ordering = self.standard_ordering obj.standard_ordering = self.standard_ordering
obj.select = self.select[:] obj.select = self.select
obj.tables = self.tables[:] obj.tables = self.tables
obj.where = self.where.clone() obj.where = self.where.clone()
obj.where_class = self.where_class obj.where_class = self.where_class
if self.group_by is None: obj.group_by = self.group_by
obj.group_by = None obj.order_by = self.order_by
elif self.group_by is True:
obj.group_by = True
else:
obj.group_by = self.group_by[:]
obj.order_by = self.order_by[:]
obj.low_mark, obj.high_mark = self.low_mark, self.high_mark obj.low_mark, obj.high_mark = self.low_mark, self.high_mark
obj.distinct = self.distinct obj.distinct = self.distinct
obj.distinct_fields = self.distinct_fields[:] obj.distinct_fields = self.distinct_fields
obj.select_for_update = self.select_for_update obj.select_for_update = self.select_for_update
obj.select_for_update_nowait = self.select_for_update_nowait obj.select_for_update_nowait = self.select_for_update_nowait
obj.select_for_update_skip_locked = self.select_for_update_skip_locked obj.select_for_update_skip_locked = self.select_for_update_skip_locked
obj.select_related = self.select_related obj.select_related = self.select_related
obj.values_select = self.values_select[:] obj.values_select = self.values_select
obj._annotations = self._annotations.copy() if self._annotations is not None else None obj._annotations = self._annotations.copy() if self._annotations is not None else None
if self.annotation_select_mask is None: if self.annotation_select_mask is None:
obj.annotation_select_mask = None obj.annotation_select_mask = None
@ -316,7 +310,7 @@ class Query:
obj._extra_select_cache = self._extra_select_cache.copy() obj._extra_select_cache = self._extra_select_cache.copy()
obj.extra_tables = self.extra_tables obj.extra_tables = self.extra_tables
obj.extra_order_by = self.extra_order_by obj.extra_order_by = self.extra_order_by
obj.deferred_loading = copy.copy(self.deferred_loading[0]), self.deferred_loading[1] obj.deferred_loading = self.deferred_loading
if self.filter_is_sticky and self.used_aliases: if self.filter_is_sticky and self.used_aliases:
obj.used_aliases = self.used_aliases.copy() obj.used_aliases = self.used_aliases.copy()
else: else:
@ -412,7 +406,7 @@ class Query:
# done in a subquery so that we are aggregating on the limit and/or # done in a subquery so that we are aggregating on the limit and/or
# distinct results instead of applying the distinct and limit after the # distinct results instead of applying the distinct and limit after the
# aggregation. # aggregation.
if (isinstance(self.group_by, list) or has_limit or has_existing_annotations or if (isinstance(self.group_by, tuple) or has_limit or has_existing_annotations or
self.distinct): self.distinct):
from django.db.models.sql.subqueries import AggregateQuery from django.db.models.sql.subqueries import AggregateQuery
outer_query = AggregateQuery(self.model) outer_query = AggregateQuery(self.model)
@ -431,7 +425,7 @@ class Query:
# clearing the select clause can alter results if distinct is # clearing the select clause can alter results if distinct is
# used. # used.
if inner_query.default_cols and has_existing_annotations: if inner_query.default_cols and has_existing_annotations:
inner_query.group_by = [self.model._meta.pk.get_col(inner_query.get_initial_alias())] inner_query.group_by = (self.model._meta.pk.get_col(inner_query.get_initial_alias()),)
inner_query.default_cols = False inner_query.default_cols = False
relabels = {t: 'subquery' for t in inner_query.tables} relabels = {t: 'subquery' for t in inner_query.tables}
@ -446,11 +440,11 @@ class Query:
del inner_query.annotations[alias] del inner_query.annotations[alias]
# Make sure the annotation_select wont use cached results. # Make sure the annotation_select wont use cached results.
inner_query.set_annotation_mask(inner_query.annotation_select_mask) inner_query.set_annotation_mask(inner_query.annotation_select_mask)
if inner_query.select == [] and not inner_query.default_cols and not inner_query.annotation_select_mask: if inner_query.select == () and not inner_query.default_cols and not inner_query.annotation_select_mask:
# In case of Model.objects[0:3].count(), there would be no # In case of Model.objects[0:3].count(), there would be no
# field selected in the inner query, yet we must use a subquery. # field selected in the inner query, yet we must use a subquery.
# So, make sure at least one field is selected. # So, make sure at least one field is selected.
inner_query.select = [self.model._meta.pk.get_col(inner_query.get_initial_alias())] inner_query.select = (self.model._meta.pk.get_col(inner_query.get_initial_alias()),)
try: try:
outer_query.add_subquery(inner_query, using) outer_query.add_subquery(inner_query, using)
except EmptyResultSet: except EmptyResultSet:
@ -460,7 +454,7 @@ class Query:
} }
else: else:
outer_query = self outer_query = self
self.select = [] self.select = ()
self.default_cols = False self.default_cols = False
self._extra = {} self._extra = {}
@ -582,9 +576,10 @@ class Query:
self.where.add(w, connector) self.where.add(w, connector)
# Selection columns and extra extensions are those provided by 'rhs'. # Selection columns and extra extensions are those provided by 'rhs'.
self.select = [] if rhs.select:
for col in rhs.select: self.set_select([col.relabeled_clone(change_map) for col in rhs.select])
self.add_select(col.relabeled_clone(change_map)) else:
self.select = ()
if connector == OR: if connector == OR:
# It would be nice to be able to handle this, but the queries don't # It would be nice to be able to handle this, but the queries don't
@ -604,7 +599,7 @@ class Query:
# Ordering uses the 'rhs' ordering, unless it has none, in which case # Ordering uses the 'rhs' ordering, unless it has none, in which case
# the current ordering is used. # the current ordering is used.
self.order_by = rhs.order_by[:] if rhs.order_by else self.order_by self.order_by = rhs.order_by if rhs.order_by else self.order_by
self.extra_order_by = rhs.extra_order_by or self.extra_order_by self.extra_order_by = rhs.extra_order_by or self.extra_order_by
def deferred_to_data(self, target, callback): def deferred_to_data(self, target, callback):
@ -716,7 +711,7 @@ class Query:
alias = table_name alias = table_name
self.table_map[alias] = [alias] self.table_map[alias] = [alias]
self.alias_refcount[alias] = 1 self.alias_refcount[alias] = 1
self.tables.append(alias) self.tables += (alias,)
return alias, True return alias, True
def ref_alias(self, alias): def ref_alias(self, alias):
@ -800,9 +795,9 @@ class Query:
# 1. Update references in "select" (normal columns plus aliases), # 1. Update references in "select" (normal columns plus aliases),
# "group by" and "where". # "group by" and "where".
self.where.relabel_aliases(change_map) self.where.relabel_aliases(change_map)
if isinstance(self.group_by, list): if isinstance(self.group_by, tuple):
self.group_by = [col.relabeled_clone(change_map) for col in self.group_by] self.group_by = tuple([col.relabeled_clone(change_map) for col in self.group_by])
self.select = [col.relabeled_clone(change_map) for col in self.select] self.select = tuple([col.relabeled_clone(change_map) for col in self.select])
if self._annotations: if self._annotations:
self._annotations = OrderedDict( self._annotations = OrderedDict(
(key, col.relabeled_clone(change_map)) for key, col in self._annotations.items()) (key, col.relabeled_clone(change_map)) for key, col in self._annotations.items())
@ -866,10 +861,12 @@ class Query:
self.subq_aliases = self.subq_aliases.union([self.alias_prefix]) self.subq_aliases = self.subq_aliases.union([self.alias_prefix])
outer_query.subq_aliases = outer_query.subq_aliases.union(self.subq_aliases) outer_query.subq_aliases = outer_query.subq_aliases.union(self.subq_aliases)
change_map = OrderedDict() change_map = OrderedDict()
for pos, alias in enumerate(self.tables): tables = list(self.tables)
for pos, alias in enumerate(tables):
new_alias = '%s%d' % (self.alias_prefix, pos) new_alias = '%s%d' % (self.alias_prefix, pos)
change_map[alias] = new_alias change_map[alias] = new_alias
self.tables[pos] = new_alias tables[pos] = new_alias
self.tables = tuple(tables)
self.change_aliases(change_map) self.change_aliases(change_map)
def get_initial_alias(self): def get_initial_alias(self):
@ -1577,7 +1574,7 @@ class Query:
def clear_select_clause(self): def clear_select_clause(self):
"""Remove all fields from SELECT clause.""" """Remove all fields from SELECT clause."""
self.select = [] self.select = ()
self.default_cols = False self.default_cols = False
self.select_related = False self.select_related = False
self.set_extra_mask(()) self.set_extra_mask(())
@ -1589,16 +1586,12 @@ class Query:
Some queryset types completely replace any existing list of select Some queryset types completely replace any existing list of select
columns. columns.
""" """
self.select = [] self.select = ()
self.values_select = [] self.values_select = ()
def add_select(self, col):
self.default_cols = False
self.select.append(col)
def set_select(self, cols): def set_select(self, cols):
self.default_cols = False self.default_cols = False
self.select = cols self.select = tuple(cols)
def add_distinct_fields(self, *field_names): def add_distinct_fields(self, *field_names):
""" """
@ -1616,6 +1609,7 @@ class Query:
opts = self.get_meta() opts = self.get_meta()
try: try:
cols = []
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.
@ -1623,7 +1617,9 @@ class Query:
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, joins, path) targets, final_alias, joins = self.trim_joins(targets, joins, path)
for target in targets: for target in targets:
self.add_select(target.get_col(final_alias)) cols.append(target.get_col(final_alias))
if cols:
self.set_select(cols)
except MultiJoin: except MultiJoin:
raise FieldError("Invalid field name: '%s'" % name) raise FieldError("Invalid field name: '%s'" % name)
except FieldError: except FieldError:
@ -1657,7 +1653,7 @@ class Query:
if errors: if errors:
raise FieldError('Invalid order_by arguments: %s' % errors) raise FieldError('Invalid order_by arguments: %s' % errors)
if ordering: if ordering:
self.order_by.extend(ordering) self.order_by += ordering
else: else:
self.default_ordering = False self.default_ordering = False
@ -1666,7 +1662,7 @@ class Query:
Remove any ordering settings. If 'force_empty' is True, there will be Remove any ordering settings. If 'force_empty' is True, there will be
no ordering in the resulting query (not even the model's default). no ordering in the resulting query (not even the model's default).
""" """
self.order_by = [] self.order_by = ()
self.extra_order_by = () self.extra_order_by = ()
if force_empty: if force_empty:
self.default_ordering = False self.default_ordering = False
@ -1680,15 +1676,12 @@ class Query:
primary key, and the query would be equivalent, the optimization primary key, and the query would be equivalent, the optimization
will be made automatically. will be made automatically.
""" """
self.group_by = [] group_by = list(self.select)
for col in self.select:
self.group_by.append(col)
if self.annotation_select: if self.annotation_select:
for alias, annotation in self.annotation_select.items(): for alias, annotation in self.annotation_select.items():
for col in annotation.get_group_by_cols(): for col in annotation.get_group_by_cols():
self.group_by.append(col) group_by.append(col)
self.group_by = tuple(group_by)
def add_select_related(self, fields): def add_select_related(self, fields):
""" """
@ -1741,7 +1734,7 @@ class Query:
def clear_deferred_loading(self): def clear_deferred_loading(self):
"""Remove any fields from the deferred loading set.""" """Remove any fields from the deferred loading set."""
self.deferred_loading = (set(), True) self.deferred_loading = (frozenset(), True)
def add_deferred_loading(self, field_names): def add_deferred_loading(self, field_names):
""" """
@ -1785,7 +1778,7 @@ class Query:
self.deferred_loading = field_names.difference(existing), False self.deferred_loading = field_names.difference(existing), False
else: else:
# Replace any existing "immediate load" field names. # Replace any existing "immediate load" field names.
self.deferred_loading = field_names, False self.deferred_loading = frozenset(field_names), False
def get_loaded_field_names(self): def get_loaded_field_names(self):
""" """
@ -1865,7 +1858,7 @@ class Query:
else: else:
field_names = [f.attname for f in self.model._meta.concrete_fields] field_names = [f.attname for f in self.model._meta.concrete_fields]
self.values_select = field_names self.values_select = tuple(field_names)
self.add_fields(field_names, True) self.add_fields(field_names, True)
@property @property

View File

@ -19,7 +19,7 @@ class DeleteQuery(Query):
compiler = 'SQLDeleteCompiler' compiler = 'SQLDeleteCompiler'
def do_query(self, table, where, using): def do_query(self, table, where, using):
self.tables = [table] self.tables = (table,)
self.where = where self.where = where
cursor = self.get_compiler(using).execute_sql(CURSOR) cursor = self.get_compiler(using).execute_sql(CURSOR)
return cursor.rowcount if cursor else 0 return cursor.rowcount if cursor else 0
@ -52,8 +52,7 @@ class DeleteQuery(Query):
innerq.get_initial_alias() innerq.get_initial_alias()
# The same for our new query. # The same for our new query.
self.get_initial_alias() self.get_initial_alias()
innerq_used_tables = [t for t in innerq.tables innerq_used_tables = tuple([t for t in innerq.tables if innerq.alias_refcount[t]])
if innerq.alias_refcount[t]]
if not innerq_used_tables or innerq_used_tables == self.tables: if not innerq_used_tables or innerq_used_tables == self.tables:
# There is only the base table in use in the query. # There is only the base table in use in the query.
self.where = innerq.where self.where = innerq.where