Skip to content
This repository

HTTPS clone URL

Subversion checkout URL

You can clone with HTTPS or Subversion.

Download ZIP
Browse code

Fixed #19964 -- Removed relabel_aliases from some structs

Before there was need to have both .relabel_aliases() and .clone() for
many structs. Now there is only relabeled_clone() for those structs
where alias is the only mutable attribute.
  • Loading branch information...
commit d744c550d51955fd623897884446b7f34318e94d 1 parent 679af40
Anssi Kääriäinen authored March 02, 2013
6  django/db/models/fields/__init__.py
@@ -344,9 +344,9 @@ def get_db_prep_lookup(self, lookup_type, value, connection,
344 344
         if hasattr(value, 'get_compiler'):
345 345
             value = value.get_compiler(connection=connection)
346 346
         if hasattr(value, 'as_sql') or hasattr(value, '_as_sql'):
347  
-            # If the value has a relabel_aliases method, it will need to
348  
-            # be invoked before the final SQL is evaluated
349  
-            if hasattr(value, 'relabel_aliases'):
  347
+            # If the value has a relabeled_clone method it means the
  348
+            # value will be handled later on.
  349
+            if hasattr(value, 'relabeled_clone'):
350 350
                 return value
351 351
             if hasattr(value, 'as_sql'):
352 352
                 sql, params = value.as_sql()
6  django/db/models/fields/related.py
@@ -153,9 +153,9 @@ def get_db_prep_lookup(self, lookup_type, value, connection, prepared=False):
11  django/db/models/sql/aggregates.py
@@ -63,14 +63,11 @@ def __init__(self, col, source=None, is_summary=False, **extra):
63 63
 
64 64
         self.field = tmp
65 65
 
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  
-
71  
-    def relabel_aliases(self, change_map):
  66
+    def relabeled_clone(self, change_map):
  67
+        clone = copy.copy(self)
72 68
         if isinstance(self.col, (list, tuple)):
73  
-            self.col = (change_map.get(self.col[0], self.col[0]), self.col[1])
  69
+            clone.col = (change_map.get(self.col[0], self.col[0]), self.col[1])
  70
+        return clone
74 71
 
75 72
     def as_sql(self, qn, connection):
76 73
         "Return the aggregate, rendered as SQL with parameters."
12  django/db/models/sql/datastructures.py
@@ -32,10 +32,8 @@ def __init__(self, col, lookup_type):
32 32
         self.col = col
33 33
         self.lookup_type = lookup_type
34 34
 
35  
-    def relabel_aliases(self, change_map):
36  
-        c = self.col
37  
-        if isinstance(c, (list, tuple)):
38  
-            self.col = (change_map.get(c[0], c[0]), c[1])
  35
+    def relabeled_clone(self, change_map):
  36
+        return self.__class__((change_map.get(self.col[0], self.col[0]), self.col[1]))
39 37
 
40 38
     def as_sql(self, qn, connection):
41 39
         if isinstance(self.col, (list, tuple)):
@@ -53,10 +51,8 @@ def __init__(self, col, lookup_type, tzname):
53 51
         self.lookup_type = lookup_type
54 52
         self.tzname = tzname
55 53
 
56  
-    def relabel_aliases(self, change_map):
57  
-        c = self.col
58  
-        if isinstance(c, (list, tuple)):
59  
-            self.col = (change_map.get(c[0], c[0]), c[1])
  54
+    def relabeled_clone(self, change_map):
  55
+        return self.__class__((change_map.get(self.col[0], self.col[0]), self.col[1]))
60 56
 
61 57
     def as_sql(self, qn, connection):
62 58
         if isinstance(self.col, (list, tuple)):
23  django/db/models/sql/expressions.py
... ...
@@ -1,6 +1,7 @@
1 1
 from django.core.exceptions import FieldError
2 2
 from django.db.models.constants import LOOKUP_SEP
3 3
 from django.db.models.fields import FieldDoesNotExist
  4
+import copy
4 5
 
5 6
 class SQLEvaluator(object):
6 7
     def __init__(self, expression, query, allow_joins=True, reuse=None):
@@ -12,23 +13,23 @@ def __init__(self, expression, query, allow_joins=True, reuse=None):
12 13
         self.reuse = reuse
13 14
         self.expression.prepare(self, query, allow_joins)
14 15
 
  16
+    def relabeled_clone(self, change_map):
  17
+        clone = copy.copy(self)
  18
+        clone.cols = []
  19
+        for node, col in self.cols[:]:
  20
+            if hasattr(col, 'relabeled_clone'):
  21
+                clone.cols.append((node, col.relabeled_clone(change_map)))
  22
+            else:
  23
+                clone.cols.append((node,
  24
+                                   (change_map.get(col[0], col[0]), col[1])))
  25
+        return clone
  26
+
15 27
     def prepare(self):
16 28
         return self
17 29
 
18 30
     def as_sql(self, qn, connection):
19 31
         return self.expression.evaluate(self, qn, connection)
20 32
 
21  
-    def relabel_aliases(self, change_map):
22  
-        new_cols = []
23  
-        for node, col in self.cols:
24  
-            if hasattr(col, "relabel_aliases"):
25  
-                col.relabel_aliases(change_map)
26  
-                new_cols.append((node, col))
27  
-            else:
28  
-                new_cols.append((node,
29  
-                                (change_map.get(col[0], col[0]), col[1])))
30  
-        self.cols = new_cols
31  
-
32 33
     #####################################################
33 34
     # Vistor methods for initial expression preparation #
34 35
     #####################################################
31  django/db/models/sql/query.py
@@ -294,8 +294,7 @@ def clone(self, klass=None, memo=None, **kwargs):
294 294
         obj.select_for_update_nowait = self.select_for_update_nowait
295 295
         obj.select_related = self.select_related
296 296
         obj.related_select_cols = []
297  
-        obj.aggregates = SortedDict((k, v.clone())
298  
-                                    for k, v in self.aggregates.items())
  297
+        obj.aggregates = self.aggregates.copy()
299 298
         if self.aggregate_select_mask is None:
300 299
             obj.aggregate_select_mask = None
301 300
         else:
@@ -559,9 +558,8 @@ def combine(self, rhs, connector):
559 558
                 new_col = change_map.get(col[0], col[0]), col[1]
560 559
                 self.select.append(SelectInfo(new_col, field))
561 560
             else:
562  
-                item = col.clone()
563  
-                item.relabel_aliases(change_map)
564  
-                self.select.append(SelectInfo(item, field))
  561
+                new_col = col.relabeled_clone(change_map)
  562
+                self.select.append(SelectInfo(new_col, field))
565 563
 
566 564
         if connector == OR:
567 565
             # It would be nice to be able to handle this, but the queries don't
@@ -769,26 +767,6 @@ def promote_disjunction(self, aliases_before, alias_usage_counts,
769 767
         The principle for promotion is: any alias which is used (it is in
770 768
         alias_usage_counts), is not used by every child of the ORed filter,
771 769
         and isn't pre-existing needs to be promoted to LOUTER join.
772  
-
773  
-        Some examples (assume all joins used are nullable):
774  
-            - existing filter: a__f1=foo
775  
-            - add filter: b__f1=foo|b__f2=foo
776  
-            In this case we should not promote either of the joins (using INNER
777  
-            doesn't remove results). We correctly avoid join promotion, because
778  
-            a is not used in this branch, and b is used two times.
779  
-
780  
-            - add filter a__f1=foo|b__f2=foo
781  
-            In this case we should promote both a and b, otherwise they will
782  
-            remove results. We will also correctly do that as both aliases are
783  
-            used, and in addition both are used only once while there are two
784  
-            filters.
785  
-
786  
-            - existing: a__f1=bar
787  
-            - add filter: a__f2=foo|b__f2=foo
788  
-            We will not promote a as it is previously used. If the join results
789  
-            in null, the existing filter can't succeed.
790  
-
791  
-        The above (and some more) are tested in queries.DisjunctionPromotionTests
792 770
         """
793 771
         for alias, use_count in alias_usage_counts.items():
794 772
             if use_count < num_childs and alias not in aliases_before:
@@ -807,8 +785,7 @@ def relabel_column(col):
807 785
                 old_alias = col[0]
808 786
                 return (change_map.get(old_alias, old_alias), col[1])
809 787
             else:
810  
-                col.relabel_aliases(change_map)
811  
-                return col
  788
+                return col.relabeled_clone(change_map)
812 789
         # 1. Update references in "select" (normal columns plus aliases),
813 790
         # "group by", "where" and "having".
814 791
         self.where.relabel_aliases(change_map)
74  django/db/models/sql/where.py
@@ -34,9 +34,15 @@ class WhereNode(tree.Node):
34 34
     The class is tied to the Query class that created it (in order to create
35 35
     the correct SQL).
36 36
 
37  
-    The children in this tree are usually either Q-like objects or lists of
38  
-    [table_alias, field_name, db_type, lookup_type, value_annotation, params].
39  
-    However, a child could also be any class with as_sql() and relabel_aliases() methods.
  37
+    A child is usually a tuple of:
  38
+        (Constraint(alias, targetcol, field), lookup_type, value)
  39
+    where value can be either raw Python value, or Query, ExpressionNode or
  40
+    something else knowing how to turn itself into SQL.
  41
+
  42
+    However, a child could also be any class with as_sql() and either
  43
+    relabeled_clone() method or relabel_aliases() and clone() methods. The
  44
+    second alternative should be used if the alias is not the only mutable
  45
+    variable.
40 46
     """
41 47
     default = AND
42 48
 
@@ -255,30 +261,22 @@ def sql_for_columns(self, data, qn, connection):
255 261
             lhs = qn(name)
256 262
         return connection.ops.field_cast_sql(db_type) % lhs
257 263
 
258  
-    def relabel_aliases(self, change_map, node=None):
  264
+    def relabel_aliases(self, change_map):
259 265
         """
260 266
         Relabels the alias values of any children. 'change_map' is a dictionary
261 267
         mapping old (current) alias values to the new values.
262 268
         """
263  
-        if not node:
264  
-            node = self
265  
-        for pos, child in enumerate(node.children):
  269
+        for pos, child in enumerate(self.children):
266 270
             if hasattr(child, 'relabel_aliases'):
  271
+                # For example another WhereNode
267 272
                 child.relabel_aliases(change_map)
268  
-            elif isinstance(child, tree.Node):
269  
-                self.relabel_aliases(change_map, child)
270 273
             elif isinstance(child, (list, tuple)):
271  
-                if isinstance(child[0], (list, tuple)):
272  
-                    elt = list(child[0])
273  
-                    if elt[0] in change_map:
274  
-                        elt[0] = change_map[elt[0]]
275  
-                        node.children[pos] = (tuple(elt),) + child[1:]
276  
-                else:
277  
-                    child[0].relabel_aliases(change_map)
278  
-
279  
-                # Check if the query value also requires relabelling
280  
-                if hasattr(child[3], 'relabel_aliases'):
281  
-                    child[3].relabel_aliases(change_map)
  274
+                # tuple starting with Constraint
  275
+                child = (child[0].relabeled_clone(change_map),) + child[1:]
  276
+                if hasattr(child[3], 'relabeled_clone'):
  277
+                    child = (child[0], child[1], child[2]) + (
  278
+                        child[3].relabeled_clone(change_map),)
  279
+                self.children[pos] = child
282 280
 
283 281
     def clone(self):
284 282
         """
@@ -290,11 +288,10 @@ def clone(self):
290 288
         clone = self.__class__._new_instance(
291 289
             children=[], connector=self.connector, negated=self.negated)
292 290
         for child in self.children:
293  
-            if isinstance(child, tuple):
294  
-                clone.children.append(
295  
-                    (child[0].clone(), child[1], child[2], child[3]))
296  
-            else:
  291
+            if hasattr(child, 'clone'):
297 292
                 clone.children.append(child.clone())
  293
+            else:
  294
+                clone.children.append(child)
298 295
         return clone
299 296
 
300 297
 class EmptyWhere(WhereNode):
@@ -313,11 +310,6 @@ class EverythingNode(object):
313 310
     def as_sql(self, qn=None, connection=None):
314 311
         return '', []
315 312
 
316  
-    def relabel_aliases(self, change_map, node=None):
317  
-        return
318  
-
319  
-    def clone(self):
320  
-        return self
321 313
 
322 314
 class NothingNode(object):
323 315
     """
@@ -326,11 +318,6 @@ class NothingNode(object):
326 318
     def as_sql(self, qn=None, connection=None):
327 319
         raise EmptyResultSet
328 320
 
329  
-    def relabel_aliases(self, change_map, node=None):
330  
-        return
331  
-
332  
-    def clone(self):
333  
-        return self
334 321
 
335 322
 class ExtraWhere(object):
336 323
     def __init__(self, sqls, params):
@@ -341,8 +328,6 @@ def as_sql(self, qn=None, connection=None):
341 328
         sqls = ["(%s)" % sql for sql in self.sqls]
342 329
         return " AND ".join(sqls), list(self.params or ())
343 330
 
344  
-    def clone(self):
345  
-        return self
346 331
 
347 332
 class Constraint(object):
348 333
     """
@@ -405,12 +390,11 @@ def process(self, lookup_type, value, connection):
405 390
 
406 391
         return (self.alias, self.col, db_type), params
407 392
 
408  
-    def relabel_aliases(self, change_map):
409  
-        if self.alias in change_map:
410  
-            self.alias = change_map[self.alias]
411  
-
412  
-    def clone(self):
413  
-        new = Empty()
414  
-        new.__class__ = self.__class__
415  
-        new.alias, new.col, new.field = self.alias, self.col, self.field
416  
-        return new
  393
+    def relabeled_clone(self, change_map):
  394
+        if self.alias not in change_map:
  395
+            return self
  396
+        else:
  397
+            new = Empty()
  398
+            new.__class__ = self.__class__
  399
+            new.alias, new.col, new.field = change_map[self.alias], self.col, self.field
  400
+            return new
5  tests/queries/models.py
@@ -470,3 +470,8 @@ class Paragraph(models.Model):
470 470
 
471 471
 class Page(models.Model):
472 472
     text = models.TextField()
  473
+
  474
+class MyObject(models.Model):
  475
+    parent = models.ForeignKey('self', null=True, blank=True, related_name='children')
  476
+    data = models.CharField(max_length=100)
  477
+    created_at = models.DateTimeField(auto_now_add=True)
16  tests/queries/tests.py
@@ -25,7 +25,7 @@
25 25
     SpecialCategory, OneToOneCategory, NullableName, ProxyCategory,
26 26
     SingleObject, RelatedObject, ModelA, ModelD, Responsibility, Job,
27 27
     JobResponsibilities, BaseA, Identifier, Program, Channel, Page, Paragraph,
28  
-    Chapter, Book)
  28
+    Chapter, Book, MyObject)
29 29
 
30 30
 
31 31
 class BaseQuerysetTest(TestCase):
@@ -2661,3 +2661,17 @@ def test_ticket_12823(self):
2661 2661
         self.assertNotIn(b1, q)
2662 2662
         self.assertIn(b2, q)
2663 2663
         self.assertIn(b3, q)
  2664
+
  2665
+class RelabelCloneTest(TestCase):
  2666
+    def test_ticket_19964(self):
  2667
+        my1 = MyObject.objects.create(data='foo')
  2668
+        my1.parent = my1
  2669
+        my1.save()
  2670
+        my2 = MyObject.objects.create(data='bar', parent=my1)
  2671
+        parents = MyObject.objects.filter(parent=F('id'))
  2672
+        children = MyObject.objects.filter(parent__in=parents).exclude(parent=F('id'))
  2673
+        self.assertEqual(list(parents), [my1])
  2674
+        # Evaluating the children query (which has parents as part of it) does
  2675
+        # not change results for the parents query.
  2676
+        self.assertEqual(list(children), [my2])
  2677
+        self.assertEqual(list(parents), [my1])

0 notes on commit d744c55

Please sign in to comment.
Something went wrong with that request. Please try again.