From: Mike Bayer Date: Sun, 15 Jul 2007 06:02:03 +0000 (+0000) Subject: - more docs X-Git-Tag: rel_0_3_9~7 X-Git-Url: http://git.ipfire.org/cgi-bin/gitweb.cgi?a=commitdiff_plain;h=9cc7d73ea1bb88c8dffcdd12bd90e312a41578fc;p=thirdparty%2Fsqlalchemy%2Fsqlalchemy.git - 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 --- diff --git a/doc/build/content/adv_datamapping.txt b/doc/build/content/adv_datamapping.txt index 289ddf3e78..6fb741b670 100644 --- a/doc/build/content/adv_datamapping.txt +++ b/doc/build/content/adv_datamapping.txt @@ -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} diff --git a/doc/build/content/datamapping.txt b/doc/build/content/datamapping.txt index 85c32adb2c..977f2a343f 100644 --- a/doc/build/content/datamapping.txt +++ b/doc/build/content/datamapping.txt @@ -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} diff --git a/doc/build/content/unitofwork.txt b/doc/build/content/unitofwork.txt index 184ece0259..4ae2c3c91b 100644 --- a/doc/build/content/unitofwork.txt +++ b/doc/build/content/unitofwork.txt @@ -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()`: diff --git a/lib/sqlalchemy/orm/query.py b/lib/sqlalchemy/orm/query.py index 8724f55a48..2c220e29dc 100644 --- a/lib/sqlalchemy/orm/query.py +++ b/lib/sqlalchemy/orm/query.py @@ -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) diff --git a/test/orm/query.py b/test/orm/query.py index e3e7677df8..22b9d8e117 100644 --- a/test/orm/query.py +++ b/test/orm/query.py @@ -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__':