]> git.ipfire.org Git - thirdparty/sqlalchemy/sqlalchemy.git/commitdiff
beginning to address cycles but its not worked out yet
authorMike Bayer <mike_mp@zzzcomputing.com>
Wed, 31 Mar 2010 21:31:34 +0000 (17:31 -0400)
committerMike Bayer <mike_mp@zzzcomputing.com>
Wed, 31 Mar 2010 21:31:34 +0000 (17:31 -0400)
lib/sqlalchemy/orm/dependency.py
lib/sqlalchemy/orm/unitofwork.py
test/orm/test_unitofworkv2.py

index 8b90fbb7fdb3b2f08cd829a6d4b335c7ed5bc065..5729f553d7f3d6837b865bb26c429ec3c686c52f 100644 (file)
@@ -191,10 +191,73 @@ class OneToManyDP(DependencyProcessor):
             uow.dependencies.update([
                 (parent_saves, after_save),
                 (after_save, child_saves),
-                (child_deletes, before_delete),
-                (before_delete, parent_deletes)
+                
+                (child_saves, parent_deletes),
+                (before_delete, child_saves),
+                
+                (child_deletes, parent_deletes)
             ])
     
+    def per_saved_state_flush_actions(self, uow, state):
+        if True:
+            parent_saves = unitofwork.SaveUpdateAll(uow, self.parent)
+            child_saves = unitofwork.SaveUpdateAll(uow, self.mapper)
+            assert parent_saves in uow.cycles
+            assert child_saves in uow.cycles
+        
+        added, updated, deleted = uow.get_attribute_history(state, self.key, passive=True)
+        if not added and not unchanged and not deleted:
+            return
+        
+        save_parent = unitofwork.SaveUpdateState(state)
+        after_save = unitofwork.ProcessState(uow, self, False, state)
+
+        for child_state in added + unchanged + deleted:
+            if child_state is None:
+                continue
+            
+            (deleted, listonly) = uow.states[child_state]
+            if deleted:
+                child_action = unitofwork.DeleteState(child_state)
+            else:
+                child_action = unitofwork.SaveUpdateState(child_state)
+            
+            uow.dependencies.update([
+                (save_parent, after_save),
+                (after_save, child_action),
+            ])
+
+    def per_deleted_state_flush_actions(self, uow, state):
+        if True:
+            parent_deletes = unitofwork.DeleteAll(uow, self.parent)
+            child_deletes = unitofwork.DeleteAll(uow, self.mapper)
+            assert parent_deletes in uow.cycles
+            assert child_deletes in uow.cycles
+
+        added, updated, deleted = uow.get_attribute_history(state, self.key, passive=True)
+        if not added and not unchanged and not deleted:
+            return
+
+        delete_parent = unitofwork.DeleteState(state)
+        after_delete = unitofwork.ProcessState(uow, self, True, state)
+
+        for child_state in added + unchanged + deleted:
+            if child_state is None:
+                continue
+
+            (deleted, listonly) = uow.states[child_state]
+            if deleted:
+                child_action = unitofwork.DeleteState(child_state)
+            else:
+                child_action = unitofwork.SaveUpdateState(child_state)
+
+            uow.dependencies.update([
+                (child_action, )
+                (save_parent, after_save),
+                (after_save, child_action),
+            ])
+        
+        
     def presort_deletes(self, uowcommit, states):
         # head object is being deleted, and we manage its list of child objects
         # the child objects have to have their foreign key to the parent set to NULL
@@ -318,12 +381,11 @@ class ManyToOneDP(DependencyProcessor):
         else:
             unitofwork.GetDependentObjects(uow, self, False, True)
             unitofwork.GetDependentObjects(uow, self, True, True)
-
+            
             uow.dependencies.update([
-                (after_save, parent_saves),
                 (child_saves, after_save),
-                (parent_deletes, before_delete),
-                (before_delete, child_deletes)
+                (after_save, parent_saves),
+                (parent_saves, child_deletes)
             ])
 
     def presort_deletes(self, uowcommit, states):
@@ -375,6 +437,7 @@ class ManyToOneDP(DependencyProcessor):
             if history:
                 for child in history.added:
                     self._synchronize(state, child, None, False, uowcommit)
+                
                 self._conditional_post_update(state, uowcommit, history.sum())
 
 
