Mike Bayer avatar Mike Bayer committed b3708ba

if an item attached to a parent is found to be already in the session, then the "save-update" cascade operation doesnt take place. currently this prevents unncessessary cascading due to backref events, which was a massive speed hit.

Comments (0)

Files changed (3)

 0.2.3
+- overhaul to mapper compilation to be deferred.  this allows mappers
+to be constructed in any order, and their relationships to each
+other are compiled when the mappers are first used..
+- fixed a pretty big speed bottleneck in cascading behavior particularly
+when backrefs were in use
 - py2.4 "set" construct used internally, falls back to sets.Set when
 "set" not available/ordering is needed.
 - "foreignkey" argument to relation() can also be a list.  fixed

lib/sqlalchemy/orm/unitofwork.py

         sess = object_session(obj)
         if sess is not None:
             sess._register_changed(obj)
-            if self.cascade is not None:
-                if not isdelete:
-                    if self.cascade.save_update:
-                        # cascade the save_update operation onto the child object,
-                        # relative to the mapper handling the parent object
-                        # TODO: easier way to do this ?
-                        mapper = object_mapper(obj)
-                        prop = mapper.props[self.key]
-                        ename = prop.mapper.entity_name
-                        sess.save_or_update(item, entity_name=ename)
+            if self.cascade is not None and not isdelete and self.cascade.save_update and item not in sess:
+                mapper = object_mapper(obj)
+                prop = mapper.props[self.key]
+                ename = prop.mapper.entity_name
+                sess.save_or_update(item, entity_name=ename)
     def append(self, item, _mapper_nohistory = False):
         if _mapper_nohistory:
             self.append_nohistory(item)
         sess = object_session(obj)
         if sess is not None:
             sess._register_changed(obj)
-            if newvalue is not None and self.cascade is not None:
-                if self.cascade.save_update:
-                    # cascade the save_update operation onto the child object,
-                    # relative to the mapper handling the parent object
-                    # TODO: easier way to do this ?
-                    mapper = object_mapper(obj)
-                    prop = mapper.props[self.key]
-                    ename = prop.mapper.entity_name
-                    sess.save_or_update(newvalue, entity_name=ename)
+            if newvalue is not None and self.cascade is not None and self.cascade.save_update and newvalue not in sess:
+                mapper = object_mapper(obj)
+                prop = mapper.props[self.key]
+                ename = prop.mapper.entity_name
+                sess.save_or_update(newvalue, entity_name=ename)
             
 class UOWAttributeManager(attributes.AttributeManager):
     """overrides AttributeManager to provide unit-of-work "dirty" hooks when scalar attribues are modified, plus factory methods for UOWProperrty/UOWListElement."""

test/perf/massload2.py

+try:
+#    import sqlalchemy.mods.threadlocal
+    pass
+except:
+    pass
+from sqlalchemy import *
+import time
+
+metadata = create_engine('sqlite://', echo=True)
+
+t1s = Table( 't1s', metadata, 
+    Column( 'id', Integer, primary_key=True),
+    Column('data', String(100))
+    ) 
+
+t2s = Table( 't2s', metadata, 
+    Column( 'id', Integer, primary_key=True),
+    Column( 't1id', Integer, ForeignKey("t1s.id"), nullable=True ))
+
+t3s = Table( 't3s', metadata, 
+    Column( 'id', Integer, primary_key=True),
+    Column( 't2id', Integer, ForeignKey("t2s.id"), nullable=True ))
+
+t4s = Table( 't4s', metadata, 
+    Column( 'id', Integer, primary_key=True), 
+    Column( 't3id', Integer, ForeignKey("t3s.id"), nullable=True ))
+    
+[t.create() for t in [t1s,t2s,t3s,t4s]]
+
+class T1( object ): pass
+class T2( object ): pass
+class T3( object ): pass
+class T4( object ): pass 
+
+mapper( T1, t1s )
+mapper( T2, t2s )       
+mapper( T3, t3s )       
+mapper( T4, t4s )       
+
+cascade = "all, delete-orphan"
+use_backref = True
+
+if use_backref:
+    class_mapper(T1).add_property( 't2s', relation(T2, backref=backref("t1", cascade=cascade), cascade=cascade))
+    class_mapper(T2).add_property ( 't3s', relation(T3, backref=backref("t2",cascade=cascade), cascade=cascade) )
+    class_mapper(T3).add_property( 't4s', relation(T4, backref=backref("t3", cascade=cascade), cascade=cascade) )
+else:
+    T1.mapper.add_property( 't2s', relation(T2, cascade=cascade))
+    T2.mapper.add_property ( 't3s', relation(T3, cascade=cascade) )
+    T3.mapper.add_property( 't4s', relation(T4, cascade=cascade) )
+
+now = time.time()
+print "start"
+sess = create_session()        
+o1 = T1()
+sess.save(o1) 
+for i2 in range(10):
+    o2 = T2()
+    o1.t2s.append( o2 )
+    
+    for i3 in range( 10 ):
+        o3 = T3()
+        o2.t3s.append( o3 )
+        
+        for i4 in range( 10 ):
+            o3.t4s.append ( T4() )
+            print i2, i3, i4
+
+print len([s for s in sess])            
+print "flushing"
+sess.flush()
+total = time.time() - now
+print "done,total time", total
Tip: Filter by directory path e.g. /media app.js to search for public/media/app.js.
Tip: Use camelCasing e.g. ProjME to search for ProjectModifiedEvent.java.
Tip: Filter by extension type e.g. /repo .js to search for all .js files in the /repo directory.
Tip: Separate your search with spaces e.g. /ssh pom.xml to search for src/ssh/pom.xml.
Tip: Use ↑ and ↓ arrow keys to navigate and return to view the file.
Tip: You can also navigate files with Ctrl+j (next) and Ctrl+k (previous) and view the file with Ctrl+o.
Tip: You can also navigate files with Alt+j (next) and Alt+k (previous) and view the file with Alt+o.