Commits

Sheila Allen  committed f8e95cc Merge

merged with mainline tip

  • Participants
  • Parent commits 1a606aa, a041d49

Comments (0)

Files changed (71)

 CHANGES
 =======
 
+0.6beta3
+========
+
+- orm
+  - Major feature: Added new "subquery" loading capability to 
+    relationship().   This is an eager loading option which
+    generates a second SELECT for each collection represented
+    in a query, across all parents at once.  The query 
+    re-issues the original end-user query wrapped in a subquery,
+    applies joins out to the target collection, and loads 
+    all those collections fully in one result, similar to 
+    "joined" eager loading but using all inner joins and not 
+    re-fetching full parent rows repeatedly (as most DBAPIs seem 
+    to do, even if columns are skipped).   Subquery loading is 
+    available at mapper config level using "lazy='subquery'" and 
+    at the query options level using "subqueryload(props..)", 
+    "subqueryload_all(props...)".  [ticket:1675]
+
+  - To accomodate the fact that there are now two kinds of eager 
+    loading available, the new names for eagerload() and 
+    eagerload_all() are joinedload() and joinedload_all().  The 
+    old names will remain as synonyms for the foreseeable future.
+    
+  - The "lazy" flag on the relationship() function now accepts 
+    a string argument for all kinds of loading: "select", "joined",
+    "subquery", "noload" and "dynamic", where the default is now
+    "select".  The old values of True/
+    False/None still retain their usual meanings and will remain
+    as synonyms for the foreseeable future.
+    
+  - Fixed bug in Query whereby calling q.join(prop).from_self(...).
+    join(prop) would fail to render the second join outside the
+    subquery, when joining on the same criterion as was on the 
+    inside.
+
+  - Fixed bug in Query whereby the usage of aliased() constructs
+    would fail if the underlying table (but not the actual alias) 
+    were referenced inside the subquery generated by 
+    q.from_self() or q.select_from().
+    
+  - Fixed bug which affected all eagerload() and similar options 
+    such that "remote" eager loads, i.e. eagerloads off of a lazy
+    load such as query(A).options(eagerload(A.b, B.c))
+    wouldn't eagerload anything, but using eagerload("b.c") would
+    work fine.
+
+  - Query gains an add_columns(*columns) method which is a multi-
+    version of add_column(col).  add_column(col) is future 
+    deprecated.
+
+  - Query.join() will detect if the end result will be 
+    "FROM A JOIN A", and will raise an error if so.
+
+  - Query.join(Cls.propname, from_joinpoint=True) will check more
+    carefully that "Cls" is compatible with the current joinpoint,
+    and act the same way as Query.join("propname", from_joinpoint=True)
+    in that regard.
+    
+- sql
+   - Fixed bug introduced in 0.6beta2 where column labels would 
+     render inside of column expressions already assigned a label.
+     [ticket:1747]
+
+- postgresql
+   - The psycopg2 dialect will log NOTICE messages via the
+     "sqlalchemy.dialects.postgresql" logger name. 
+     [ticket:877]
+
+   - the TIME and TIMESTAMP types are now availble from the
+     postgresql dialect directly, which add the PG-specific
+     argument 'precision' to both.   'precision' and 
+     'timezone' are correctly reflected for both TIME and 
+     TIMEZONE types. [ticket:997]
+     
+- oracle
+   - The Oracle dialect will issue VARCHAR type definitions
+     using character counts, i.e. VARCHAR2(50 CHAR), so that
+     the column is sized in terms of characters and not bytes.
+     Column reflection of character types will also use
+     ALL_TAB_COLUMNS.CHAR_LENGTH instead of 
+     ALL_TAB_COLUMNS.DATA_LENGTH.  Both of these behaviors take
+     effect when the server version is 9 or higher - for 
+     version 8, the old behaviors are used.  [ticket:1744]
+
+- declarative
+   - Using a mixin won't break if the mixin implements an 
+     unpredictable __getattribute__(), i.e. Zope interfaces.
+     [ticket:1746]
+
+   - Using @classdecorator and similar on mixins to define 
+     __tablename__, __table_args__, etc. now works if
+     the method references attributes on the ultimate 
+     subclass. [ticket:1749]
+     
 0.6beta2
 ========
 

File doc/build/dbengine.rst

 This section assumes familiarity with the above linked logging module.  All logging performed by SQLAlchemy exists underneath the ``sqlalchemy`` namespace, as used by ``logging.getLogger('sqlalchemy')``.  When logging has been configured (i.e. such as via ``logging.basicConfig()``), the general namespace of SA loggers that can be turned on is as follows:
 
 * ``sqlalchemy.engine`` - controls SQL echoing.  set to ``logging.INFO`` for SQL query output, ``logging.DEBUG`` for query + result set output.
+* ``sqlalchemy.dialects`` - controls custom logging for SQL dialects.  See the documentation of individual dialects for details. 
 * ``sqlalchemy.pool`` - controls connection pool logging.  set to ``logging.INFO`` or lower to log connection pool checkouts/checkins.
 * ``sqlalchemy.orm`` - controls logging of various ORM functions.  set to ``logging.INFO`` for configurational logging as well as unit of work dumps, ``logging.DEBUG`` for extensive logging during query and flush() operations.  Subcategories of ``sqlalchemy.orm`` include:
     * ``sqlalchemy.orm.attributes`` - logs certain instrumented attribute operations, such as triggered callables

File doc/build/mappers.rst

         'addresses': relationship(Address, order_by=addresses_table.c.address_id)
     })
 
-Note that when using eager loaders with relationships, the tables used by the eager load's join are anonymously aliased.  You can only order by these columns if you specify it at the :func:`~sqlalchemy.orm.relationship` level.  To control ordering at the query level based on a related table, you ``join()`` to that relationship, then order by it::
+Note that when using joined eager loaders with relationships, the tables used by the eager load's join are anonymously aliased.  You can only order by these columns if you specify it at the :func:`~sqlalchemy.orm.relationship` level.  To control ordering at the query level based on a related table, you ``join()`` to that relationship, then order by it::
 
     session.query(User).join('addresses').order_by(Address.street)
 
 Configuring Eager Loading
 ~~~~~~~~~~~~~~~~~~~~~~~~~~
 
-Eager loading of relationships occurs using joins or outerjoins from parent to child table during a normal query operation, such that the parent and its child collection can be populated from a single SQL statement.  SQLAlchemy's eager loading uses aliased tables in all cases when joining to related items, so it is compatible with self-referential joining.  However, to use eager loading with a self-referential relationship, SQLAlchemy needs to be told how many levels deep it should join; otherwise the eager load will not take place.  This depth setting is configured via ``join_depth``:
+Eager loading of relationships occurs using joins or outerjoins from parent to child table during a normal query operation, such that the parent and its child collection can be populated from a single SQL statement, or a second statement for all collections at once.  SQLAlchemy's joined and subquery eager loading uses aliased tables in all cases when joining to related items, so it is compatible with self-referential joining.  However, to use eager loading with a self-referential relationship, SQLAlchemy needs to be told how many levels deep it should join; otherwise the eager load will not take place.  This depth setting is configured via ``join_depth``:
 
 .. sourcecode:: python+sql
 
     mapper(Node, nodes, properties={
-        'children': relationship(Node, lazy=False, join_depth=2)
+        'children': relationship(Node, lazy='joined', join_depth=2)
     })
 
     {sql}session.query(Node).all()
 
 The collections package provides additional decorators and support for authoring custom types.  See the :mod:`sqlalchemy.orm.collections` package for more information and discussion of advanced usage and Python 2.3-compatible decoration options.
 
+.. _mapper_loader_strategies:
+
 Configuring Loader Strategies: Lazy Loading, Eager Loading
 -----------------------------------------------------------
 
+.. note:: SQLAlchemy version 0.6beta3 introduces the :func:`~sqlalchemy.orm.joinedload`, :func:`~sqlalchemy.orm.joinedload_all`, :func:`~sqlalchemy.orm.subqueryload` and :func:`~sqlalchemy.orm.subqueryload_all` functions described in this section.  In previous versions, including 0.5 and 0.4, use :func:`~sqlalchemy.orm.eagerload` and :func:`~sqlalchemy.orm.eagerload_all`.  Additionally, the ``lazy`` keyword argument on :func:`~sqlalchemy.orm.relationship` accepts the values ``True``, ``False`` and ``None`` in previous versions, whereas in the latest 0.6 it also accepts the arguments ``select``, ``joined``, ``noload``, and ``subquery``.
+
 In the :ref:`ormtutorial_toplevel`, we introduced the concept of **Eager Loading**.  We used an ``option`` in conjunction with the :class:`~sqlalchemy.orm.query.Query` object in order to indicate that a relationship should be loaded at the same time as the parent, within a single SQL query:
 
 .. sourcecode:: python+sql
 
-    {sql}>>> jack = session.query(User).options(eagerload('addresses')).filter_by(name='jack').all() #doctest: +NORMALIZE_WHITESPACE
+    {sql}>>> jack = session.query(User).options(joinedload('addresses')).filter_by(name='jack').all() #doctest: +NORMALIZE_WHITESPACE
     SELECT addresses_1.id AS addresses_1_id, addresses_1.email_address AS addresses_1_email_address,
     addresses_1.user_id AS addresses_1_user_id, users.id AS users_id, users.name AS users_name,
     users.fullname AS users_fullname, users.password AS users_password
 .. sourcecode:: python+sql
 
     {sql}>>> jack.addresses
