summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorMike Bayer <mike_mp@zzzcomputing.com>2013-06-10 13:45:19 -0400
committerMike Bayer <mike_mp@zzzcomputing.com>2013-06-10 13:45:19 -0400
commitb614a24c5ddf4c7c7aa45e1eaeb3f82e36737729 (patch)
tree95c31f73e46603c154124f7c69d2a2cb7943157a
parent1652491cc6ef44c803e58c0d842818ab7310f498 (diff)
downloadsqlalchemy-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.rst9
-rw-r--r--doc/build/changelog/changelog_09.rst10
-rw-r--r--lib/sqlalchemy/orm/descriptor_props.py38
-rw-r--r--lib/sqlalchemy/orm/util.py1
-rw-r--r--test/orm/test_composites.py39
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
)