]> git.ipfire.org Git - thirdparty/sqlalchemy/sqlalchemy.git/commitdiff
added new style of begin/commit which returns a tranactional object
authorMike Bayer <mike_mp@zzzcomputing.com>
Sat, 18 Feb 2006 21:11:20 +0000 (21:11 +0000)
committerMike Bayer <mike_mp@zzzcomputing.com>
Sat, 18 Feb 2006 21:11:20 +0000 (21:11 +0000)
doc/build/content/document_base.myt
doc/build/content/unitofwork.myt
lib/sqlalchemy/mapping/objectstore.py
test/objectstore.py
test/tables.py

index 85c791eb35c6331da26c344fe97db043b37ac27d..f0f09130e96dba855c1149b983d8a061eccd2a2d 100644 (file)
@@ -23,7 +23,7 @@
     onepage='documentation'
     index='index'
     title='SQLAlchemy Documentation'
-    version = '0.1.0'
+    version = '0.1.1'
 </%attr>
 
 <%method title>
index 30e09a5ce12a929fa102ee3b6b90cee69619ce41..40ea5bcd9e433afac9b55f01211974512ddec34d 100644 (file)
     <&|doclib.myt:item, name="begin", description="Controlling Scope with begin()" &>
     
     <p>The "scope" of the unit of work commit can be controlled further by issuing a begin().  A begin operation constructs a new UnitOfWork object and sets it as the currently used UOW.  It maintains a reference to the original UnitOfWork as its "parent", and shares the same "identity map" of objects that have been loaded from the database within the scope of the parent UnitOfWork.  However, the "new", "dirty", and "deleted" lists are empty.  This has the effect that only changes that take place after the begin() operation get logged to the current UnitOfWork, and therefore those are the only changes that get commit()ted.  When the commit is complete, the "begun" UnitOfWork removes itself and places the parent UnitOfWork as the current one again.</p>
+<p>The begin() method returns a transactional object, upon which you can call commit() or rollback().  <b>Only this transactional object controls the transaction</b> - commit() upon the Session will do nothing until commit() or rollback() is called upon the transactional object.</p>
     <&|formatting.myt:code&>
         # modify an object
         myobj1.foo = "something new"
         
         # begin an objectstore scope
         # this is equivalent to objectstore.get_session().begin()
-        objectstore.begin()
+        trans = objectstore.begin()
         
         # modify another object
         myobj2.lala = "something new"
         
         # only 'myobj2' is saved
-        objectstore.commit()
+        trans.commit()
     </&>
-    <p>As always, the actual database transaction begin/commit occurs entirely within the objectstore.commit() operation.</p>
-    <p>At the moment, begin/commit supports the same "nesting" behavior as the SQLEngine (note this behavior is not the original "nested" behavior), meaning that repeated calls to begin() will increment a counter, which is not released until that same number of commit() statements occur.</p>
+    <p>begin/commit supports the same "nesting" behavior as the SQLEngine (note this behavior is not the original "nested" behavior), meaning that many begin() calls can be made, but only the outermost transactional object will actually perform a commit().  Similarly, calls to the commit() method on the Session, which might occur in function calls within the transaction, will not do anything; this allows an external function caller to control the scope of transactions used within the functions.</p>
     </&>
     <&|doclib.myt:item, name="transactionnesting", description="Nesting UnitOfWork in a Database Transaction" &>
     <p>The UOW commit operation places its INSERT/UPDATE/DELETE operations within the scope of a database transaction controlled by a SQLEngine:
index 91c0e9bca0ddd0dd81dd57f3002311928f5e000f..f2214d5750fa31f81ac6fecbad7db298ac0f87f6 100644 (file)
@@ -82,16 +82,43 @@ class Session(object):
         """
         return (class_, table.hash_key(), tuple([row[column] for column in primary_key]))
     get_row_key = staticmethod(get_row_key)
-    
+
+    class UOWTrans(object):
+        def __init__(self, parent, uow, isactive):
+            self.__parent = parent
+            self.__isactive = isactive
+            self.__uow = uow
+        isactive = property(lambda s:s.__isactive)
+        parent = property(lambda s:s.__parent)
+        uow = property(lambda s:s.__uow)
+        def begin(self):
+            return self.parent.begin()
+        def commit(self):
+            self.__parent._trans_commit(self)
+            self.__isactive = False
+        def rollback(self):
+            self.__parent._trans_rollback(self)
+            self.__isactive = False
+
     def begin(self):
         """begins a new UnitOfWork transaction.  the next commit will affect only
         objects that are created, modified, or deleted following the begin statement."""
-        self.begin_count += 1
         if self.parent_uow is not None:
-            return
-        self.parent_uow = self.uow            
+            return Session.UOWTrans(self, self.uow, False)
+        self.parent_uow = self.uow
         self.uow = UnitOfWork(identity_map = self.uow.identity_map)
-        
+        return Session.UOWTrans(self, self.uow, True)
+    
+    def _trans_commit(self, trans):
+        if trans.uow is self.uow and trans.isactive:
+            self.uow.commit()
+            self.uow = self.parent_uow
+            self.parent_uow = None
+    def _trans_rollback(self, trans):
+        if trans.uow is self.uow:
+            self.uow = self.parent_uow
+            self.parent_uow = None
+                        
     def commit(self, *objects):
         """commits the current UnitOfWork transaction.  if a transaction was begun 
         via begin(), commits only those objects that were created, modified, or deleted
