diff options
author | Mike Bayer <mike_mp@zzzcomputing.com> | 2012-08-08 11:54:28 -0400 |
---|---|---|
committer | Mike Bayer <mike_mp@zzzcomputing.com> | 2012-08-08 11:54:28 -0400 |
commit | 965496dd8e0a4ef81277b8149c917ae74a5d0807 (patch) | |
tree | 4a519c7e830dadbe5acadf05d8efaed1be1b6a57 | |
parent | d93fb56d86d4a75dba41a0bbc67bc45ecadbe1d5 (diff) | |
download | sqlalchemy-965496dd8e0a4ef81277b8149c917ae74a5d0807.tar.gz |
- continue editing the merge docs
-rw-r--r-- | doc/build/orm/session.rst | 88 | ||||
-rw-r--r-- | lib/sqlalchemy/orm/session.py | 48 |
2 files changed, 79 insertions, 57 deletions
diff --git a/doc/build/orm/session.rst b/doc/build/orm/session.rst index 252a81742..47e17cbae 100644 --- a/doc/build/orm/session.rst +++ b/doc/build/orm/session.rst @@ -392,40 +392,61 @@ the ``save-update`` cascade. For more details see the section Merging ------- -:func:`~sqlalchemy.orm.session.Session.merge` reconciles the current state of -an instance and its associated children with existing data in the database, -and returns a copy of the instance associated with the session. Usage is as -follows:: +:func:`~sqlalchemy.orm.session.Session.merge` transfers state from an +outside object into a new or already existing instance within a session. It +also reconciles the incoming data against the state of the +database, producing a history stream which will be applied towards the next +flush, or alternatively can be made to produce a simple "transfer" of +state without producing change history or accessing the database. Usage is as follows:: merged_object = session.merge(existing_object) When given an instance, it follows these steps: * It examines the primary key of the instance. If it's present, it attempts - to load an instance with that primary key (or pulls from the local - identity map). -* If there's no primary key on the given instance, or the given primary key - does not exist in the database, a new instance is created. + to locate that instance in the local identity map. If the ``load=True`` + flag is left at its default, it also checks the database for this primary + key if not located locally. +* If the given instance has no primary key, or if no instance can be found + with the primary key given, a new instance is created. * The state of the given instance is then copied onto the located/newly - created instance. -* The operation is cascaded to associated child items along the ``merge`` - cascade. Note that all changes present on the given instance, including - changes to collections, are merged. + created instance. For attributes which are present on the source + instance, the value is transferred to the target instance. For mapped + attributes which aren't present on the source, the attribute is + expired on the target instance, discarding its existing value. + + If the ``load=True`` flag is left at its default, + this copy process emits events and will load the target object's + unloaded collections for each attribute present on the source object, + so that the incoming state can be reconciled against what's + present in the database. If ``load`` + is passed as ``False``, the incoming data is "stamped" directly without + producing any history. +* The operation is cascaded to related objects and collections, as + indicated by the ``merge`` cascade (see :ref:`unitofwork_cascades`). * The new instance is returned. -With :func:`~sqlalchemy.orm.session.Session.merge`, the given instance is not -placed within the session, and can be associated with a different session or -detached. :func:`~sqlalchemy.orm.session.Session.merge` is very useful for +With :meth:`~.Session.merge`, the given "source" +instance is not modifed nor is it associated with the target :class:`.Session`, +and remains available to be merged with any number of other :class:`.Session` +objects. :meth:`~.Session.merge` is useful for taking the state of any kind of object structure without regard for its -origins or current session associations and placing that state within a -session. Here's two examples: +origins or current session associations and copying its state into a +new session. Here's some examples: -* An application wants to transfer the state of a series of objects - into a :class:`.Session` maintained by a worker thread or other - concurrent system. :meth:`~.Session.merge` makes a copy of each object - to be placed into this new :class:`.Session`. At the end of the operation, - the parent thread/process maintains the objects it started with, - and the thread/worker can proceed with local copies of those objects. +* An application which reads an object structure from a file and wishes to + save it to the database might parse the file, build up the + structure, and then use + :meth:`~.Session.merge` to save it + to the database, ensuring that the data within the file is + used to formulate the primary key of each element of the + structure. Later, when the file has changed, the same + process can be re-run, producing a slightly different + object structure, which can then be ``merged`` in again, + and the :class:`~sqlalchemy.orm.session.Session` will + automatically update the database to reflect those + changes, loading each object from the database by primary key and + then updating its state with the new state given. * An application is storing objects in an in-memory cache, shared by many :class:`.Session` objects simultaneously. :meth:`~.Session.merge` @@ -442,19 +463,16 @@ session. Here's two examples: that was designed to work with cache-extended :class:`.Query` objects - see the section :ref:`examples_caching`. -* An application which reads an object structure from a file and wishes to - save it to the database might parse the file, build up the - structure, and then use - :meth:`~.Session.merge` to save it - to the database, ensuring that the data within the file is - used to formulate the primary key of each element of the - structure. Later, when the file has changed, the same - process can be re-run, producing a slightly different - object structure, which can then be ``merged`` in again, - and the :class:`~sqlalchemy.orm.session.Session` will - automatically update the database to reflect those - changes. +* An application wants to transfer the state of a series of objects + into a :class:`.Session` maintained by a worker thread or other + concurrent system. :meth:`~.Session.merge` makes a copy of each object + to be placed into this new :class:`.Session`. At the end of the operation, + the parent thread/process maintains the objects it started with, + and the thread/worker can proceed with local copies of those objects. + In the "transfer between threads/processes" use case, the application + may want to use the ``load=False`` flag as well to avoid overhead and + redundant SQL queries as the data is transferred. Merge Tips ~~~~~~~~~~ diff --git a/lib/sqlalchemy/orm/session.py b/lib/sqlalchemy/orm/session.py index e665fbdb6..2bd765258 100644 --- a/lib/sqlalchemy/orm/session.py +++ b/lib/sqlalchemy/orm/session.py @@ -1315,14 +1315,18 @@ class Session(object): self._delete_impl(st_) def merge(self, instance, load=True): - """Copy the state an instance onto the persistent instance with the - same identifier. - - If there is no persistent instance currently associated with the - session, it will be loaded. Return the persistent instance. If the - given instance is unsaved, save a copy of and return it as a newly - persistent instance. The given instance does not become associated - with the session. + """Copy the state of a given instance into a corresponding instance + within this :class:`.Session`. + + :meth:`.Session.merge` examines the primary key attributes of the + source instance, and attempts to reconcile it with an instance of the + same primary key in the session. If not found locally, it attempts + to load the object from the database based on primary key, and if + none can be located, creates a new instance. The state of each attribute + on the source instance is then copied to the target instance. + The resulting target instance is then returned by the method; the + original source instance is left unmodified, and un-associated with the + :class:`.Session` if not already. This operation cascades to associated instances if the association is mapped with ``cascade="merge"``. @@ -1331,26 +1335,26 @@ class Session(object): :param instance: Instance to be merged. :param load: Boolean, when False, :meth:`.merge` switches into - a "high performance" mode which causes it to skip all database - access. The state of the given object is transferred directly - into the :class:`.Session` without checking the database - for existing data or discrepancies. This flag is used for + a "high performance" mode which causes it to forego emitting history + events as well as all database access. This flag is used for cases such as transferring graphs of objects into a :class:`.Session` from a second level cache, or to transfer just-loaded objects into the :class:`.Session` owned by a worker thread or process without re-querying the database. The ``load=False`` use case adds the caveat that the given - object has to be in a "clean" state. This is so that when - the merge operation cascades onto related objects and - collections, the related values can be "stamped" onto the - target object as is, without concern for reconciling their - contents with any existing database value. While there's no technical - reason the state of the object can't be taken as is whether or - not it's dirty, it suggests a mis-use of the method, as state which - wasn't pulled from the database originally can't reliably be passed back to - the database without knowing the database's current state. - + object has to be in a "clean" state, that is, has no pending changes + to be flushed - even if the incoming object is detached from any + :class:`.Session`. This is so that when + the merge operation populates local attributes and + cascades to related objects and + collections, the values can be "stamped" onto the + target object as is, without generating any history or attribute + events, and without the need to reconcile the incoming data with + any existing related objects or collections that might not + be loaded. The resulting objects from ``load=False`` are always + produced as "clean", so it is only appropriate that the given objects + should be "clean" as well, else this suggests a mis-use of the method. """ |