index 6c53586e7d1ce2197a24e85efa4a6b578990ff05..65d85a37b0407bb4e6d262f2d1664467b2223af1 100644 (file)
@@ -166,16 +166,23 @@ class UOWTransaction(object):
             if not ret:
                 break
         
+        self.cycles = cycles = topological.find_cycles(self.dependencies, self.postsort_actions.values())
+        assert not cycles
+        for rec in cycles:
+            rec.per_state_flush_actions(self)
+
+        for edge in list(self.dependencies):
+            # both nodes in this edge were part of a cycle.
+            # remove that from our deps as it has replaced
+            # itself with per-state actions
+            if cycles.issuperset(edge):
+                self.dependencies.remove(edge)
+            
         sort = topological.sort(self.dependencies, self.postsort_actions.values())
         print sort
         for rec in sort:
             rec.execute(self)
             
-#        if cycles:
-#            break up actions into finer grained actions along those cycles
-            
-#        for rec in topological.sort(self.dependencies, self.actions):
-#            rec.execute()
 
     def finalize_flush_changes(self):
         """mark processed objects as clean / deleted after a successful flush().
@@ -276,6 +283,13 @@ class ProcessAll(PropertyRecMixin, PostSortRec):
         else:
             self.dependency_processor.process_saves(uow, states)
 
+    def per_state_flush_actions(self, uow):
+        for state in self._elements(uow):
+            if self.delete:
+                self.dependency_processor.per_deleted_state_flush_actions(uow, self.dependency_processor, state)
+            else:
+                self.dependency_processor.per_saved_state_flush_actions(uow, self.dependency_processor, state)
+
 class SaveUpdateAll(PostSortRec):
     def __init__(self, uow, mapper):
         self.mapper = mapper
@@ -285,6 +299,10 @@ class SaveUpdateAll(PostSortRec):
             uow.states_for_mapper_hierarchy(self.mapper, False, False),
             uow
         )
+    
+    def per_state_flush_actions(self, uow):
+        for state in uow.states_for_mapper_hierarchy(self.mapper, False, False):
+            SaveUpdateState(uow, state)
         
 class DeleteAll(PostSortRec):
     def __init__(self, uow, mapper):
@@ -296,6 +314,10 @@ class DeleteAll(PostSortRec):
             uow
         )
 
+    def per_state_flush_actions(self, uow):
+        for state in uow.states_for_mapper_hierarchy(self.mapper, True, False):
+            DeleteState(uow, state)
+
 class ProcessState(PostSortRec):
     def __init__(self, uow, dependency_processor, delete, state):
         self.dependency_processor = dependency_processor
index 268e83121c9b020fe0aee60b1b3d2a10bcd69575..15b481898ebb7b0106744abddba6cec1487373cf 100644 (file)
@@ -15,7 +15,7 @@ class UOWTest(_fixtures.FixtureTest, testing.AssertsExecutionResults):
 
 class RudimentaryFlushTest(UOWTest):
 
-    def test_one_to_many(self):
+    def test_one_to_many_save(self):
         mapper(User, users, properties={
             'addresses':relationship(Address),
         })
@@ -44,6 +44,17 @@ class RudimentaryFlushTest(UOWTest):
                     lambda ctx: {'email_address': 'a2', 'user_id':u1.id} 
                 ),
             )
+
+    def test_one_to_many_delete_all(self):
+        mapper(User, users, properties={
+            'addresses':relationship(Address),
+        })
+        mapper(Address, addresses)
+        sess = create_session()
+        a1, a2 = Address(email_address='a1'), Address(email_address='a2')
+        u1 = User(name='u1', addresses=[a1, a2])
+        sess.add(u1)
+        sess.flush()
         
         sess.delete(u1)
         sess.delete(a1)
@@ -60,8 +71,37 @@ class RudimentaryFlushTest(UOWTest):
                     {'id':u1.id}
                 ),
         )
+
+    def test_one_to_many_delete_parent(self):
+        mapper(User, users, properties={
+            'addresses':relationship(Address),
+        })
+        mapper(Address, addresses)
+        sess = create_session()
+        a1, a2 = Address(email_address='a1'), Address(email_address='a2')
+        u1 = User(name='u1', addresses=[a1, a2])
+        sess.add(u1)
+        sess.flush()
+
+        sess.delete(u1)
+        self.assert_sql_execution(
+                testing.db,
+                sess.flush,
+                CompiledSQL(
+                    "UPDATE addresses SET user_id=:user_id WHERE addresses.id = :addresses_id",
+                    [{u'addresses_id': 1, 'user_id': None}]
+                ),
+                CompiledSQL(
+                    "UPDATE addresses SET user_id=:user_id WHERE addresses.id = :addresses_id",
+                    [{u'addresses_id': 2, 'user_id': None}]
+                ),
+                CompiledSQL(
+                    "DELETE FROM users WHERE users.id = :id",
+                    {'id':u1.id}
+                ),
+        )
         
-    def test_many_to_one(self):
+    def test_many_to_one_save(self):
         
         mapper(User, users)
         mapper(Address, addresses, properties={
@@ -93,6 +133,19 @@ class RudimentaryFlushTest(UOWTest):
                 ),
             )
 
+    def test_many_to_one_delete_all(self):
+        mapper(User, users)
+        mapper(Address, addresses, properties={
+            'user':relationship(User)
+        })
+        sess = create_session()
+
+        u1 = User(name='u1')
+        a1, a2 = Address(email_address='a1', user=u1), \
+                    Address(email_address='a2', user=u1)
+        sess.add_all([a1, a2])
+        sess.flush()
+        
         sess.delete(u1)
         sess.delete(a1)
         sess.delete(a2)
@@ -108,4 +161,84 @@ class RudimentaryFlushTest(UOWTest):
                     {'id':u1.id}
                 ),
         )
-        
\ No newline at end of file
+
+    def test_many_to_one_delete_target(self):
+        mapper(User, users)
+        mapper(Address, addresses, properties={
+            'user':relationship(User)
+        })
+        sess = create_session()
+
+        u1 = User(name='u1')
+        a1, a2 = Address(email_address='a1', user=u1), \
+                    Address(email_address='a2', user=u1)
+        sess.add_all([a1, a2])
+        sess.flush()
+
+        sess.delete(u1)
+        a1.user = a2.user = None
+        self.assert_sql_execution(
+                testing.db,
+                sess.flush,
+                CompiledSQL(
+                    "UPDATE addresses SET user_id=:user_id WHERE addresses.id = :addresses_id",
+                    [{u'addresses_id': 1, 'user_id': None}]
+                ),
+                CompiledSQL(
+                    "UPDATE addresses SET user_id=:user_id WHERE addresses.id = :addresses_id",
+                    [{u'addresses_id': 2, 'user_id': None}]
+                ),
+                CompiledSQL(
+                    "DELETE FROM users WHERE users.id = :id",
+                    {'id':u1.id}
+                ),
+        )
+
+class SingleCycleTest(UOWTest):
+    def test_one_to_many(self):
+        mapper(Node, nodes, properties={
+            'children':relationship(Node)
+        })
+        sess = create_session()
+
+        n2, n3 = Node(data='n2'), Node(data='n3')
+        n1 = Node(data='n1', children=[n2, n3])
+        
+        sess.add(n1)
+    
+        self.assert_sql_execution(
+                testing.db,
+                sess.flush,
+                CompiledSQL(
+                    "INSERT INTO nodes (data) VALUES (:data)",
+                    {'data': 'n1'} 
+                ),
+                CompiledSQL(
+                    "INSERT INTO addresses (user_id, email_address) "
+                    "VALUES (:user_id, :email_address)",
+                    lambda ctx: {'email_address': 'a1', 'user_id':u1.id} 
+                ),
+                CompiledSQL(
+                    "INSERT INTO addresses (user_id, email_address) "
+                    "VALUES (:user_id, :email_address)",
+                    lambda ctx: {'email_address': 'a2', 'user_id':u1.id} 
+                ),
+            )
+        
+        return
+        sess.delete(n1)
+        sess.delete(n2)
+        sess.delete(n3)
+        self.assert_sql_execution(
+                testing.db,
+                sess.flush,
+                CompiledSQL(
+                    "DELETE FROM addresses WHERE addresses.id = :id",
+                    [{'id':a1.id},{'id':a2.id}]
+                ),
+                CompiledSQL(
+                    "DELETE FROM users WHERE users.id = :id",
+                    {'id':u1.id}
+                ),
+        )
+