[Sqlalchemy-commits] [1467] sqlalchemy/branches/schema/test: doc edits , slight tweak to join_to all
Brought to you by:
zzzeek
From: <co...@sq...> - 2006-05-17 01:09:42
|
<!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>[1467] sqlalchemy/branches/schema/test: doc edits , slight tweak to join_to allowing the keyname of a relation or column property</title> </head> <body> <div id="msg"> <dl> <dt>Revision</dt> <dd>1467</dd> <dt>Author</dt> <dd>zzzeek</dd> <dt>Date</dt> <dd>2006-05-16 20:09:24 -0500 (Tue, 16 May 2006)</dd> </dl> <h3>Log Message</h3> <pre>doc edits , slight tweak to join_to allowing the keyname of a relation or column property</pre> <h3>Modified Paths</h3> <ul> <li><a href="#sqlalchemybranchesschemadocbuildcontentdatamappingtxt">sqlalchemy/branches/schema/doc/build/content/datamapping.txt</a></li> <li><a href="#sqlalchemybranchesschemadocbuildcontentpluginstxt">sqlalchemy/branches/schema/doc/build/content/plugins.txt</a></li> <li><a href="#sqlalchemybranchesschemalibsqlalchemyormquerypy">sqlalchemy/branches/schema/lib/sqlalchemy/orm/query.py</a></li> <li><a href="#sqlalchemybranchesschematestmapperpy">sqlalchemy/branches/schema/test/mapper.py</a></li> </ul> </div> <div id="patch"> <h3>Diff</h3> <a id="sqlalchemybranchesschemadocbuildcontentdatamappingtxt"></a> <div class="modfile"><h4>Modified: sqlalchemy/branches/schema/doc/build/content/datamapping.txt (1466 => 1467)</h4> <pre class="diff"><span> <span class="info">--- sqlalchemy/branches/schema/doc/build/content/datamapping.txt 2006-05-16 00:25:50 UTC (rev 1466) +++ sqlalchemy/branches/schema/doc/build/content/datamapping.txt 2006-05-17 01:09:24 UTC (rev 1467) </span><span class="lines">@@ -79,13 +79,17 @@ </span><span class="cx"> # get a query from a Session given a Mapper: </span><span class="cx"> query = session.query(usermapper) </span><span class="cx"> </span><del>- # select_by, using property names or column names as keys - # the keys are grouped together by an AND operator </del><ins>+ # select_by, which takes keyword arguments. the + # keyword arguments represent property names and the values + # represent values which will be compared via the = operator. + # the comparisons are joined together via "AND". </ins><span class="cx"> result = query.select_by(name='john', street='123 green street') </span><span class="cx"> </span><del>- # select_by can also combine SQL criterion with key/value properties - result = query.select_by(users.c.user_name=='john', - addresses.c.zip_code=='12345', street=='123 green street') </del><ins>+ # select_by can also combine ClauseElements with key/value properties. + # all ClauseElements and keyword-based criterion are combined together + # via "AND". + result = query.select_by(users_table.c.user_name=='john', + addresses_table.c.zip_code=='12345', street='123 green street') </ins><span class="cx"> </span><span class="cx"> # get_by, which takes the same arguments as select_by </span><span class="cx"> # returns a single scalar result or None if no results </span><span class="lines">@@ -106,17 +110,17 @@ </span><span class="cx"> myobj = query.get((27, 3, 'receipts')) </span><span class="cx"> </span><span class="cx"> # using a WHERE criterion </span><del>- result = query.select(or_(users.c.user_name == 'john', users.c.user_name=='fred')) </del><ins>+ result = query.select(or_(users_table.c.user_name == 'john', users_table.c.user_name=='fred')) </ins><span class="cx"> </span><span class="cx"> # using a WHERE criterion to get a scalar </span><del>- u = query.selectfirst(users.c.user_name=='john') </del><ins>+ u = query.selectfirst(users_table.c.user_name=='john') </ins><span class="cx"> </span><span class="cx"> # selectone() is a stricter version of selectfirst() which </span><span class="cx"> # will raise an exception if there is not exactly one row </span><del>- u = query.selectone(users.c.user_name=='john') </del><ins>+ u = query.selectone(users_table.c.user_name=='john') </ins><span class="cx"> </span><span class="cx"> # using a full select object </span><del>- result = query.select(users.select(users.c.user_name=='john')) </del><ins>+ result = query.select(users_table.select(users_table.c.user_name=='john')) </ins><span class="cx"> </span><span class="cx"> Some of the above examples above illustrate the usage of the mapper's Table object to provide the columns for a WHERE Clause. These columns are also accessible off of the mapped class directly. When a mapper is assigned to a class, it also attaches a special property accessor `c` to the class itself, which can be used just like the table metadata to access the columns of the table: </span><span class="cx"> </span><span class="lines">@@ -197,8 +201,12 @@ </span><span class="cx"> </span><span class="cx"> ### Defining and Using Relationships {@name=relations} </span><span class="cx"> </span><del>-So that covers how to map the columns in a table to an object, how to load objects, create new ones, and save changes. The next step is how to define an object's relationships to other database-persisted objects. This is done via the `relation` function provided by the mapper module. So with our User class, lets also define the User has having one or more mailing addresses. First, the table metadata: </del><ins>+So that covers how to map the columns in a table to an object, how to load objects, create new ones, and save changes. The next step is how to define an object's relationships to other database-persisted objects. This is done via the `relation` function provided by the `orm` module. </ins><span class="cx"> </span><ins>+#### One to Many {@name=onetomany} + +So with our User class, lets also define the User has having one or more mailing addresses. First, the table metadata: + </ins><span class="cx"> {python} </span><span class="cx"> from sqlalchemy import * </span><span class="cx"> </span><span class="lines">@@ -221,7 +229,7 @@ </span><span class="cx"> Column('zip', String(10)) </span><span class="cx"> ) </span><span class="cx"> </span><del>-Of importance here is the addresses table's definition of a *foreign key* relationship to the users table, relating the user_id column into a parent-child relationship. When a `Mapper` wants to indicate a relation of one object to another, th `ForeignKey` relationships are the default method by which the relationship is determined (options also exist to describe the relationships explicitly). </del><ins>+Of importance here is the addresses table's definition of a *foreign key* relationship to the users table, relating the user_id column into a parent-child relationship. When a `Mapper` wants to indicate a relation of one object to another, the `ForeignKey` relationships are the default method by which the relationship is determined (options also exist to describe the relationships explicitly). </ins><span class="cx"> </span><span class="cx"> So then lets define two classes, the familiar `User` class, as well as an `Address` class: </span><span class="cx"> </span><span class="lines">@@ -238,7 +246,7 @@ </span><span class="cx"> self.state = state </span><span class="cx"> self.zip = zip </span><span class="cx"> </span><del>-And then a Mapper that will define a relationship of the User and the Address classes to each other as well as their table metadata. We will add an additional mapper keyword argument `properties` which is a dictionary relating the name of an object property to a database relationship, in this case a `relation` object against a newly defined mapper for the Address class: </del><ins>+And then a `Mapper` that will define a relationship of the `User` and the `Address` classes to each other as well as their table metadata. We will add an additional mapper keyword argument `properties` which is a dictionary relating the names of class attributes to database relationships, in this case a `relation` object against a newly defined mapper for the Address class: </ins><span class="cx"> </span><span class="cx"> {python} </span><span class="cx"> mapper(Address, addresses_table) </span><span class="lines">@@ -250,7 +258,7 @@ </span><span class="cx"> Lets do some operations with these classes and see what happens: </span><span class="cx"> </span><span class="cx"> {python} </span><del>- engine = create_engine('sqlite://filename=mydb') </del><ins>+ engine = create_engine('sqlite:///mydb.db') </ins><span class="cx"> metadata.create_all(engine) </span><span class="cx"> </span><span class="cx"> session = create_session(bind_to=engine) </span><span class="lines">@@ -268,9 +276,9 @@ </span><span class="cx"> INSERT INTO addresses (user_id, street, city, state, zip) VALUES (:user_id, :street, :city, :state, :zip) </span><span class="cx"> {'city': 'some other city', 'state': 'OK', 'street': '1 Park Place', 'user_id':1, 'zip': '83923'} </span><span class="cx"> </span><del>-A lot just happened there! The Mapper object figured out how to relate rows in the addresses table to the users table, and also upon flush had to determine the proper order in which to insert rows. After the insert, all the User and Address objects have all their new primary and foreign keys populated. </del><ins>+A lot just happened there! The `Mapper` figured out how to relate rows in the addresses table to the users table, and also upon flush had to determine the proper order in which to insert rows. After the insert, all the `User` and `Address` objects have their new primary and foreign key attributes populated. </ins><span class="cx"> </span><del>-Also notice that when we created a Mapper on the User class which defined an 'addresses' relation, the newly created User instance magically had an "addresses" attribute which behaved like a list. This list is in reality a property accessor function, which returns an instance of `sqlalchemy.util.HistoryArraySet`, which fulfills the full set of Python list accessors, but maintains a *unique* set of objects (based on their in-memory identity), and also tracks additions and deletions to the list: </del><ins>+Also notice that when we created a `Mapper` on the `User` class which defined an `addresses` relation, the newly created `User` instance magically had an "addresses" attribute which behaved like a list. This list is in reality a property function which returns an instance of `sqlalchemy.util.HistoryArraySet`. This object fulfills the full set of Python list accessors, but maintains a *unique* set of objects (based on their in-memory identity), and also tracks additions and deletions to the list: </ins><span class="cx"> </span><span class="cx"> {python} </span><span class="cx"> del u.addresses[1] </span><span class="lines">@@ -287,9 +295,9 @@ </span><span class="cx"> </span><span class="cx"> Note that when creating a relation with the `relation()` function, the target can either be a class, in which case the primary mapper for that class is used as the target, or a `Mapper` instance itself, as returned by the `mapper()` function. </span><span class="cx"> </span><del>-#### Useful Feature: Lifecycle Relations {@name=lifecycle} </del><ins>+#### Lifecycle Relations {@name=lifecycle} </ins><span class="cx"> </span><del>-So our one address that was removed from the list, was updated to have a user_id of `None`, and a new address object was inserted to correspond to the new Address added to the User. But now, theres a mailing address with no user_id floating around in the database of no use to anyone. How can we avoid this ? This is acheived by using the `cascade` parameter of `relation`: </del><ins>+In the previous example, a single address was removed from the `addresses` attribute of a `User` object, resulting in the corresponding database row being updated to have a user_id of `None`. But now, theres a mailing address with no user_id floating around in the database of no use to anyone. How can we avoid this ? This is acheived by using the `cascade` parameter of `relation`: </ins><span class="cx"> </span><span class="cx"> {python} </span><span class="cx"> clear_mappers() # clear mappers from the previous example </span><span class="lines">@@ -309,13 +317,13 @@ </span><span class="cx"> DELETE FROM addresses WHERE addresses.address_id = :address_id </span><span class="cx"> [{'address_id': 2}] </span><span class="cx"> </span><del>-In this case, with `delete-orphan` set, the element that was removed from the addresses list was also removed from the database. The `delete-orphan` cascade rule indicates that the lifecycle of an `Address` object bounded by that of the `User`. </del><ins>+In this case, with the `delete-orphan` **cascade rule** set, the element that was removed from the addresses list was also removed from the database. Specifying `cascade="all, delete-orphan"` means that every persistence operation performed on the parent object will be *cascaded* to the child object or objects handled by the relation, and additionally that each child object cannot exist without being attached to a parent. Such a relationship indicates that the **lifecycle** of the `Address` objects are bounded by that of their parent `User` object. </ins><span class="cx"> </span><span class="cx"> Cascading is described fully in [unitofwork_cascade](rel:unitofwork_cascade). </span><span class="cx"> </span><del>-#### Useful Feature: Backreferences {@name=backreferences} </del><ins>+#### Backreferences {@name=backreferences} </ins><span class="cx"> </span><del>-By creating relations with the `backref` keyword, a bi-directional relationship can be created which will keep both ends of the relationship updated automatically, even without any database queries being executed. Below, the User mapper is created with an "addresses" property, and the corresponding Address mapper receives a "backreference" to the User object via the property name "user": </del><ins>+By creating relations with the `backref` keyword, a bi-directional relationship can be created which will keep both ends of the relationship updated automatically, independently of database operations. Below, the `User` mapper is created with an `addresses` property, and the corresponding `Address` mapper receives a "backreference" to the `User` object via the property name `user`: </ins><span class="cx"> </span><span class="cx"> {python} </span><span class="cx"> Address = mapper(Address, addresses_table) </span><span class="lines">@@ -340,31 +348,37 @@ </span><span class="cx"> >>> a1.user is user and a2.user is user </span><span class="cx"> True </span><span class="cx"> </span><del>-The backreference feature also works with many-to-many relationships, which are described later. When creating a backreference, a corresponding property is placed on the child mapper. The default arguments to this property can be overridden using the `backref()` function: </del><ins>+The backreference feature also works with many-to-many relationships, which are described later. When creating a backreference, a corresponding property (i.e. a second `relation()`) is placed on the child mapper. The default arguments to this property can be overridden using the `backref()` function: </ins><span class="cx"> </span><span class="cx"> {python} </span><del>- mapper(Address, addresseses) </del><ins>+ mapper(User, users_table) + mapper(Address, addresses_table, properties={ + 'user':relation(User, backref=backref('addresses', cascade="all, delete-orphan")) + }) </ins><span class="cx"> </span><del>- mapper(User, users, properties = { - 'addresses' : relation(Address, - backref=backref('user', lazy=False, cascade="all, delete-orphan") - ) - } - ) </del><span class="cx"> </span><del>-#### Selecting from Relationships: Lazy Load {@name=lazyload} </del><ins>+The `backref()` function is often used to set up a bi-directional one-to-one relationship. This is because the `relation()` function by default creates a "one-to-many" relationship when presented with a primary key/foreign key relationship, but the `backref()` function can redefine the `uselist` property to make it a scalar: </ins><span class="cx"> </span><del>-We've seen how the `relation` specifier affects the saving of an object and its child items, how does it affect selecting them? By default, the relation keyword indicates that the related property should be attached a *Lazy Loader* when instances of the parent object are loaded from the database; this is just a callable function that when accessed will invoke a second SQL query to load the child objects of the parent. </del><ins>+ {python} + mapper(User, users_table) + mapper(Address, addresses_table, properties={ + 'user' : relation(User, backref=backref('address', uselist=False)) + }) + + +### Selecting from Relationships {@name=selectrelations} + +We've seen how the `relation` specifier affects the saving of an object and its child items, how does it affect selecting them? By default, the relation keyword indicates that the related property should be attached a *lazy loader* when instances of the parent object are loaded from the database; this is just a callable function that when accessed will invoke a second SQL query to load the child objects of the parent. </ins><span class="cx"> </span><span class="cx"> {python} </span><span class="cx"> # define a mapper </span><span class="cx"> mapper(User, users_table, properties = { </span><del>- 'addresses' : relation(mapper(Address, addresses_table), cascade="all,delete-orphan") </del><ins>+ 'addresses' : relation(mapper(Address, addresses_table)) </ins><span class="cx"> }) </span><span class="cx"> </span><span class="cx"> # select users where username is 'jane', get the first element of the list </span><span class="cx"> # this will incur a load operation for the parent table </span><del>- {sql}user = session.query(User).select_by(user_name='jane')[0] </del><ins>+ {sql}user = session.query(User).select(User.c.user_name=='jane')[0] </ins><span class="cx"> SELECT users.user_id AS users_user_id, </span><span class="cx"> users.user_name AS users_user_name, users.password AS users_password </span><span class="cx"> FROM users WHERE users.user_name = :users_user_name ORDER BY users.oid </span><span class="lines">@@ -382,9 +396,9 @@ </span><span class="cx"> </span><span class="cx"> print repr(a) </span><span class="cx"> </span><del>-##### Useful Feature: Creating Joins Across Relations {@name=relselectby} </del><ins>+#### Creating Joins Across Relations {@name=relselectby} </ins><span class="cx"> </span><del>-For mappers that have relationships, the `select_by` method of the Query object can create queries that include automatically created joins. Just specify a key in the argument list which is not present in the primary mapper's list of properties or columns, but *is* present in the property list of one of its relationships: </del><ins>+For mappers that have relationships, the `select_by` method of the `Query` object can create queries that include automatically created joins. Just specify a key in the argument list which is not present in the primary mapper's list of properties or columns, but *is* present in the property list of one of its relationships: </ins><span class="cx"> </span><span class="cx"> {python} </span><span class="cx"> {sql}l = session.query(User).select_by(street='123 Green Street') </span><span class="lines">@@ -404,16 +418,28 @@ </span><span class="cx"> Address.c.street=='123 Green Street') </span><span class="cx"> ) </span><span class="cx"> </span><del>-All keyword arguments sent to `select_by` are used to create query criterion. This means that familiar `select` keyword options like `order_by` and `limit` are not directly available. To enable these options with `select_by`, you can try the `SelectResults` extension which offers methods off the result of a `select` or `select_by` such as `order_by()` and array slicing functions that generate new queries. Or you can use `select` in conjunction with `join_to`. </del><ins>+All keyword arguments sent to `select_by` are used to create query criterion. This means that familiar `select` keyword options like `order_by` and `limit` are not directly available. To enable these options with `select_by`, you can try the [plugins_selectresults](rel:plugins_selectresults) extension which offers methods off the result of a `select` or `select_by` such as `order_by()` and array slicing functions that generate new queries. </ins><span class="cx"> </span><del>-The `join_to` method of `Query` is a component of the `select_by` operation, and is given a keyname in order to return a "join path" from the Query's mapper to the mapper which contains the property of the given name: </del><ins>+Also, `select_by` will *not* create joins derived from `Column`-based expressions (i.e. `ClauseElement` objects); the reason is that a `Column`-based expression may include many columns, and `select_by` has no way to know which columns in the expression correspond to properties and which don't (it also prefers not to dig into column expressions which may be very complex). The next section describes some ways to combine `Column` expressions with `select_by`'s auto-joining capabilities. </ins><span class="cx"> </span><ins>+#### More Granular Join Control Using join\_to, join\_via {@name=jointo} + +The `join_to` method of `Query` is a component of the `select_by` operation, and is given a keyname in order to return a "join path" from the Query's mapper to the mapper which is referenced by a `relation()` of the given name: + </ins><span class="cx"> {python} </span><span class="cx"> >>> q = session.query(User) </span><ins>+ >>> j = q.join_to('addresses') + >>> print j + users.user_id=addresses.user_id + +`join_to` can also be given the name of a column-based property, in which case it will locate a path to the nearest mapper which has that property as a column: + + {python} + >>> q = session.query(User) </ins><span class="cx"> >>> j = q.join_to('street') </span><span class="cx"> >>> print j </span><span class="cx"> users.user_id=addresses.user_id </span><del>- </del><ins>+ </ins><span class="cx"> Also available is the `join_via` function, which is similar to `join_to`, except instead of traversing through all properties to find a path to the given key, its given an explicit path to the target property: </span><span class="cx"> </span><span class="cx"> {python} </span><span class="lines">@@ -422,19 +448,19 @@ </span><span class="cx"> >>> print j </span><span class="cx"> users.c.user_id==orders.c.user_id AND orders.c.item_id==items.c.item_id </span><span class="cx"> </span><del>-Expressions produced by `join_to` and `join_via` can be used with `select` to create query criterion: </del><ins>+Expressions produced by `join_to` and `join_via` can be used with `select` to create more complicated query criterion across multiple relations: </ins><span class="cx"> </span><span class="cx"> {python} </span><span class="cx"> >>> l = q.select( </span><span class="cx"> (addresses_table.c.street=='some address') & </span><span class="cx"> (items_table.c.item_name=='item #4') & </span><del>- q.join_to('address') & </del><ins>+ q.join_to('addresses') & </ins><span class="cx"> q.join_via(['orders', 'items']) </span><span class="cx"> ) </span><del>- - -#### Selecting from Relationships: Eager Load {@name=eagerload} </del><span class="cx"> </span><ins>+ +#### Eager Loading {@name=eagerload} + </ins><span class="cx"> With just a single parameter `lazy=False` specified to the relation object, the parent and child SQL queries can be joined together. </span><span class="cx"> </span><span class="cx"> {python} </span><span class="lines">@@ -479,7 +505,7 @@ </span><span class="cx"> </span><span class="cx"> The join implied by passing the "street" parameter is stated as an *additional* join between the `addresses` and `users` tables. Also, since the eager join is "aliasized", no name conflict occurs. </span><span class="cx"> </span><del>-#### Switching Lazy/Eager, No Load {@name=options} </del><ins>+#### Using Options to Change the Loading Strategy {@name=options} </ins><span class="cx"> </span><span class="cx"> The `options` method on the `Query` object provides an easy way to get alternate forms of a mapper query from an original one. The most common use of this feature is to change the "eager/lazy" loading behavior of a particular mapper, via the functions `eagerload()`, `lazyload()` and `noload()`: </span><span class="cx"> </span></span></pre></div> <a id="sqlalchemybranchesschemadocbuildcontentpluginstxt"></a> <div class="modfile"><h4>Modified: sqlalchemy/branches/schema/doc/build/content/plugins.txt (1466 => 1467)</h4> <pre class="diff"><span> <span class="info">--- sqlalchemy/branches/schema/doc/build/content/plugins.txt 2006-05-16 00:25:50 UTC (rev 1466) +++ sqlalchemy/branches/schema/doc/build/content/plugins.txt 2006-05-17 01:09:24 UTC (rev 1467) </span><span class="lines">@@ -30,6 +30,8 @@ </span><span class="cx"> user = User() </span><span class="cx"> </span><span class="cx"> session.flush() </span><ins>+ +#### get_session() Implemented on All Mappers </ins><span class="cx"> </span><span class="cx"> All `Mapper` objects constructed after the `threadlocal` import will receive a default `MapperExtension` which implements the `get_session()` method, returning the `Session` that is associated with the current thread by the global `SessionContext`. All newly constructed objects will automatically be attached to the `Session` corresponding to the current thread, i.e. they will skip the "transient" state and go right to "pending". </span><span class="cx"> </span><span class="lines">@@ -145,6 +147,19 @@ </span><span class="cx"> </span><span class="cx"> ### SelectResults </span><span class="cx"> </span><ins>+ {python} + res = SelectResults(mapper, table.c.column == "something") + res = res.order_by([table.c.column]) #add an order clause + + for x in res[:10]: # Fetch and print the top ten instances + print x.column2 + + x = list(res) # execute the query + + # Count how many instances that have column2 > 42 + # and column == "something" + print res.filter(table.c.column2 > 42).count() + </ins><span class="cx"> ### LegacySession </span><span class="cx"> </span><span class="cx"> (this plugin probably doesnt even work right now) </span><span class="cx">\ No newline at end of file </span></span></pre></div> <a id="sqlalchemybranchesschemalibsqlalchemyormquerypy"></a> <div class="modfile"><h4>Modified: sqlalchemy/branches/schema/lib/sqlalchemy/orm/query.py (1466 => 1467)</h4> <pre class="diff"><span> <span class="info">--- sqlalchemy/branches/schema/lib/sqlalchemy/orm/query.py 2006-05-16 00:25:50 UTC (rev 1466) +++ sqlalchemy/branches/schema/lib/sqlalchemy/orm/query.py 2006-05-17 01:09:24 UTC (rev 1467) </span><span class="lines">@@ -114,7 +114,10 @@ </span><span class="cx"> keys = [] </span><span class="cx"> def search_for_prop(mapper): </span><span class="cx"> if mapper.props.has_key(key): </span><del>- return mapper.props[key] </del><ins>+ prop = mapper.props[key] + if isinstance(prop, properties.PropertyLoader): + keys.insert(0, prop.key) + return prop </ins><span class="cx"> else: </span><span class="cx"> for prop in mapper.props.values(): </span><span class="cx"> if not isinstance(prop, properties.PropertyLoader): </span></span></pre></div> <a id="sqlalchemybranchesschematestmapperpy"></a> <div class="modfile"><h4>Modified: sqlalchemy/branches/schema/test/mapper.py (1466 => 1467)</h4> <pre class="diff"><span> <span class="info">--- sqlalchemy/branches/schema/test/mapper.py 2006-05-16 00:25:50 UTC (rev 1466) +++ sqlalchemy/branches/schema/test/mapper.py 2006-05-17 01:09:24 UTC (rev 1467) </span><span class="lines">@@ -230,6 +230,8 @@ </span><span class="cx"> l = q.select((orderitems.c.item_name=='item 4') & q.join_to('item_name')) </span><span class="cx"> self.assert_result(l, User, user_result[0]) </span><span class="cx"> </span><ins>+ l = q.select((orderitems.c.item_name=='item 4') & q.join_to('items')) + self.assert_result(l, User, user_result[0]) </ins><span class="cx"> </span><span class="cx"> def testorderby(self): </span><span class="cx"> # TODO: make a unit test out of these various combinations </span></span></pre> </div> </div> </body> </html> |