-    SELECT addresses.id AS addresses_id, addresses.email_address AS addresses_email_address, addresses.user_id AS addresses_user_id
+    SELECT addresses.id AS addresses_id, addresses.email_address AS addresses_email_address, 
+    addresses.user_id AS addresses_user_id
     FROM addresses
     WHERE ? = addresses.user_id
     [5]
     {stop}[<Address(u'jack@google.com')>, <Address(u'j25@yahoo.com')>]
 
-The default **loader strategy** for any :func:`~sqlalchemy.orm.relationship` is configured by the ``lazy`` keyword argument, which defaults to ``True``.  Below we set it as ``False`` so that the ``children`` relationship is eager loading:
+A second option for eager loading exists, called "subquery" loading.   This kind of eager loading emits an additional SQL statement for each collection requested, aggregated across all parent objects:
 
 .. sourcecode:: python+sql
-
-    # eager load 'children' attribute
+    
+    {sql}>>>jack = session.query(User).options(subqueryload('addresses')).filter_by(name='jack').all() 
+    SELECT users.id AS users_id, users.name AS users_name, users.fullname AS users_fullname, 
+    users.password AS users_password 
+    FROM users 
+    WHERE users.name = ?
+    ('jack',)
+    SELECT addresses.id AS addresses_id, addresses.email_address AS addresses_email_address, 
+    addresses.user_id AS addresses_user_id, anon_1.users_id AS anon_1_users_id 
+    FROM (SELECT users.id AS users_id 
+    FROM users 
+    WHERE users.name = ?) AS anon_1 JOIN addresses ON anon_1.users_id = addresses.user_id 
+    ORDER BY anon_1.users_id, addresses.id
+    ('jack',)
+
+The default **loader strategy** for any :func:`~sqlalchemy.orm.relationship` is configured by the ``lazy`` keyword argument, which defaults to ``select``.  Below we set it as ``joined`` so that the ``children`` relationship is eager loading, using a join:
+
+.. sourcecode:: python+sql
+
+    # load the 'children' collection using LEFT OUTER JOIN
     mapper(Parent, parent_table, properties={
-        'children': relationship(Child, lazy=False)
+        'children': relationship(Child, lazy='joined')
     })
 
-The loader strategy can be changed from lazy to eager as well as eager to lazy using the :func:`~sqlalchemy.orm.eagerload` and :func:`~sqlalchemy.orm.lazyload` query options:
+We can also set it to eagerly load using a second query for all collections, using ``subquery``:
+
+.. sourcecode:: python+sql
+
+    # load the 'children' attribute using a join to a subquery
+    mapper(Parent, parent_table, properties={
+        'children': relationship(Child, lazy='subquery')
+    })
+
+When querying, all three choices of loader strategy are available on a per-query basis, using the :func:`~sqlalchemy.orm.joinedload`, :func:`~sqlalchemy.orm.subqueryload` and :func:`~sqlalchemy.orm.lazyload` query options:
 
 .. sourcecode:: python+sql
 
     # set children to load lazily
     session.query(Parent).options(lazyload('children')).all()
 
-    # set children to load eagerly
-    session.query(Parent).options(eagerload('children')).all()
+    # set children to load eagerly with a join
+    session.query(Parent).options(joinedload('children')).all()
+
+    # set children to load eagerly with a second statement
+    session.query(Parent).options(subqueryload('children')).all()
 
 To reference a relationship that is deeper than one level, separate the names by periods:
 
 .. sourcecode:: python+sql
 
-    session.query(Parent).options(eagerload('foo.bar.bat')).all()
-
-When using dot-separated names with :func:`~sqlalchemy.orm.eagerload`, option applies **only** to the actual attribute named, and **not** its ancestors.  For example, suppose a mapping from ``A`` to ``B`` to ``C``, where the relationships, named ``atob`` and ``btoc``, are both lazy-loading.  A statement like the following:
+    session.query(Parent).options(joinedload('foo.bar.bat')).all()
+
+When using dot-separated names with :func:`~sqlalchemy.orm.joinedload` or :func:`~sqlalchemy.orm.subqueryload`, option applies **only** to the actual attribute named, and **not** its ancestors.  For example, suppose a mapping from ``A`` to ``B`` to ``C``, where the relationships, named ``atob`` and ``btoc``, are both lazy-loading.  A statement like the following:
 
 .. sourcecode:: python+sql
 
-    session.query(A).options(eagerload('atob.btoc')).all()
+    session.query(A).options(joinedload('atob.btoc')).all()
 
 will load only ``A`` objects to start.  When the ``atob`` attribute on each ``A`` is accessed, the returned ``B`` objects will *eagerly* load their ``C`` objects.
 
-Therefore, to modify the eager load to load both ``atob`` as well as ``btoc``, place eagerloads for both:
+Therefore, to modify the eager load to load both ``atob`` as well as ``btoc``, place joinedloads for both:
 
 .. sourcecode:: python+sql
 
-    session.query(A).options(eagerload('atob'), eagerload('atob.btoc')).all()
-
-or more simply just use :func:`~sqlalchemy.orm.eagerload_all`:
+    session.query(A).options(joinedload('atob'), joinedload('atob.btoc')).all()
+
+or more simply just use :func:`~sqlalchemy.orm.joinedload_all` or :func:`~sqlalchemy.orm.subqueryload_all`:
 
 .. sourcecode:: python+sql
 
-    session.query(A).options(eagerload_all('atob.btoc')).all()
+    session.query(A).options(joinedload_all('atob.btoc')).all()
 
 There are two other loader strategies available, **dynamic loading** and **no loading**; these are described in :ref:`largecollections`.
 
+What Kind of Loading to Use ?
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+Which type of loading to use typically comes down to optimizing the tradeoff between number of SQL executions, complexity of SQL emitted, and amount of data fetched.   Lets take two examples, a :func:`~sqlalchemy.orm.relationship` which references a collection, and a :func:`~sqlalchemy.orm.relationship` that references a scalar many-to-one reference.
+
+* One to Many Collection
+
+ * When using the default lazy loading, if you load 100 objects, and then access a collection on each of
+   them, a total of 101 SQL statements will be emitted, although each statement will typically be a
+   simple SELECT without any joins.
+   
+ * When using joined loading, the load of 100 objects and their collections will emit only one SQL
+   statement.  However, the 
+   total number of rows fetched will be equal to the sum of the size of all the collections, plus one 
+   extra row for each parent object that has an empty collection.  Each row will also contain the full
+   set of columns represented by the parents, repeated for each collection item - SQLAlchemy does not
+   re-fetch these columns other than those of the primary key, however most DBAPIs (with some 
+   exceptions) will transmit the full data of each parent over the wire to the client connection in 
+   any case.  Therefore joined eager loading only makes sense when the size of the collections are 
+   relatively small.  The LEFT OUTER JOIN can also be performance intensive compared to an INNER join.
+   
+ * When using subquery loading, the load of 100 objects will emit two SQL statements.  The second
+   statement will fetch a total number of rows equal to the sum of the size of all collections.  An
+   INNER JOIN is used, and a minimum of parent columns are requested, only the primary keys.  So a 
+   subquery load makes sense when the collections are larger.
+   
+ * When multiple levels of depth are used with joined or subquery loading, loading collections-within-
+   collections will multiply the total number of rows fetched in a cartesian fashion.  Both forms
+   of eager loading always join from the original parent class.
+   
+* Many to One Reference
+
+  * When using the default lazy loading, a load of 100 objects will like in the case of the collection
+    emit as many as 101 SQL statements.  However - there is a significant exception to this, in that
+    if the many-to-one reference is a simple foreign key reference to the target's primary key, each
+    reference will be checked first in the current identity map using ``query.get()``.  So here, 
+    if the collection of objects references a relatively small set of target objects, or the full set
+    of possible target objects have already been loaded into the session and are strongly referenced,
+    using the default of `lazy='select'` is by far the most efficient way to go.
+  
+  * When using joined loading, the load of 100 objects will emit only one SQL statement.   The join
+    will be a LEFT OUTER JOIN, and the total number of rows will be equal to 100 in all cases.  
+    If you know that each parent definitely has a child (i.e. the foreign
+    key reference is NOT NULL), the joined load can be configured with ``innerjoin=True``, which is
+    usually specified within the :func:`~sqlalchemy.orm.relationship`.   For a load of objects where
+    there are many possible target references which may have not been loaded already, joined loading
+    with an INNER JOIN is extremely efficient.
+   
+  * Subquery loading will issue a second load for all the child objects, so for a load of 100 objects
+    there would be two SQL statements emitted.  There's probably not much advantage here over
+    joined loading, however, except perhaps that subquery loading can use an INNER JOIN in all cases
+    whereas joined loading requires that the foreign key is NOT NULL.
+
 Routing Explicit Joins/Statements into Eagerly Loaded Collections
 ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
 
