track objects that had any flush state for the lifespan of transaction scope similarly to self._new, self._deleted, take advantage of this for savepoint rollbacks

Issue #2452 resolved
Mike Bayer repo owner created an issue

No description provided.

Comments (6)

  1. Mike Bayer reporter
    diff -r 7142a17291deba2eb9d4a2b30e1635129c2284ea lib/sqlalchemy/orm/session.py
    --- a/lib/sqlalchemy/orm/session.py Wed Mar 21 22:58:55 2012 -0400
    +++ b/lib/sqlalchemy/orm/session.py Thu Mar 29 10:04:19 2012 -0400
    @@ -219,7 +219,7 @@
             self._new = weakref.WeakKeyDictionary()
             self._deleted = weakref.WeakKeyDictionary()
    
    -    def _restore_snapshot(self):
    +    def _restore_snapshot(self, dirty_only=False):
             assert self._is_transaction_boundary
    
             for s in set(self._new).union(self.session._new):
    @@ -236,7 +236,8 @@
             assert not self.session._deleted
    
             for s in self.session.identity_map.all_states():
    -            s.expire(s.dict, self.session.identity_map._modified)
    +            if s.modified or not dirty_only:
    +                s.expire(s.dict, self.session.identity_map._modified)
    
         def _remove_snapshot(self):
             assert self._is_transaction_boundary
    @@ -351,7 +352,7 @@
                         "Session's state has been changed on "
                         "a non-active transaction - this state "
                         "will be discarded.")
    -            self._restore_snapshot()
    +            self._restore_snapshot(dirty_only=self.nested)
    
             self.close()
             if self._parent and _capture_exception:
    @@ -366,7 +367,7 @@
                 t[1](1).rollback()
    
             if self.session._enable_transaction_accounting:
    -            self._restore_snapshot()
    +            self._restore_snapshot(dirty_only=self.nested)
    
             self.session.dispatch.after_rollback(self.session)
    
  2. Former user Account Deleted

    (original author: werner) Michael,

    My initial tests with this patch applied to 0.7.6 look very good.

    Will keep watching this and let you know should I run into anything.

    Thanks Werner

  3. Mike Bayer reporter

    unfortunately this patch fails very easily as "dirty" has nothing to do with objects that were already flushed:

    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)
        data = Column(String(50))
    
    e = create_engine("postgresql://scott:tiger@localhost/test")
    
    Base.metadata.drop_all(e)
    Base.metadata.create_all(e)
    
    s = Session(e)
    
    a1, a2, a3 = A(data='a1'), A(data='a2'), A(data='a3')
    s.add_all([a2, a3](a1,))
    
    s.commit()
    
    a1 = s.query(A).filter_by(data='a1').one()
    
    s.begin_nested()
    a1.data = 'a1modified'
    a2 = s.query(A).filter_by(data='a2').one()
    a3 = s.query(A).filter_by(data='a3').one()
    s.rollback()
    
    assert a1.data == 'a1', "%s != %s" % (a1.data, 'a1')
    

    the approach is not feasible as we can't store the full state of all flushed objects in memory. They have to be loaded from the database, which is the only place the data is present. So for this to work, every object in the Session which was affected in the flush needs to be added to a new transaction collection called ._dirty or ._flushed, which will indicate those objects that had changes.

  4. Log in to comment