summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorAnssi Kääriäinen <akaariai@gmail.com>2017-07-07 12:49:23 -0400
committerTim Graham <timograham@gmail.com>2017-07-31 15:31:29 -0400
commit6155bc4a51d44afa096c4c00766cbfb9ba9d660c (patch)
tree1d65cb9b4c099ce15d8a388f9904b8454720975f
parent66933a6619be386248ea9329c81b257d5e2e5990 (diff)
Refs #20880 -- Removed non-cloning logic from Query.clone().
-rw-r--r--django/db/models/query.py10
-rw-r--r--django/db/models/sql/compiler.py2
-rw-r--r--django/db/models/sql/query.py64
-rw-r--r--django/db/models/sql/subqueries.py11
4 files changed, 39 insertions, 48 deletions
diff --git a/django/db/models/query.py b/django/db/models/query.py
index 9b9388f2f5..1984ba6006 100644
--- a/django/db/models/query.py
+++ b/django/db/models/query.py
@@ -335,7 +335,7 @@ class QuerySet:
raise TypeError("Complex aggregates require an alias")
kwargs[arg.default_alias] = arg
- query = self.query.clone()
+ query = self.query.chain()
for (alias, aggregate_expr) in kwargs.items():
query.add_annotation(aggregate_expr, alias, is_summary=True)
if not query.annotations[alias].contains_aggregate:
@@ -632,7 +632,7 @@ class QuerySet:
assert self.query.can_filter(), \
"Cannot update a query once a slice has been taken."
self._for_write = True
- query = self.query.clone(sql.UpdateQuery)
+ query = self.query.chain(sql.UpdateQuery)
query.add_update_values(kwargs)
# Clear any annotations so that they won't be present in subqueries.
query._annotations = None
@@ -651,7 +651,7 @@ class QuerySet:
"""
assert self.query.can_filter(), \
"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)
self._result_cache = None
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
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._for_write = self._for_write
c._prefetch_related_lookups = self._prefetch_related_lookups[:]
@@ -1267,7 +1267,7 @@ class RawQuerySet:
"""Select the database this RawQuerySet should execute against."""
return RawQuerySet(
self.raw_query, model=self.model,
- query=self.query.clone(using=alias),
+ query=self.query.chain(using=alias),
params=self.params, translations=self.translations,
using=alias,
)
diff --git a/django/db/models/sql/compiler.py b/django/db/models/sql/compiler.py
index 6e5ed79a41..980f6ee79f 100644
--- a/django/db/models/sql/compiler.py
+++ b/django/db/models/sql/compiler.py
@@ -1334,7 +1334,7 @@ class SQLUpdateCompiler(SQLCompiler):
count = self.query.count_active_tables()
if not self.query.related_updates and count == 1:
return
- query = self.query.clone(klass=Query)
+ query = self.query.chain(klass=Query)
query.select_related = False
query.clear_ordering(True)
query._extra = {}
diff --git a/django/db/models/sql/query.py b/django/db/models/sql/query.py
index 5e262d1765..bdec204351 100644
--- a/django/db/models/sql/query.py
+++ b/django/db/models/sql/query.py
@@ -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.
"""
from collections import Counter, Iterator, Mapping, OrderedDict
+from contextlib import suppress
from itertools import chain, count, product
from string import ascii_uppercase
@@ -58,6 +59,9 @@ class RawQuery:
self.extra_select = {}
self.annotation_select = {}
+ def chain(self, using):
+ return self.clone(using)
+
def clone(self, using):
return RawQuery(self.sql, using, params=self.params)
@@ -262,35 +266,21 @@ class Query:
"""
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
- used by clients to update attributes after copying has taken place.
+ Return a copy of the current Query. A lightweight alternative to
+ to deepcopy().
"""
obj = Empty()
- obj.__class__ = klass or self.__class__
- obj.model = self.model
+ obj.__class__ = self.__class__
+ # 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_map = self.alias_map.copy()
obj.external_aliases = self.external_aliases.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_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
if self.annotation_select_mask is 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
# used.
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
if self.extra_select_mask is None:
obj.extra_select_mask = None
@@ -315,21 +301,25 @@ class Query:
obj._extra_select_cache = None
else:
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__:
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'):
obj._setup_query()
return obj
diff --git a/django/db/models/sql/subqueries.py b/django/db/models/sql/subqueries.py
index 1cb9aa9357..edff51a56a 100644
--- a/django/db/models/sql/subqueries.py
+++ b/django/db/models/sql/subqueries.py
@@ -87,16 +87,17 @@ class UpdateQuery(Query):
def _setup_query(self):
"""
- Run on initialization and after cloning. Any attributes that would
- normally be set in __init__ should go in here, instead, so that they
- are also set up after a clone() call.
+ Run on initialization and at the end of chaining. Any attributes that
+ would normally be set in __init__() should go here instead.
"""
self.values = []
self.related_ids = None
self.related_updates = {}
- def clone(self, klass=None, **kwargs):
- return super().clone(klass, related_updates=self.related_updates.copy(), **kwargs)
+ def clone(self):
+ obj = super().clone()
+ obj.related_updates = self.related_updates.copy()
+ return obj
def update_batch(self, pk_list, values, using):
self.add_update_values(values)