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:
- 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
- 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)
- 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.
Describe the bug
When using chained
joinedload()options withwith_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
Expected behavior
The generated SQL should include a LEFT OUTER JOIN to the
metatable to eagerly load themetarelationship.Actual behavior
The SQL query does NOT include a join to the
metatable. Themetarelationship is not eagerly loaded.Error messages/logs
No error occurs, but the generated SQL is missing the expected join:
Versions
Additional context
The issue occurs because:
.joinedload(Foo.meta), the loader is stored with the path(Bar, foo, Foo, meta)using theFoomapper specified by the usermetais declared on theTopbase class, the path becomes(Bar, foo, Top, meta)Workaround
Use
selectinload()for the first relationship instead ofjoinedload():This works because selectinload creates a separate query where the path starts fresh from the
Topmapper.