--- /dev/null
+.. change::
+ :tags: bug, sql
+ :tickets: 12931
+
+ Fixed an issue in :meth:`_sql.Select.join_from` where the join condition
+ between the left and right tables specified in the method call could be
+ incorrectly determined based on an intermediate table already present in
+ the FROM clause, rather than matching the foreign keys between the
+ immediate left and right arguments. The join condition is now determined by
+ matching primary keys between the two tables explicitly passed to
+ :meth:`_sql.Select.join_from`, ensuring consistent and predictable join
+ behavior regardless of the order of join operations or other tables present
+ in the query. The fix is applied to both the Core and ORM implementations
+ of :meth:`_sql.Select.join_from`.
"""
+ explicit_left = left
if left is None:
# left not given (e.g. no relationship object/name specified)
# figure out the best "left" side based on our existing froms /
# self._from_obj list
left_clause = self.from_clauses[replace_from_obj_index]
+ if explicit_left is not None and onclause is None:
+ onclause = _ORMJoin._join_condition(explicit_left, right)
+
self.from_clauses = (
self.from_clauses[:replace_from_obj_index]
+ [
if onclause is not None:
assert isinstance(onclause, ColumnElement)
+ explicit_left = left
isouter = flags["isouter"]
full = flags["full"]
# self._from_obj list
left_clause = self.from_clauses[replace_from_obj_index]
+ if explicit_left is not None and onclause is None:
+ onclause = Join._join_condition(explicit_left, right)
+
self.from_clauses = (
self.from_clauses[:replace_from_obj_index]
+ (
from sqlalchemy.orm import deferred
from sqlalchemy.orm import join as orm_join
from sqlalchemy.orm import joinedload
+from sqlalchemy.orm import Mapped
+from sqlalchemy.orm import mapped_column
from sqlalchemy.orm import query_expression
from sqlalchemy.orm import relationship
from sqlalchemy.orm import Session
self.assert_compile(stmt, expected, checkparams=expected_params)
+ @testing.fixture
+ def grandchild_fixture(self, decl_base):
+ class Parent(decl_base):
+ __tablename__ = "parent"
+ id: Mapped[int] = mapped_column(primary_key=True)
+ data: Mapped[str]
+
+ class Child(decl_base):
+ __tablename__ = "child"
+ id: Mapped[int] = mapped_column(primary_key=True)
+ parent_id: Mapped[int] = mapped_column(ForeignKey("parent.id"))
+
+ class GrandchildWParent(decl_base):
+ __tablename__ = "grandchildwparent"
+ id: Mapped[int] = mapped_column(primary_key=True)
+ parent_id: Mapped[int] = mapped_column(ForeignKey("parent.id"))
+ child_id: Mapped[int] = mapped_column(ForeignKey("child.id"))
+
+ return Parent, Child, GrandchildWParent
+
+ @testing.variation(
+ "jointype",
+ ["child_grandchild", "parent_grandchild", "grandchild_alone"],
+ )
+ def test_join_from_favors_explicit_left(
+ self, grandchild_fixture, jointype
+ ):
+ """test #12931 in terms of ORM joins"""
+
+ Parent, Child, GrandchildWParent = grandchild_fixture
+
+ if jointype.child_grandchild:
+ stmt = (
+ select(Parent)
+ .join_from(Parent, Child)
+ .join_from(Child, GrandchildWParent)
+ )
+
+ self.assert_compile(
+ stmt,
+ "SELECT parent.id, parent.data FROM parent JOIN "
+ "child ON parent.id = child.parent_id "
+ "JOIN grandchildwparent "
+ "ON child.id = grandchildwparent.child_id",
+ )
+
+ elif jointype.parent_grandchild:
+ stmt = (
+ select(Parent)
+ .join_from(Parent, Child)
+ .join_from(Parent, GrandchildWParent)
+ )
+
+ self.assert_compile(
+ stmt,
+ "SELECT parent.id, parent.data FROM parent "
+ "JOIN child ON parent.id = child.parent_id "
+ "JOIN grandchildwparent "
+ "ON parent.id = grandchildwparent.parent_id",
+ )
+ elif jointype.grandchild_alone:
+ stmt = (
+ select(Parent).join_from(Parent, Child).join(GrandchildWParent)
+ )
+
+ self.assert_compile(
+ stmt,
+ "SELECT parent.id, parent.data FROM parent "
+ "JOIN child ON parent.id = child.parent_id "
+ "JOIN grandchildwparent "
+ "ON child.id = grandchildwparent.child_id",
+ )
+ else:
+ jointype.fail()
+
class LoadersInSubqueriesTest(QueryTest, AssertsCompiledSQL):
"""The Query object calls eanble_eagerloads(False) when you call
Column("child_id", ForeignKey("child.id")),
)
+grandchild_w_parent = Table(
+ "grandchildwparent",
+ metadata,
+ Column("id", Integer, primary_key=True),
+ Column("parent_id", ForeignKey("parent.id")),
+ Column("child_id", ForeignKey("child.id")),
+)
+
class SelectTest(fixtures.TestBase, AssertsCompiledSQL):
__dialect__ = "default"
"ON mytable.myid = myothertable.otherid",
)
+ @testing.variation(
+ "jointype",
+ ["child_grandchild", "parent_grandchild", "grandchild_alone"],
+ )
+ def test_join_from_multiple_explicit_left_side_implicit_onclause(
+ self, jointype
+ ):
+ """test #12931
+
+ when join_from() is indicated, favor the explicit "left" side given
+ over the "left side of hte join" for creating onclause.
+
+ when join() is indicated, use the normal behavior of assuming
+ right side of the previous join is the new left side.
+
+ """
+
+ if jointype.child_grandchild:
+ stmt = (
+ select(parent)
+ .join_from(parent, child)
+ .join_from(child, grandchild_w_parent)
+ )
+
+ self.assert_compile(
+ stmt,
+ "SELECT parent.id, parent.data FROM parent JOIN "
+ "child ON parent.id = child.parent_id "
+ "JOIN grandchildwparent "
+ "ON child.id = grandchildwparent.child_id",
+ )
+
+ elif jointype.parent_grandchild:
+ stmt = (
+ select(parent)
+ .join_from(parent, child)
+ .join_from(parent, grandchild_w_parent)
+ )
+
+ self.assert_compile(
+ stmt,
+ "SELECT parent.id, parent.data FROM parent "
+ "JOIN child ON parent.id = child.parent_id "
+ "JOIN grandchildwparent "
+ "ON parent.id = grandchildwparent.parent_id",
+ )
+ elif jointype.grandchild_alone:
+ stmt = (
+ select(parent)
+ .join_from(parent, child)
+ .join(grandchild_w_parent)
+ )
+ self.assert_compile(
+ stmt,
+ "SELECT parent.id, parent.data FROM parent "
+ "JOIN child ON parent.id = child.parent_id "
+ "JOIN grandchildwparent "
+ "ON child.id = grandchildwparent.child_id",
+ )
+ else:
+ jointype.fail()
+
def test_outerjoin_nofrom_explicit_left_side_explicit_onclause(self):
stmt = select(table1).outerjoin_from(
table1, table2, table1.c.myid == table2.c.otherid