diff options
author | Mike Bayer <mike_mp@zzzcomputing.com> | 2013-06-10 13:45:19 -0400 |
---|---|---|
committer | Mike Bayer <mike_mp@zzzcomputing.com> | 2013-06-10 13:45:19 -0400 |
commit | b614a24c5ddf4c7c7aa45e1eaeb3f82e36737729 (patch) | |
tree | 95c31f73e46603c154124f7c69d2a2cb7943157a | |
parent | 1652491cc6ef44c803e58c0d842818ab7310f498 (diff) | |
download | sqlalchemy-b614a24c5ddf4c7c7aa45e1eaeb3f82e36737729.tar.gz |
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. [ticket:2755]
-rw-r--r-- | doc/build/changelog/changelog_08.rst | 9 | ||||
-rw-r--r-- | doc/build/changelog/changelog_09.rst | 10 | ||||
-rw-r--r-- | lib/sqlalchemy/orm/descriptor_props.py | 38 | ||||
-rw-r--r-- | lib/sqlalchemy/orm/util.py | 1 | ||||
-rw-r--r-- | test/orm/test_composites.py | 39 |
5 files changed, 78 insertions, 19 deletions
diff --git a/doc/build/changelog/changelog_08.rst b/doc/build/changelog/changelog_08.rst index b342a7661..bedb5da02 100644 --- a/doc/build/changelog/changelog_08.rst +++ b/doc/build/changelog/changelog_08.rst @@ -7,6 +7,15 @@ :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 diff --git a/doc/build/changelog/changelog_09.rst b/doc/build/changelog/changelog_09.rst index 91990d646..1f14b33ff 100644 --- a/doc/build/changelog/changelog_09.rst +++ b/doc/build/changelog/changelog_09.rst @@ -6,6 +6,16 @@ .. changelog:: :version: 0.9.0 + + .. 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: feature, sql diff --git a/lib/sqlalchemy/orm/descriptor_props.py b/lib/sqlalchemy/orm/descriptor_props.py index 86b445bb6..c565cb715 100644 --- a/lib/sqlalchemy/orm/descriptor_props.py +++ b/lib/sqlalchemy/orm/descriptor_props.py @@ -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)) diff --git a/lib/sqlalchemy/orm/util.py b/lib/sqlalchemy/orm/util.py index 38cf58792..ef9de760e 100644 --- a/lib/sqlalchemy/orm/util.py +++ b/lib/sqlalchemy/orm/util.py @@ -575,7 +575,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, diff --git a/test/orm/test_composites.py b/test/orm/test_composites.py index b6e5d81a1..379abfa13 100644 --- a/test/orm/test_composites.py +++ b/test/orm/test_composites.py @@ -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 ) |