<p dir="auto"><strong>Migrated issue, originally created by Michael Bayer (<a href="https://github.com/zzzeek">@zzzeek</a>)</strong></p>
<p dir="auto">lazyloading can be made to work for this case:</p>
<div class="snippet-clipboard-content notranslate position-relative overflow-auto" data-snippet-clipboard-copy-content="def get_age():
    return 15


class Child(Base):
    __tablename__ = 'child'
    id = Column('id', Integer, primary_key=True)
    age = Column(Integer)
    parent_id = Column(Integer, ForeignKey('parent.id'))


class Parent(Base):
    __tablename__ = 'parent'
    id = Column('id', Integer, primary_key=True)
    @classmethod
    def __declare_last__(cls):
        cls.children_by_age = relationship(
            Child,
            lazy=True, 
            primaryjoin=and_(
                Parent.id == Child.parent_id,
                Child.age == bindparam('age', callable_=get_age)
            )
        )
"><pre class="notranslate"><code class="notranslate">def get_age():
    return 15


class Child(Base):
    __tablename__ = 'child'
    id = Column('id', Integer, primary_key=True)
    age = Column(Integer)
    parent_id = Column(Integer, ForeignKey('parent.id'))


class Parent(Base):
    __tablename__ = 'parent'
    id = Column('id', Integer, primary_key=True)
    @classmethod
    def __declare_last__(cls):
        cls.children_by_age = relationship(
            Child,
            lazy=True, 
            primaryjoin=and_(
                Parent.id == Child.parent_id,
                Child.age == bindparam('age', callable_=get_age)
            )
        )

</code></pre></div>
<p dir="auto">if we do this:</p>
<div class="snippet-clipboard-content notranslate position-relative overflow-auto" data-snippet-clipboard-copy-content="diff --git a/lib/sqlalchemy/orm/strategies.py b/lib/sqlalchemy/orm/strategies.py
index 67dac1c..aa0a9f7 100644
--- a/lib/sqlalchemy/orm/strategies.py
+++ b/lib/sqlalchemy/orm/strategies.py
@@ -434,6 +434,8 @@ class LazyLoader(AbstractRelationshipLoader, util.MemoizedSlots):
         params = []
 
         def visit_bindparam(bindparam):
