<p dir="auto"><strong>Migrated issue, originally created by Michael Bayer (<a href="https://github.com/zzzeek">@zzzeek</a>)</strong></p>
<p dir="auto">because we don't include the parentmapper annotation in the _optimized_compare():</p>
<div class="snippet-clipboard-content notranslate position-relative overflow-auto" data-snippet-clipboard-copy-content="from sqlalchemy import Column, Integer, Text, ForeignKey, create_engine
from sqlalchemy.ext.declarative import declarative_base
from sqlalchemy.orm import Session, relationship

Base = declarative_base()


class Parent(Base):
    __tablename__ = &quot;parent&quot;
    id = Column(Integer, primary_key=True)


class Child(Base):
    __tablename__ = &quot;child&quot;
    _id_parent = Column(
        &quot;id_parent&quot;, Integer, ForeignKey(Parent.id), primary_key=True)
    name = Column(Text, primary_key=True)
    parent = relationship(Parent)

engine = create_engine('sqlite://')
Base.metadata.create_all(engine)
session = Session(engine)

p = Parent(id=1)
session.add(p)
session.commit()

c = Child(name=&quot;foo&quot;, parent=p)
session.add(c)
session.commit()

session.query(Child).filter(Child.parent == p).delete(&quot;evaluate&quot;)"><pre class="notranslate"><code class="notranslate">from sqlalchemy import Column, Integer, Text, ForeignKey, create_engine
from sqlalchemy.ext.declarative import declarative_base
from sqlalchemy.orm import Session, relationship

Base = declarative_base()


class Parent(Base):
    __tablename__ = "parent"
    id = Column(Integer, primary_key=True)


class Child(Base):
    __tablename__ = "child"
    _id_parent = Column(
        "id_parent", Integer, ForeignKey(Parent.id), primary_key=True)
    name = Column(Text, primary_key=True)
    parent = relationship(Parent)

engine = create_engine('sqlite://')
Base.metadata.create_all(engine)
session = Session(engine)

p = Parent(id=1)
session.add(p)
session.commit()

c = Child(name="foo", parent=p)
session.add(c)
session.commit()

session.query(Child).filter(Child.parent == p).delete("evaluate")
</code></pre></div>
<p dir="auto">There are two approaches.</p>
<p dir="auto">One is that we look into getting parentmapper back into PJ/SJ.   This was removed a long time ago but we might want to revisit this assumption as mechanics have changed a lot.  It would be nice if these annotations were set up at relationship startup time, rather than having to re-inject when we build up relationship comparator operations.</p>
<p dir="auto">The other is probably what we should do here and is just search for the column in the local mapper.  We also shouldn't assume ".key" on the clause itself is important, there don't seem to be tests for that case and it should probably raise, so this needs a lot more tests:</p>
<div class="snippet-clipboard-content notranslate position-relative overflow-auto" data-snippet-clipboard-copy-content="diff --git a/lib/sqlalchemy/orm/evaluator.py b/lib/sqlalchemy/orm/evaluator.py
index 1e828ff..814b28f 100644
--- a/lib/sqlalchemy/orm/evaluator.py
+++ b/lib/sqlalchemy/orm/evaluator.py
@@ -7,6 +7,7 @@
 
 import operator
 from ..sql import operators
+from .. import inspect
 
 
 class UnevaluatableError(Exception):
@@ -28,6 +29,7 @@ _notimplemented_ops = set(getattr(operators, op)
 class EvaluatorCompiler(object):
     def __init__(self, target_cls=None):
         self.target_cls = target_cls
+        self.mapper = inspect(target_cls) if target_cls is not None else None
 
     def process(self, clause):
         meth = getattr(self, &quot;visit_%s&quot; % clause.__visit_name__, None)
@@ -58,8 +60,12 @@ class EvaluatorCompiler(object):
                     parentmapper.class_
                 )
             key = parentmapper._columntoproperty[clause].key
+        elif self.mapper and clause in self.mapper._columntoproperty:
+            key = self.mapper._columntoproperty[clause].key
         else:
-            key = clause.key
+            raise UnevaluatableError(
+                &quot;Can't evaluate column: %s&quot; % clause
+            )
 
         get_corresponding_attr = operator.attrgetter(key)
         return lambda obj: get_corresponding_attr(obj)
diff --git a/lib/sqlalchemy/orm/relationships.py b/lib/sqlalchemy/orm/relationships.py
index b649c9e..7ac0ab5 100644
--- a/lib/sqlalchemy/orm/relationships.py
+++ b/lib/sqlalchemy/orm/relationships.py
@@ -1372,6 +1372,7 @@ class RelationshipProperty(StrategizedProperty):
 
         if adapt_source:
             criterion = adapt_source(criterion)
+
         return criterion
 
     def _lazy_none_clause(self, reverse_direction=False, adapt_source=None):
"><pre class="notranslate"><code class="notranslate">diff --git a/lib/sqlalchemy/orm/evaluator.py b/lib/sqlalchemy/orm/evaluator.py
index 1e828ff..814b28f 100644
--- a/lib/sqlalchemy/orm/evaluator.py
+++ b/lib/sqlalchemy/orm/evaluator.py
@@ -7,6 +7,7 @@
 
 import operator
 from ..sql import operators
