From: Mike Bayer Date: Fri, 13 Feb 2009 18:08:40 +0000 (+0000) Subject: - annotations store 'parententity' as well as 'parentmapper' X-Git-Tag: rel_0_5_3~30 X-Git-Url: http://git.ipfire.org/gitweb.cgi?a=commitdiff_plain;h=1de90125739067b1a97208af020ee2fcded07db0;p=thirdparty%2Fsqlalchemy%2Fsqlalchemy.git - annotations store 'parententity' as well as 'parentmapper' - ORMAdapter filters all replacements against a non-compatible 'parentmapper' annotation - Other filterings, like query(A).join(A.bs).filter(B.foo=='bar'), were erroneously adapting "B.foo" as though it were an "A". --- diff --git a/CHANGES b/CHANGES index 9c4ab7d73d..1603ce6594 100644 --- a/CHANGES +++ b/CHANGES @@ -34,10 +34,18 @@ CHANGES - Fixed bugs in Query regarding simultaneous selection of multiple joined-table inheritance entities with common base - classes, previously the adaption applied to "e2" on - "e1 JOIN e2" would be partially applied to "e1". Additionally, - comparisons on relations (i.e. Entity2.related==e2) - were not getting adapted correctly. + classes: + + - previously the adaption applied to "B" on + "A JOIN B" would be erroneously partially applied + to "A". + + - comparisons on relations (i.e. A.related==someb) + were not getting adapted when they should. + + - Other filterings, like + query(A).join(A.bs).filter(B.foo=='bar'), were erroneously + adapting "B.foo" as though it were an "A". - sql - Fixed missing _label attribute on Function object, others diff --git a/lib/sqlalchemy/orm/evaluator.py b/lib/sqlalchemy/orm/evaluator.py index 03955afa8a..4611dd91b4 100644 --- a/lib/sqlalchemy/orm/evaluator.py +++ b/lib/sqlalchemy/orm/evaluator.py @@ -30,8 +30,8 @@ class EvaluatorCompiler(object): return lambda obj: None def visit_column(self, clause): - if 'parententity' in clause._annotations: - key = clause._annotations['parententity']._get_col_to_prop(clause).key + if 'parentmapper' in clause._annotations: + key = clause._annotations['parentmapper']._get_col_to_prop(clause).key else: key = clause.key get_corresponding_attr = operator.attrgetter(key) diff --git a/lib/sqlalchemy/orm/properties.py b/lib/sqlalchemy/orm/properties.py index 42c3bffa3b..2a772dcac2 100644 --- a/lib/sqlalchemy/orm/properties.py +++ b/lib/sqlalchemy/orm/properties.py @@ -121,7 +121,7 @@ class ColumnProperty(StrategizedProperty): if self.adapter: return self.adapter(self.prop.columns[0]) else: - return self.prop.columns[0]._annotate({"parententity": self.mapper}) + return self.prop.columns[0]._annotate({"parententity": self.mapper, "parentmapper":self.mapper}) def operate(self, op, *other, **kwargs): return op(self.__clause_element__(), *other, **kwargs) diff --git a/lib/sqlalchemy/orm/util.py b/lib/sqlalchemy/orm/util.py index c863729017..1cca9e00b8 100644 --- a/lib/sqlalchemy/orm/util.py +++ b/lib/sqlalchemy/orm/util.py @@ -258,13 +258,20 @@ class ORMAdapter(sql_util.ColumnAdapter): """ def __init__(self, entity, equivalents=None, chain_to=None): - mapper, selectable, is_aliased_class = _entity_info(entity) + self.mapper, selectable, is_aliased_class = _entity_info(entity) if is_aliased_class: self.aliased_class = entity else: self.aliased_class = None sql_util.ColumnAdapter.__init__(self, selectable, equivalents, chain_to) + def replace(self, elem): + entity = elem._annotations.get('parentmapper', None) + if not entity or entity.isa(self.mapper): + return sql_util.ColumnAdapter.replace(self, elem) + else: + return None + class AliasedClass(object): """Represents an 'alias'ed form of a mapped class for usage with Query. @@ -303,7 +310,7 @@ class AliasedClass(object): self.__name__ = 'AliasedClass_' + str(self.__target) def __adapt_element(self, elem): - return self.__adapter.traverse(elem)._annotate({'parententity': self}) + return self.__adapter.traverse(elem)._annotate({'parententity': self, 'parentmapper':self.__mapper}) def __adapt_prop(self, prop): existing = getattr(self.__target, prop.key) diff --git a/test/orm/inheritance/query.py b/test/orm/inheritance/query.py index 07bf068b72..ca789f8338 100644 --- a/test/orm/inheritance/query.py +++ b/test/orm/inheritance/query.py @@ -750,7 +750,7 @@ class SelfReferentialTestJoinedToBase(ORMTest): sess.query(Engineer).join('reports_to', aliased=True).filter(Person.name=='dogbert').first(), Engineer(name='dilbert')) -class SelfReferentialTestJoinedToJoined(ORMTest): +class SelfReferentialJ2JTest(ORMTest): keep_mappers = True def define_tables(self, metadata): @@ -803,6 +803,36 @@ class SelfReferentialTestJoinedToJoined(ORMTest): sess.query(Engineer).join('reports_to', aliased=True).filter(Manager.name=='dogbert').first(), Engineer(name='dilbert')) + def test_filter_aliasing(self): + m1 = Manager(name='dogbert') + m2 = Manager(name='foo') + e1 = Engineer(name='wally', primary_language='java', reports_to=m1) + e2 = Engineer(name='dilbert', primary_language='c++', reports_to=m2) + e3 = Engineer(name='etc', primary_language='c++') + sess = create_session() + sess.add_all([m1, m2, e1, e2, e3]) + sess.flush() + sess.expunge_all() + + # filter aliasing applied to Engineer doesn't whack Manager + self.assertEquals( + sess.query(Manager).join(Manager.engineers).filter(Manager.name=='dogbert').all(), + [m1] + ) + + self.assertEquals( + sess.query(Manager).join(Manager.engineers).filter(Engineer.name=='dilbert').all(), + [m2] + ) + + self.assertEquals( + sess.query(Manager, Engineer).join(Manager.engineers).order_by(Manager.name.desc()).all(), + [ + (m2, e2), + (m1, e1), + ] + ) + def test_relation_compare(self): m1 = Manager(name='dogbert') m2 = Manager(name='foo') @@ -827,8 +857,7 @@ class SelfReferentialTestJoinedToJoined(ORMTest): sess.query(Manager).join(Manager.engineers).filter(Engineer.reports_to==m1).all(), [m1] ) - - + class M2MFilterTest(ORMTest):