[Sqlalchemy-commits] [1366] sqlalchemy/branches/schema/doc/build/content: uow docs.
Brought to you by:
zzzeek
From: <co...@sq...> - 2006-05-01 00:43:37
|
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.1//EN" "http://www.w3.org/TR/xhtml11/DTD/xhtml11.dtd"> <html xmlns="http://www.w3.org/1999/xhtml"> <head><style type="text/css"><!-- #msg dl { border: 1px #006 solid; background: #369; padding: 6px; color: #fff; } #msg dt { float: left; width: 6em; font-weight: bold; } #msg dt:after { content:':';} #msg dl, #msg dt, #msg ul, #msg li { font-family: verdana,arial,helvetica,sans-serif; font-size: 10pt; } #msg dl a { font-weight: bold} #msg dl a:link { color:#fc3; } #msg dl a:active { color:#ff0; } #msg dl a:visited { color:#cc6; } h3 { font-family: verdana,arial,helvetica,sans-serif; font-size: 10pt; font-weight: bold; } #msg pre { overflow: auto; background: #ffc; border: 1px #fc0 solid; padding: 6px; } #msg ul, pre { overflow: auto; } #patch { width: 100%; } #patch h4 {font-family: verdana,arial,helvetica,sans-serif;font-size:10pt;padding:8px;background:#369;color:#fff;margin:0;} #patch .propset h4, #patch .binary h4 {margin:0;} #patch pre {padding:0;line-height:1.2em;margin:0;} #patch .diff {width:100%;background:#eee;padding: 0 0 10px 0;overflow:auto;} #patch .propset .diff, #patch .binary .diff {padding:10px 0;} #patch span {display:block;padding:0 10px;} #patch .modfile, #patch .addfile, #patch .delfile, #patch .propset, #patch .binary, #patch .copfile {border:1px solid #ccc;margin:10px 0;} #patch ins {background:#dfd;text-decoration:none;display:block;padding:0 10px;} #patch del {background:#fdd;text-decoration:none;display:block;padding:0 10px;} #patch .lines, .info {color:#888;background:#fff;} --></style> <title>[1366] sqlalchemy/branches/schema/doc/build/content: uow docs.</title> </head> <body> <div id="msg"> <dl> <dt>Revision</dt> <dd>1366</dd> <dt>Author</dt> <dd>zzzeek</dd> <dt>Date</dt> <dd>2006-04-30 19:43:22 -0500 (Sun, 30 Apr 2006)</dd> </dl> <h3>Log Message</h3> <pre>uow docs. going to go with cascade='all' as default for now....</pre> <h3>Modified Paths</h3> <ul> <li><a href="#sqlalchemybranchesschemadocbuildcontentdbenginetxt">sqlalchemy/branches/schema/doc/build/content/dbengine.txt</a></li> <li><a href="#sqlalchemybranchesschemadocbuildcontentunitofworktxt">sqlalchemy/branches/schema/doc/build/content/unitofwork.txt</a></li> <li><a href="#sqlalchemybranchesschemalibsqlalchemyormpropertiespy">sqlalchemy/branches/schema/lib/sqlalchemy/orm/properties.py</a></li> </ul> </div> <div id="patch"> <h3>Diff</h3> <a id="sqlalchemybranchesschemadocbuildcontentdbenginetxt"></a> <div class="modfile"><h4>Modified: sqlalchemy/branches/schema/doc/build/content/dbengine.txt (1365 => 1366)</h4> <pre class="diff"><span> <span class="info">--- sqlalchemy/branches/schema/doc/build/content/dbengine.txt 2006-04-30 16:01:08 UTC (rev 1365) +++ sqlalchemy/branches/schema/doc/build/content/dbengine.txt 2006-05-01 00:43:22 UTC (rev 1366) </span><span class="lines">@@ -5,7 +5,7 @@ </span><span class="cx"> </span><span class="cx"> * **URL** - represents the identifier for a particular database. URL objects are usually created automatically based on a given connect string passed to the `create_engine()` function. </span><span class="cx"> * **Engine** - Combines a connection-providing resource with implementation-provided objects that know how to generate, execute, and gather information about SQL statements. It also provides the primary interface by which Connections are obtained, as well as a context for constructed SQL objects and schema constructs to "implicitly execute" themselves, which is an optional feature of SA 0.2. The Engine object that is normally dealt with is an instance of `sqlalchemy.engine.base.ComposedSQLEngine`. </span><del>-* **Connection** - represents a connection to the database. The underlying connection object returned by a DBAPI's connect() method is referenced internally by the Connection object. Connection provides methods that handle the execution of SQLAlchemy's own SQL construct objects, as well as literal string-based statements. </del><ins>+* **Connection** - represents a connection to the database. The underlying connection object returned by a DBAPI's connect() method is referenced internally by the Connection object. Connection provides methods that handle the execution of SQLAlchemy's own SQL constructs, as well as literal string-based statements. </ins><span class="cx"> * **Transaction** - represents a transaction on a single Connection. Includes `begin()`, `commit()` and `rollback()` methods that support basic "nestable" behavior, meaning an outermost transaction is maintained against multiple nested calls to begin/commit. </span><span class="cx"> * **ResultProxy** - Represents the results of an execution, and is most analgous to the cursor object in DBAPI. It primarily allows iteration over result sets, but also provides an interface to information about inserts/updates/deletes, such as the count of rows affected, last inserted IDs, etc. </span><span class="cx"> * **RowProxy** - Represents a single row returned by the fetchone() method on ResultProxy. </span><span class="lines">@@ -13,8 +13,8 @@ </span><span class="cx"> Underneath the public-facing API of `ComposedSQLEngine`, several components are provided by database implementations to provide the full behavior, including: </span><span class="cx"> </span><span class="cx"> * **Dialect** - this object is provided by database implementations to describe the behavior of a particular database. It acts as a repository for metadata about a database's characteristics, and provides factory methods for other objects that deal with generating SQL strings and objects that handle some of the details of statement execution. </span><del>-* **ConnectionProvider** - this object knows how to return a DBAPI connection object. It typically talks to a connection pool. -* **ExecutionContext** - this object is created for each execution of a single SQL statement, and stores information such as the last primary keys inserted, the total count of rows affected, etc. It also may implement any special logic that various DBAPI modules may require before or after a statement execution. </del><ins>+* **ConnectionProvider** - this object knows how to return a DBAPI connection object. It typically talks to a connection pool which maintains one or more connections in memory for quick re-use. +* **ExecutionContext** - this object is created for each execution of a single SQL statement, and tracks information about its execution such as primary keys inserted, the total count of rows affected, etc. It also may implement any special logic that various DBAPI implementations may require before or after a statement execution. </ins><span class="cx"> * **Compiler** - receives SQL expression objects and assembles them into strings that are suitable for direct execution, as well as collecting bind parameters into a dictionary or list to be sent along with the statement. </span><span class="cx"> * **SchemaGenerator** - receives collections of Schema objects and knows how to generate the appropriate SQL for `CREATE` and `DROP` statements. </span><span class="cx"> </span><span class="lines">@@ -153,11 +153,10 @@ </span><span class="cx"> # are returned to the pool. </span><span class="cx"> r2 = None </span><span class="cx"> </span><del>-While the `close()` method is still available with the "threadlocal" strategy, it should be used carefully. Above, if we issued a `close()` call on `r1`, and then tried to further work with results from `r2`, `r2` would be in an invalid state since its connection was already returned to the pool. By relying on __del__() to automatically clean up resources, this condition will never occur. </del><ins>+While the `close()` method is still available with the "threadlocal" strategy, it should be used carefully. Above, if we issued a `close()` call on `r1`, and then tried to further work with results from `r2`, `r2` would be in an invalid state since its connection was already returned to the pool. By relying on `__del__()` to automatically clean up resources, this condition will never occur. </ins><span class="cx"> </span><del>-At this point, you're probably saying, "wow, why would anyone *ever* want to use the [insert name here] strategy ??" Advantages to `plain` include that connection resources are immediately returned to the connection pool, without any reliance upon the __del__() method; there is no chance of resources being left around by a Python implementation that doesn't necessarily call __del__() immediately. Advantages to `threadlocal` include that resources can be left to clean up after themselves, application code can be more minimal, its guaranteed that only one connection is used per thread, and there is no chance of a "connection pool block", which is when an execution hangs because the current thread has already checked out all remaining resources. </del><ins>+At this point, you're probably saying, "wow, why would anyone *ever* want to use the [insert name here] strategy ??" Advantages to `plain` include that connection resources are immediately returned to the connection pool, without any reliance upon the `__del__()` method; there is no chance of resources being left around by a Python implementation that doesn't necessarily call `__del__()` immediately. Advantages to `threadlocal` include that resources can be left to clean up after themselves, application code can be more minimal, its guaranteed that only one connection is used per thread, and there is no chance of a "connection pool block", which is when an execution hangs because the current thread has already checked out all remaining resources. </ins><span class="cx"> </span><del>- </del><span class="cx"> ### Transactions {@name=transactions} </span><span class="cx"> </span><span class="cx"> The `Connection` object provides a `begin()` method which returns a `Transaction` object. This object is usually used within a try/except clause so that it is guaranteed to `rollback()` or `commit()`: </span><span class="lines">@@ -172,78 +171,36 @@ </span><span class="cx"> trans.rollback() </span><span class="cx"> raise </span><span class="cx"> </span><del>- -A SQLEngine also provides an interface to the transactional capabilities of the underlying DBAPI connection object, as well as the connection object itself. Note that when using the object-relational-mapping package, described in a later section, basic transactional operation is handled for you automatically by its "Unit of Work" system; the methods described here will usually apply just to literal SQL update/delete/insert operations or those performed via the SQL construction library. - -Typically, a connection is opened with `autocommit=False`. So to perform SQL operations and just commit as you go, you can simply pull out a connection from the connection pool, keep it in the local scope, and call commit() on it as needed. As long as the connection remains referenced, all other SQL operations within the same thread will use this same connection, including those used by the SQL construction system as well as the object-relational mapper, both described in later sections: </del><ins>+The `Transaction` object also handles "nested" behavior by keeping track of the outermost begin/commit pair. In this example, two functions both issue a transaction on a Connection, but only the outermost Transaction object actually takes effect when it is committed. </ins><span class="cx"> </span><del>- {python}conn = engine.connection() - - # execute SQL via the engine - engine.execute("insert into mytable values ('foo', 'bar')") - conn.commit() - - # execute SQL via the SQL construction library - mytable.insert().execute(col1='bat', col2='lala') - conn.commit() - -There is a more automated way to do transactions, and that is to use the engine's begin()/commit() functionality. When the begin() method is called off the engine, a connection is checked out from the pool and stored in a thread-local context. That way, all subsequent SQL operations within the same thread will use that same connection. Subsequent commit() or rollback() operations are performed against that same connection. In effect, its a more automated way to perform the "commit as you go" example above. - - {python}engine.begin() - engine.execute("insert into mytable values ('foo', 'bar')") - mytable.insert().execute(col1='foo', col2='bar') - engine.commit() - - -A traditional "rollback on exception" pattern looks like this: - - {python}engine.begin() - try: - engine.execute("insert into mytable values ('foo', 'bar')") - mytable.insert().execute(col1='foo', col2='bar') - except: - engine.rollback() - raise - engine.commit() - - -An shortcut which is equivalent to the above is provided by the `transaction` method: - - {python}def do_stuff(): - engine.execute("insert into mytable values ('foo', 'bar')") - mytable.insert().execute(col1='foo', col2='bar') - - engine.transaction(do_stuff) - -An added bonus to the engine's transaction methods is "reentrant" functionality; once you call begin(), subsequent calls to begin() will increment a counter that must be decremented corresponding to each commit() statement before an actual commit can happen. This way, any number of methods that want to insure a transaction can call begin/commit, and be nested arbitrarily: - - {python}# method_a starts a transaction and calls method_b - def method_a(): - engine.begin() </del><ins>+ {python} + # method_a starts a transaction and calls method_b + def method_a(connection): + trans = connection.begin() </ins><span class="cx"> try: </span><del>- method_b() </del><ins>+ method_b(connection) + trans.commit() # transaction is committed here </ins><span class="cx"> except: </span><del>- engine.rollback() </del><ins>+ trans.rollback() # this rolls back the transaction unconditionally </ins><span class="cx"> raise </span><del>- engine.commit() </del><span class="cx"> </span><del>- # method_b starts a transaction, or joins the one already in progress, - # and does some SQL - def method_b(): - engine.begin() </del><ins>+ # method_b also starts a transaction + def method_b(connection): + trans.begin() </ins><span class="cx"> try: </span><del>- engine.execute("insert into mytable values ('bat', 'lala')") - mytable.insert().execute(col1='bat', col2='lala') </del><ins>+ connection.execute("insert into mytable values ('bat', 'lala')") + connection.execute(mytable.insert(), dict(col1='bat', col2='lala')) + trans.commit() # transaction is not committed yet </ins><span class="cx"> except: </span><del>- engine.rollback() </del><ins>+ trans.rollback() # this rolls back the transaction unconditionally </ins><span class="cx"> raise </span><del>- engine.commit() </del><span class="cx"> </span><del>- # call method_a - method_a() </del><ins>+ # open a Connection and call method_a + conn = engine.connect() + method_a(conn) + conn.close() </ins><span class="cx"> </span><del>-Above, `method_a` is called first, which calls `engine.begin()`. Then it calls `method_b`. When `method_b` calls `engine.begin()`, it just increments a counter that is decremented when it calls `commit()`. If either `method_a` or `method_b` calls `rollback()`, the whole transaction is rolled back. The transaction is not committed until `method_a` calls the `commit()` method. </del><ins>+Above, `method_a` is called first, which calls `connection.begin()`. Then it calls `method_b`. When `method_b` calls `connection.begin()`, it just increments a counter that is decremented when it calls `commit()`. If either `method_a` or `method_b` calls `rollback()`, the whole transaction is rolled back. The transaction is not committed until `method_a` calls the `commit()` method. </ins><span class="cx"> </span><del>-The object-relational-mapper capability of SQLAlchemy includes its own `commit()` method that gathers SQL statements into a batch and runs them within one transaction. That transaction is also invokved within the scope of the "reentrant" methodology above; so multiple objectstore.commit() operations can also be bundled into a larger database transaction via the above methodology. - </del><ins>+Note that SQLAlchemy's Object Relational Mapper also provides a way to control transaction scope at a higher level; this is described in [unitofwork_transactions](rel:unitofwork_transactions). </ins><span class="cx"> </span></span></pre></div> <a id="sqlalchemybranchesschemadocbuildcontentunitofworktxt"></a> <div class="modfile"><h4>Modified: sqlalchemy/branches/schema/doc/build/content/unitofwork.txt (1365 => 1366)</h4> <pre class="diff"><span> <span class="info">--- sqlalchemy/branches/schema/doc/build/content/unitofwork.txt 2006-04-30 16:01:08 UTC (rev 1365) +++ sqlalchemy/branches/schema/doc/build/content/unitofwork.txt 2006-05-01 00:43:22 UTC (rev 1366) </span><span class="lines">@@ -1,4 +1,4 @@ </span><del>-Unit of Work {@name=unitofwork} </del><ins>+Session / Unit of Work {@name=unitofwork} </ins><span class="cx"> ============ </span><span class="cx"> </span><span class="cx"> ### Overview {@name=overview} </span><span class="lines">@@ -11,45 +11,79 @@ </span><span class="cx"> * The ability to maintain and process a list of modified objects, and based on the relationships set up by the mappers for those objects as well as the foreign key relationships of the underlying tables, figure out the proper order of operations so that referential integrity is maintained, and also so that on-the-fly values such as newly created primary keys can be propigated to dependent objects that need them before they are saved. The central algorithm for this is the *topological sort*. </span><span class="cx"> * The ability to define custom functionality that occurs within the unit-of-work flush phase, such as "before insert", "after insert", etc. This is accomplished via MapperExtension. </span><span class="cx"> * an Identity Map, which is a dictionary storing the one and only instance of an object for a particular table/primary key combination. This allows many parts of an application to get a handle to a particular object without any chance of modifications going to two different places. </span><del>-* Thread-local operation. the Identity map as well as its enclosing Unit of Work are normally instantiated and accessed in a manner that is local to the current thread, within an object called a Session. Another concurrently executing thread will therefore have its own Session, so unless an application explicitly shares objects between threads, the operation of the object relational mapping is automatically threadsafe. Session objects can also be constructed manually to allow any user-defined scoping. </del><ins>+* The sole interface to the unit of work is provided via the `Session` object. Transactional capability, which rides on top of the transactions provided by `Engine` objects, is provided by the `SessionTransaction` object. +* Thread-locally scoped Session behavior is available as an option, which allows new objects to be automatically added to the Session corresponding to by the *default Session context*. Without a default Session context, an application must explicitly create a Session manually as well as add new objects to it. The default Session context, disabled by default, can also be plugged in with other user-defined schemes, which may also take into account the specific class being dealt with for a particular operation. +* The Session object in SQLAlchemy 0.2 borrows conceptually from that of [hibernate](http://www.hibernate.org), which is a leading ORM for Java. SQLAlchemy is in general very different from Hibernate, providing a different paradigm for producing queries, a SQL API that is useable independently of the ORM, and of course Pythonic configuration as opposed to XML; however, Hibernate has covered the bases pretty well with its Session ideology. </ins><span class="cx"> </span><del>-### The Session Interface {@name=session} </del><ins>+### Object States {@name=states} </ins><span class="cx"> </span><del>-The current unit of work is accessed via a Session object. The Session is available in a thread-local context from the objectstore module as follows: </del><ins>+When dealing with mapped instances with regards to Sessions, an instance may be *attached* or *unattached* to a particular Session. An instance also may or may not correspond to an actual row in the database. The product of these two binary conditions yields us four general states a particular instance can have within the perspective of the Session: </ins><span class="cx"> </span><ins>+* Transient - a transient instance exists within memory only and is not associated with any Session. It also has no database identity and does not have a corresponding record in the database. When a new instance of a class is constructed, and no default session context exists with which to automatically attach the new instance, it is a transient instance. The instance can then be saved to a particular session in which case it becomes a *pending* instance. If a default session context exists, new instances are added to that Session by default and therefore become *pending* instances immediately. + +* Pending - a pending instance is a Session-attached object that has not yet been assigned a database identity. When the Session is flushed (i.e. changes are persisted to the database), a pending instance becomes persistent. + +* Persistent - a persistent instance has a database identity and a corresponding record in the database, and is also associated with a particular Session. By "database identity" we mean the object is associated with a table or relational concept in the database combined with a particular primary key in that table. Objects that are loaded by SQLAlchemy in the context of a particular session are automatically considered persistent, as are formerly pending instances which have been subject to a session `flush()`. + +* Detached - a detached instance is an instance which has a database identity and corresponding row in the database, but is not attached to any Session. This occurs when an instance has been removed from a Session, either because the session itself was cleared or closed, or the instance was explicitly removed from the Session. The object can be re-attached with a session again in which case it becomes Persistent again. Detached instances are useful when an application needs to represent a long-running operation across multiple Sessions, needs to store an object in a serialized state and then restore it later (such as within an HTTP "session" object), or in some cases where code needs to load instances locally which will later be associated with some other Session. + +### Acquiring a Session {@name=getting} + +A new Session object is constructed via the `create_session()` function: + </ins><span class="cx"> {python} </span><del>- # get the current thread's session - session = objectstore.get_session() - -The Session object acts as a proxy to an underlying UnitOfWork object. Common methods include flush(), clear(), and delete(). Most of these methods are available at the module level in the objectstore module, which operate upon the Session returned by the get_session() function: </del><ins>+ session = create_session() + +The session to which an object is attached can be acquired via the `object_session()` method, which returns the appropriate `Session` if the object is pending or persistent, or `None` if the object is transient or detached: + + {python} + session = object_session(obj) + +When default session contexts are enabled, the current contextual session can be acquired by the `current_session()` function. This function takes an optional instance argument, which allows session contexts that are specific to particular class hierarchies to return the correct session. When using the "threadlocal" session context, enabled via the *mod* `sqlalchemy.mods.threadlocal`, no instance argument is required: + + {python} + # enable the thread-local default session context (only need to call this once per application) + import sqlalchemy.mods.threadlocal </ins><span class="cx"> </span><del>- {python}# this... - objectstore.get_session().flush() </del><ins>+ # return the Session that is bound to the current thread + session = current_session() </ins><span class="cx"> </span><ins>+When using the `threadlocal` mod, a familiar SA 0.1 keyword `objectstore` is imported into the `sqlalchemy` namespace. Using `objectstore`, methods can be called which will automatically be *proxied* to the Session that corresponds to `current_session()`: + + {python} + # load the 'threadlocal' mod *first* + import sqlalchemy.mods.threadlocal + + # then 'objectstore' is available within the 'sqlalchemy' namespace + from sqlalchemy import * + + # and then this... + current_session().flush() + </ins><span class="cx"> # is the same as this: </span><span class="cx"> objectstore.flush() </span><span class="cx"> </span><del>-A description of the most important methods and concepts follows. </del><ins>+We will now cover some of the key concepts used by Sessions and its underlying Unit of Work. </ins><span class="cx"> </span><del>-#### Identity Map {@name=identitymap} </del><ins>+### Introduction to the Identity Map {@name=identitymap} </ins><span class="cx"> </span><del>-The first concept to understand about the Unit of Work is that it is keeping track of all mapped objects which have been loaded from the database, as well as all mapped objects which have been saved to the database in the current session. This means that everytime you issue a `select` call to a mapper which returns results, all of those objects are now installed within the current Session, mapped to their identity. </del><ins>+A primary concept of the Session's underlying Unit of Work is that it is keeping track of all persistent instances; recall that a persistent instance has a database identity and is attached to a Session. In particular, the Unit of Work must insure that only *one* copy of a particular persistent instance exists within the Session at any given time. The UOW accomplishes this task using a dictionary known as an *Identity Map*. When a `Query` is used to issue `select` or `get` requests to the database, it will in nearly all cases result in an actual SQL execution to the database, and a corresponding traversal of rows received from that execution. However, when the underlying mapper *instantiates* objects corresponding to the result set rows it receives, it will *check the session's identity map first* before instantating a new object, and return *the same instance* already present in the identiy map if it already exists, essentially *ignoring* the object state r! epresented by that row. There are several ways to override this behavior and truly refresh an already-loaded instance which are described later, but the main idea is that once your instance is loaded into a particular Session, it will *never change* its state without your explicit approval, regardless of what the database says about it. </ins><span class="cx"> </span><del>-In particular, it is insuring that only *one* instance of a particular object, corresponding to a particular database identity, exists within the Session at one time. By "database identity" we mean a table or relational concept in the database combined with a particular primary key in that table. The session accomplishes this task using a dictionary known as an *Identity Map*. When `select` or `get` calls on mappers issue queries to the database, they will in nearly all cases go out to the database on each call to fetch results. However, when the mapper *instantiates* objects corresponding to the result set rows it receives, it will *check the current identity map first* before instantating a new object, and return *the same instance* already present in the identiy map if it already exists. - -Example: </del><ins>+For example; below, two separate calls to load an instance with database identity "15" are issued, and the results assigned to two separate variables. However, since the same `Session` was used, the two instances are the same instance: </ins><span class="cx"> </span><del>- {python}mymapper = mapper(MyClass, mytable) </del><ins>+ {python} + mymapper = mapper(MyClass, mytable) </ins><span class="cx"> </span><del>- obj1 = mymapper.selectfirst(mytable.c.id==15) - obj2 = mymapper.selectfirst(mytable.c.id==15) </del><ins>+ session = create_session() + obj1 = session.query(MyClass).selectfirst(mytable.c.id==15) + obj2 = session.query(MyClass).selectfirst(mytable.c.id==15) </ins><span class="cx"> </span><span class="cx"> >>> obj1 is obj2 </span><span class="cx"> True </span><span class="cx"> </span><del>-The Identity Map is an instance of `weakref.WeakValueDictionary`, so that when an in-memory object falls out of scope, it will be removed automatically. However, this may not be instant if there are circular references upon the object. The current SA attributes implementation places some circular refs upon objects, although this may change in the future. There are other ways to remove object instances from the current session, as well as to clear the current session entirely, which are described later in this section. </del><ins>+The Identity Map is an instance of `weakref.WeakValueDictionary`, so that when an in-memory object falls out of scope, it will be removed automatically. However, this may not be instant if there are circular references upon the object. To guarantee that an instance is removed from the identity map before removing references to it, use the `expunge()` method, described later, to remove it. </ins><span class="cx"> </span><del>-To view the Session's identity map, it is accessible via the `identity_map` accessor, and is an instance of `weakref.WeakValueDictionary`: </del><ins>+The Session's identity map is accessible via the `identity_map` accessor: </ins><span class="cx"> </span><span class="cx"> {python} </span><span class="cx"> >>> objectstore.get_session().identity_map.values() </span><span class="lines">@@ -61,72 +95,38 @@ </span><span class="cx"> >>> obj._instance_key </span><span class="cx"> (<class 'test.tables.User'>, (7,)) </span><span class="cx"> </span><del>-At the moment that an object is assigned this key, it is also added to the current thread's unit-of-work's identity map. </del><ins>+At the moment that an object is assigned this key within a `flush()` operation, it is also added to the session's identity map. </ins><span class="cx"> </span><del>-The get() method on a mapper, which retrieves an object based on primary key identity, also checks in the current identity map first to save a database round-trip if possible. In the case of an object lazy-loading a single child object, the get() method is used as well, so scalar-based lazy loads may in some cases not query the database; this is particularly important for backreference relationships as it can save a lot of queries. - -Methods on mappers and the objectstore module, which are relevant to identity include the following: </del><ins>+The get() method on `Query`, which retrieves an object based on primary key identity, also checks in the Session's identity map first to save a database round-trip if possible. In the case of an object lazy-loading a single child object, the get() method is used as well, so scalar-based lazy loads may in some cases not query the database; this is particularly important for backreference relationships as it can save a lot of queries. </ins><span class="cx"> </span><del>- {python} - # assume 'm' is a mapper - m = mapper(User, users) </del><ins>+### Whats Changed ? {@name=changed} </ins><span class="cx"> </span><del>- # get the identity key corresponding to a primary key - key = m.identity_key(7) - - # for composite key, list out the values in the order they - # appear in the table - key = m.identity_key(12, 'rev2') - - # get the identity key given a primary key - # value as a tuple and a class - key = objectstore.get_id_key((12, 'rev2'), User) - - # get the identity key for an object, whether or not it actually - # has one attached to it (m is the mapper for obj's class) - key = m.instance_key(obj) </del><ins>+The next concept is that in addition to the Session storing a record of all objects loaded or saved, it also stores lists of all *newly created* (i.e. pending) objects, lists of all persistent objects whose attributes have been *modified*, and lists of all persistent objects that have been marked as *deleted*. These lists are used when a `flush()` call is issued to save all changes. After the flush occurs, these lists are all cleared out. </ins><span class="cx"> </span><del>- # is this key in the current identity map? - session.has_key(key) </del><ins>+These records are all tracked by a collection of `Set` objects (which are a SQLAlchemy-specific instance called a `HashSet`) that are also viewable off the Session: </ins><span class="cx"> </span><del>- # is this object in the current identity map? - session.has_instance(obj) - - # get this object from the current identity map based on - # singular/composite primary key, or if not go - # and load from the database - obj = m.get(12, 'rev2') - -#### Whats Changed ? {@name=changed} - -The next concept is that in addition to the Session storing a record of all objects loaded or saved, it also stores records of all *newly created* objects, records of all objects whose attributes have been *modified*, records of all objects that have been marked as *deleted*, and records of all *modified list-based attributes* where additions or deletions have occurred. These lists are used when a `flush()` call is issued to save all changes. After the flush occurs, these lists are all cleared out. - -These records are all tracked by a collection of `Set` objects (which are a SQLAlchemy-specific instance called a `HashSet`) that are also viewable off the Session: - </del><span class="cx"> {python} </span><del>- # new objects that were just constructed </del><ins>+ # pending objects recently added to the Session </ins><span class="cx"> session.new </span><span class="cx"> </span><del>- # objects that exist in the database, that were modified </del><ins>+ # persistent objects with modifications </ins><span class="cx"> session.dirty </span><span class="cx"> </span><del>- # objects that have been marked as deleted via session.delete(obj) </del><ins>+ # persistent objects that have been marked as deleted via session.delete(obj) </ins><span class="cx"> session.deleted </span><span class="cx"> </span><del>- # list-based attributes thave been appended - session.modified_lists - </del><span class="cx"> Heres an interactive example, assuming the `User` and `Address` mapper setup first outlined in [datamapping_relations](rel:datamapping_relations): </span><span class="cx"> </span><span class="cx"> {python} </span><del>- >>> # get the current thread's session - >>> session = objectstore.get_session() </del><ins>+ >>> # create a session + >>> session = create_session() </ins><span class="cx"> </span><span class="cx"> >>> # create a new object, with a list-based attribute </span><span class="cx"> >>> # containing two more new objects </span><span class="cx"> >>> u = User(user_name='Fred') </span><span class="cx"> >>> u.addresses.append(Address(city='New York')) </span><span class="cx"> >>> u.addresses.append(Address(city='Boston')) </span><ins>+ >>> session.save(u) </ins><span class="cx"> </span><span class="cx"> >>> # objects are in the "new" list </span><span class="cx"> >>> session.new </span><span class="lines">@@ -134,11 +134,6 @@ </span><span class="cx"> <__main__.Address object at 0x713a70>, </span><span class="cx"> <__main__.Address object at 0x713b30>] </span><span class="cx"> </span><del>- >>> # view the "modified lists" member, - >>> # reveals our two Address objects as well, inside of a list - >>> session.modified_lists - [[<__main__.Address object at 0x713a70>, <__main__.Address object at 0x713b30>]] - </del><span class="cx"> >>> # lets view what the class/ID is for the list object </span><span class="cx"> >>> ["%s %s" % (l.__class__, id(l)) for l in session.modified_lists] </span><span class="cx"> ['sqlalchemy.mapping.unitofwork.UOWListElement 7391872'] </span><span class="lines">@@ -150,10 +145,6 @@ </span><span class="cx"> >>> session.new </span><span class="cx"> [] </span><span class="cx"> </span><del>- >>> # the "modified lists" list is now empty - >>> session.modified_lists - [] - </del><span class="cx"> >>> # now lets modify an object </span><span class="cx"> >>> u.user_name='Ed' </span><span class="cx"> </span></span></pre></div> <a id="sqlalchemybranchesschemalibsqlalchemyormpropertiespy"></a> <div class="modfile"><h4>Modified: sqlalchemy/branches/schema/lib/sqlalchemy/orm/properties.py (1365 => 1366)</h4> <pre class="diff"><span> <span class="info">--- sqlalchemy/branches/schema/lib/sqlalchemy/orm/properties.py 2006-04-30 16:01:08 UTC (rev 1365) +++ sqlalchemy/branches/schema/lib/sqlalchemy/orm/properties.py 2006-05-01 00:43:22 UTC (rev 1366) </span><span class="lines">@@ -144,9 +144,9 @@ </span><span class="cx"> self.cascade = mapperutil.CascadeOptions(cascade) </span><span class="cx"> else: </span><span class="cx"> if private: </span><del>- self.cascade = mapperutil.CascadeOptions("save-update, delete-orphan, delete") </del><ins>+ self.cascade = mapperutil.CascadeOptions("all, delete-orphan") </ins><span class="cx"> else: </span><del>- self.cascade = mapperutil.CascadeOptions() </del><ins>+ self.cascade = mapperutil.CascadeOptions("all") </ins><span class="cx"> </span><span class="cx"> self.association = association </span><span class="cx"> self.order_by = order_by </span></span></pre> </div> </div> </body> </html> |