On Aug 17, 2006, at 5:58 PM, Daniel Miller wrote:
Suggested behavior:instead of immediately registering objects that are added to a collection as "new", the registration should be deferred until the session is flushed. On session.flush() any object "u" with a non-persistent child "a" and a cascading save rule will cause "a" to be persisted at that time.
Keep in mind this approach, for which there is a patch attached for you to try out, would behave inconsistently as compared to a setup that is using a contextual session, where objects get saved to the session unconditionally upon construction (i.e. cascade is more or less superfluous). this errant behavior would still remain in that case. the idea that I had about the unit of work, long ago, was that everything could be inside of it, and it would figure everything out; insuring that certain objects dont go into the session at all as a method of insuring the correct flush operations wasnt part of the plan, since that puts extra responsibilities on the programmer to make sure only the "right" objects go into the Session, and as you know there wasnt really any way to even do it back then since everything was threadlocal.
Hibernate's model is very different from this, and its a lot less "smart" about these kinds of things (which is not completely a bad thing). such as today I noticed if you save() the same object a second time in the session, it goofily inserts it again into the table, and then upon cascading to associated collections trips over itself with inappropriate error messages and such.
unfortunately we've gone pretty far down the road of "auto-save" and this particular issue seems to be extremely difficult to detect. Classes that are in an "orphanable child" relationship would need to be flagged and get an extra step to make sure they have a parent regardless of there being any parent to start with. right now, they are only detected as part of the "deleted items" of a particular parent's collection (although we are able to detect if they've been moved to a new parent).
just out of curiousity, what do you think should happen if we did this: session = create_session() a = Address() session.save(a) session.flush()note that no User was ever created at all. should the Address be saved ? I think it should not, since the Address class is in a relationship that says "delete-orphan"; orphaned Address objects should not be saved. this would basically be the logic im thinking of. deferring the save() cascade doesnt address this issue.
So with regards to the "deferred save" approach, this is a fundamental behavior change and its not clear at all to me if existing applications are going to play well with it; as their new objects are now not in the session until flush. it also could create confusion as turning on "threadlocal" makes a bigger difference now. and im not sure if its even the right way to go since it doenst fully enforce the "orphaned" relationship semantics. (on the other hand, it might be good enough for now).
Anyway, to my absolute astonishment, the patch for this, which seemed like it would be a total rewrite, took five minutes, and then completely blowing me out of the water is that all the unit tests pass. this only makes me trust the unit tests for session behavior (which are lacking) that much less.
So I would like you to try out the attached patch, and even better would be to add more tests to your session unit test script since i have never had the time since 0.2.0 to really start nailing down a lot of the behavior that we specified with proper tests (i.e., i think glitches like this are everywhere).
Index: lib/sqlalchemy/orm/session.py =================================================================== --- lib/sqlalchemy/orm/session.py (revision 1775) +++ lib/sqlalchemy/orm/session.py (working copy) @@ -295,7 +295,7 @@ instance. """ self._save_impl(object, entity_name=entity_name)- object_mapper(object).cascade_callable('save-update', object, lambda c, e:self._save_or_update_impl(c, e)) +# object_mapper(object).cascade_callable('save-update', object, lambda c, e:self._save_or_update_impl(c, e))
def update(self, object, entity_name=None):"""Brings the given detached (saved) instance into this Session.
@@ -304,11 +304,11 @@This operation cascades the "save_or_update" method to associated instances if the relation is mapped
with cascade="save-update".""" self._update_impl(object, entity_name=entity_name)- object_mapper(object).cascade_callable('save-update', object, lambda c, e:self._save_or_update_impl(c, e)) +# object_mapper(object).cascade_callable('save-update', object, lambda c, e:self._save_or_update_impl(c, e))
def save_or_update(self, object, entity_name=None): self._save_or_update_impl(object, entity_name=entity_name)- object_mapper(object).cascade_callable('save-update', object, lambda c, e:self._save_or_update_impl(c, e)) +# object_mapper(object).cascade_callable('save-update', object, lambda c, e:self._save_or_update_impl(c, e))
def _save_or_update_impl(self, object, entity_name=None): key = getattr(object, '_instance_key', None) Index: lib/sqlalchemy/orm/unitofwork.py =================================================================== --- lib/sqlalchemy/orm/unitofwork.py (revision 1805) +++ lib/sqlalchemy/orm/unitofwork.py (working copy) @@ -38,11 +38,11 @@ sess = object_session(obj) if sess is not None: sess._register_changed(obj)- if self.cascade is not None 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)+ #if self.cascade is not None 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 delete(self, event, obj, item): sess = object_session(obj) @@ -53,11 +53,11 @@ sess = object_session(obj) if sess is not None: sess._register_changed(obj)- 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)+ #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 UOWProperty(attributes.InstrumentedAttribute):"""overrides InstrumentedAttribute to provide an extra AttributeExtension to all managed attributes
@@ -180,6 +180,9 @@ objset = None for obj in [n for n in self.new] + [d for d in self.dirty]:+ object_mapper(obj).cascade_callable('save-update', obj, lambda c, e:session._save_or_update_impl(c, e))
+ + for obj in [n for n in self.new] + [d for d in self.dirty]: if objset is not None and not obj in objset: continue if obj in self.deleted:
deferred_save.patch
Description: Binary data
------------------------------------------------------------------------- Using Tomcat but need to do more? Need to support web services, security? Get stuff done quickly with pre-integrated technology to make your job easier Download IBM WebSphere Application Server v.1.0.1 based on Apache Geronimo http://sel.as-us.falkag.net/sel?cmd=lnk&kid=120709&bid=263057&dat=121642
_______________________________________________ Sqlalchemy-users mailing list Sqlalchemy-users@lists.sourceforge.net https://lists.sourceforge.net/lists/listinfo/sqlalchemy-users