@@ -1371,3 +1371,169 @@ def test_joinedload_of_type_chained_vs_options(
13711371 " AS c_sub_1 ON c_1.id = c_sub_1.id) ON b_1.id = c_1.b_id"
13721372 )
13731373 )
1374+
1375+
1376+ class ChainedLoaderAfterOfTypeTest (
1377+ testing .AssertsCompiledSQL , fixtures .DeclarativeMappedTest
1378+ ):
1379+ """Regression test for issue #13209.
1380+
1381+ Tests that loader options chained after of_type() are properly applied.
1382+ """
1383+
1384+ run_setup_classes = "once"
1385+ run_setup_mappers = "once"
1386+ run_inserts = "once"
1387+ run_deletes = None
1388+ __dialect__ = "default"
1389+
1390+ @classmethod
1391+ def setup_classes (cls ):
1392+ Base = cls .DeclarativeBasic
1393+
1394+ class TopABC (ComparableEntity , Base ):
1395+ __tablename__ = "top_abc"
1396+ id = Column (Integer , primary_key = True )
1397+
1398+ class Top (ComparableEntity , Base ):
1399+ __tablename__ = "top"
1400+ id = Column (Integer , ForeignKey ("top_abc.id" ), primary_key = True )
1401+ top_abc_id = Column (Integer , ForeignKey ("top_abc.id" ))
1402+ type = Column (String (50 ))
1403+ __mapper_args__ = {"polymorphic_on" : type }
1404+
1405+ class Foo (Top ):
1406+ __tablename__ = "foo"
1407+ id = Column (Integer , ForeignKey ("top.id" ), primary_key = True )
1408+ foo_name = Column (String (50 ))
1409+ __mapper_args__ = {"polymorphic_identity" : "FOO" }
1410+
1411+ class Bar (Top ):
1412+ __tablename__ = "bar"
1413+ id = Column (Integer , ForeignKey ("top.id" ), primary_key = True )
1414+ bar_name = Column (String (50 ))
1415+ foo_id = Column (Integer , ForeignKey ("foo.id" ))
1416+ __mapper_args__ = {"polymorphic_identity" : "BAR" }
1417+
1418+ TopABC .top = relationship (
1419+ Top , foreign_keys = [Top .top_abc_id ], uselist = False
1420+ )
1421+ Bar .foo = relationship (Foo , foreign_keys = [Bar .foo_id ], uselist = False )
1422+
1423+ @classmethod
1424+ def insert_data (cls , connection ):
1425+ with Session (connection ) as sess :
1426+ TopABC , Foo , Bar = cls .classes ("TopABC" , "Foo" , "Bar" )
1427+ sess .add (
1428+ TopABC (id = 1 , top = Foo (id = 1 , top_abc_id = 1 , foo_name = "foo1" ))
1429+ )
1430+ sess .add (
1431+ TopABC (
1432+ id = 2 ,
1433+ top = Bar (id = 2 , top_abc_id = 2 , bar_name = "bar1" , foo_id = 1 ),
1434+ )
1435+ )
1436+ sess .commit ()
1437+
1438+ @testing .variation ("loader" , ["joined" , "selectin" , "subquery" ])
1439+ def test_chained_loader_after_of_type (self , loader : testing .Variation ):
1440+ """Test that selectinload/joinedload/subqueryload works when chained
1441+ after joinedload with of_type().
1442+
1443+ Regression test for issue #13209 where chaining a loader option
1444+ after joinedload(...of_type(poly)) would not properly apply the
1445+ chained loader, resulting in lazy loads.
1446+ """
1447+ TopABC , Top , Foo , Bar = self .classes ("TopABC" , "Top" , "Foo" , "Bar" )
1448+
1449+ top_poly = with_polymorphic (Top , "*" , flat = True )
1450+
1451+ if loader .selectin :
1452+ stmt = select (TopABC ).options (
1453+ joinedload (TopABC .top .of_type (top_poly )).selectinload (
1454+ top_poly .Bar .foo
1455+ )
1456+ )
1457+ elif loader .joined :
1458+ stmt = select (TopABC ).options (
1459+ joinedload (TopABC .top .of_type (top_poly )).joinedload (
1460+ top_poly .Bar .foo
1461+ )
1462+ )
1463+ elif loader .subquery :
1464+ stmt = select (TopABC ).options (
1465+ joinedload (TopABC .top .of_type (top_poly )).subqueryload (
1466+ top_poly .Bar .foo
1467+ )
1468+ )
1469+ else :
1470+ loader .fail ()
1471+
1472+ session = fixture_session ()
1473+ with self .sql_execution_asserter (testing .db ) as asserter_ :
1474+ result = session .scalars (stmt ).unique ().all ()
1475+ # Access the chained relationship - should not trigger lazy load
1476+ for obj in result :
1477+ if isinstance (obj .top , Bar ):
1478+ _ = obj .top .foo
1479+
1480+ if loader .selectin :
1481+ asserter_ .assert_ (
1482+ CompiledSQL (
1483+ "SELECT top_abc.id, top_1.id AS id_1, top_1.top_abc_id,"
1484+ " top_1.type, foo_1.id AS id_2, foo_1.foo_name,"
1485+ " bar_1.id AS id_3, bar_1.bar_name, bar_1.foo_id"
1486+ " FROM top_abc LEFT OUTER JOIN (top AS top_1 LEFT"
1487+ " OUTER JOIN foo AS foo_1 ON top_1.id = foo_1.id"
1488+ " LEFT OUTER JOIN bar AS bar_1 ON top_1.id ="
1489+ " bar_1.id) ON top_abc.id = top_1.top_abc_id"
1490+ ),
1491+ CompiledSQL (
1492+ "SELECT top.id, foo.id, top.top_abc_id, top.type,"
1493+ " foo.foo_name FROM top JOIN foo ON top.id = foo.id"
1494+ " WHERE top.id IN (__[POSTCOMPILE_primary_keys])"
1495+ ),
1496+ )
1497+ elif loader .subquery :
1498+ asserter_ .assert_ (
1499+ CompiledSQL (
1500+ "SELECT top_abc.id, top_1.id AS id_1, top_1.top_abc_id,"
1501+ " top_1.type, foo_1.id AS id_2, foo_1.foo_name,"
1502+ " bar_1.id AS id_3, bar_1.bar_name, bar_1.foo_id"
1503+ " FROM top_abc LEFT OUTER JOIN (top AS top_1 LEFT"
1504+ " OUTER JOIN foo AS foo_1 ON top_1.id = foo_1.id"
1505+ " LEFT OUTER JOIN bar AS bar_1 ON top_1.id ="
1506+ " bar_1.id) ON top_abc.id = top_1.top_abc_id"
1507+ ),
1508+ CompiledSQL (
1509+ "SELECT foo.id AS foo_id, top.id AS top_id,"
1510+ " top.top_abc_id AS top_top_abc_id, top.type AS"
1511+ " top_type, foo.foo_name AS foo_foo_name,"
1512+ " anon_1.bar_foo_id AS anon_1_bar_foo_id FROM"
1513+ " (SELECT top_abc.id AS top_abc_id FROM top_abc)"
1514+ " AS anon_2 JOIN (SELECT top.id AS top_id,"
1515+ " top.top_abc_id AS top_top_abc_id, top.type AS"
1516+ " top_type, bar.id AS bar_id, bar.bar_name AS"
1517+ " bar_bar_name, bar.foo_id AS bar_foo_id FROM top"
1518+ " JOIN bar ON top.id = bar.id) AS anon_1 ON"
1519+ " anon_2.top_abc_id = anon_1.top_top_abc_id JOIN"
1520+ " (top JOIN foo ON top.id = foo.id) ON foo.id ="
1521+ " anon_1.bar_foo_id"
1522+ ),
1523+ )
1524+ elif loader .joined :
1525+ asserter_ .assert_ (
1526+ CompiledSQL (
1527+ "SELECT top_abc.id, top_1.id AS id_1, top_1.top_abc_id,"
1528+ " top_1.type, foo_1.id AS id_2, foo_1.foo_name,"
1529+ " bar_1.id AS id_3, bar_1.bar_name, bar_1.foo_id,"
1530+ " foo_2.id AS id_4, top_2.id AS id_5, top_2.top_abc_id"
1531+ " AS top_abc_id_1, top_2.type AS type_1, foo_2.foo_name"
1532+ " AS foo_name_1 FROM top_abc LEFT OUTER JOIN (top AS"
1533+ " top_1 LEFT OUTER JOIN foo AS foo_1 ON top_1.id ="
1534+ " foo_1.id LEFT OUTER JOIN bar AS bar_1 ON top_1.id ="
1535+ " bar_1.id) ON top_abc.id = top_1.top_abc_id LEFT"
1536+ " OUTER JOIN (top AS top_2 JOIN foo AS foo_2 ON"
1537+ " top_2.id = foo_2.id) ON foo_2.id = bar_1.foo_id"
1538+ )
1539+ )
0 commit comments