Refs #20880 -- Removed non-cloning logic from Query.clone().

This commit is contained in:
Anssi Kääriäinen 2017-07-07 12:49:23 -04:00 committed by Tim Graham
parent 66933a6619
commit 6155bc4a51
4 changed files with 39 additions and 48 deletions

View File

@ -335,7 +335,7 @@ class QuerySet:
raise TypeError("Complex aggregates require an alias") raise TypeError("Complex aggregates require an alias")
kwargs[arg.default_alias] = arg kwargs[arg.default_alias] = arg
query = self.query.clone() query = self.query.chain()
for (alias, aggregate_expr) in kwargs.items(): for (alias, aggregate_expr) in kwargs.items():
query.add_annotation(aggregate_expr, alias, is_summary=True) query.add_annotation(aggregate_expr, alias, is_summary=True)
if not query.annotations[alias].contains_aggregate: if not query.annotations[alias].contains_aggregate:
@ -632,7 +632,7 @@ class QuerySet:
assert self.query.can_filter(), \ assert self.query.can_filter(), \
"Cannot update a query once a slice has been taken." "Cannot update a query once a slice has been taken."
self._for_write = True self._for_write = True
query = self.query.clone(sql.UpdateQuery) query = self.query.chain(sql.UpdateQuery)
query.add_update_values(kwargs) query.add_update_values(kwargs)
# Clear any annotations so that they won't be present in subqueries. # Clear any annotations so that they won't be present in subqueries.
query._annotations = None query._annotations = None
@ -651,7 +651,7 @@ class QuerySet:
""" """
assert self.query.can_filter(), \ assert self.query.can_filter(), \
"Cannot update a query once a slice has been taken." "Cannot update a query once a slice has been taken."
query = self.query.clone(sql.UpdateQuery) query = self.query.chain(sql.UpdateQuery)
query.add_update_fields(values) query.add_update_fields(values)
self._result_cache = None self._result_cache = None
return query.get_compiler(self.db).execute_sql(CURSOR) return query.get_compiler(self.db).execute_sql(CURSOR)
@ -1087,7 +1087,7 @@ class QuerySet:
Return a copy of the current QuerySet. A lightweight alternative Return a copy of the current QuerySet. A lightweight alternative
to deepcopy(). to deepcopy().
""" """
c = self.__class__(model=self.model, query=self.query.clone(), using=self._db, hints=self._hints) c = self.__class__(model=self.model, query=self.query.chain(), using=self._db, hints=self._hints)
c._sticky_filter = self._sticky_filter c._sticky_filter = self._sticky_filter
c._for_write = self._for_write c._for_write = self._for_write
c._prefetch_related_lookups = self._prefetch_related_lookups[:] c._prefetch_related_lookups = self._prefetch_related_lookups[:]
@ -1267,7 +1267,7 @@ class RawQuerySet:
"""Select the database this RawQuerySet should execute against.""" """Select the database this RawQuerySet should execute against."""
return RawQuerySet( return RawQuerySet(
self.raw_query, model=self.model, self.raw_query, model=self.model,
query=self.query.clone(using=alias), query=self.query.chain(using=alias),
params=self.params, translations=self.translations, params=self.params, translations=self.translations,
using=alias, using=alias,
) )

View File

@ -1334,7 +1334,7 @@ class SQLUpdateCompiler(SQLCompiler):
count = self.query.count_active_tables() count = self.query.count_active_tables()
if not self.query.related_updates and count == 1: if not self.query.related_updates and count == 1:
return return
query = self.query.clone(klass=Query) query = self.query.chain(klass=Query)
query.select_related = False query.select_related = False
query.clear_ordering(True) query.clear_ordering(True)
query._extra = {} query._extra = {}

View File

