Ticket #16759: #16759-remove_deepcopy_in_qs.diff

File #16759-remove_deepcopy_in_qs.diff, 9.4 KB (added by Kronuz, 2 years ago)

Fixes #19964

  • django/db/models/sql/expressions.py

    diff --git a/django/db/models/sql/expressions.py b/django/db/models/sql/expressions.py
    index 1bbf742..51061ff 100644
     
    11from django.core.exceptions import FieldError
    22from django.db.models.fields import FieldDoesNotExist
    33from django.db.models.sql.constants import LOOKUP_SEP
     4import copy
    45
    56class SQLEvaluator(object):
    67    def __init__(self, expression, query, allow_joins=True):
    def __init__(self, expression, query, allow_joins=True, reuse=None): 
    1213        self.contains_aggregate = False
    1314        self.expression.prepare(self, query, allow_joins)
    1415
     16    def clone(self):
     17        clone = copy.copy(self)
     18        clone.cols = {}
     19        for key, col in self.cols.items():
     20            if hasattr(col, 'clone'):
     21                clone.cols[key] = col.clone()
     22            else:
     23                clone.cols[key] = col
     24        return clone
     25
     26
    1527    def prepare(self):
    1628        return self
    1729
  • django/db/models/sql/aggregates.py

    diff --git a/django/db/models/sql/aggregates.py b/django/db/models/sql/aggregates.py
    index b41314a..75a330f 100644
     
    11"""
    22Classes to represent the default SQL aggregate functions
    33"""
     4import copy
    45
    56from django.db.models.fields import IntegerField, FloatField
    67
    def __init__(self, col, source=None, is_summary=False, **extra): 
    6263
    6364        self.field = tmp
    6465
     66    def clone(self):
     67        # Different aggregates have different init methods, so use copy here
     68        # deepcopy is not needed, as self.col is only changing variable.
     69        return copy.copy(self)
     70
    6571    def relabel_aliases(self, change_map):
    6672        if isinstance(self.col, (list, tuple)):
    6773            self.col = (change_map.get(self.col[0], self.col[0]), self.col[1])
  • django/db/models/sql/query.py

    diff --git a/django/db/models/sql/query.py b/django/db/models/sql/query.py
    index 7f331bf..449404a 100644
    def clone(self, klass=None, memo=None, **kwargs): 
    256256        obj.dupe_avoidance = self.dupe_avoidance.copy()
    257257        obj.select = self.select[:]
    258258        obj.tables = self.tables[:]
    259         obj.where = copy.deepcopy(self.where, memo=memo)
     259        obj.where = self.where.clone()
    260260        obj.where_class = self.where_class
    261261        if self.group_by is None:
    262262            obj.group_by = None
    263263        else:
    264264            obj.group_by = self.group_by[:]
    265         obj.having = copy.deepcopy(self.having, memo=memo)
     265        obj.having = self.having.clone()
    266266        obj.order_by = self.order_by[:]
    267267        obj.low_mark, obj.high_mark = self.low_mark, self.high_mark
    268268        obj.distinct = self.distinct
    def clone(self, klass=None, memo=None, **kwargs): 
    271271        obj.select_for_update_nowait = self.select_for_update_nowait
    272272        obj.select_related = self.select_related
    273273        obj.related_select_cols = []
    274         obj.aggregates = copy.deepcopy(self.aggregates, memo=memo)
     274        obj.aggregates = SortedDict((k, v.clone())
     275                                    for k, v in self.aggregates.items())
    275276        if self.aggregate_select_mask is None:
    276277            obj.aggregate_select_mask = None
    277278        else:
    def clone(self, klass=None, memo=None, **kwargs): 
    294295            obj._extra_select_cache = self._extra_select_cache.copy()
    295296        obj.extra_tables = self.extra_tables
    296297        obj.extra_order_by = self.extra_order_by
    297         obj.deferred_loading = copy.deepcopy(self.deferred_loading, memo=memo)
     298        obj.deferred_loading = copy.copy(self.deferred_loading[0]), self.deferred_loading[1]
    298299        if self.filter_is_sticky and self.used_aliases:
    299300            obj.used_aliases = self.used_aliases.copy()
    300301        else:
    def combine(self, rhs, connector): 
    509510        # Now relabel a copy of the rhs where-clause and add it to the current
    510511        # one.
    511512        if rhs.where:
    512             w = copy.deepcopy(rhs.where)
     513            w = rhs.where.clone()
    513514            w.relabel_aliases(change_map)
    514515            if not self.where:
    515516                # Since 'self' matches everything, add an explicit "include
    def combine(self, rhs, connector): 
    530531            if isinstance(col, (list, tuple)):
    531532                self.select.append((change_map.get(col[0], col[0]), col[1]))
    532533            else:
    533                 item = copy.deepcopy(col)
     534                item = col.clone()
    534535                item.relabel_aliases(change_map)
    535536                self.select.append(item)
    536537        self.select_fields = rhs.select_fields[:]
  • django/db/models/sql/where.py

    diff --git a/django/db/models/sql/where.py b/django/db/models/sql/where.py
    index 5515bc4..f9475a6 100644
     
    1010
    1111from django.utils import tree
    1212from django.db.models.fields import Field
    13 from django.db.models.sql.datastructures import EmptyResultSet, FullResultSet
     13from django.db.models.sql.datastructures import EmptyResultSet, FullResultSet, Empty
    1414from django.db.models.sql.aggregates import Aggregate
    1515
    1616# Connection types
    def relabel_aliases(self, change_map, node=None): 
    254254                # Check if the query value also requires relabelling
    255255                if hasattr(child[3], 'relabel_aliases'):
    256256                    child[3].relabel_aliases(change_map)
     257
     258    def clone(self):
     259        """
     260        Creates a clone of the tree. Must only be called on root nodes (nodes
     261        with empty subtree_parents). Childs must be either Contraint, lookup,
     262        value tuples, or objects supporting .clone().
     263        """
     264        assert not self.subtree_parents, '.clone() can only be called on root nodes'
     265        clone = self.__class__._new_instance(
     266            children=[], connector=self.connector, negated=self.negated)
     267        for child in self.children:
     268            if isinstance(child, tuple):
     269                clone.children.append(
     270                    tuple(map(lambda o: o.clone() if hasattr(o, 'clone') else o, child)))
     271            else:
     272                clone.children.append(child.clone())
     273        return clone
    257274
    258275class EverythingNode(object):
    259276    """
    def as_sql(self, qn=None, connection=None): 
    266283    def relabel_aliases(self, change_map, node=None):
    267284        return
    268285
     286    def clone(self):
     287        return self
     288
    269289class NothingNode(object):
    270290    """
    271291    A node that matches nothing.
    def as_sql(self, qn=None, connection=None): 
    276296    def relabel_aliases(self, change_map, node=None):
    277297        return
    278298
     299    def clone(self):
     300        return self
     301
    279302class ExtraWhere(object):
    280303    def __init__(self, sqls, params):
    281304        self.sqls = sqls
    def as_sql(self, qn=None, connection=None): 
    285308        sqls = ["(%s)" % sql for sql in self.sqls]
    286309        return " AND ".join(sqls), tuple(self.params or ())
    287310
     311    def clone(self):
     312        return self
     313
    288314class Constraint(object):
    289315    """
    290316    An object that can be passed to WhereNode.add() and knows how to
    def process(self, lookup_type, value, connection): 
    349375    def relabel_aliases(self, change_map):
    350376        if self.alias in change_map:
    351377            self.alias = change_map[self.alias]
     378
     379    def clone(self):
     380        new = Empty()
     381        new.__class__ = self.__class__
     382        new.alias, new.col, new.field = self.alias, self.col, self.field
     383        return new
  • tests/regressiontests/queries/tests.py

    diff --git a/tests/regressiontests/queries/tests.py b/tests/regressiontests/queries/tests.py
    index 1035a38..9ef06ca 100644
    def test_sliced_delete(self): 
    16491649
    16501650
    16511651class CloneTests(TestCase):
     1652
    16521653    def test_evaluated_queryset_as_argument(self):
    16531654        "#13227 -- If a queryset is already evaluated, it can still be used as a query arg"
    16541655        n = Note(note='Test1', misc='misc')
    def test_evaluated_queryset_as_argument(self): 
    16661667        except:
    16671668            self.fail('Query should be clonable')
    16681669
     1670    def test_no_model_options_cloning(self):
     1671        """
     1672        Test that cloning a queryset does not get out of hand. While complete
     1673        testing is impossible, this is a sanity check against invalid use of
     1674        deepcopy. refs #16759.
     1675        """
     1676        opts_class = type(Note._meta)
     1677        note_deepcopy = getattr(opts_class, "__deepcopy__", None)
     1678        opts_class.__deepcopy__ = lambda obj, memo: self.fail("Model options shouldn't be cloned.")
     1679        try:
     1680            Note.objects.filter(pk__lte=F('pk') + 1).all()
     1681        finally:
     1682            if note_deepcopy is None:
     1683                delattr(opts_class, "__deepcopy__")
     1684            else:
     1685                opts_class.__deepcopy__ = note_deepcopy
     1686
     1687    def test_no_fields_cloning(self):
     1688        """
     1689        Test that cloning a queryset does not get out of hand. While complete
     1690        testing is impossible, this is a sanity check against invalid use of
     1691        deepcopy. refs #16759.
     1692        """
     1693        opts_class = type(Note._meta.get_field_by_name("misc")[0])
     1694        note_deepcopy = getattr(opts_class, "__deepcopy__", None)
     1695        opts_class.__deepcopy__ = lambda obj, memo: self.fail("Model fields shouldn't be cloned")
     1696        try:
     1697            Note.objects.filter(note=F('misc')).all()
     1698        finally:
     1699            if note_deepcopy is None:
     1700                delattr(opts_class, "__deepcopy__")
     1701            else:
     1702                opts_class.__deepcopy__ = note_deepcopy
    16691703
    16701704class EmptyQuerySetTests(TestCase):
    16711705    def test_emptyqueryset_values(self):
Back to Top