summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--CHANGES5
-rw-r--r--lib/sqlalchemy/orm/unitofwork.py29
-rw-r--r--test/perf/massload2.py73
3 files changed, 88 insertions, 19 deletions
diff --git a/CHANGES b/CHANGES
index 6e50343ab..5a5f24e70 100644
--- a/CHANGES
+++ b/CHANGES
@@ -1,4 +1,9 @@
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
diff --git a/lib/sqlalchemy/orm/unitofwork.py b/lib/sqlalchemy/orm/unitofwork.py
index bf97f3b79..5c222edee 100644
--- a/lib/sqlalchemy/orm/unitofwork.py
+++ b/lib/sqlalchemy/orm/unitofwork.py
@@ -46,16 +46,11 @@ class UOWListElement(attributes.ListAttribute):
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)
@@ -71,15 +66,11 @@ class UOWScalarElement(attributes.ScalarAttribute):
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."""
diff --git a/test/perf/massload2.py b/test/perf/massload2.py
new file mode 100644
index 000000000..955e2b283
--- /dev/null
+++ b/test/perf/massload2.py
@@ -0,0 +1,73 @@
+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 \ No newline at end of file