+from .. import inspect
 
 
 class UnevaluatableError(Exception):
@@ -28,6 +29,7 @@ _notimplemented_ops = set(getattr(operators, op)
 class EvaluatorCompiler(object):
     def __init__(self, target_cls=None):
         self.target_cls = target_cls
+        self.mapper = inspect(target_cls) if target_cls is not None else None
 
     def process(self, clause):
         meth = getattr(self, "visit_%s" % clause.__visit_name__, None)
@@ -58,8 +60,12 @@ class EvaluatorCompiler(object):
                     parentmapper.class_
                 )
             key = parentmapper._columntoproperty[clause].key
+        elif self.mapper and clause in self.mapper._columntoproperty:
+            key = self.mapper._columntoproperty[clause].key
         else:
-            key = clause.key
+            raise UnevaluatableError(
+                "Can't evaluate column: %s" % clause
+            )
 
         get_corresponding_attr = operator.attrgetter(key)
         return lambda obj: get_corresponding_attr(obj)
diff --git a/lib/sqlalchemy/orm/relationships.py b/lib/sqlalchemy/orm/relationships.py
index b649c9e..7ac0ab5 100644
--- a/lib/sqlalchemy/orm/relationships.py
+++ b/lib/sqlalchemy/orm/relationships.py
@@ -1372,6 +1372,7 @@ class RelationshipProperty(StrategizedProperty):
 
         if adapt_source:
             criterion = adapt_source(criterion)
+
         return criterion
 
     def _lazy_none_clause(self, reverse_direction=False, adapt_source=None):

</code></pre></div>
<p dir="auto"><strong>Migrated issue, originally created by Michael Bayer (<a href="https://github.com/zzzeek">@zzzeek</a>)</strong></p>
<p dir="auto">this is related to <a class="issue-link js-issue-link" data-error-text="Failed to load title" data-id="384633460" data-permission-text="Title is private" data-url="https://github.com/sqlalchemy/sqlalchemy/issues/3365" data-hovercard-type="issue" data-hovercard-url="/sqlalchemy/sqlalchemy/issues/3365/hovercard" href="https://github.com/sqlalchemy/sqlalchemy/issues/3365">#3365</a> but is a more fundamental change.</p>
<p dir="auto">First, test case from <a class="issue-link js-issue-link" data-error-text="Failed to load title" data-id="384633460" data-permission-text="Title is private" data-url="https://github.com/sqlalchemy/sqlalchemy/issues/3365" data-hovercard-type="issue" data-hovercard-url="/sqlalchemy/sqlalchemy/issues/3365/hovercard" href="https://github.com/sqlalchemy/sqlalchemy/issues/3365">#3365</a>:</p>
<div class="snippet-clipboard-content notranslate position-relative overflow-auto" data-snippet-clipboard-copy-content="from sqlalchemy import Column, Integer, Text, ForeignKey, create_engine
from sqlalchemy.ext.declarative import declarative_base
from sqlalchemy.orm import Session, relationship

Base = declarative_base()


class Parent(Base):
    __tablename__ = &quot;parent&quot;
    id = Column(Integer, primary_key=True)


class Child(Base):
    __tablename__ = &quot;child&quot;
    _id_parent = Column(
        &quot;id_parent&quot;, Integer, ForeignKey(Parent.id), primary_key=True)
    name = Column(Text, primary_key=True)
    parent = relationship(Parent)

engine = create_engine('sqlite://')
Base.metadata.create_all(engine)
session = Session(engine)

p = Parent(id=1)
session.add(p)
session.commit()

c = Child(name=&quot;foo&quot;, parent=p)
session.add(c)
session.commit()

session.query(Child).filter(Child.parent == p).delete(&quot;evaluate&quot;)"><pre class="notranslate"><code class="notranslate">from sqlalchemy import Column, Integer, Text, ForeignKey, create_engine
from sqlalchemy.ext.declarative import declarative_base
from sqlalchemy.orm import Session, relationship

Base = declarative_base()


class Parent(Base):
    __tablename__ = "parent"
    id = Column(Integer, primary_key=True)


class Child(Base):
    __tablename__ = "child"
    _id_parent = Column(
        "id_parent", Integer, ForeignKey(Parent.id), primary_key=True)
    name = Column(Text, primary_key=True)
    parent = relationship(Parent)

engine = create_engine('sqlite://')
Base.metadata.create_all(engine)
session = Session(engine)

p = Parent(id=1)
session.add(p)
session.commit()

c = Child(name="foo", parent=p)
session.add(c)
session.commit()