@@ -104,14 +131,8 @@ class Session(object):
         if len(objects):
             self.uow.commit(*objects)
             return
-        if self.parent_uow is not None:
-            self.begin_count -= 1
-            if self.begin_count > 0:
-                return
-        self.uow.commit()
-        if self.parent_uow is not None:
-            self.uow = self.parent_uow
-            self.parent_uow = None
+        if self.parent_uow is None:
+            self.uow.commit()
 
     def rollback(self):
         """rolls back the current UnitOfWork transaction, in the case that begin()
index 141fc15370d21f9f0b5c9358e13dad678667ac11..a117b4112fedcf88e655a40bcc9577ca9676d25c 100644 (file)
@@ -76,7 +76,6 @@ class SessionTest(AssertMixin):
     def setUpAll(self):
         db.echo = False
         users.create()
-        tables.user_data()
         db.echo = testbase.echo
     def tearDownAll(self):
         db.echo = False
@@ -85,6 +84,10 @@ class SessionTest(AssertMixin):
     def setUp(self):
         objectstore.get_session().clear()
         clear_mappers()
+        tables.user_data()
+        #db.echo = "debug"
+    def tearDown(self):
+        tables.delete_user_data()
         
     def test_nested_begin_commit(self):
         """test nested session.begin/commit"""
@@ -97,21 +100,47 @@ class SessionTest(AssertMixin):
         self.assert_(name_of(7) != name1, msg="user_name should not be %s" % name1)
         self.assert_(name_of(8) != name2, msg="user_name should not be %s" % name2)
         s = objectstore.get_session()
-        s.begin()
-        s.begin()
+        trans = s.begin()
+        trans2 = s.begin()
         m.get(7).user_name = name1
-        s.begin()
+        trans3 = s.begin()
         m.get(8).user_name = name2
-        s.commit()
+        trans3.commit()
+        s.commit() # should do nothing
         self.assert_(name_of(7) != name1, msg="user_name should not be %s" % name1)
         self.assert_(name_of(8) != name2, msg="user_name should not be %s" % name2)
-        s.commit()
+        trans2.commit()
+        s.commit()  # should do nothing
         self.assert_(name_of(7) != name1, msg="user_name should not be %s" % name1)
         self.assert_(name_of(8) != name2, msg="user_name should not be %s" % name2)
-        s.commit()
+        trans.commit()
         self.assert_(name_of(7) == name1, msg="user_name should be %s" % name1)
         self.assert_(name_of(8) == name2, msg="user_name should be %s" % name2)
 
+    def test_nested_rollback(self):
+        class User(object):pass
+        m = mapper(User, users)
+        def name_of(id):
+            return users.select(users.c.user_id == id).execute().fetchone().user_name
+        name1 = "Oliver Twist"
+        name2 = 'Mr. Bumble'
+        self.assert_(name_of(7) != name1, msg="user_name should not be %s" % name1)
+        self.assert_(name_of(8) != name2, msg="user_name should not be %s" % name2)
+        s = objectstore.get_session()
+        trans = s.begin()
+        trans2 = s.begin()
+        m.get(7).user_name = name1
+        trans3 = s.begin()
+        m.get(8).user_name = name2
+        trans3.rollback()
+        self.assert_(name_of(7) != name1, msg="user_name should not be %s" % name1)
+        self.assert_(name_of(8) != name2, msg="user_name should not be %s" % name2)
+        trans2.commit()
+        self.assert_(name_of(7) != name1, msg="user_name should not be %s" % name1)
+        self.assert_(name_of(8) != name2, msg="user_name should not be %s" % name2)
+        trans.commit()
+        self.assert_(name_of(7) != name1, msg="user_name should not be %s" % name1)
+        self.assert_(name_of(8) != name2, msg="user_name should not be %s" % name2)
 
 class PKTest(AssertMixin):
     def setUpAll(self):
index 7cc524674c668ae678a01f4b6bc424772c1c308a..927903e48681aa8f6b1b002e30d719223ad02722 100644 (file)
@@ -83,7 +83,10 @@ def user_data():
         dict(user_id = 8, user_name = 'ed'),
         dict(user_id = 9, user_name = 'fred')
     )
-    
+def delete_user_data():
+    users.delete().execute()
+    db.commit()
+        
 def data():
     delete()