diff options
-rw-r--r-- | doc/build/changelog/changelog_10.rst | 13 | ||||
-rw-r--r-- | lib/sqlalchemy/orm/strategies.py | 4 | ||||
-rw-r--r-- | test/orm/inheritance/test_relationship.py | 72 |
3 files changed, 86 insertions, 3 deletions
diff --git a/doc/build/changelog/changelog_10.rst b/doc/build/changelog/changelog_10.rst index 4ae8a8fbb..aa3a4c7e6 100644 --- a/doc/build/changelog/changelog_10.rst +++ b/doc/build/changelog/changelog_10.rst @@ -16,6 +16,19 @@ :start-line: 5 .. changelog:: + :version: 1.0.17 + + .. change:: + :tags: bug, orm + :tickets: 3884 + :versions: 1.1.5 + + Fixed bug involving joined eager loading against multiple entities + when polymorphic inheritance is also in use which would throw + "'NoneType' object has no attribute 'isa'". The issue was introduced + by the fix for :ticket:`3611`. + +.. changelog:: :version: 1.0.16 :released: November 15, 2016 diff --git a/lib/sqlalchemy/orm/strategies.py b/lib/sqlalchemy/orm/strategies.py index 33feab0dc..273ec1280 100644 --- a/lib/sqlalchemy/orm/strategies.py +++ b/lib/sqlalchemy/orm/strategies.py @@ -1368,8 +1368,8 @@ class JoinedLoader(AbstractRelationshipLoader): # name on it. efm = inspect(adapter.aliased_class).\ _entity_for_mapper( - parentmapper - if parentmapper.isa(self.parent) else self.parent) + localparent + if localparent.isa(self.parent) else self.parent) # look for our attribute on the adapted entity, else fall back # to our straight property diff --git a/test/orm/inheritance/test_relationship.py b/test/orm/inheritance/test_relationship.py index 46b934d7b..9f58896af 100644 --- a/test/orm/inheritance/test_relationship.py +++ b/test/orm/inheritance/test_relationship.py @@ -1,6 +1,6 @@ from sqlalchemy.orm import create_session, relationship, mapper, \ contains_eager, joinedload, subqueryload, subqueryload_all,\ - Session, aliased, with_polymorphic, joinedload_all + Session, aliased, with_polymorphic, joinedload_all, backref from sqlalchemy import Integer, String, ForeignKey, select, func from sqlalchemy.engine import default @@ -1934,3 +1934,73 @@ class SameNameOnJoined(fixtures.MappedTest): s.commit() eq_(s.query(B).count(), 0) + + +class BetweenSubclassJoinWExtraJoinedLoad( + fixtures.DeclarativeMappedTest, + testing.AssertsCompiledSQL): + """test for [ticket:3884]""" + + run_define_tables = None + __dialect__ = 'default' + + @classmethod + def setup_classes(cls): + Base = cls.DeclarativeBasic + + class Person(Base): + __tablename__ = 'people' + id = Column(Integer, primary_key=True) + discriminator = Column('type', String(50)) + __mapper_args__ = {'polymorphic_on': discriminator} + + class Manager(Person): + __tablename__ = 'managers' + __mapper_args__ = {'polymorphic_identity': 'manager'} + id = Column(Integer, ForeignKey('people.id'), primary_key=True) + + class Engineer(Person): + __tablename__ = 'engineers' + __mapper_args__ = {'polymorphic_identity': 'engineer'} + id = Column(Integer, ForeignKey('people.id'), primary_key=True) + primary_language = Column(String(50)) + manager_id = Column(Integer, ForeignKey('managers.id')) + manager = relationship( + Manager, primaryjoin=(Manager.id == manager_id)) + + class LastSeen(Base): + __tablename__ = 'seen' + id = Column(Integer, ForeignKey('people.id'), primary_key=True) + timestamp = Column(Integer) + taggable = relationship( + Person, primaryjoin=(Person.id == id), + backref=backref("last_seen", lazy=False)) + + def test_query(self): + Engineer, Manager = self.classes("Engineer", "Manager") + + sess = Session() + + # eager join is both from Enginer->LastSeen as well as + # Manager->LastSeen. In the case of Manager->LastSeen, + # Manager is internally aliased, and comes to JoinedEagerLoader + # with no "parent" entity but an adapter. + q = sess.query(Engineer, Manager).join(Engineer.manager) + self.assert_compile( + q, + "SELECT people.type AS people_type, engineers.id AS engineers_id, " + "people.id AS people_id, " + "engineers.primary_language AS engineers_primary_language, " + "engineers.manager_id AS engineers_manager_id, " + "people_1.type AS people_1_type, managers_1.id AS managers_1_id, " + "people_1.id AS people_1_id, seen_1.id AS seen_1_id, " + "seen_1.timestamp AS seen_1_timestamp, seen_2.id AS seen_2_id, " + "seen_2.timestamp AS seen_2_timestamp " + "FROM people JOIN engineers ON people.id = engineers.id " + "JOIN (people AS people_1 JOIN managers AS managers_1 " + "ON people_1.id = managers_1.id) " + "ON managers_1.id = engineers.manager_id LEFT OUTER JOIN " + "seen AS seen_1 ON people.id = seen_1.id LEFT OUTER JOIN " + "seen AS seen_2 ON people_1.id = seen_2.id" + ) + |