From 6155bc4a51d44afa096c4c00766cbfb9ba9d660c Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Anssi=20K=C3=A4=C3=A4ri=C3=A4inen?= <akaariai@gmail.com>
Date: Fri, 7 Jul 2017 12:49:23 -0400
Subject: [PATCH] Refs #20880 -- Removed non-cloning logic from Query.clone().

---
 django/db/models/query.py          | 10 ++---
 django/db/models/sql/compiler.py   |  2 +-
 django/db/models/sql/query.py      | 64 +++++++++++++-----------------
 django/db/models/sql/subqueries.py | 11 ++---
 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)