On 03/16/2016 06:10 PM, Thorsten von Stein wrote:
Mike,

Thank you very much for your quick reply and for creating the test case.
Upon closer inspection, I noticed that the errors in my system resulted
from a special case of a self-referential relationship as in the
following modification of your test case, in which additionally the
post_update flag is required. In that case, the assertion fails.
However, with an additional remote annotation to make the _del==0
condition unambiguous, the example works again.

Right you'd need that remote() there for that mapping to be correct. The post_update part does not matter.


Now the only question
that remains is why the original version was working fine up to 0.9.3.

Looking at the 0.9.4 changelog this issue describes just what we're doing here:

http://docs.sqlalchemy.org/en/rel_1_0/changelog/changelog_09.html#change-c7ec7a2b899f6d33f4ef3c25d538daa0

and indeed the revision at https://bitbucket.org/zzzeek/sqlalchemy/commits/825d3b0d6db4 is where the test fails without the extra remote(). I don't really remember what we're doing here and it would take me a few hours of staring to re-understand this, it looks like the pattern you have is possibly the reverse of what's being fixed here but nonetheless was impacted to do "the right thing", which in your case was the thing you didn't want :).









from sqlalchemy import *
from sqlalchemy.orm import *
from sqlalchemy.ext.declarative import declarative_base

Base = declarative_base()



*class C(Base):
      __tablename__ = 'c'
      id = Column(Integer, primary_key=True)
      parent_id = Column(ForeignKey('c.id <http://c.id>'))
      _del = Column("del", Integer, default=0)
*

def make_rel(cls, remoteCls, foreignKey, backref_name):
      br = backref(
          backref_name,
          collection_class=list,
          primaryjoin=and_(
              remoteCls.id == remote(getattr(cls, foreignKey)),
              cls._del == 0*# works with: remote(cls._del) == 0*
          )
      )

      rel = relationship(
          remoteCls,
          remote_side=remoteCls.id,
          primaryjoin=getattr(cls, foreignKey) == remoteCls.id,
*post_update = True*,
          backref=br)
      return rel

*C.parent = make_rel(C, C, "parent_id", "children")*

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

s = Session(e)


*c1, c2, c3 = C(), C(), C()
c0 = C(children=[c1, c2, c3])*

s.add(c0)
s.commit()

*c2._del = 1
*s.commit()


*assert c0.children == [c1, c3]*



On Wed, Mar 16, 2016 at 12:42 PM, Mike Bayer <[email protected]
<mailto:[email protected]>> wrote:



    On 03/16/2016 02:37 PM, Thorsten von Stein wrote:

        For several years, I have been using a pattern for making a
        many-to-one
        relationship from *cls* to *remoteCls* with a one-to-many
        backref with a
        join condition cls.foreignKey == remoteCls.id, where
        *cls* has a deletion flag _del which should exclude *cls*
        instances with

        del != 0 from the backref collection.

        Since the condition involving _del is only relevant in the
        one-to-many
        direction, I defined separate primaryjoin conditions which
        included this
        condition only for the backref.

        br = backref(
              backref,
              collection_class=list,
              primaryjoin=and_(remoteCls.id==remote(getattr(cls,
        foreignKey)),
        cls._del==0))

        rel = relationship(
              remoteCls,
              remote_side=remoteCls.id,
              primaryjoin=getattr(cls, foreignKey)==remoteCls.id,
              backref=br)

        I have used this pattern successfully for years until I recently
        upgraded SqlAlchemy to the latest version and found that the join
        condition on the backref seems to be ignored and queries include
        instances that are flagged as deleted via the _del column. I tested
        several intermediate SqlAlchemy version and found that the first one
        which breaks the pattern is 0.9.4.

        Subsequently I found that removing the primary join condition on the
        backref and including the _del != 0 condition in the forward primary
        join condition seems to restore the intended behavior, but now many
        queries involving large collections are dramatically slowed to
        make this
        solution unworkable.

        I reviewed the desciptions of changes, but they did not clarify
        for me
        why the pattern above does not work any more. Is there a flaw in
        my code
        that I am missing?


    There are no changes that should affect the behavior of relationship
    in this way.  If anything, I'd wonder if the "0" value here is
    actually a boolean and is interacting with some backend-specific
    typing behavior, but there's not enough detail here to know.

    Below is a complete test of your concept which succeeds.  Please
    alter this test appropriately to illustrate your failure condition
    occurring, thanks!

    from sqlalchemy import *
    from sqlalchemy.orm import *
    from sqlalchemy.ext.declarative import declarative_base

    Base = declarative_base()


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


    class B(Base):
         __tablename__ = 'b'
         id = Column(Integer, primary_key=True)
         a_id = Column(ForeignKey('a.id <http://a.id>'))
         _del = Column("del", Integer, default=0)


    def make_rel(cls, remoteCls, foreignKey, backref_name):
         br = backref(
             backref_name,
             collection_class=list,
             primaryjoin=and_(
                 remoteCls.id == remote(getattr(cls, foreignKey)),
                 cls._del == 0)
         )

         rel = relationship(
             remoteCls,
             remote_side=remoteCls.id,
             primaryjoin=getattr(cls, foreignKey) == remoteCls.id,
             backref=br)
         return rel

    B.a = make_rel(B, A, "a_id", "bs")

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

    s = Session(e)

    b1, b2, b3 = B(), B(), B()
    a1 = A(bs=[b1, b2, b3])

    s.add(a1)
    s.commit()

    b2._del = 1
    s.commit()

    assert a1.bs <http://a1.bs> == [b1, b3]







        --
        You received this message because you are subscribed to the Google
        Groups "sqlalchemy" group.
        To unsubscribe from this group and stop receiving emails from
        it, send
        an email to [email protected]
        <mailto:sqlalchemy%[email protected]>
        <mailto:[email protected]
        <mailto:sqlalchemy%[email protected]>>.
        To post to this group, send email to [email protected]
        <mailto:[email protected]>
        <mailto:[email protected]
        <mailto:[email protected]>>.
        Visit this group at https://groups.google.com/group/sqlalchemy.
        For more options, visit https://groups.google.com/d/optout.


    --
    You received this message because you are subscribed to a topic in
    the Google Groups "sqlalchemy" group.
    To unsubscribe from this topic, visit
    https://groups.google.com/d/topic/sqlalchemy/43rA8XsVuBQ/unsubscribe.
    To unsubscribe from this group and all its topics, send an email to
    [email protected]
    <mailto:sqlalchemy%[email protected]>.
    To post to this group, send email to [email protected]
    <mailto:[email protected]>.
    Visit this group at https://groups.google.com/group/sqlalchemy.
    For more options, visit https://groups.google.com/d/optout.


--
You received this message because you are subscribed to the Google
Groups "sqlalchemy" group.
To unsubscribe from this group and stop receiving emails from it, send
an email to [email protected]
<mailto:[email protected]>.
To post to this group, send email to [email protected]
<mailto:[email protected]>.
Visit this group at https://groups.google.com/group/sqlalchemy.
For more options, visit https://groups.google.com/d/optout.

--
You received this message because you are subscribed to the Google Groups 
"sqlalchemy" group.
To unsubscribe from this group and stop receiving emails from it, send an email 
to [email protected].
To post to this group, send email to [email protected].
Visit this group at https://groups.google.com/group/sqlalchemy.
For more options, visit https://groups.google.com/d/optout.

Reply via email to