@ -7,6 +7,7 @@ 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
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
@ -58,6 +59,9 @@ class RawQuery:
self.extra_select = {} self.extra_select = {}
self.annotation_select = {} self.annotation_select = {}
def chain(self, using):
return self.clone(using)
def clone(self, using): def clone(self, using):
return RawQuery(self.sql, using, params=self.params) return RawQuery(self.sql, using, params=self.params)
@ -262,35 +266,21 @@ class Query:
""" """
return self.model._meta return self.model._meta
def clone(self, klass=None, **kwargs): def clone(self):
""" """
Create a copy of the current instance. The 'kwargs' parameter can be Return a copy of the current Query. A lightweight alternative to
used by clients to update attributes after copying has taken place. to deepcopy().
""" """
obj = Empty() obj = Empty()
obj.__class__ = klass or self.__class__ obj.__class__ = self.__class__
obj.model = self.model # Copy references to everything.
obj.__dict__ = self.__dict__.copy()
# Clone attributes that can't use shallow copy.
obj.alias_refcount = self.alias_refcount.copy() obj.alias_refcount = self.alias_refcount.copy()
obj.alias_map = self.alias_map.copy() obj.alias_map = self.alias_map.copy()
obj.external_aliases = self.external_aliases.copy() obj.external_aliases = self.external_aliases.copy()
obj.table_map = self.table_map.copy() obj.table_map = self.table_map.copy()
obj.default_cols = self.default_cols
obj.default_ordering = self.default_ordering
obj.standard_ordering = self.standard_ordering
obj.select = self.select
obj.where = self.where.clone() obj.where = self.where.clone()
obj.where_class = self.where_class
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.distinct = self.distinct
obj.distinct_fields = self.distinct_fields
obj.select_for_update = self.select_for_update
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_of = self.select_for_update_of
obj.select_related = self.select_related
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
@ -302,10 +292,6 @@ class Query:
# It will get re-populated in the cloned queryset the next time it's # It will get re-populated in the cloned queryset the next time it's
# used. # used.
obj._annotation_select_cache = None obj._annotation_select_cache = None
obj.max_depth = self.max_depth
obj.combinator = self.combinator
obj.combinator_all = self.combinator_all
obj.combined_queries = self.combined_queries
obj._extra = self._extra.copy() if self._extra is not None else None obj._extra = self._extra.copy() if self._extra is not None else None
if self.extra_select_mask is None: if self.extra_select_mask is None:
obj.extra_select_mask = None obj.extra_select_mask = None
@ -315,21 +301,25 @@ class Query:
obj._extra_select_cache = None obj._extra_select_cache = None
else: else:
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_order_by = self.extra_order_by
obj.deferred_loading = self.deferred_loading
if self.filter_is_sticky and self.used_aliases:
obj.used_aliases = self.used_aliases.copy()
else:
obj.used_aliases = set()
obj.filter_is_sticky = False
obj.subquery = self.subquery
if 'alias_prefix' in self.__dict__:
obj.alias_prefix = self.alias_prefix
if 'subq_aliases' in self.__dict__: if 'subq_aliases' in self.__dict__:
obj.subq_aliases = self.subq_aliases.copy() obj.subq_aliases = self.subq_aliases.copy()
obj.used_aliases = self.used_aliases.copy()
# Clear the cached_property
with suppress(AttributeError):
del obj.base_table
return obj
obj.__dict__.update(kwargs) def chain(self, klass=None):
"""
Return a copy of the current Query that's ready for another operation.
The klass argument changes the type of the Query, e.g. UpdateQuery.
"""
obj = self.clone()
if klass and obj.__class__ != klass:
obj.__class__ = klass
if not obj.filter_is_sticky:
obj.used_aliases = set()
obj.filter_is_sticky = False
if hasattr(obj, '_setup_query'): if hasattr(obj, '_setup_query'):
obj._setup_query() obj._setup_query()
return obj return obj

View File

@ -87,16 +87,17 @@ class UpdateQuery(Query):
def _setup_query(self): def _setup_query(self):
""" """
Run on initialization and after cloning. Any attributes that would Run on initialization and at the end of chaining. Any attributes that
normally be set in __init__ should go in here, instead, so that they would normally be set in __init__() should go here instead.
are also set up after a clone() call.
""" """
self.values = [] self.values = []
self.related_ids = None self.related_ids = None
self.related_updates = {} self.related_updates = {}
def clone(self, klass=None, **kwargs): def clone(self):
return super().clone(klass, related_updates=self.related_updates.copy(), **kwargs) obj = super().clone()
obj.related_updates = self.related_updates.copy()
return obj
def update_batch(self, pk_list, values, using): def update_batch(self, pk_list, values, using):
self.add_update_values(values) self.add_update_values(values)