session.query(Child).filter(Child.parent == p).delete("evaluate")
</code></pre></div>
<p dir="auto">raises: "AttributeError: 'Child' object has no attribute 'id_parent'"</p>
<p dir="auto">with the fix, raises: "sqlalchemy.exc.InvalidRequestError: Could not evaluate current criteria in Python. Specify 'fetch' or False for the synchronize_session parameter."</p>
<p dir="auto">the patch involves adding ORM context to the primaryjoin/secondaryjoin:</p>
<div class="snippet-clipboard-content notranslate position-relative overflow-auto" data-snippet-clipboard-copy-content="diff --git a/lib/sqlalchemy/orm/evaluator.py b/lib/sqlalchemy/orm/evaluator.py
index 1e828ff..fb59186 100644
--- a/lib/sqlalchemy/orm/evaluator.py
+++ b/lib/sqlalchemy/orm/evaluator.py
@@ -59,7 +59,9 @@ class EvaluatorCompiler(object):
                 )
             key = parentmapper._columntoproperty[clause].key
         else:
-            key = clause.key
+            raise UnevaluatableError(
+                &quot;Cannot evaluate column: %s&quot; % clause
+            )
 
         get_corresponding_attr = operator.attrgetter(key)
         return lambda obj: get_corresponding_attr(obj)
diff --git a/lib/sqlalchemy/orm/relationships.py b/lib/sqlalchemy/orm/relationships.py
index b649c9e..291af09 100644
--- a/lib/sqlalchemy/orm/relationships.py
+++ b/lib/sqlalchemy/orm/relationships.py
@@ -1942,6 +1942,7 @@ class JoinCondition(object):
         self._annotate_fks()
         self._annotate_remote()
         self._annotate_local()
+        self._annotate_parentmapper()
         self._setup_pairs()
         self._check_foreign_cols(self.primaryjoin, True)
         if self.secondaryjoin is not None:
@@ -2405,6 +2406,19 @@ class JoinCondition(object):
             self.primaryjoin, {}, locals_
         )
 
+    def _annotate_parentmapper(self):
+        if self.prop is None:
+            return
+
+        def parentmappers_(elem):
+            if &quot;remote&quot; in elem._annotations:
+                return elem._annotate({&quot;parentmapper&quot;: self.prop.mapper})
+            elif &quot;local&quot; in elem._annotations:
+                return elem._annotate({&quot;parentmapper&quot;: self.prop.parent})
+        self.primaryjoin = visitors.replacement_traverse(
+            self.primaryjoin, {}, parentmappers_
+        )
+
     def _check_remote_side(self):
         if not self.local_remote_pairs:
             raise sa_exc.ArgumentError(
@@ -2811,9 +2825,6 @@ class JoinCondition(object):
 
         bind_to_col = dict((binds[col].key, col) for col in binds)
 
-        # this is probably not necessary
-        lazywhere = _deep_deannotate(lazywhere)
-
         return lazywhere, bind_to_col, equated_columns
 
 
"><pre class="notranslate"><code class="notranslate">diff --git a/lib/sqlalchemy/orm/evaluator.py b/lib/sqlalchemy/orm/evaluator.py
index 1e828ff..fb59186 100644
--- a/lib/sqlalchemy/orm/evaluator.py
+++ b/lib/sqlalchemy/orm/evaluator.py
@@ -59,7 +59,9 @@ class EvaluatorCompiler(object):
                 )
             key = parentmapper._columntoproperty[clause].key
         else:
-            key = clause.key
+            raise UnevaluatableError(
+                "Cannot evaluate column: %s" % clause
+            )
 
         get_corresponding_attr = operator.attrgetter(key)
         return lambda obj: get_corresponding_attr(obj)
diff --git a/lib/sqlalchemy/orm/relationships.py b/lib/sqlalchemy/orm/relationships.py
index b649c9e..291af09 100644
--- a/lib/sqlalchemy/orm/relationships.py
+++ b/lib/sqlalchemy/orm/relationships.py
@@ -1942,6 +1942,7 @@ class JoinCondition(object):
         self._annotate_fks()
         self._annotate_remote()
         self._annotate_local()
+        self._annotate_parentmapper()
         self._setup_pairs()
         self._check_foreign_cols(self.primaryjoin, True)
         if self.secondaryjoin is not None:
@@ -2405,6 +2406,19 @@ class JoinCondition(object):
             self.primaryjoin, {}, locals_
         )
 
+    def _annotate_parentmapper(self):
+        if self.prop is None:
+            return
+
+        def parentmappers_(elem):
+            if "remote" in elem._annotations:
+                return elem._annotate({"parentmapper": self.prop.mapper})
+            elif "local" in elem._annotations:
+                return elem._annotate({"parentmapper": self.prop.parent})
+        self.primaryjoin = visitors.replacement_traverse(
+            self.primaryjoin, {}, parentmappers_
+        )
+
     def _check_remote_side(self):
         if not self.local_remote_pairs:
             raise sa_exc.ArgumentError(
@@ -2811,9 +2825,6 @@ class JoinCondition(object):
 
         bind_to_col = dict((binds[col].key, col) for col in binds)
 
-        # this is probably not necessary
-        lazywhere = _deep_deannotate(lazywhere)
-
         return lazywhere, bind_to_col, equated_columns
 
 

</code></pre></div>
<p dir="auto">We'd just start tracking "parentmapper" throughout all PJ/SJ conditions.</p>