]> git.ipfire.org Git - thirdparty/sqlalchemy/sqlalchemy.git/commitdiff
Fixed the interaction between composite attributes and
authorMike Bayer <mike_mp@zzzcomputing.com>
Mon, 10 Jun 2013 17:45:19 +0000 (13:45 -0400)
committerMike Bayer <mike_mp@zzzcomputing.com>
Mon, 10 Jun 2013 17:50:56 +0000 (13:50 -0400)
the :func:`.aliased` function.  Previously, composite attributes
wouldn't work correctly in comparison operations when aliasing
was applied.  Also in 0.8.2. [ticket:2755]

Conflicts:
doc/build/changelog/changelog_09.rst

doc/build/changelog/changelog_08.rst
lib/sqlalchemy/orm/descriptor_props.py
lib/sqlalchemy/orm/util.py
test/orm/test_composites.py

index b342a766165d896b93dcddfdb4ec3da5de20c59a..bedb5da024f3a7bb689f1c3b84cb7e54b0d65cf1 100644 (file)
@@ -6,6 +6,15 @@
 .. changelog::
     :version: 0.8.2
 
+    .. change::
+        :tags: bug, orm
+        :tickets: 2755
+
+        Fixed the interaction between composite attributes and
+        the :func:`.aliased` function.  Previously, composite attributes
+        wouldn't work correctly in comparison operations when aliasing
+        was applied.  Also in 0.8.2.
+
     .. change::
         :tags: bug, mysql
         :tickets: 2715
index 1969bd03b55c2d856167f35b17e40154e4cd16bd..dc2d770d74eed492d7688ecff7ebefe96b667ea0 100644 (file)
@@ -298,7 +298,7 @@ class CompositeProperty(DescriptorProperty):
             )
 
     def _comparator_factory(self, mapper):
-        return self.comparator_factory(self)
+        return self.comparator_factory(self, mapper)
 
     class Comparator(PropComparator):
         """Produce boolean, comparison, and other operators for
@@ -318,29 +318,39 @@ class CompositeProperty(DescriptorProperty):
         :attr:`.TypeEngine.comparator_factory`
 
         """
-        def __init__(self, prop, adapter=None):
-            self.prop = self.property = prop
-            self.adapter = adapter
 
         def __clause_element__(self):
-            if self.adapter:
-                # TODO: test coverage for adapted composite comparison
-                return expression.ClauseList(
-                    *[self.adapter(x) for x in self.prop._comparable_elements])
-            else:
-                return expression.ClauseList(*self.prop._comparable_elements)
+            return expression.ClauseList(*self._comparable_elements)
 
         __hash__ = None
 
+        @util.memoized_property
+        def _comparable_elements(self):
+            if self.adapter:
+                # we need to do a little fudging here because
+                # the adapter function we're given only accepts
+                # ColumnElements, but our prop._comparable_elements is returning
+                # InstrumentedAttribute, because we support the use case
+                # of composites that refer to relationships.  The better
+                # solution here is to open up how AliasedClass interacts
+                # with PropComparators so more context is available.
+                return [self.adapter(x.__clause_element__())
+                            for x in self.prop._comparable_elements]
+            else:
+                return self.prop._comparable_elements
+
         def __eq__(self, other):
             if other is None:
                 values = [None] * len(self.prop._comparable_elements)
             else:
                 values = other.__composite_values__()
-            return sql.and_(
-                    *[a == b
-                      for a, b in zip(self.prop._comparable_elements, values)]
-                )
+            comparisons = [
+                a == b
+                for a, b in zip(self.prop._comparable_elements, values)
+            ]
+            if self.adapter:
+                comparisons = [self.adapter(x) for x in comparisons]
+            return sql.and_(*comparisons)
 
         def __ne__(self, other):
             return sql.not_(self.__eq__(other))
index 390e83538d34c3b0fa6b780fbd18009f899d1591..e2b3ddd01cee56c722bcb5af8468b3a48113be21 100644 (file)
@@ -572,7 +572,6 @@ class AliasedClass(object):
 
     def __adapt_prop(self, existing, key):
         comparator = existing.comparator.adapted(self.__adapt_element)
-
         queryattr = attributes.QueryableAttribute(
                                 self, key,
                                 impl=existing.impl,
index f9af0c702e1ec7f48c44601a44f5686f70bfdf4d..ed80d6105e8f43ca7063c7130ecb08b937ffb3ec 100644 (file)
@@ -172,18 +172,31 @@ class PointTest(fixtures.MappedTest):
         g = sess.query(Graph).first()
 
         assert sess.query(Edge).\
-                    filter(Edge.start==Point(3, 4)).one() is \
+                    filter(Edge.start == Point(3, 4)).one() is \
                     g.edges[0]
 
         assert sess.query(Edge).\
-                    filter(Edge.start!=Point(3, 4)).first() is \
+                    filter(Edge.start != Point(3, 4)).first() is \
                     g.edges[1]
 
         eq_(
-            sess.query(Edge).filter(Edge.start==None).all(),
+            sess.query(Edge).filter(Edge.start == None).all(),
             []
         )
 
+    def test_comparator_aliased(self):
+        Graph, Edge, Point = (self.classes.Graph,
+                                self.classes.Edge,
+                                self.classes.Point)
+
+        sess = self._fixture()
+
+        g = sess.query(Graph).first()
+        ea = aliased(Edge)
+        assert sess.query(ea).\
+                    filter(ea.start != Point(3, 4)).first() is \
+                    g.edges[1]
+
     def test_get_history(self):
         Edge = self.classes.Edge
         Point = self.classes.Point
@@ -587,7 +600,25 @@ class ManyToOneTest(fixtures.MappedTest):
         sess.commit()
 
         eq_(
-            sess.query(A).filter(A.c==C('a2b1', b2)).one(),
+            sess.query(A).filter(A.c == C('a2b1', b2)).one(),
+            a2
+        )
+
+    def test_query_aliased(self):
+        A, C, B = (self.classes.A,
+                                self.classes.C,
+                                self.classes.B)
+
+        sess = Session()
+        b1, b2 = B(data='b1'), B(data='b2')
+        a1 = A(c=C('a1b1', b1))
+        a2 = A(c=C('a2b1', b2))
+        sess.add_all([a1, a2])
+        sess.commit()
+
+        ae = aliased(A)
+        eq_(
+            sess.query(ae).filter(ae.c == C('a2b1', b2)).one(),
             a2
         )