+            if bindparam.callable:
+                return
             bindparam.unique = False
             if bindparam._identifying_key in bind_to_col:
                 params.append(("><pre class="notranslate"><code class="notranslate">diff --git a/lib/sqlalchemy/orm/strategies.py b/lib/sqlalchemy/orm/strategies.py
index 67dac1c..aa0a9f7 100644
--- a/lib/sqlalchemy/orm/strategies.py
+++ b/lib/sqlalchemy/orm/strategies.py
@@ -434,6 +434,8 @@ class LazyLoader(AbstractRelationshipLoader, util.MemoizedSlots):
         params = []
 
         def visit_bindparam(bindparam):
+            if bindparam.callable:
+                return
             bindparam.unique = False
             if bindparam._identifying_key in bind_to_col:
                 params.append((
</code></pre></div>
<p dir="auto">should we do that (and is that where we should do that)?   Is there a better way to support bound callables in join conditions? what are the use cases for this ?  note that the bindparam w/ callable_ works for joinedload, subqueryload, works for query.join(Parent.foo), so it's likely we should do this so that the behavior is consistent.</p>
<p dir="auto"><strong>Migrated issue, originally created by Michael Bayer (<a href="https://github.com/zzzeek">@zzzeek</a>)</strong></p>
<div class="snippet-clipboard-content notranslate position-relative overflow-auto" data-snippet-clipboard-copy-content="from sqlalchemy import *
from sqlalchemy.orm import *
from sqlalchemy.ext.declarative import declarative_base

Base = declarative_base()


class A(Base):
    __tablename__ = 'a'
    id1 = Column(Integer, primary_key=True)
    id2 = Column(Integer, primary_key=True)


class B(Base):
    __tablename__ = 'b'
    id = Column(Integer, primary_key=True)
    a_id1 = Column(Integer, ForeignKey('a.id1'))

    a = relationship(
        &quot;A&quot;, uselist=False,
        primaryjoin=and_(
            a_id1 == A.id1,
            A.id2 == bindparam(&quot;my_bindparam&quot;, callable_=lambda: 2)))


configure_mappers()

assert not B.a.property.strategy.use_get

e = create_engine(&quot;sqlite://&quot;, echo=True)
Base.metadata.create_all(e)

s = Session(e)
s.add(A(id1=1, id2=1))
s.add(A(id1=1, id2=2))
s.add(B(a_id1=1))
s.commit()

b1 = s.query(B).first()
print b1.a


"><pre class="notranslate"><code class="notranslate">from sqlalchemy import *
from sqlalchemy.orm import *
from sqlalchemy.ext.declarative import declarative_base

Base = declarative_base()


class A(Base):
    __tablename__ = 'a'
    id1 = Column(Integer, primary_key=True)
    id2 = Column(Integer, primary_key=True)


class B(Base):
    __tablename__ = 'b'
    id = Column(Integer, primary_key=True)
    a_id1 = Column(Integer, ForeignKey('a.id1'))

    a = relationship(
        "A", uselist=False,
        primaryjoin=and_(
            a_id1 == A.id1,
            A.id2 == bindparam("my_bindparam", callable_=lambda: 2)))


configure_mappers()

assert not B.a.property.strategy.use_get

e = create_engine("sqlite://", echo=True)
Base.metadata.create_all(e)

s = Session(e)
s.add(A(id1=1, id2=1))
s.add(A(id1=1, id2=2))
s.add(B(a_id1=1))
s.commit()

b1 = s.query(B).first()
print b1.a



</code></pre></div>
<p dir="auto">first, use_get is improperly detected.   We would need to compare for something like the "callable" as well (we don't seem to be able to compare for the "key", not sure why):</p>
<div class="snippet-clipboard-content notranslate position-relative overflow-auto" data-snippet-clipboard-copy-content="diff --git a/lib/sqlalchemy/sql/elements.py b/lib/sqlalchemy/sql/elements.py
index e277b28..1d19707 100644
--- a/lib/sqlalchemy/sql/elements.py
+++ b/lib/sqlalchemy/sql/elements.py
@@ -1142,7 +1142,8 @@ class BindParameter(ColumnElement):
 
         return isinstance(other, BindParameter) \
             and self.type._compare_type_affinity(other.type) \
-            and self.value == other.value
+            and self.value == other.value \
+            and self.callable == other.callable
 
     def __getstate__(self):
         &quot;&quot;&quot;execute a deferred value for serialization purposes.&quot;&quot;&quot;
"><pre class="notranslate"><code class="notranslate">diff --git a/lib/sqlalchemy/sql/elements.py b/lib/sqlalchemy/sql/elements.py
index e277b28..1d19707 100644
--- a/lib/sqlalchemy/sql/elements.py
+++ b/lib/sqlalchemy/sql/elements.py
@@ -1142,7 +1142,8 @@ class BindParameter(ColumnElement):
 
         return isinstance(other, BindParameter) \
             and self.type._compare_type_affinity(other.type) \
-            and self.value == other.value
+            and self.value == other.value \
+            and self.callable == other.callable
 
     def __getstate__(self):
         """execute a deferred value for serialization purposes."""

</code></pre></div>
<p dir="auto">next, the visitation in <code class="notranslate">_memoized_attr__simple_lazy_clause</code> is picking up on this bind as one of their own.   ideally we'd use annotations to get around this but then we need to get rid of that deep_deannotate, this seems overall more risky:</p>
<div class="snippet-clipboard-content notranslate position-relative overflow-auto" data-snippet-clipboard-copy-content="diff --git a/lib/sqlalchemy/orm/relationships.py b/lib/sqlalchemy/orm/relationships.py
index e8a2992..e16c52d 100644
--- a/lib/sqlalchemy/orm/relationships.py
+++ b/lib/sqlalchemy/orm/relationships.py
@@ -2827,7 +2827,8 @@ class JoinCondition(object):
             ):
                 if col not in binds:
                     binds[col] = sql.bindparam(
-                        None, None, type_=col.type, unique=True)
+                        None, None, type_=col.type, unique=True).\
+                        _annotate({&quot;lazyloader&quot;: True})
                 return binds[col]
             return None
 
@@ -2845,9 +2846,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
 
 
diff --git a/lib/sqlalchemy/orm/strategies.py b/lib/sqlalchemy/orm/strategies.py
index 8260732..f5bf041 100644
--- a/lib/sqlalchemy/orm/strategies.py
+++ b/lib/sqlalchemy/orm/strategies.py
@@ -473,6 +473,9 @@ class LazyLoader(AbstractRelationshipLoader, util.MemoizedSlots):
         params = []
 
         def visit_bindparam(bindparam):
+            if not bindparam._annotations.get(&quot;lazyloader&quot;):
+                return
+
             bindparam.unique = False
             if bindparam._identifying_key in bind_to_col:
                 params.append((
"><pre class="notranslate"><code class="notranslate">diff --git a/lib/sqlalchemy/orm/relationships.py b/lib/sqlalchemy/orm/relationships.py
index e8a2992..e16c52d 100644
--- a/lib/sqlalchemy/orm/relationships.py
+++ b/lib/sqlalchemy/orm/relationships.py
@@ -2827,7 +2827,8 @@ class JoinCondition(object):
             ):
                 if col not in binds:
                     binds[col] = sql.bindparam(
-                        None, None, type_=col.type, unique=True)
+                        None, None, type_=col.type, unique=True).\
+                        _annotate({"lazyloader": True})
                 return binds[col]
             return None
 
@@ -2845,9 +2846,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
 
 
diff --git a/lib/sqlalchemy/orm/strategies.py b/lib/sqlalchemy/orm/strategies.py
index 8260732..f5bf041 100644
--- a/lib/sqlalchemy/orm/strategies.py
+++ b/lib/sqlalchemy/orm/strategies.py
@@ -473,6 +473,9 @@ class LazyLoader(AbstractRelationshipLoader, util.MemoizedSlots):
         params = []
 
         def visit_bindparam(bindparam):
+            if not bindparam._annotations.get("lazyloader"):
+                return
+
             bindparam.unique = False
             if bindparam._identifying_key in bind_to_col:
                 params.append((

</code></pre></div>