From: Mike Bayer Date: Sat, 18 Dec 2010 17:14:23 +0000 (-0500) Subject: - some good inlinings to the whole cascade_iterator() thing. X-Git-Tag: rel_0_7b1~156 X-Git-Url: http://git.ipfire.org/cgi-bin/gitweb.cgi?a=commitdiff_plain;h=182fc1a7c6fcb39593fed982b3e40de59712ec99;p=thirdparty%2Fsqlalchemy%2Fsqlalchemy.git - some good inlinings to the whole cascade_iterator() thing. cascade_iterator() should probably not yield the "instance" at all and only deal in states. 30-40K methods taken off the orm2010 test. --- diff --git a/lib/sqlalchemy/dialects/sqlite/base.py b/lib/sqlalchemy/dialects/sqlite/base.py index 47a43bdf11..2f29337464 100644 --- a/lib/sqlalchemy/dialects/sqlite/base.py +++ b/lib/sqlalchemy/dialects/sqlite/base.py @@ -440,7 +440,7 @@ class SQLiteDialect(default.DefaultDialect): qtable = quote(table_name) cursor = _pragma_cursor(connection.execute("%stable_info(%s)" % (pragma, qtable))) row = cursor.fetchone() - + # consume remaining rows, to work around # http://www.sqlite.org/cvstrac/tktview?tn=1884 while cursor.fetchone() is not None: diff --git a/lib/sqlalchemy/orm/dependency.py b/lib/sqlalchemy/orm/dependency.py index 39ea1db35c..35b197f636 100644 --- a/lib/sqlalchemy/orm/dependency.py +++ b/lib/sqlalchemy/orm/dependency.py @@ -152,7 +152,7 @@ class DependencyProcessor(object): # detect if there's anything changed or loaded # by a preprocessor on this state/attribute. if not, # we should be able to skip it entirely. - sum_ = attributes.get_all_pending(state, state.dict, self.key) + sum_ = state.manager[self.key].impl.get_all_pending(state, state.dict) if not sum_: continue @@ -439,10 +439,10 @@ class OneToManyDP(DependencyProcessor): elif self.hasparent(child) is False: uowcommit.register_object(child, isdelete=True, operation="delete", prop=self.prop) - for c, m in self.mapper.cascade_iterator( + for c, m, st_, dct_ in self.mapper.cascade_iterator( 'delete', child): uowcommit.register_object( - attributes.instance_state(c), + st_, isdelete=True) if pks_changed: @@ -661,10 +661,10 @@ class ManyToOneDP(DependencyProcessor): continue uowcommit.register_object(child, isdelete=True, operation="delete", prop=self.prop) - for c, m in self.mapper.cascade_iterator( + for c, m, st_, dct_ in self.mapper.cascade_iterator( 'delete', child): uowcommit.register_object( - attributes.instance_state(c), isdelete=True) + st_, isdelete=True) def presort_saves(self, uowcommit, states): for state in states: @@ -681,10 +681,10 @@ class ManyToOneDP(DependencyProcessor): uowcommit.register_object(child, isdelete=True, operation="delete", prop=self.prop) - for c, m in self.mapper.cascade_iterator( + for c, m, st_, dct_ in self.mapper.cascade_iterator( 'delete', child): uowcommit.register_object( - attributes.instance_state(c), + st_, isdelete=True) def process_deletes(self, uowcommit, states): @@ -939,11 +939,11 @@ class ManyToManyDP(DependencyProcessor): if self.hasparent(child) is False: uowcommit.register_object(child, isdelete=True, operation="delete", prop=self.prop) - for c, m in self.mapper.cascade_iterator( + for c, m, st_, dct_ in self.mapper.cascade_iterator( 'delete', child): uowcommit.register_object( - attributes.instance_state(c), isdelete=True) + st_, isdelete=True) def process_deletes(self, uowcommit, states): secondary_delete = [] diff --git a/lib/sqlalchemy/orm/mapper.py b/lib/sqlalchemy/orm/mapper.py index 48c37f80d7..8bd8bf3c80 100644 --- a/lib/sqlalchemy/orm/mapper.py +++ b/lib/sqlalchemy/orm/mapper.py @@ -1382,10 +1382,10 @@ class Mapper(object): reference so that they don't fall out of scope immediately. """ - visited_instances = util.IdentitySet() + visited_states = set() prp, mpp = object(), object() - visitables = [(deque(self._props.values()), prp, state, state.dict)] + visitables = deque([(deque(self._props.values()), prp, state, state.dict)]) while visitables: iterator, item_type, parent_state, parent_dict = visitables[-1] @@ -1398,13 +1398,13 @@ class Mapper(object): if type_ not in prop.cascade: continue queue = deque(prop.cascade_iterator(type_, parent_state, - parent_dict, visited_instances, halt_on)) + parent_dict, visited_states, halt_on)) if queue: visitables.append((queue,mpp, None, None)) elif item_type is mpp: instance, instance_mapper, corresponding_state, \ corresponding_dict = iterator.popleft() - yield (instance, instance_mapper) + yield instance, instance_mapper, corresponding_state, corresponding_dict visitables.append((deque(instance_mapper._props.values()), prp, corresponding_state, corresponding_dict)) diff --git a/lib/sqlalchemy/orm/properties.py b/lib/sqlalchemy/orm/properties.py index 239159f3e4..1b144ea74e 100644 --- a/lib/sqlalchemy/orm/properties.py +++ b/lib/sqlalchemy/orm/properties.py @@ -820,7 +820,7 @@ class RelationshipProperty(StrategizedProperty): dest_state.get_impl(self.key).set(dest_state, dest_dict, obj, None) - def cascade_iterator(self, type_, state, dict_, visited_instances, halt_on=None): + def cascade_iterator(self, type_, state, dict_, visited_states, halt_on=None): if not type_ in self.cascade: return @@ -831,7 +831,7 @@ class RelationshipProperty(StrategizedProperty): passive = attributes.PASSIVE_OFF if type_ == 'save-update': - instances = attributes.get_all_pending(state, dict_, self.key) + instances = state.manager[self.key].impl.get_all_pending(state, dict_) else: instances = state.value_as_iterable(dict_, self.key, @@ -842,10 +842,12 @@ class RelationshipProperty(StrategizedProperty): if instances: for c in instances: if c is not None and \ - c is not attributes.PASSIVE_NO_RESULT and \ - c not in visited_instances: - + c is not attributes.PASSIVE_NO_RESULT: + instance_state = attributes.instance_state(c) + if instance_state in visited_states: + continue + instance_dict = attributes.instance_dict(c) if halt_on and halt_on(instance_state): @@ -865,7 +867,7 @@ class RelationshipProperty(StrategizedProperty): c.__class__ )) - visited_instances.add(c) + visited_states.add(instance_state) # cascade using the mapper local to this # object, so that its individual properties are located diff --git a/lib/sqlalchemy/orm/session.py b/lib/sqlalchemy/orm/session.py index e2c1308b89..ba1a38bb9f 100644 --- a/lib/sqlalchemy/orm/session.py +++ b/lib/sqlalchemy/orm/session.py @@ -1105,13 +1105,10 @@ class Session(object): def _save_or_update_state(self, state): self._save_or_update_impl(state) - self._cascade_save_or_update(state) - def _cascade_save_or_update(self, state): - for state, mapper in _cascade_unknown_state_iterator( - 'save-update', state, - halt_on=self._contains_state): - self._save_or_update_impl(state) + mapper = _state_mapper(state) + for o, m, st_, dct_ in mapper.cascade_iterator('save-update', state, halt_on=self._contains_state): + self._save_or_update_impl(st_) def delete(self, instance): """Mark an instance as deleted. @@ -1603,13 +1600,8 @@ def _cascade_state_iterator(cascade, state, **kwargs): # yield the state, object, mapper. yielding the object # allows the iterator's results to be held in a list without # states being garbage collected - for (o, m) in mapper.cascade_iterator(cascade, state, **kwargs): - yield attributes.instance_state(o), o, m - -def _cascade_unknown_state_iterator(cascade, state, **kwargs): - mapper = _state_mapper(state) - for (o, m) in mapper.cascade_iterator(cascade, state, **kwargs): - yield _state_for_unknown_persistence_instance(o), m + for o, m, st_, dct_ in mapper.cascade_iterator(cascade, state, **kwargs): + yield st_, o, m def _state_for_unsaved_instance(instance, create=False): try: diff --git a/lib/sqlalchemy/orm/unitofwork.py b/lib/sqlalchemy/orm/unitofwork.py index 1e1eda4a3c..17b73e5f98 100644 --- a/lib/sqlalchemy/orm/unitofwork.py +++ b/lib/sqlalchemy/orm/unitofwork.py @@ -28,7 +28,9 @@ class UOWEventHandler(interfaces.AttributeExtension): def __init__(self, key): self.key = key - + + # TODO: migrate these to unwrapped events + def append(self, state, item, initiator): # process "save_update" cascade rules for when # an instance is appended to the list of another instance @@ -36,10 +38,11 @@ class UOWEventHandler(interfaces.AttributeExtension): sess = session._state_session(state) if sess: prop = _state_mapper(state)._props[self.key] + item_state = attributes.instance_state(item) if prop.cascade.save_update and \ (prop.cascade_backrefs or self.key == initiator.key) and \ - item not in sess: - sess.add(item) + not sess._contains_state(item_state): + sess._save_or_update_state(item_state) return item def remove(self, state, item, initiator): @@ -47,9 +50,10 @@ class UOWEventHandler(interfaces.AttributeExtension): if sess: prop = _state_mapper(state)._props[self.key] # expunge pending orphans + item_state = attributes.instance_state(item) if prop.cascade.delete_orphan and \ - item in sess.new and \ - prop.mapper._is_orphan(attributes.instance_state(item)): + item_state in sess._new and \ + prop.mapper._is_orphan(item_state): sess.expunge(item) def set(self, state, newvalue, oldvalue, initiator): @@ -61,15 +65,19 @@ class UOWEventHandler(interfaces.AttributeExtension): sess = session._state_session(state) if sess: prop = _state_mapper(state)._props[self.key] - if newvalue is not None and \ - prop.cascade.save_update and \ - (prop.cascade_backrefs or self.key == initiator.key) and \ - newvalue not in sess: - sess.add(newvalue) - if prop.cascade.delete_orphan and \ - oldvalue in sess.new and \ - prop.mapper._is_orphan(attributes.instance_state(oldvalue)): - sess.expunge(oldvalue) + if newvalue is not None: + newvalue_state = attributes.instance_state(newvalue) + if prop.cascade.save_update and \ + (prop.cascade_backrefs or self.key == initiator.key) and \ + not sess._contains_state(newvalue_state): + sess._save_or_update_state(newvalue_state) + + if oldvalue is not None and prop.cascade.delete_orphan: + oldvalue_state = attributes.instance_state(oldvalue) + + if oldvalue_state in sess._new and \ + prop.mapper._is_orphan(oldvalue_state): + sess.expunge(oldvalue) return newvalue diff --git a/test/perf/orm2010.py b/test/perf/orm2010.py index 8cdf774aec..46703b6da6 100644 --- a/test/perf/orm2010.py +++ b/test/perf/orm2010.py @@ -151,6 +151,7 @@ counts_by_methname = dict((key[2], stats.stats[key][0]) for key in stats.stats) print "SQLA Version: %s" % __version__ print "Total calls %d" % stats.total_calls +print "Total cpu seconds: %.2f" % stats.total_tt print 'Total execute calls: %d' \ % counts_by_methname[""]