primaryjoin w/ user defined bind parameter

Issue #3767 resolved
Mike Bayer repo owner created an issue
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((

Comments (3)

  1. Mike Bayer reporter

    easier:

    diff --git a/lib/sqlalchemy/orm/strategies.py b/lib/sqlalchemy/orm/strategies.py
    index 8260732..245f9e9 100644
    --- a/lib/sqlalchemy/orm/strategies.py
    +++ b/lib/sqlalchemy/orm/strategies.py
    @@ -473,12 +473,13 @@ class LazyLoader(AbstractRelationshipLoader, util.MemoizedSlots):
             params = []
    
             def visit_bindparam(bindparam):
    +
                 bindparam.unique = False
                 if bindparam._identifying_key in bind_to_col:
                     params.append((
                         bindparam.key, bind_to_col[bindparam._identifying_key],
                         None))
    -            else:
    +            elif bindparam.callable is None:
                     params.append((bindparam.key, None, bindparam.value))
    
             criterion = visitors.cloned_traverse(
    
  2. Mike Bayer reporter

    Support bindparam() with callable for primaryjoin

    Fixes the comparison of bindparam() objects based on the "callable" parameter being present which helps to correctly detect use_get, and also checks for "callable" when detecting parameters for value substitution and will not impact the object if present.

    Change-Id: I4c93ee5d404d2648dd9835beeae0c5fb67e37d19 Fixes: #3767

    → <<cset 8c3b9d608370>>

  3. Log in to comment