.. 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
)
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
: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))
def __adapt_prop(self, existing, key):
comparator = existing.comparator.adapted(self.__adapt_element)
-
queryattr = attributes.QueryableAttribute(
self, key,
impl=existing.impl,
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
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
)