From: Mike Bayer Date: Thu, 15 Jun 2006 15:27:39 +0000 (+0000) Subject: if an item attached to a parent is found to be already in the session, then the ... X-Git-Tag: rel_0_2_3~10 X-Git-Url: http://git.ipfire.org/cgi-bin/gitweb.cgi?a=commitdiff_plain;h=f9468e8759d3c8401f71cd6f4b4e9ffeadf4c817;p=thirdparty%2Fsqlalchemy%2Fsqlalchemy.git 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. --- diff --git a/CHANGES b/CHANGES index 6e50343ab6..5a5f24e70a 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 bf97f3b796..5c222edee2 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 0000000000..955e2b2831 --- /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