From: Mike Bayer Date: Mon, 10 Jun 2013 17:45:19 +0000 (-0400) Subject: Fixed the interaction between composite attributes and X-Git-Tag: rel_0_8_2~51 X-Git-Url: http://git.ipfire.org/cgi-bin/gitweb.cgi?a=commitdiff_plain;h=6d0b2f34d2b829d92e173cb5e3cb0c941586759e;p=thirdparty%2Fsqlalchemy%2Fsqlalchemy.git 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] Conflicts: doc/build/changelog/changelog_09.rst --- diff --git a/doc/build/changelog/changelog_08.rst b/doc/build/changelog/changelog_08.rst index b342a76616..bedb5da024 100644 --- a/doc/build/changelog/changelog_08.rst +++ b/doc/build/changelog/changelog_08.rst @@ -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 diff --git a/lib/sqlalchemy/orm/descriptor_props.py b/lib/sqlalchemy/orm/descriptor_props.py index 1969bd03b5..dc2d770d74 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 390e83538d..e2b3ddd01c 100644 --- a/lib/sqlalchemy/orm/util.py +++ b/lib/sqlalchemy/orm/util.py @@ -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, diff --git a/test/orm/test_composites.py b/test/orm/test_composites.py index f9af0c702e..ed80d6105e 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 )