Mike Bayer avatar Mike Bayer committed 4d786b8

- [bug] Fixed bug in expression annotation
mechanics which could lead to incorrect
rendering of SELECT statements with aliases
and joins, particularly when using
column_property(). [ticket:2453]

Comments (0)

Files changed (3)

     in a merge() operation, raising an error.
     [ticket:2449]
 
+  - [bug] Fixed bug in expression annotation
+    mechanics which could lead to incorrect
+    rendering of SELECT statements with aliases
+    and joins, particularly when using 
+    column_property().  [ticket:2453]
+
 - postgresql
   - [feature] Added new for_update/with_lockmode()
     options for Postgresql: for_update="read"/

lib/sqlalchemy/sql/util.py

         exec "annotated_classes[cls] = Annotated%s" % (cls.__name__)
 
 def _deep_annotate(element, annotations, exclude=None):
-    """Deep copy the given ClauseElement, annotating each element with the given annotations dictionary.
+    """Deep copy the given ClauseElement, annotating each element 
+    with the given annotations dictionary.
 
     Elements within the exclude collection will be cloned but not annotated.
 
     """
+    cloned = util.column_dict()
+
     def clone(elem):
         # check if element is present in the exclude list.
         # take into account proxying relationships.
-        if exclude and \
+        if elem in cloned:
+            return cloned[elem]
+        elif exclude and \
                     hasattr(elem, 'proxy_set') and \
                     elem.proxy_set.intersection(exclude):
-            elem = elem._clone()
+            newelem = elem._clone()
         elif annotations != elem._annotations:
-            elem = elem._annotate(annotations.copy())
-        elem._copy_internals(clone=clone)
-        return elem
+            newelem = elem._annotate(annotations)
+        else:
+            newelem = elem
+        newelem._copy_internals(clone=clone)
+        cloned[elem] = newelem
+        return newelem
 
     if element is not None:
         element = clone(element)
 def _deep_deannotate(element):
     """Deep copy the given element, removing all annotations."""
 
+    cloned = util.column_dict()
+
     def clone(elem):
-        elem = elem._deannotate()
-        elem._copy_internals(clone=clone)
-        return elem
+        if elem not in cloned:
+            newelem = elem._deannotate()
+            newelem._copy_internals(clone=clone)
+            cloned[elem] = newelem
+        return cloned[elem]
 
     if element is not None:
         element = clone(element)

test/sql/test_selectable.py

         assert b4.left is bin.left  # since column is immutable
         assert b4.right is not bin.right is not b2.right is not b3.right
 
+    def test_annotate_unique_traversal(self):
+        """test that items are copied only once during
+        annotate, deannotate traversal"""
+        table1 = table('table1', column('x'))
+        table2 = table('table1', column('y'))
+        a1 = table1.alias()
+        s = select([a1.c.x]).select_from(
+                a1.join(table2, a1.c.x==table2.c.y)
+            )
+
+        for sel in (
+            sql_util._deep_deannotate(s),
+            sql_util._deep_annotate(s, {'foo':'bar'}),
+            visitors.cloned_traverse(s, {}, {}),
+            visitors.replacement_traverse(s, {}, lambda x:None)
+        ):
+            # the columns clause isn't changed at all
+            assert sel._raw_columns[0].table is a1
+            # the from objects are internally consistent,
+            # i.e. the Alias at position 0 is the same
+            # Alias in the Join object in position 1
+            assert sel._froms[0] is sel._froms[1].left
+            eq_(str(s), str(sel))
+
     def test_bind_unique_test(self):
         t1 = table('t', column('a'), column('b'))
 
Tip: Filter by directory path e.g. /media app.js to search for public/media/app.js.
Tip: Use camelCasing e.g. ProjME to search for ProjectModifiedEvent.java.
Tip: Filter by extension type e.g. /repo .js to search for all .js files in the /repo directory.
Tip: Separate your search with spaces e.g. /ssh pom.xml to search for src/ssh/pom.xml.
Tip: Use ↑ and ↓ arrow keys to navigate and return to view the file.
Tip: You can also navigate files with Ctrl+j (next) and Ctrl+k (previous) and view the file with Ctrl+o.
Tip: You can also navigate files with Alt+j (next) and Alt+k (previous) and view the file with Alt+o.