- more docs

- got from_statement() to actually work with query, tests were not covering
- added auto-labeling of anonymous columns sent to add_column(), tests
This commit is contained in:
Mike Bayer
2007-07-15 06:02:03 +00:00
parent d92489fd2a
commit 9cc7d73ea1
5 changed files with 46 additions and 23 deletions
+1
View File
@@ -150,6 +150,7 @@ In this example, a class `MyClass` is defined, which is associated with a parent
myparent.myclasses.append(MyClass('this is myclass'))
myclass = myparent.myclasses['this is myclass']
Note: SQLAlchemy 0.4 has an overhauled and much improved implementation for custom list classes, with some slight API changes.
#### Custom Join Conditions {@name=customjoin}
+8
View File
@@ -585,6 +585,14 @@ The above syntax is shorthand for using the `add_entity()` method:
{python}
session.query(User).add_entity(Address).join('addresses').all()
Theres also a way to combine scalar results with objects, using `add_column()`. This is often used for functions and aggregates.
{python}
r = session.query(User).add_column(func.max(users_table.c.name)).group_by([c for c in users_table.c]).all()
for r in result:
print "user:", r[0]
print "max name:", r[1]
To join across multiple relationships, specify them in a list. Below, we load a `ShoppingCart`, limiting its `cartitems` collection to the single item which has a `price` object whose `amount` column is 47.95:
{python}
+10 -11
View File
@@ -61,7 +61,7 @@ The session to which an object is attached can be acquired via the `object_sessi
Session Facts:
* the Session object is **not threadsafe**. For thread-local management of Sessions, the recommended approch is to use the [plugins_sessioncontext](rel:plugins_sessioncontext) extension module.
* the Session object is **not threadsafe**. For thread-local management of Sessions, the recommended approach is to use the [plugins_sessioncontext](rel:plugins_sessioncontext) extension module.
We will now cover some of the key concepts used by Sessions and its underlying Unit of Work.
@@ -77,13 +77,13 @@ For example; below, two separate calls to load an instance with database identit
mymapper = mapper(MyClass, mytable)
session = create_session()
obj1 = session.query(MyClass).selectfirst(mytable.c.id==15)
obj2 = session.query(MyClass).selectfirst(mytable.c.id==15)
obj1 = session.query(MyClass).filter(mytable.c.id==15).first()
obj2 = session.query(MyClass).filter(mytable.c.id==15).first()
>>> obj1 is obj2
True
The Identity Map is an instance of `dict` by default. (This is new as of version 0.3.2). As an option, you can specify the flag `weak_identity_map=True` to the `create_session` function so that it will use a `weakref.WeakValueDictionary`, so that when an in-memory object falls out of scope, it will be removed automatically, thereby providing some automatic management of memory. 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. Additionally, note that an object that has changes marked on it (i.e. "dirty") can still fall out of scope when using `weak_identity_map`.
The Identity Map is an instance of `dict` by default. As an option, you can specify the flag `weak_identity_map=True` to the `create_session` function so that it will use a `weakref.WeakValueDictionary`, so that when an in-memory object falls out of scope, it will be removed automatically, thereby providing some automatic management of memory. 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. Additionally, note that an object that has changes marked on it (i.e. "dirty") can still fall out of scope when using `weak_identity_map`.
The Session supports an iterator interface in order to see all objects in the identity map:
@@ -138,11 +138,14 @@ As for objects inside of `new` and `deleted`, if you abandon all references to n
#### query() {@name=query}
The `query()` function takes a class or `Mapper` as an argument, along with an optional `entity_name` parameter, and returns a new `Query` object which will issue mapper queries within the context of this Session. If a Mapper is passed, then the Query uses that mapper. Otherwise, if a class is sent, it will locate the primary mapper for that class which is used to construct the Query.
The `query()` function takes one or more classes and/or mappers, along with an optional `entity_name` parameter, and returns a new `Query` object which will issue mapper queries within the context of this Session. For each mapper is passed, the Query uses that mapper. For each class, the Query will locate the primary mapper for the class using `class_mapper()`.
{python}
# query from a class
session.query(User).select_by(name='ed')
session.query(User).filter_by(name='ed').all()
# query with multiple classes, returns tuples
session.query(User, Address).join('addresses').filter_by(name='ed').all()
# query from a mapper
query = session.query(usermapper)
@@ -150,7 +153,7 @@ The `query()` function takes a class or `Mapper` as an argument, along with an o
# query from a class mapped with entity name 'alt_users'
q = session.query(User, entity_name='alt_users')
y = q.options(eagerload('orders')).select()
y = q.options(eagerload('orders')).all()
`entity_name` is an optional keyword argument sent with a class object, in order to further qualify which primary mapper to be used; this only applies if there was a `Mapper` created with that particular class/entity name combination, else an exception is raised. All of the methods on Session which take a class or mapper argument also take the `entity_name` argument, so that a given class can be properly matched to the desired primary mapper.
@@ -309,8 +312,6 @@ This method is a combination of the `save()` and `update()` methods, which will
#### merge() {@name=merge}
Feature Status: [Alpha Implementation][alpha_implementation]
`merge()` is used to return the persistent version of an instance that is not attached to this Session. When passed an instance, if an instance with its database identity already exists within this Session, it is returned. If the instance does not exist in this Session, it is loaded from the database and then returned.
A future version of `merge()` will also update the Session's instance with the state of the given instance (hence the name "merge").
@@ -329,8 +330,6 @@ Note that `merge()` *does not* associate the given instance with the Session; it
### Cascade rules {@name=cascade}
Feature Status: [Alpha Implementation][alpha_implementation]
Mappers support the concept of configurable *cascade* behavior on `relation()`s. This behavior controls how the Session should treat the instances that have a parent-child relationship with another instance that is operated upon by the Session. Cascade is indicated as a comma-separated list of string keywords, with the possible values `all`, `delete`, `save-update`, `refresh-expire`, `merge`, `expunge`, and `delete-orphan`.
Cascading is configured by setting the `cascade` keyword argument on a `relation()`:
+16 -7
View File
@@ -7,6 +7,7 @@
from sqlalchemy import sql, util, exceptions, sql_util, logging, schema
from sqlalchemy.orm import mapper, class_mapper, object_mapper
from sqlalchemy.orm.interfaces import OperationContext, SynonymProperty
import random
__all__ = ['Query', 'QueryContext', 'SelectionContext']
@@ -53,7 +54,8 @@ class Query(object):
self._func = None
self._joinpoint = self.mapper
self._from_obj = [self.table]
self._statement = None
for opt in util.flatten_iterator(self.with_options):
opt.process_query(self)
@@ -82,6 +84,7 @@ class Query(object):
q._from_obj = list(self._from_obj)
q._joinpoint = self._joinpoint
q._criterion = self._criterion
q._statement = self._statement
q._col = self._col
q._func = self._func
return q
@@ -486,9 +489,6 @@ class Query(object):
of this Query along with the additional entities. The Query selects
from all tables with no joining criterion by default.
When tuple-based results are returned, the 'uniquing' of returned entities
is disabled to maintain grouping.
entity
a class or mapper which will be added to the results.
@@ -511,15 +511,18 @@ class Query(object):
table or selectable that is not the primary mapped selectable. The Query selects
from all tables with no joining criterion by default.
When tuple-based results are returned, the 'uniquing' of returned entities
is disabled to maintain grouping.
column
a string column name or sql.ColumnElement to be added to the results.
"""
q = self._clone()
# alias non-labeled column elements.
# TODO: make the generation deterministic
if isinstance(column, sql.ColumnElement) and not hasattr(column, '_label'):
column = column.label("anon_" + hex(random.randint(0, 65535))[2:])
q._entities.append(column)
return q
@@ -1015,9 +1018,11 @@ class Query(object):
process.append((proc, appender))
x(m)
elif isinstance(m, sql.ColumnElement) or isinstance(m, basestring):
print "M IS", m
def y(m):
res = []
def proc(context, row):
print "ROW VAL", m, "KEYS", row.keys()
res.append(row[m])
process.append((proc, res))
y(m)
@@ -1089,6 +1094,10 @@ class Query(object):
the arguments to this function are deprecated and are removed in version 0.4.
"""
if self._statement:
self._statement.use_labels = True
return self._statement
if self._criterion:
whereclause = sql.and_(self._criterion, whereclause)
+11 -5
View File
@@ -274,17 +274,17 @@ class InstancesTest(QueryTest):
def test_contains_eager(self):
selectquery = users.outerjoin(addresses).select(use_labels=True, order_by=[users.c.id, addresses.c.id])
selectquery = users.outerjoin(addresses).select(users.c.id<10, use_labels=True, order_by=[users.c.id, addresses.c.id])
q = create_session().query(User)
def go():
l = q.options(contains_eager('addresses')).instances(selectquery.execute())
assert fixtures.user_address_result == l
assert fixtures.user_address_result[0:3] == l
self.assert_sql_count(testbase.db, go, 1)
def go():
l = q.options(contains_eager('addresses')).from_statement(selectquery).all()
assert fixtures.user_address_result == l
assert fixtures.user_address_result[0:3] == l
self.assert_sql_count(testbase.db, go, 1)
def test_contains_eager_alias(self):
@@ -366,7 +366,7 @@ class InstancesTest(QueryTest):
s = select([users, func.count(addresses.c.id).label('count')], from_obj=[users.outerjoin(addresses)], group_by=[c for c in users.c], order_by=users.c.id)
q = sess.query(User)
l = q.instances(s.execute(), "count")
l = q.add_column("count").from_statement(s).all()
assert l == expected
@testbase.unsupported('mysql') # only because of "+" operator requiring "concat" in mysql (fix #475)
@@ -381,8 +381,14 @@ class InstancesTest(QueryTest):
s = select([users, func.count(addresses.c.id).label('count'), ("Name:" + users.c.name).label('concat')], from_obj=[users.outerjoin(addresses)], group_by=[c for c in users.c], order_by=[users.c.id])
q = create_session().query(User)
l = q.instances(s.execute(), "count", "concat")
l = q.add_column("count").add_column("concat").from_statement(s).all()
assert l == expected
q = create_session().query(User).add_column(func.count(addresses.c.id))\
.add_column(("Name:" + users.c.name)).select_from(users.outerjoin(addresses))\
.group_by([c for c in users.c]).order_by(users.c.id)
assert q.all() == expected
if __name__ == '__main__':