Issue 1:
Migrated issue, originally created by Michael Bayer (@zzzeek)

lazyloading can be made to work for this case:

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)
)
)

if we do this:

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((

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.

Issue 2:
Migrated issue, originally created by Michael Bayer (@zzzeek)

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

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):

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."""

next, the visitation in _memoized_attr__simple_lazy_clause 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:

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((