-The behavior of :func:`~sqlalchemy.orm.eagerload()` is such that joins are created automatically, the results of which are routed into collections and scalar references on loaded objects.  It is often the case that a query already includes the necessary joins which represent a particular collection or scalar reference, and the joins added by the eagerload feature are redundant - yet you'd still like the collections/references to be populated.
-
-For this SQLAlchemy supplies the :func:`~sqlalchemy.orm.contains_eager()` option.  This option is used in the same manner as the :func:`~sqlalchemy.orm.eagerload()` option except it is assumed that the :class:`~sqlalchemy.orm.query.Query` will specify the appropriate joins explicitly.  Below it's used with a ``from_statement`` load::
+The behavior of :func:`~sqlalchemy.orm.joinedload()` is such that joins are created automatically, the results of which are routed into collections and scalar references on loaded objects.  It is often the case that a query already includes the necessary joins which represent a particular collection or scalar reference, and the joins added by the joinedload feature are redundant - yet you'd still like the collections/references to be populated.
+
+For this SQLAlchemy supplies the :func:`~sqlalchemy.orm.contains_eager()` option.  This option is used in the same manner as the :func:`~sqlalchemy.orm.joinedload()` option except it is assumed that the :class:`~sqlalchemy.orm.query.Query` will specify the appropriate joins explicitly.  Below it's used with a ``from_statement`` load::
 
     # mapping is the users->addresses mapping
     mapper(User, users_table, properties={
 Setting Noload
 ~~~~~~~~~~~~~~~
 
-The opposite of the dynamic relationship is simply "noload", specified using ``lazy=None``:
+The opposite of the dynamic relationship is simply "noload", specified using ``lazy='noload'``:
 
 .. sourcecode:: python+sql
 
     mapper(MyClass, table, properties={
-        'children': relationship(MyOtherClass, lazy=None)
+        'children': relationship(MyOtherClass, lazy='noload')
     })
 
 Above, the ``children`` collection is fully writeable, and changes to it will be persisted to the database as well as locally available for reading at the time they are added.  However when instances of  ``MyClass`` are freshly loaded from the database, the ``children`` collection stays empty.

File doc/build/ormtutorial.rst

 
 When we accessed the ``addresses`` collection, SQL was suddenly issued.  This is an example of a **lazy loading relationship**.  The ``addresses`` collection is now loaded and behaves just like an ordinary list.
 
-If you want to reduce the number of queries (dramatically, in many cases), we can apply an **eager load** to the query operation, using the :func:`~sqlalchemy.orm.eagerload` function.  This function is a **query option** that gives additional instructions to the query on how we would like it to load, in this case we'd like to indicate that we'd like ``addresses`` to load "eagerly".  SQLAlchemy then constructs an outer join between the ``users`` and ``addresses`` tables, and loads them at once, populating the ``addresses`` collection on each ``User`` object if it's not already populated:
+If you want to reduce the number of queries (dramatically, in many cases), we can apply an **eager load** to the query operation, using the :func:`~sqlalchemy.orm.joinedload` function.  This function is a **query option** that gives additional instructions to the query on how we would like it to load, in this case we'd like to indicate that we'd like ``addresses`` to load "eagerly".  SQLAlchemy then constructs an outer join between the ``users`` and ``addresses`` tables, and loads them at once, populating the ``addresses`` collection on each ``User`` object if it's not already populated:
 
 .. sourcecode:: python+sql
 
-    >>> from sqlalchemy.orm import eagerload
+    >>> from sqlalchemy.orm import joinedload
 
     {sql}>>> jack = session.query(User).\
-    ...                        options(eagerload('addresses')).\
+    ...                        options(joinedload('addresses')).\
     ...                        filter_by(name='jack').one() #doctest: +NORMALIZE_WHITESPACE
     SELECT users.id AS users_id, users.name AS users_name, users.fullname AS users_fullname,
     users.password AS users_password, addresses_1.id AS addresses_1_id, addresses_1.email_address
     >>> jack.addresses
     [<Address('jack@google.com')>, <Address('j25@yahoo.com')>]
 
-See :func:`~sqlalchemy.orm.eagerload` for further detail.  We'll also see another way to "eagerly" load in the next section.
+See :ref:`mapper_loader_strategies` for information on :func:`~sqlalchemy.orm.joinedload` and its new brother, :func:`~sqlalchemy.orm.subqueryload`.   We'll also see another way to "eagerly" load in the next section.
 
 Querying with Joins
 ====================
 
-While :func:`~sqlalchemy.orm.eagerload` created a JOIN specifically to populate a collection, we can also work explicitly with joins in many ways.  For example, to construct a simple inner join between ``User`` and ``Address``, we can just :meth:`~sqlalchemy.orm.query.Query.filter()` their related columns together.  Below we load the ``User`` and ``Address`` entities at once using this method:
+While :func:`~sqlalchemy.orm.joinedload` created a JOIN specifically to populate a collection, we can also work explicitly with joins in many ways.  For example, to construct a simple inner join between ``User`` and ``Address``, we can just :meth:`~sqlalchemy.orm.query.Query.filter()` their related columns together.  Below we load the ``User`` and ``Address`` entities at once using this method:
 
 .. sourcecode:: python+sql
 
 Using join() to Eagerly Load Collections/Attributes
 -------------------------------------------------------
 
-The "eager loading" capabilities of the :func:`~sqlalchemy.orm.eagerload` function and the join-construction capabilities of :meth:`~sqlalchemy.orm.query.Query.join()` or an equivalent can be combined together using the :func:`~sqlalchemy.orm.contains_eager` option.   This is typically used 
+The "eager loading" capabilities of the :func:`~sqlalchemy.orm.joinedload` function and the join-construction capabilities of :meth:`~sqlalchemy.orm.query.Query.join()` or an equivalent can be combined together using the :func:`~sqlalchemy.orm.contains_eager` option.   This is typically used 
 for a query that is already joining to some related entity (more often than not via many-to-one), and you'd like the related entity to also be loaded onto the resulting objects
 in one step without the need for additional queries and without the "automatic" join embedded
-by the :func:`~sqlalchemy.orm.eagerload` function:
+by the :func:`~sqlalchemy.orm.joinedload` function:
 
 .. sourcecode:: python+sql
 

File doc/build/reference/orm/query.rst

 
 Options which are passed to ``query.options()``, to affect the behavior of loading.
 
+.. autofunction:: contains_alias
+
 .. autofunction:: contains_eager
 
 .. autofunction:: defer
 
 .. autofunction:: extension
 
+.. autofunction:: joinedload
+
+.. autofunction:: joinedload_all
+
 .. autofunction:: lazyload
 
+.. autofunction:: subqueryload
+
+.. autofunction:: subqueryload_all
+
 .. autofunction:: undefer
 

File doc/build/session.rst

 
 The above mapper specifies two relationships, ``items`` and ``customer``.  The ``items`` relationship specifies "all, delete-orphan" as its ``cascade`` value, indicating that all  ``add``, ``merge``, ``expunge``, ``refresh`` ``delete`` and ``expire`` operations performed on a parent ``Order`` instance should also be performed on the child ``Item`` instances attached to it.  The ``delete-orphan`` cascade value additionally indicates that if an ``Item`` instance is no longer associated with an ``Order``, it should also be deleted.  The "all, delete-orphan" cascade argument allows a so-called *lifecycle* relationship between an ``Order`` and an ``Item`` object.
 
-The ``customer`` relationship specifies only the "save-update" cascade value, indicating most operations will not be cascaded from a parent ``Order`` instance to a child ``User`` instance except for the :func:`~sqlalchemy.orm.session.Session.add` operation.  "save-update" cascade indicates that an :func:`~sqlalchemy.orm.session.Session.add` on the parent will cascade to all child items, and also that items added to a parent which is already present in the session will also be added.  "save-update" cascade also cascades the *pending history* of a relationship()-based attribute, meaning that objects which were removed from a scalar or collection attribute whose changes have not yet been flushed are also placed into the new session - this so that foreign key clear operations and deletions will take place (new in 0.6).
+The ``customer`` relationship specifies only the "save-update" cascade value, indicating most operations will not be cascaded from a parent ``Order`` instance to a child ``User`` instance except for the :func:`~sqlalchemy.orm.session.Session.add` operation.  "save-update" cascade indicates that an :func:`~sqlalchemy.orm.session.Session.add` on the parent will cascade to all child items, and also that items added to a parent which is already present in a session will also be added to that same session.  "save-update" cascade also cascades the *pending history* of a relationship()-based attribute, meaning that objects which were removed from a scalar or collection attribute whose changes have not yet been flushed are also placed into the new session - this so that foreign key clear operations and deletions will take place (new in 0.6).
 
 Note that the ``delete-orphan`` cascade only functions for relationships where the target object can have a single parent at a time, meaning it is only appropriate for one-to-one or one-to-many relationships.  For a :func:`~sqlalchemy.orm.relationship` which establishes one-to-one via a local foreign key, i.e. a many-to-one that stores only a single parent, or one-to-one/one-to-many via a "secondary" (association) table, a warning will be issued if ``delete-orphan`` is configured.  To disable this warning, also specify the ``single_parent=True`` flag on the relationship, which constrains objects to allow attachment to only one parent at a time.
 

File examples/adjacency_list/adjacency_list.py

                         Integer, String, create_engine
                         
 from sqlalchemy.orm import sessionmaker, mapper, relationship, backref,\
-                                eagerload_all
+                                joinedload_all
                                 
 from sqlalchemy.orm.collections import attribute_mapped_collection
 
         "selecting tree on root, using eager loading to join four levels deep.")
     session.expunge_all()
     node = session.query(TreeNode).\
-                        options(eagerload_all("children", "children", 
+                        options(joinedload_all("children", "children", 
                                                 "children", "children")).\
                         filter(TreeNode.name=="rootnode").\
                         first()

File examples/association/basic_association.py

 })
 mapper(Item, items)
 mapper(OrderItem, orderitems, properties={
-    'item': relationship(Item, lazy=False)
+    'item': relationship(Item, lazy='joined')
 })
 
 session = create_session()

File examples/association/proxied_association.py

 
 
 mapper(Order, orders, properties={
-    'itemassociations':relationship(OrderItem, cascade="all, delete-orphan", lazy=False)
+    'itemassociations':relationship(OrderItem, cascade="all, delete-orphan", lazy='joined')
 })
 mapper(Item, items)
 mapper(OrderItem, orderitems, properties={
-    'item':relationship(Item, lazy=False)
+    'item':relationship(Item, lazy='joined')
 })
 
 session = create_session()

File examples/beaker_caching/advanced.py

 
 import environment
 from model import Person, Address, cache_address_bits
-from meta import Session, FromCache, RelationCache
-from sqlalchemy.orm import eagerload
+from meta import Session, FromCache, RelationshipCache
+from sqlalchemy.orm import joinedload
 
 def load_name_range(start, end, invalidate=False):
     """Load Person objects on a range of names.
     
     The `Person.addresses` collections are also cached.  Its basically
     another level of tuning here, as that particular cache option
-    can be transparently replaced with eagerload(Person.addresses). 
+    can be transparently replaced with joinedload(Person.addresses). 
     The effect is that each Person and his/her Address collection
     is cached either together or separately, affecting the kind of
     SQL that emits for unloaded Person objects as well as the distribution
 
     # have the "addresses" collection cached separately
     # each lazyload of Person.addresses loads from cache.
-    q = q.options(RelationCache("default", "by_person", Person.addresses))
+    q = q.options(RelationshipCache("default", "by_person", Person.addresses))
     
     # alternatively, eagerly load the "addresses" collection, so that they'd
     # be cached together.   This issues a bigger SQL statement and caches
     # a single, larger value in the cache per person rather than two
     # separate ones.
-    #q = q.options(eagerload(Person.addresses))
+    #q = q.options(joinedload(Person.addresses))
     
     # if requested, invalidate the cache on current criterion.
     if invalidate:

File examples/beaker_caching/relation_caching.py

 import environment
 from model import Person, Address, cache_address_bits
 from meta import Session
-from sqlalchemy.orm import eagerload
+from sqlalchemy.orm import joinedload
 import os
 
-for p in Session.query(Person).options(eagerload(Person.addresses), cache_address_bits):
+for p in Session.query(Person).options(joinedload(Person.addresses), cache_address_bits):
     print p.format_full()
 
 

File examples/elementtree/adjacency_list.py

 
 # setup mappers.  Document will eagerly load a list of _Node objects.
 mapper(Document, documents, properties={
-    '_root':relationship(_Node, lazy=False, cascade="all")
+    '_root':relationship(_Node, lazy='joined', cascade="all")
 })
 
 mapper(_Node, elements, properties={
     'children':relationship(_Node, cascade="all"),
     # eagerly load attributes
-    'attributes':relationship(_Attribute, lazy=False, cascade="all, delete-orphan"),
+    'attributes':relationship(_Attribute, lazy='joined', cascade="all, delete-orphan"),
 })
 
 mapper(_Attribute, attributes)

File examples/elementtree/optimized_al.py

 # they will be ordered in primary key/insert order, so that we can reconstruct
 # an ElementTree structure from the list.
 mapper(Document, documents, properties={
-    '_nodes':relationship(_Node, lazy=False, cascade="all, delete-orphan")
+    '_nodes':relationship(_Node, lazy='joined', cascade="all, delete-orphan")
 })
 
 # the _Node objects change the way they load so that a list of _Nodes will organize
 # ordering to rows which will suffice.
 mapper(_Node, elements, properties={
     'children':relationship(_Node, lazy=None),  # doesnt load; used only for the save relationship
-    'attributes':relationship(_Attribute, lazy=False, cascade="all, delete-orphan"), # eagerly load attributes
+    'attributes':relationship(_Attribute, lazy='joined', cascade="all, delete-orphan"), # eagerly load attributes
 })
 
 mapper(_Attribute, attributes)

File examples/inheritance/polymorph.py

 mapper(Manager, managers, inherits=person_mapper, polymorphic_identity='manager')
 
 mapper(Company, companies, properties={
-    'employees': relationship(Person, lazy=False, backref='company', cascade="all, delete-orphan")
+    'employees': relationship(Person, lazy='joined', backref='company', cascade="all, delete-orphan")
 })
 
 session = create_session()

File lib/sqlalchemy/dialects/oracle/base.py

             return "%(name)s(%(precision)s, %(scale)s)" % {'name':name,'precision': precision, 'scale' : scale}
         
     def visit_VARCHAR(self, type_):
-        return "VARCHAR(%(length)s)" % {'length' : type_.length}
+        if self.dialect.supports_char_length:
+            return "VARCHAR(%(length)s CHAR)" % {'length' : type_.length}
+        else:
+            return "VARCHAR(%(length)s)" % {'length' : type_.length}
 
     def visit_NVARCHAR(self, type_):
         return "NVARCHAR2(%(length)s)" % {'length' : type_.length}
     execution_ctx_cls = OracleExecutionContext
     
     reflection_options = ('oracle_resolve_synonyms', )
-    
+
+    supports_char_length = True    
     
     def __init__(self, 
                 use_ansi=True, 
         self.implicit_returning = self.server_version_info > (10, ) and \
                                         self.__dict__.get('implicit_returning', True)
 
+        self.supports_char_length = self.server_version_info >= (9, )
+
         if self.server_version_info < (9,):
             self.colspecs = self.colspecs.copy()
             self.colspecs.pop(sqltypes.Interval)
                                           resolve_synonyms, dblink,
                                           info_cache=info_cache)
         columns = []
+        if self.supports_char_length:
+            char_length_col = 'char_length'
+        else:
+            char_length_col = 'data_length'
+ 
         c = connection.execute(sql.text(
-                "SELECT column_name, data_type, data_length, data_precision, data_scale, "
+                "SELECT column_name, data_type, %(char_length_col)s, data_precision, data_scale, "
                 "nullable, data_default FROM ALL_TAB_COLUMNS%(dblink)s "
                 "WHERE table_name = :table_name AND owner = :owner " 
-                "ORDER BY column_id" % {'dblink': dblink}),
+                "ORDER BY column_id" % {'dblink': dblink, 'char_length_col':char_length_col}),
                                table_name=table_name, owner=schema)
 
         for row in c:
 
             if coltype == 'NUMBER' :
                 coltype = NUMBER(precision, scale)
-            elif coltype=='CHAR' or coltype=='VARCHAR2':
+            elif coltype in ('VARCHAR2', 'NVARCHAR2', 'CHAR'):
                 coltype = self.ischema_names.get(coltype)(length)
             elif 'WITH TIME ZONE' in coltype: 
                 coltype = TIMESTAMP(timezone=True)

File lib/sqlalchemy/dialects/postgresql/base.py

 
 from sqlalchemy.types import INTEGER, BIGINT, SMALLINT, VARCHAR, \
         CHAR, TEXT, FLOAT, NUMERIC, \
-        TIMESTAMP, TIME, DATE, BOOLEAN
+        DATE, BOOLEAN
 
 class REAL(sqltypes.Float):
     __visit_name__ = "REAL"
     __visit_name__ = "MACADDR"
 PGMacAddr = MACADDR
 
+class TIMESTAMP(sqltypes.TIMESTAMP):
+    def __init__(self, timezone=False, precision=None):
+        super(TIMESTAMP, self).__init__(timezone=timezone)
+        self.precision = precision
+        
+class TIME(sqltypes.TIME):
+    def __init__(self, timezone=False, precision=None):
+        super(TIME, self).__init__(timezone=timezone)
+        self.precision = precision
+    
 class INTERVAL(sqltypes.TypeEngine):
     __visit_name__ = 'INTERVAL'
     def __init__(self, precision=None):
         return self.dialect.identifier_preparer.format_type(type_)
         
     def visit_TIMESTAMP(self, type_):
-        return "TIMESTAMP " + (type_.timezone and "WITH" or "WITHOUT") + " TIME ZONE"
+        return "TIMESTAMP%s %s" % (
+            getattr(type_, 'precision', None) and "(%d)" % type_.precision or "",
+            (type_.timezone and "WITH" or "WITHOUT") + " TIME ZONE"
+        )
 
     def visit_TIME(self, type_):
-        return "TIME " + (type_.timezone and "WITH" or "WITHOUT") + " TIME ZONE"
+        return "TIME%s %s" % (
+            getattr(type_, 'precision', None) and "(%d)" % type_.precision or "",
+            (type_.timezone and "WITH" or "WITHOUT") + " TIME ZONE"
+        )
 
     def visit_INTERVAL(self, type_):
         if type_.precision is not None:
         # format columns
         columns = []
         for name, format_type, default, notnull, attnum, table_oid in rows:
-            ## strip (30) from character varying(30)
-            attype = re.search('([^\([]+)', format_type).group(1)
+            ## strip (5) from character varying(5), timestamp(5) with time zone, etc
+            attype = re.sub(r'\([\d,]+\)', '', format_type)
+            
+            # strip '[]' from integer[], etc.
+            attype = re.sub(r'\[\]', '', attype)
+            
             nullable = not notnull
             is_array = format_type.endswith('[]')
-            try:
-                charlen = re.search('\(([\d,]+)\)', format_type).group(1)
-            except:
-                charlen = False
-            numericprec = False
-            numericscale = False
+            charlen = re.search('\(([\d,]+)\)', format_type)
+            if charlen:
+                charlen = charlen.group(1)
+            kwargs = {}
+                
             if attype == 'numeric':
-                if charlen is False:
-                    numericprec, numericscale = (None, None)
+                if charlen:
+                    prec, scale = charlen.split(',')
+                    args = (int(prec), int(scale))
                 else:
-                    numericprec, numericscale = charlen.split(',')
-                charlen = False
+                    args = ()
             elif attype == 'double precision':
-                numericprec, numericscale = (53, False)
-                charlen = False
+                args = (53, )
             elif attype == 'integer':
-                numericprec, numericscale = (32, 0)
-                charlen = False
-            args = []
-            for a in (charlen, numericprec, numericscale):
-                if a is None:
-                    args.append(None)
-                elif a is not False:
-                    args.append(int(a))
-            kwargs = {}
-            if attype == 'timestamp with time zone':
+                args = (32, 0)
+            elif attype in ('timestamp with time zone', 'time with time zone'):
                 kwargs['timezone'] = True
-            elif attype == 'timestamp without time zone':
+                if charlen:
+                    kwargs['precision'] = int(charlen)
+                args = ()
+            elif attype in ('timestamp without time zone', 'time without time zone', 'time'):
                 kwargs['timezone'] = False
+                if charlen:
+                    kwargs['precision'] = int(charlen)
+                args = ()
+            elif attype in ('interval','interval year to month','interval day to second'):
+                if charlen:
+                    kwargs['precision'] = int(charlen)
+                args = ()
+            elif charlen:
+                args = (int(charlen),)
+            else:
+                args = ()
+            
             if attype in self.ischema_names:
                 coltype = self.ischema_names[attype]
             elif attype in enums:

File lib/sqlalchemy/dialects/postgresql/psycopg2.py

 Connecting
 ----------
 
-URLs are of the form `postgresql+psycopg2://user@password@host:port/dbname[?key=value&key=value...]`.
+URLs are of the form `postgresql+psycopg2://user:password@host:port/dbname[?key=value&key=value...]`.
 
 psycopg2-specific keyword arguments which are accepted by :func:`~sqlalchemy.create_engine()` are:
 
 
 The psycopg2 dialect fully supports SAVEPOINT and two-phase commit operations.
 
+NOTICE logging
+---------------
+
+The psycopg2 dialect will log Postgresql NOTICE messages via the 
+``sqlalchemy.dialects.postgresql`` logger::
+
+    import logging
+    logging.getLogger('sqlalchemy.dialects.postgresql').setLevel(logging.INFO)
+
 
 Per-Statement Execution Options
 -------------------------------
 
 """
 
-import random, re
+import random
+import re
 import decimal
+import logging
 
 from sqlalchemy import util
 from sqlalchemy import processors
                                             PGIdentifierPreparer, PGExecutionContext, \
                                             ENUM, ARRAY
 
+
+logger = logging.getLogger('sqlalchemy.dialects.postgresql')
+
+
 class _PGNumeric(sqltypes.Numeric):
     def bind_processor(self, dialect):
         return None
             return self._connection.connection.cursor()
 
     def get_result_proxy(self):
+        if logger.isEnabledFor(logging.INFO):
+            self._log_notices(self.cursor)
+        
         if self.__is_server_side:
             return base.BufferedRowResultProxy(self)
         else:
             return base.ResultProxy(self)
 
+    def _log_notices(self, cursor):
+        for notice in cursor.connection.notices:
+            # NOTICE messages have a 
+            # newline character at the end
+            logger.info(notice.rstrip())
+
+        cursor.connection.notices[:] = []
+
 
 class PGCompiler_psycopg2(PGCompiler):
     def visit_mod(self, binary, **kw):
             return connect
         else:
             return base_on_connect
-            
+
     def create_connect_args(self, url):
         opts = url.translate_connect_args(username='user')
         if 'port' in opts:

File lib/sqlalchemy/dialects/sqlite/base.py

     colspecs = colspecs
     isolation_level = None
 
+    supports_cast = True
+    supports_default_values = True
+
     def __init__(self, isolation_level=None, native_datetime=False, **kwargs):
         default.DefaultDialect.__init__(self, **kwargs)
         if isolation_level and isolation_level not in ('SERIALIZABLE',
         # conversions (and perhaps datetime/time as well on some 
         # hypothetical driver ?)
         self.native_datetime = native_datetime
+
+        if self.dbapi is not None:
+            self.supports_default_values = \
+                                self.dbapi.sqlite_version_info >= (3, 3, 8)
+            self.supports_cast = \
+                                self.dbapi.sqlite_version_info >= (3, 2, 3)
+
         
     def on_connect(self):
         if self.isolation_level is not None:

File lib/sqlalchemy/dialects/sqlite/pysqlite.py

     
     def __init__(self, **kwargs):
         SQLiteDialect.__init__(self, **kwargs)
-        def vers(num):
-            return tuple([int(x) for x in num.split('.')])
+
         if self.dbapi is not None:
             sqlite_ver = self.dbapi.version_info
-            if sqlite_ver < (2, 1, '3'):
+            if sqlite_ver < (2, 1, 3):
                 util.warn(
                     ("The installed version of pysqlite2 (%s) is out-dated "
                      "and will cause errors in some cases.  Version 2.1.3 "
                      "or greater is recommended.") %
                     '.'.join([str(subver) for subver in sqlite_ver]))
-            if self.dbapi.sqlite_version_info < (3, 3, 8):
-                self.supports_default_values = False
-        self.supports_cast = (self.dbapi is None or vers(self.dbapi.sqlite_version) >= vers("3.2.3"))
-
 
     @classmethod
     def dbapi(cls):

File lib/sqlalchemy/ext/declarative.py

     
 def _as_declarative(cls, classname, dict_):
 
-    # doing it this way enables these attributes to be descriptors,
-    # see below...
-    get_mapper_args = '__mapper_args__' in dict_
-    get_table_args = '__table_args__' in dict_
-    
     # dict_ will be a dictproxy, which we can't write to, and we need to!
     dict_ = dict(dict_)
 
     column_copies = dict()
-
+    unmapped_mixins = False
     for base in cls.__bases__:
         names = dir(base)
         if not _is_mapped_class(base):
+            unmapped_mixins = True
             for name in names:
-                obj = getattr(base,name)
+                obj = getattr(base,name, None)
                 if isinstance(obj, Column):
                     dict_[name]=column_copies[obj]=obj.copy()
-            get_mapper_args = get_mapper_args or getattr(base,'__mapper_args__',None)
-            get_table_args = get_table_args or getattr(base,'__table_args__',None)
-            tablename = getattr(base,'__tablename__',None)
-            if tablename:
-                # subtle: if tablename is a descriptor here, we actually
-                # put the wrong value in, but it serves as a marker to get
-                # the right value value...
-                dict_['__tablename__']=tablename
+
+    # doing it this way enables these attributes to be descriptors
+    get_mapper_args = '__mapper_args__' in dict_
+    get_table_args = '__table_args__' in dict_
+    if unmapped_mixins:
+        get_mapper_args = get_mapper_args or getattr(cls,'__mapper_args__',None)
+        get_table_args = get_table_args or getattr(cls,'__table_args__',None)
+        tablename = getattr(cls,'__tablename__',None)
+        if tablename:
+            # subtle: if tablename is a descriptor here, we actually
+            # put the wrong value in, but it serves as a marker to get
+            # the right value value...
+            dict_['__tablename__']=tablename
 
     # now that we know whether or not to get these, get them from the class
     # if we should, enabling them to be decorators

File lib/sqlalchemy/orm/__init__.py

     'eagerload_all',
     'extension',
     'join',
+    'joinedload',
+    'joinedload_all',
     'lazyload',
     'mapper',
     'make_transient',
     'relation',
     'scoped_session',
     'sessionmaker',
+    'subqueryload',
+    'subqueryload_all',
     'synonym',
     'undefer',
     'undefer_group',
 
       Available cascades are:
 
-        ``save-update`` - cascade the "add()" operation (formerly
-        known as save() and update())
+        * ``save-update`` - cascade the :meth:`~sqlalchemy.orm.session.Session.add` 
+          operation.  This cascade applies both to future and
+          past calls to :meth:`~sqlalchemy.orm.session.Session.add`, 
+          meaning new items added to a collection or scalar relationship
+          get placed into the same session as that of the parent, and 
+          also applies to items which have been removed from this 
+          relationship but are still part of unflushed history.
 
-        ``merge`` - cascade the "merge()" operation
+        * ``merge`` - cascade the :meth:`~sqlalchemy.orm.session.Session.merge`
+          operation
 
-        ``expunge`` - cascade the "expunge()" operation
+        * ``expunge`` - cascade the :meth:`~sqlalchemy.orm.session.Session.expunge`
+          operation
 
-        ``delete`` - cascade the "delete()" operation
+        * ``delete`` - cascade the :meth:`~sqlalchemy.orm.session.Session.delete`
+          operation
 
-        ``delete-orphan`` - if an item of the child's type with no
-        parent is detected, mark it for deletion.  Note that this
-        option prevents a pending item of the child's class from being
-        persisted without a parent present.
+        * ``delete-orphan`` - if an item of the child's type with no
+          parent is detected, mark it for deletion.  Note that this
+          option prevents a pending item of the child's class from being
+          persisted without a parent present.
 
-        ``refresh-expire`` - cascade the expire() and refresh()
-        operations
+        * ``refresh-expire`` - cascade the :meth:`~sqlalchemy.orm.session.Session.expire` 
+          and :meth:`~sqlalchemy.orm.session.Session.refresh` operations
 
-        ``all`` - shorthand for "save-update,merge, refresh-expire,
-        expunge, delete"
+        * ``all`` - shorthand for "save-update,merge, refresh-expire,
+          expunge, delete"
 
     :param collection_class:
       a class or callable that returns a new list-holding object. will
       change the value used in the operation.
 
     :param foreign_keys:
-
       a list of columns which are to be used as "foreign key" columns.
       this parameter should be used in conjunction with explicit
       ``primaryjoin`` and ``secondaryjoin`` (if needed) arguments, and
       the table-defined foreign keys.
 
     :param innerjoin=False:
-      when ``True``, eager loads will use an inner join to join
+      when ``True``, joined eager loads will use an inner join to join
       against related tables instead of an outer join.  The purpose
       of this option is strictly one of performance, as inner joins
       generally perform better than outer joins.  This flag can
       
     :param join_depth:
       when non-``None``, an integer value indicating how many levels
-      deep eagerload joins should be constructed on a self-referring
-      or cyclical relationship.  The number counts how many times the
-      same Mapper shall be present in the loading condition along a
-      particular join branch.  When left at its default of ``None``,
-      eager loads will automatically stop chaining joins when they
-      encounter a mapper which is already higher up in the chain.
+      deep "eager" loaders should join on a self-referring or cyclical 
+      relationship.  The number counts how many times the same Mapper 
+      shall be present in the loading condition along a particular join 
+      branch.  When left at its default of ``None``, eager loaders
+      will stop chaining when they encounter a the same target mapper 
+      which is already higher up in the chain.  This option applies
+      both to joined- and subquery- eager loaders.
 
-    :param lazy=(True|False|None|'dynamic'):
+    :param lazy=('select'|'joined'|'subquery'|'noload'|'dynamic'):
       specifies how the related items should be loaded. Values include:
 
-      True - items should be loaded lazily when the property is first
-             accessed.
+      * 'select' - items should be loaded lazily when the property is first
+        accessed.
 
-      False - items should be loaded "eagerly" in the same query as
-              that of the parent, using a JOIN or LEFT OUTER JOIN.
+      * 'joined' - items should be loaded "eagerly" in the same query as
+        that of the parent, using a JOIN or LEFT OUTER JOIN.
+              
+      * 'subquery' - items should be loaded "eagerly" within the same
+        query as that of the parent, using a second SQL statement
+        which issues a JOIN to a subquery of the original
+        statement.
 
-      None - no loading should occur at any time.  This is to support
-             "write-only" attributes, or attributes which are
-             populated in some manner specific to the application.
+      * 'noload' - no loading should occur at any time.  This is to support
+         "write-only" attributes, or attributes which are
+         populated in some manner specific to the application.
 
-      'dynamic' - a ``DynaLoader`` will be attached, which returns a
-                  ``Query`` object for all read operations.  The
-                  dynamic- collection supports only ``append()`` and
-                  ``remove()`` for write operations; changes to the
-                  dynamic property will not be visible until the data
-                  is flushed to the database.
-
+      * 'dynamic' - the attribute will return a pre-configured
+        :class:`~sqlalchemy.orm.query.Query` object for all read 
+        operations, onto which further filtering operations can be
+        applied before iterating the results.  The dynamic 
+        collection supports a limited set of mutation operations,
+        allowing ``append()`` and ``remove()``.  Changes to the
+        collection will not be visible until flushed 
+        to the database, where it is then refetched upon iteration.
+       
+      * True - a synonym for 'select'
+       
+      * False - a synonyn for 'joined'
+       
+      * None - a synonym for 'noload'
+       
     :param order_by:
       indicates the ordering that should be applied when loading these
       items.
     return ExtensionOption(ext)
 
 @sa_util.accepts_a_list_as_starargs(list_deprecation='deprecated')
-def eagerload(*keys, **kw):
+def joinedload(*keys, **kw):
     """Return a ``MapperOption`` that will convert the property of the given
-    name into an eager load.
+    name into an joined eager load.
+
+    .. note:: This function is known as :func:`eagerload` in all versions
+      of SQLAlchemy prior to version 0.6beta3, including the 0.5 and 0.4 series.
+      :func:`eagerload` will remain available for 
+      the foreseeable future in order to enable cross-compatibility.
 
     Used with :meth:`~sqlalchemy.orm.query.Query.options`.
 
     examples::
     
-        # eagerload the "orders" colleciton on "User"
-        query(User).options(eagerload(User.orders))
+        # joined-load the "orders" colleciton on "User"
+        query(User).options(joinedload(User.orders))
         
-        # eagerload the "keywords" collection on each "Item",
+        # joined-load the "keywords" collection on each "Item",
         # but not the "items" collection on "Order" - those 
         # remain lazily loaded.
-        query(Order).options(eagerload(Order.items, Item.keywords))
+        query(Order).options(joinedload(Order.items, Item.keywords))
 
-        # to eagerload across both, use eagerload_all()
-        query(Order).options(eagerload_all(Order.items, Item.keywords))
+        # to joined-load across both, use joinedload_all()
+        query(Order).options(joinedload_all(Order.items, Item.keywords))
 
-    :func:`eagerload` also accepts a keyword argument `innerjoin=True` which
+    :func:`joinedload` also accepts a keyword argument `innerjoin=True` which
     indicates using an inner join instead of an outer::
     
-        query(Order).options(eagerload(Order.user, innerjoin=True))
+        query(Order).options(joinedload(Order.user, innerjoin=True))
         
-    Note that the join created by :func:`eagerload` is aliased such that
-    no other aspects of the query will affect what it loads.  To use eager
+    Note that the join created by :func:`joinedload` is aliased such that
+    no other aspects of the query will affect what it loads.  To use joined eager
     loading with a join that is constructed manually using :meth:`~sqlalchemy.orm.query.Query.join`
     or :func:`~sqlalchemy.orm.join`, see :func:`contains_eager`.
     
+    See also:  :func:`subqueryload`, :func:`lazyload`
+    
     """
     innerjoin = kw.pop('innerjoin', None)
     if innerjoin is not None:
         return (
-             strategies.EagerLazyOption(keys, lazy=False), 
+             strategies.EagerLazyOption(keys, lazy='joined'), 
              strategies.EagerJoinOption(keys, innerjoin)
          )
     else:
-        return strategies.EagerLazyOption(keys, lazy=False)
+        return strategies.EagerLazyOption(keys, lazy='joined')
 
 @sa_util.accepts_a_list_as_starargs(list_deprecation='deprecated')
-def eagerload_all(*keys, **kw):
+def joinedload_all(*keys, **kw):
     """Return a ``MapperOption`` that will convert all properties along the
-    given dot-separated path into an eager load.
+    given dot-separated path into an joined eager load.
+
+    .. note:: This function is known as :func:`eagerload_all` in all versions
+      of SQLAlchemy prior to version 0.6beta3, including the 0.5 and 0.4 series.
+      :func:`eagerload_all` will remain available for 
+      the foreseeable future in order to enable cross-compatibility.
 
     Used with :meth:`~sqlalchemy.orm.query.Query.options`.
 
     For example::
 
-        query.options(eagerload_all('orders.items.keywords'))...
+        query.options(joinedload_all('orders.items.keywords'))...
 
     will set all of 'orders', 'orders.items', and 'orders.items.keywords' to
-    load in one eager load.
+    load in one joined eager load.
 
     Individual descriptors are accepted as arguments as well::
     
-        query.options(eagerload_all(User.orders, Order.items, Item.keywords))
+        query.options(joinedload_all(User.orders, Order.items, Item.keywords))
 
     The keyword arguments accept a flag `innerjoin=True|False` which will 
     override the value of the `innerjoin` flag specified on the relationship().
 
+    See also:  :func:`subqueryload_all`, :func:`lazyload`
+
     """
     innerjoin = kw.pop('innerjoin', None)
     if innerjoin is not None:
         return (
-            strategies.EagerLazyOption(keys, lazy=False, chained=True), 
+            strategies.EagerLazyOption(keys, lazy='joined', chained=True), 
             strategies.EagerJoinOption(keys, innerjoin, chained=True)
         )
     else:
-        return strategies.EagerLazyOption(keys, lazy=False, chained=True)
+        return strategies.EagerLazyOption(keys, lazy='joined', chained=True)
 
+def eagerload(*args, **kwargs):
+    """A synonym for :func:`joinedload()`."""
+    return joinedload(*args, **kwargs)
+    
+def eagerload_all(*args, **kwargs):
+    """A synonym for :func:`joinedload_all()`"""
+    return joinedload_all(*args, **kwargs)
+    
+def subqueryload(*keys):
+    """Return a ``MapperOption`` that will convert the property 
+    of the given name into an subquery eager load.
+
+    .. note:: This function is new as of SQLAlchemy version 0.6beta3.
+
+    Used with :meth:`~sqlalchemy.orm.query.Query.options`.
+
+    examples::
+    
+        # subquery-load the "orders" colleciton on "User"
+        query(User).options(subqueryload(User.orders))
+        
+        # subquery-load the "keywords" collection on each "Item",
+        # but not the "items" collection on "Order" - those 
+        # remain lazily loaded.
+        query(Order).options(subqueryload(Order.items, Item.keywords))
+
+        # to subquery-load across both, use subqueryload_all()
+        query(Order).options(subqueryload_all(Order.items, Item.keywords))
+
+    See also:  :func:`joinedload`, :func:`lazyload`
+    
+    """
+    return strategies.EagerLazyOption(keys, lazy="subquery")
+
+def subqueryload_all(*keys):
+    """Return a ``MapperOption`` that will convert all properties along the
+    given dot-separated path into a subquery eager load.
+
+    .. note:: This function is new as of SQLAlchemy version 0.6beta3.
+
+    Used with :meth:`~sqlalchemy.orm.query.Query.options`.
+
+    For example::
+
+        query.options(subqueryload_all('orders.items.keywords'))...
+
+    will set all of 'orders', 'orders.items', and 'orders.items.keywords' to
+    load in one subquery eager load.
+
+    Individual descriptors are accepted as arguments as well::
+    
+        query.options(subqueryload_all(User.orders, Order.items, Item.keywords))
+
+    See also:  :func:`joinedload_all`, :func:`lazyload`
+
+    """
+    return strategies.EagerLazyOption(keys, lazy="subquery", chained=True)
+    
 @sa_util.accepts_a_list_as_starargs(list_deprecation='deprecated')
 def lazyload(*keys):
     """Return a ``MapperOption`` that will convert the property of the given
 
     Used with :meth:`~sqlalchemy.orm.query.Query.options`.
 
+    See also:  :func:`eagerload`, :func:`subqueryload`
+
     """
     return strategies.EagerLazyOption(keys, lazy=True)
 
 
     Used with :meth:`~sqlalchemy.orm.query.Query.options`.
 
+    See also:  :func:`lazyload`, :func:`eagerload`, :func:`subqueryload`
+
     """
     return strategies.EagerLazyOption(keys, lazy=None)
 
         raise exceptions.ArgumentError("Invalid kwargs for contains_eager: %r" % kwargs.keys())
 
     return (
-            strategies.EagerLazyOption(keys, lazy=False, propagate_to_loaders=False), 
+            strategies.EagerLazyOption(keys, lazy='joined', propagate_to_loaders=False), 
             strategies.LoadEagerFromAliasOption(keys, alias=alias)
         )
 

File lib/sqlalchemy/orm/interfaces.py

         self.do_init()
         self._compile_finished = True
 
+    @property
+    def class_attribute(self):
+        """Return the class-bound descriptor corresponding to this MapperProperty."""
+        
+        return getattr(self.parent.class_, self.key)
+        
     def do_init(self):
         """Perform subclass-specific initialization post-mapper-creation steps.
 
     
     """
 
-    def __get_context_strategy(self, context, path):
+    def _get_context_strategy(self, context, path):
         cls = context.attributes.get(("loaderstrategy", _reduce_path(path)), None)
         if cls:
             try:
         return strategy
 
     def setup(self, context, entity, path, adapter, **kwargs):
-        self.__get_context_strategy(context, path + (self.key,)).\
+        self._get_context_strategy(context, path + (self.key,)).\
                     setup_query(context, entity, path, adapter, **kwargs)
 
     def create_row_processor(self, context, path, mapper, row, adapter):
-        return self.__get_context_strategy(context, path + (self.key,)).\
+        return self._get_context_strategy(context, path + (self.key,)).\
                     create_row_processor(context, path, mapper, row, adapter)
 
     def do_init(self):
         self._process(query, False)
 
     def _process(self, query, raiseerr):
-        paths, mappers = self.__get_paths(query, raiseerr)
+        paths, mappers = self._get_paths(query, raiseerr)
         if paths:
             self.process_query_property(query, paths, mappers)
 
     def process_query_property(self, query, paths, mappers):
         pass
 
-    def __find_entity(self, query, mapper, raiseerr):
-        from sqlalchemy.orm.util import _class_to_mapper, _is_aliased_class
-
-        if _is_aliased_class(mapper):
-            searchfor = mapper
-            isa = False
-        else:
-            searchfor = _class_to_mapper(mapper)
-            isa = True
-            
-        for ent in query._mapper_entities:
-            if searchfor is ent.path_entity or (isa and searchfor.common_parent(ent.path_entity)):
-                return ent
-        else:
-            if raiseerr:
-                raise sa_exc.ArgumentError("Can't find entity %s in Query.  Current list: %r" 
-                    % (searchfor, [str(m.path_entity) for m in query._entities]))
-            else:
-                return None
-
     def __getstate__(self):
         d = self.__dict__.copy()
         d['key'] = ret = []
         state['key'] = tuple(ret)
         self.__dict__ = state
 
-    def __get_paths(self, query, raiseerr):
+    def _find_entity(self, query, mapper, raiseerr):
+        from sqlalchemy.orm.util import _class_to_mapper, _is_aliased_class
+
+        if _is_aliased_class(mapper):
+            searchfor = mapper
+            isa = False
+        else:
+            searchfor = _class_to_mapper(mapper)
+            isa = True
+
+        for ent in query._mapper_entities:
+            if searchfor is ent.path_entity or (
+                                isa and
+                                searchfor.common_parent(ent.path_entity)):
+                return ent
+        else:
+            if raiseerr:
+                raise sa_exc.ArgumentError(
+                    "Can't find entity %s in Query.  Current list: %r" 
+                    % (searchfor, [
+                                str(m.path_entity) for m in query._entities
+                                ]))
+            else:
+                return None
+
+    def _get_paths(self, query, raiseerr):
         path = None
         entity = None
         l = []
         # with an existing path
         current_path = list(query._current_path)
             
-        if self.mapper:
-            entity = self.__find_entity(query, self.mapper, raiseerr)
-            mapper = entity.mapper
-            path_element = entity.path_entity
-
+        tokens = []
         for key in util.to_list(self.key):
             if isinstance(key, basestring):
-                tokens = key.split('.')
+                tokens += key.split('.')
             else:
-                tokens = [key]
-            for token in tokens:
-                if isinstance(token, basestring):
+                tokens += [key]
+        
+        for token in tokens:
+            if isinstance(token, basestring):
+                if not entity:
+                    if current_path:
+                        if current_path[1] == token:
+                            current_path = current_path[2:]
+                            continue
+                    
+                    entity = query._entity_zero()
+                    path_element = entity.path_entity
+                    mapper = entity.mapper
+                mappers.append(mapper)
+                prop = mapper.get_property(
+                                    token, 
+                                    resolve_synonyms=True, 
+                                    raiseerr=raiseerr)
+                key = token
+            elif isinstance(token, PropComparator):
+                prop = token.property
+                if not entity:
+                    if current_path:
+                        if current_path[0:2] == [token.parententity, prop.key]:
+                            current_path = current_path[2:]
+                            continue
+
+                    entity = self._find_entity(
+                                            query, 
+                                            token.parententity, 
+                                            raiseerr)
                     if not entity:
-                        entity = query._entity_zero()
-                        path_element = entity.path_entity
-                        mapper = entity.mapper
-                    mappers.append(mapper)
-                    prop = mapper.get_property(token, resolve_synonyms=True, raiseerr=raiseerr)
-                    key = token
-                elif isinstance(token, PropComparator):
-                    prop = token.property
-                    if not entity:
-                        entity = self.__find_entity(query, token.parententity, raiseerr)
-                        if not entity:
-                            return [], []
-                        path_element = entity.path_entity
-                    mappers.append(prop.parent)
-                    key = prop.key
-                else:
-                    raise sa_exc.ArgumentError("mapper option expects string key "
-                                                "or list of attributes")
+                        return [], []
+                    path_element = entity.path_entity
+                    mapper = entity.mapper
+                mappers.append(prop.parent)
+                key = prop.key
+            else:
+                raise sa_exc.ArgumentError("mapper option expects string key "
+                                            "or list of attributes")
 
-                if current_path and key == current_path[1]:
-                    current_path = current_path[2:]
-                    continue
-                    
-                if prop is None:
-                    return [], []
+            if prop is None:
+                return [], []
 
-                path = build_path(path_element, prop.key, path)
-                l.append(path)
-                if getattr(token, '_of_type', None):
-                    path_element = mapper = token._of_type
-                else:
-                    path_element = mapper = getattr(prop, 'mapper', None)
+            path = build_path(path_element, prop.key, path)
+            l.append(path)
+            if getattr(token, '_of_type', None):
+                path_element = mapper = token._of_type
+            else:
+                path_element = mapper = getattr(prop, 'mapper', None)
 
-                if path_element:
-                    path_element = path_element
+            if path_element:
+                path_element = path_element
                     
                 
         # if current_path tokens remain, then
         # we didn't have an exact path match.
         if current_path:
             return [], []
-            
+
         return l, mappers
 
 class AttributeExtension(object):
     for an operation by a StrategizedProperty.
     """
 
-    def is_chained(self):
-        return False
+    is_chained = False
 
     def process_query_property(self, query, paths, mappers):
-        # __get_context_strategy may receive the path in terms of
+        # _get_context_strategy may receive the path in terms of
         # a base mapper - e.g.  options(eagerload_all(Company.employees, Engineer.machines))
         # in the polymorphic tests leads to "(Person, 'machines')" in 
         # the path due to the mechanics of how the eager strategy builds
         # up the path
-        if self.is_chained():
+        if self.is_chained:
             for path in paths:
                 query._attributes[("loaderstrategy", _reduce_path(path))] = \
                  self.get_strategy_class()

File lib/sqlalchemy/orm/properties.py

         self.comparator_factory = comparator_factory or RelationshipProperty.Comparator
         self.comparator = self.comparator_factory(self, None)
         util.set_creation_order(self)
-
+        
         if strategy_class:
             self.strategy_class = strategy_class
-        elif self.lazy == 'dynamic':
+        elif self.lazy== 'dynamic':
             from sqlalchemy.orm import dynamic
             self.strategy_class = dynamic.DynaLoader
-        elif self.lazy is False:
-            self.strategy_class = strategies.EagerLoader
-        elif self.lazy is None:
-            self.strategy_class = strategies.NoLoader
         else:
-            self.strategy_class = strategies.LazyLoader
-
+            self.strategy_class = strategies.factory(self.lazy)
+            
         self._reverse_property = set()
 
         if cascade is not False:

File lib/sqlalchemy/orm/query.py

                 self._polymorphic_adapters[m.mapped_table] = self._polymorphic_adapters[m.local_table] = adapter
 
     def _set_select_from(self, *obj):
-        
+
         fa = []
         for from_obj in obj:
             if isinstance(from_obj, expression._SelectBaseMixin):
 
         self._from_obj = tuple(fa)
 
-        # TODO: only use this adapter for from_self() ?   right
-        # now its usage is somewhat arbitrary.
-        if len(self._from_obj) == 1 and isinstance(self._from_obj[0], expression.Alias):
+        if len(self._from_obj) == 1 and \
+            isinstance(self._from_obj[0], expression.Alias):
             equivs = self.__all_equivs()
             self._from_obj_alias = sql_util.ColumnAdapter(self._from_obj[0], equivs)
         
     @_generative()
     def _adapt_all_clauses(self):
         self._disable_orm_filtering = True
-
+    
+    def _adapt_col_list(self, cols):
+        return [
+                    self._adapt_clause(expression._literal_as_text(o), True, True) 
+                    for o in cols
+                ]
+        
     def _adapt_clause(self, clause, as_filter, orm_only):
         adapters = []
         if as_filter and self._filter_aliases:
                         statement._annotate({'_halt_adapt': True})
 
     def subquery(self):
-        """return the full SELECT statement represented by this Query, embedded within an Alias.
+        """return the full SELECT statement represented by this Query, 
+        embedded within an Alias.
 
         Eager JOIN generation within the query is disabled.
 
 
     @_generative()
     def enable_eagerloads(self, value):
-        """Control whether or not eager joins are rendered.
+        """Control whether or not eager joins and subqueries are 
+        rendered.
 
         When set to False, the returned Query will not render
-        eager joins regardless of eagerload() options
-        or mapper-level lazy=False configurations.
+        eager joins regardless of :func:`~sqlalchemy.orm.joinedload`,
+        :func:`~sqlalchemy.orm.subqueryload` options
+        or mapper-level ``lazy='joined'``/``lazy='subquery'``
+        configurations.
 
         This is used primarily when nesting the Query's
         statement into a subquery or other
         overwritten.
 
         In particular, it's usually impossible to use this setting with
-        eagerly loaded collections (i.e. any lazy=False) since those
-        collections will be cleared for a new load when encountered in a
-        subsequent result batch.
+        eagerly loaded collections (i.e. any lazy='joined' or 'subquery') 
+        since those collections will be cleared for a new load when 
+        encountered in a subsequent result batch.   In the case of 'subquery'
+        loading, the full result for all rows is fetched which generally
+        defeats the purpose of :meth:`~sqlalchemy.orm.query.Query.yield_per`.
 
         Also note that many DBAPIs do not "stream" results, pre-buffering
         all rows before making them available, including mysql-python and 
-        psycopg2.  yield_per() will also set the ``stream_results`` execution
+        psycopg2.  :meth:`~sqlalchemy.orm.query.Query.yield_per` will also 
+        set the ``stream_results`` execution
         option to ``True``, which currently is only understood by psycopg2
         and causes server side cursors to be used.
         
         those being selected.
 
         """
-        fromclause = self.with_labels().enable_eagerloads(False).statement.correlate(None)
+        fromclause = self.with_labels().enable_eagerloads(False).\
+                                    statement.correlate(None)
         q = self._from_selectable(fromclause)
         if entities:
             q._set_entities(entities)
         return q
-
+    
     @_generative()
     def _from_selectable(self, fromclause):
-        self._statement = self._criterion = None
-        self._order_by = self._group_by = self._distinct = False
-        self._limit = self._offset = None
+        for attr in ('_statement', '_criterion', '_order_by', '_group_by',
+                '_limit', '_offset', '_joinpath', '_joinpoint', 
+                '_distinct'
+        ):
+            self.__dict__.pop(attr, None)
         self._set_select_from(fromclause)
         old_entities = self._entities
         self._entities = []
             return None
 
     @_generative()
-    def add_column(self, column):
-        """Add a SQL ColumnElement to the list of result columns to be returned."""
+    def add_columns(self, *column):
+        """Add one or more column expressions to the list 
+        of result columns to be returned."""
 
         self._entities = list(self._entities)
         l = len(self._entities)
-        _ColumnEntity(self, column)
+        for c in column:
+            _ColumnEntity(self, c)
         # _ColumnEntity may add many entities if the
         # given arg is a FROM clause
         self._setup_aliasizers(self._entities[l:])
 
+    @util.pending_deprecation("add_column() superceded by add_columns()")
+    def add_column(self, column):
+        """Add a column expression to the list of result columns
+        to be returned."""
+        
+        return self.add_columns(column)
+
     def options(self, *args):
         """Return a new Query object, applying the given list of
         MapperOptions.
 
         return self.filter(sql.and_(*clauses))
 
-
     @_generative(_no_statement_condition, _no_limit_offset)
     @util.accepts_a_list_as_starargs(list_deprecation='deprecated')
     def order_by(self, *criterion):
         if len(criterion) == 1 and criterion[0] is None:
             self._order_by = None
         else:
-            criterion = [self._adapt_clause(expression._literal_as_text(o), True, True) for o in criterion]
+            criterion = self._adapt_col_list(criterion)
 
             if self._order_by is False or self._order_by is None:
                 self._order_by = criterion
 
         criterion = list(chain(*[_orm_columns(c) for c in criterion]))
 
-        criterion = [self._adapt_clause(expression._literal_as_text(o), True, True) for o in criterion]
+        criterion = self._adapt_col_list(criterion)
 
         if self._group_by is False:
             self._group_by = criterion
 
                 descriptor, prop = _entity_descriptor(left_entity, onclause)
                 onclause = descriptor
+            
+            # check for q.join(Class.propname, from_joinpoint=True)
+            # and Class is that of the current joinpoint
+            elif from_joinpoint and isinstance(onclause, interfaces.PropComparator):
+                left_entity = onclause.parententity
+                
+                left_mapper, left_selectable, left_is_aliased = \
+                                    _entity_info(self._joinpoint_zero())
+                if left_mapper is left_entity:
+                    left_entity = self._joinpoint_zero()
+                    descriptor, prop = _entity_descriptor(left_entity, onclause.key)
+                    onclause = descriptor
 
             if isinstance(onclause, interfaces.PropComparator):
                 if right_entity is None:
                         right_entity = of_type
                     else:
                         right_entity = onclause.property.mapper
-                
+            
                 left_entity = onclause.parententity
                 
                 prop = onclause.property
         if left is None:
             left = self._joinpoint_zero()
 
+        if left is right and \
+                not create_aliases:
+            raise sa_exc.InvalidRequestError(
+                        "Can't construct a join from %s to %s, they are the same entity" % 
+                        (left, right))
+            
         left_mapper, left_selectable, left_is_aliased = _entity_info(left)
         right_mapper, right_selectable, is_aliased_class = _entity_info(right)
 
            
         first() applies a limit of one within the generated SQL, so that
         only one primary entity row is generated on the server side 
-        (note this may consist of multiple result rows if eagerly loaded
+        (note this may consist of multiple result rows if join-loaded 
         collections are present).
 
         Calling ``first()`` results in an execution of the underlying query.
         query._entities.append(self)
 
     def _get_entity_clauses(self, query, context):
-
+            
         adapter = None
         if not self.is_aliased_class and query._polymorphic_adapters:
             adapter = query._polymorphic_adapters.get(self.mapper, None)
 
         if not adapter and self.adapter:
             adapter = self.adapter
-
+        
         if adapter:
             if query._from_obj_alias:
                 ret = adapter.wrap(query._from_obj_alias)
     def __str__(self):
         return str(self.mapper)
 
-
 class _ColumnEntity(_QueryEntity):
     """Column/expression based entity."""
 

File lib/sqlalchemy/orm/strategies.py

 # This module is part of SQLAlchemy and is released under
 # the MIT License: http://www.opensource.org/licenses/mit-license.php
 
-"""sqlalchemy.orm.interfaces.LoaderStrategy implementations, and related MapperOptions."""
+"""sqlalchemy.orm.interfaces.LoaderStrategy 
+   implementations, and related MapperOptions."""
 
 from sqlalchemy import exc as sa_exc
 from sqlalchemy import sql, util, log
     )
 from sqlalchemy.orm import session as sessionlib
 from sqlalchemy.orm import util as mapperutil
+import itertools
 
 def _register_attribute(strategy, mapper, useobject,
         compare_function=None, 
         attribute_ext.insert(0, _SingleParentValidator(prop))
 
     if prop.key in prop.parent._validators:
-        attribute_ext.insert(0, mapperutil.Validator(prop.key, prop.parent._validators[prop.key]))
+        attribute_ext.insert(0, 
+            mapperutil.Validator(prop.key, prop.parent._validators[prop.key])
+        )
     
     if useobject:
         attribute_ext.append(sessionlib.UOWEventHandler(prop.key))
                 )
 
 class UninstrumentedColumnLoader(LoaderStrategy):
-    """Represent the strategy for a MapperProperty that doesn't instrument the class.
+    """Represent the a non-instrumented MapperProperty.
     
     The polymorphic_on argument of mapper() often results in this,
     if the argument is against the with_polymorphic selectable.
     def init(self):
         self.columns = self.parent_property.columns
 
-    def setup_query(self, context, entity, path, adapter, column_collection=None, **kwargs):
+    def setup_query(self, context, entity, path, adapter, 
+                            column_collection=None, **kwargs):
         for c in self.columns:
             if adapter:
                 c = adapter.columns[c]
             column_collection.append(c)
 
     def create_row_processor(self, selectcontext, path, mapper, row, adapter):
-        return (None, None)
+        return None, None
 
 class ColumnLoader(LoaderStrategy):
     """Strategize the loading of a plain column-based MapperProperty."""
         self.columns = self.parent_property.columns
         self.is_composite = hasattr(self.parent_property, 'composite_class')
         
-    def setup_query(self, context, entity, path, adapter, column_collection=None, **kwargs):
+    def setup_query(self, context, entity, path, adapter, 
+                            column_collection=None, **kwargs):
         for c in self.columns:
             if adapter: