From: Mike Bayer Date: Sat, 27 Jun 2015 04:40:34 +0000 (-0400) Subject: - Fixed 1.0 regression where the "parent entity" of a synonym- X-Git-Tag: rel_1_0_7~23 X-Git-Url: http://git.ipfire.org/cgi-bin/gitweb.cgi?a=commitdiff_plain;h=fcb7c784e9479b9bff7de20c41a05bc1aa550ffb;p=thirdparty%2Fsqlalchemy%2Fsqlalchemy.git - Fixed 1.0 regression where the "parent entity" of a synonym- mapped attribute on top of an :func:`.aliased` object would resolve to the original mapper, not the :func:`.aliased` version of it, thereby causing problems for a :class:`.Query` that relies on this attribute (e.g. it's the only representative attribute given in the constructor) to figure out the correct FROM clause for the query. fixes #3466 - apply consitency to ._parententity vs. __clause_element__()._annotations['parententity'] in terms of aliased class, test it all. --- diff --git a/doc/build/changelog/changelog_10.rst b/doc/build/changelog/changelog_10.rst index 9e21c9c7ae..e857445018 100644 --- a/doc/build/changelog/changelog_10.rst +++ b/doc/build/changelog/changelog_10.rst @@ -15,6 +15,21 @@ .. include:: changelog_07.rst :start-line: 5 +.. changelog:: + :version: 1.0.7 + + .. change:: + :tags: bug, orm + :tickets: 3466 + + Fixed 1.0 regression where the "parent entity" of a synonym- + mapped attribute on top of an :func:`.aliased` object would + resolve to the original mapper, not the :func:`.aliased` + version of it, thereby causing problems for a :class:`.Query` + that relies on this attribute (e.g. it's the only representative + attribute given in the constructor) to figure out the correct FROM + clause for the query. + .. changelog:: :version: 1.0.6 :released: June 25, 2015 diff --git a/lib/sqlalchemy/__init__.py b/lib/sqlalchemy/__init__.py index afddd59413..093e90bbf2 100644 --- a/lib/sqlalchemy/__init__.py +++ b/lib/sqlalchemy/__init__.py @@ -120,7 +120,7 @@ from .schema import ( from .inspection import inspect from .engine import create_engine, engine_from_config -__version__ = '1.0.6' +__version__ = '1.0.7' def __go(lcls): diff --git a/lib/sqlalchemy/orm/interfaces.py b/lib/sqlalchemy/orm/interfaces.py index 6cc613baa7..cd4a0116d0 100644 --- a/lib/sqlalchemy/orm/interfaces.py +++ b/lib/sqlalchemy/orm/interfaces.py @@ -338,7 +338,7 @@ class PropComparator(operators.ColumnOperators): def __init__(self, prop, parentmapper, adapt_to_entity=None): self.prop = self.property = prop - self._parententity = parentmapper + self._parententity = adapt_to_entity or parentmapper self._adapt_to_entity = adapt_to_entity def __clause_element__(self): diff --git a/lib/sqlalchemy/orm/properties.py b/lib/sqlalchemy/orm/properties.py index 5694f72558..55e02984b5 100644 --- a/lib/sqlalchemy/orm/properties.py +++ b/lib/sqlalchemy/orm/properties.py @@ -245,6 +245,8 @@ class ColumnProperty(StrategizedProperty): if self.adapter: return self.adapter(self.prop.columns[0]) else: + # no adapter, so we aren't aliased + # assert self._parententity is self._parentmapper return self.prop.columns[0]._annotate({ "parententity": self._parententity, "parentmapper": self._parententity}) diff --git a/lib/sqlalchemy/orm/util.py b/lib/sqlalchemy/orm/util.py index 66cb2a3198..6d38696795 100644 --- a/lib/sqlalchemy/orm/util.py +++ b/lib/sqlalchemy/orm/util.py @@ -530,7 +530,7 @@ class AliasedInsp(InspectionAttr): def _adapt_element(self, elem): return self._adapter.traverse(elem).\ _annotate({ - 'parententity': self.entity, + 'parententity': self, 'parentmapper': self.mapper} ) diff --git a/test/orm/test_query.py b/test/orm/test_query.py index 62c97ec90d..55af023b1c 100644 --- a/test/orm/test_query.py +++ b/test/orm/test_query.py @@ -3390,7 +3390,8 @@ class WithTransientOnNone(_fixtures.FixtureTest, AssertsCompiledSQL): ) -class SynonymTest(QueryTest): +class SynonymTest(QueryTest, AssertsCompiledSQL): + __dialect__ = 'default' @classmethod def setup_mappers(cls): @@ -3510,6 +3511,20 @@ class SynonymTest(QueryTest): Order(description="order 1"), Order(description="order 3"), Order(description="order 5")] == o + def test_froms_aliased_col(self): + Address, User = self.classes.Address, self.classes.User + + sess = create_session() + ua = aliased(User) + + q = sess.query(ua.name_syn).join( + Address, ua.id == Address.user_id) + self.assert_compile( + q, + "SELECT users_1.name AS users_1_name FROM " + "users AS users_1 JOIN addresses ON users_1.id = addresses.user_id" + ) + class ImmediateTest(_fixtures.FixtureTest): run_inserts = 'once' diff --git a/test/orm/test_utils.py b/test/orm/test_utils.py index ae225ad923..168cee19ca 100644 --- a/test/orm/test_utils.py +++ b/test/orm/test_utils.py @@ -222,6 +222,56 @@ class AliasedClassTest(fixtures.TestBase, AssertsCompiledSQL): "WHERE point_1.x > point.x" ) + def test_parententity_vs_parentmapper(self): + class Point(object): + pass + + self._fixture(Point, properties={ + 'x_syn': synonym("x") + }) + pa = aliased(Point) + + is_(Point.x_syn._parententity, inspect(Point)) + is_(Point.x._parententity, inspect(Point)) + is_(Point.x_syn._parentmapper, inspect(Point)) + is_(Point.x._parentmapper, inspect(Point)) + + is_( + Point.x_syn.__clause_element__()._annotations['parententity'], + inspect(Point)) + is_( + Point.x.__clause_element__()._annotations['parententity'], + inspect(Point)) + is_( + Point.x_syn.__clause_element__()._annotations['parentmapper'], + inspect(Point)) + is_( + Point.x.__clause_element__()._annotations['parentmapper'], + inspect(Point)) + + pa = aliased(Point) + + is_(pa.x_syn._parententity, inspect(pa)) + is_(pa.x._parententity, inspect(pa)) + is_(pa.x_syn._parentmapper, inspect(Point)) + is_(pa.x._parentmapper, inspect(Point)) + + is_( + pa.x_syn.__clause_element__()._annotations['parententity'], + inspect(pa) + ) + is_( + pa.x.__clause_element__()._annotations['parententity'], + inspect(pa) + ) + is_( + pa.x_syn.__clause_element__()._annotations['parentmapper'], + inspect(Point)) + is_( + pa.x.__clause_element__()._annotations['parentmapper'], + inspect(Point)) + + class IdentityKeyTest(_fixtures.FixtureTest): run_inserts = None