Skip to content

Chained joinedload fails for inherited relationships in with_polymorphic query #13193

@zzzeek

Description

@zzzeek

Describe the bug

When using chained joinedload() options with with_polymorphic(), if the second relationship in the chain is inherited from a base mapper, the JOIN for that relationship is not generated in the SQL query.

To Reproduce

from sqlalchemy import String, select, ForeignKey
from sqlalchemy.orm import (
    declarative_base, sessionmaker, mapped_column, Mapped, 
    relationship, with_polymorphic, joinedload
)

Base = declarative_base()

class Meta(Base):
    __tablename__ = 'meta'
    id: Mapped[str] = mapped_column(String(), primary_key=True)
    name: Mapped[str] = mapped_column(String())

class Top(Base):
    __tablename__ = 'top'
    id: Mapped[str] = mapped_column(String(), primary_key=True)
    type: Mapped[str] = mapped_column(String())
    meta_id: Mapped[str] = mapped_column(String(), ForeignKey(Meta.id))
    meta: Mapped[Meta] = relationship(Meta)
    
    __mapper_args__ = dict(polymorphic_on=type)

class Foo(Top):
    __tablename__ = 'foo'
    id: Mapped[str] = mapped_column(String(), ForeignKey(Top.id), primary_key=True)
    __mapper_args__ = dict(polymorphic_identity='FOO')

class Bar(Top):
    __tablename__ = 'bar'
    id: Mapped[str] = mapped_column(String(), ForeignKey(Top.id), primary_key=True)
    foo_id: Mapped[str] = mapped_column(String(), ForeignKey(Foo.id))
    foo: Mapped[Foo] = relationship(Foo, foreign_keys=[foo_id])
    __mapper_args__ = dict(polymorphic_identity='BAR')

# Query with chained joinedload
tp = with_polymorphic(Top, '*', flat=True)
q = select(tp).options(
    joinedload(tp.Bar.foo.of_type(Foo)).joinedload(Foo.meta)
)
result = session.execute(q).scalars().all()

Expected behavior

The generated SQL should include a LEFT OUTER JOIN to the meta table to eagerly load the meta relationship.

Actual behavior

The SQL query does NOT include a join to the meta table. The meta relationship is not eagerly loaded.

Error messages/logs

No error occurs, but the generated SQL is missing the expected join:

SELECT ... 
FROM top AS top_1 
LEFT OUTER JOIN foo AS foo_1 ON top_1.id = foo_1.id 
LEFT OUTER JOIN bar AS bar_1 ON top_1.id = bar_1.id 
LEFT OUTER JOIN (
    SELECT top.id AS top_id, top.type AS top_type, top.meta_id AS top_meta_id, foo.id AS foo_id 
    FROM top LEFT OUTER JOIN foo ON top.id = foo.id
) AS anon_1 ON anon_1.foo_id = bar_1.foo_id
-- Missing: LEFT OUTER JOIN meta AS meta_1 ON meta_1.id = anon_1.top_meta_id

Versions

  • SQLAlchemy: 2.0.x (main branch)
  • Python: 3.14
  • Database: SQLite (but issue is database-agnostic)

Additional context

The issue occurs because:

  1. When the user specifies .joinedload(Foo.meta), the loader is stored with the path (Bar, foo, Foo, meta) using the Foo mapper specified by the user
  2. However, when looking up the loader during query construction, since meta is declared on the Top base class, the path becomes (Bar, foo, Top, meta)
  3. This path mismatch causes the loader lookup to fail, so the joinedload is never processed

Workaround

Use selectinload() for the first relationship instead of joinedload():

q = select(tp).options(
    selectinload(tp.Bar.foo).joinedload(Foo.meta)  # Works
)

This works because selectinload creates a separate query where the path starts fresh from the Top mapper.

Metadata

Metadata

Assignees

No one assigned

    Labels

    bugSomething isn't workinginheritanceissues to do wtih ORM inheritance, a particularly tricky arealoader optionsORM options like joinedload(), load_only(), these are complicated and have a lot of issuesorm

    Type

    No type

    Projects

    No projects

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions