Commits

Mike Bayer committed c57c2ef

- more docs
- it appears we can get rid of all those "XYZ_toplevel" names and use :doc:.

Comments (0)

Files changed (18)

doc/build/changelog/migration_08.rst

 supported feature, and users tended to not have a clear idea
 how to get at this information.
 
-0.8 has a plan to produce a consistent, stable and fully
-documented API for this purpose, which would provide an
-inspection system that works on classes, instances, and
-possibly other things as well.   While many elements of this
-system are already available, the plan is to lock down the
-API including various accessors available from such objects
-as :class:`.Mapper`, :class:`.InstanceState`, and :class:`.MapperProperty`:
+0.8 now provides a consistent, stable and fully
+documented API for this purpose, including an inspection
+system which works on mapped classes, instances, attributes,
+and other Core and ORM constructs.  The entrypoint to this
+system is the core-level :func:`.inspect` function.
+In most cases, the object being inspected
+is one already part of SQLAlchemy's system,
+such as :class:`.Mapper`, :class:`.InstanceState`,
+:class:`.Inspector`.  In some cases, new objects have been
+added with the job of providing the inspection API in
+certain contexts, such as :class:`.AliasedInsp` and
+:class:`.AttributeState`.
 
-::
+A walkthrough of some key capabilities follows::
 
     >>> class User(Base):
     ...     __tablename__ = 'user'
 of_type() works with alias(), with_polymorphic(), any(), has(), joinedload(), subqueryload(), contains_eager()
 --------------------------------------------------------------------------------------------------------------
 
-You can use :meth:`.PropComparator.of_type` with aliases and polymorphic
-constructs; also works with most relationship functions like
-:func:`.joinedload`, :func:`.subqueryload`, :func:`.contains_eager`,
-:meth:`.PropComparator.any`, and :meth:`.PropComparator.has`:
-
-::
+The :meth:`.PropComparator.of_type` method is used to specify
+a specific subtype to use when constructing SQL expressions along
+a :func:`.relationship` that has a :term:`polymorphic` mapping as its target.
+This method can now be used to target *any number* of target subtypes,
+by combining it with the new :func:`.with_polymorphic` function::
 
     # use eager loading in conjunction with with_polymorphic targets
-    Job_P = with_polymorphic(Job, SubJob, aliased=True)
+    Job_P = with_polymorphic(Job, [SubJob, ExtraJob], aliased=True)
+    q = s.query(DataContainer).\
+                join(DataContainer.jobs.of_type(Job_P)).\
+                    options(contains_eager(DataContainer.jobs.of_type(Job_P)))
+
+The method now works equally well in most places a regular relationship
+attribute is accepted, including with loader functions like
+:func:`.joinedload`, :func:`.subqueryload`, :func:`.contains_eager`,
+and comparison methods like :meth:`.PropComparator.any`
+and :meth:`.PropComparator.has`::
+
+    # use eager loading in conjunction with with_polymorphic targets
+    Job_P = with_polymorphic(Job, [SubJob, ExtraJob], aliased=True)
     q = s.query(DataContainer).\
                 join(DataContainer.jobs.of_type(Job_P)).\
                     options(contains_eager(DataContainer.jobs.of_type(Job_P)))
 ORM Classes Now Accepted by Core Constructs
 -------------------------------------------
 
-While the SQL expressions used with :class:`.Query.filter`,
+While the SQL expressions used with :meth:`.Query.filter`,
 such as ``User.id == 5``, have always been compatible for
 use with core constructs such as :func:`.select`, the mapped
 class itself would not be recognized when passed to :func:`.select`,
 
 :ticket:`2276`
 
-"Prefixes" now supported for :func:`.insert`, :func:`.update`, :func:`.delete`
--------------------------------------------------------------------------------
+"Prefixes" now supported for :func:`.update`, :func:`.delete`
+-------------------------------------------------------------
 
-Geared towards MySQL, a "prefix" can be rendered within any of these
-statements::
+Geared towards MySQL, a "prefix" can be rendered within any of
+these constructs.   E.g.::
 
-    stmt = table.insert().prefix_with("LOW_PRIORITY", dialect="mysql")
+    stmt = table.delete().prefix_with("LOW_PRIORITY", dialect="mysql")
 
 
     stmt = table.update().prefix_with("LOW_PRIORITY", dialect="mysql")
 
+The method is new in addition to those which already existed
+on :func:`.insert`, :func:`.select` and :class:`.Query`.
+
 .. seealso::
 
-    :meth:`.Insert.prefix_with`
-
     :meth:`.Update.prefix_with`
 
     :meth:`.Delete.prefix_with`
 
+    :meth:`.Insert.prefix_with`
+
     :meth:`.Select.prefix_with`
 
+    :meth:`.Query.prefix_with`
+
 :ticket:`2431`
 
 
 Query now auto-correlates like a select() does
 ----------------------------------------------
 
-Previously it was necessary to call ``Query.correlate`` in
+Previously it was necessary to call :meth:`.Query.correlate` in
 order to have a column- or WHERE-subquery correlate to the
 parent:
 
 
 :ticket:`2277`
 
-Fixed the behavior of Session.is_modified()
+Fixed the behavior of :meth:`.Session.is_modified`
 -------------------------------------------
 
-The ``Session.is_modified()`` method accepts an argument
+The :meth:`.Session.is_modified` method accepts an argument
 ``passive`` which basically should not be necessary, the
 argument in all cases should be the value ``True`` - when
 left at its default of ``False`` it would have the effect of
 never be checked for history since by definition there can
 be no pending state change on an unloaded attribute.
 
+.. seealso::
+
+    :meth:`.Session.is_modified`
+
 :ticket:`2320`
 
-``column.key`` is honored in the ``.c.`` attribute of ``select()`` with ``apply_labels()``
-------------------------------------------------------------------------------------------
+:attr:`.Column.key` is honored in the :attr:`.Select.c` attribute of :func:`.select` with :meth:`.Select.apply_labels`
+-----------------------------------------------------------------------------------------------------------------------
 
-Users of the expression system know that ``apply_labels()``
+Users of the expression system know that :meth:`.Select.apply_labels`
 prepends the table name to each column name, affecting the
-names that are available from ``.c.``:
+names that are available from :attr:`.Select.c`:
 
 ::
 
     s.c.table1_col1
     s.c.table1_col2
 
-Before 0.8, if the ``Column`` had a different ``key``, this
+Before 0.8, if the :class:`.Column` had a different :attr:`.Column.key`, this
 key would be ignored, inconsistently versus when
-``apply_labels()`` were not used:
+:meth:`.Select.apply_labels` were not used:
 
 ::
 
     s.c.table1_column_one # would raise AttributeError
     s.c.table1_col1 # would be accessible like this
 
-In 0.8, ``key`` is honored in both cases:
+In 0.8, :attr:`.Column.key` is honored in both cases:
 
 ::
 
 All other behavior regarding "name" and "key" are the same,
 including that the rendered SQL will still use the form
 ``<tablename>_<colname>`` - the emphasis here was on
-preventing the ``key`` contents from being rendered into the
+preventing the :attr:`.Column.key` contents from being rendered into the
 ``SELECT`` statement so that there are no issues with
-special/ non-ascii characters used in the ``key``.
+special/ non-ascii characters used in the :attr:`.Column.key`.
 
 :ticket:`2397`
 

doc/build/core/connections.rst

 Basic Usage
 ===========
 
-Recall from :ref:`engines_toplevel` that an :class:`.Engine` is created via
+Recall from :doc:`/core/engines` that an :class:`.Engine` is created via
 the :func:`.create_engine` call::
 
     engine = create_engine('mysql://scott:tiger@localhost/test')

doc/build/core/engines.rst

-.. _engines_toplevel:
-
 ====================
 Engine Configuration
 ====================

doc/build/core/inspection.rst

 Available Inspection Targets
 ----------------------------
 
-Following is a listing of all inspection targets.
+Below is a listing of many of the most common inspection targets.
 
 * :class:`.Connectable` (i.e. :class:`.Engine`,
   :class:`.Connection`) - returns an :class:`.Inspector` object.

doc/build/core/tutorial.rst

 
 Database Metadata Reference: :ref:`metadata_toplevel`
 
-Engine Reference: :ref:`engines_toplevel`
+Engine Reference: :doc:`/core/engines`
 
 Connection Reference: :ref:`connections_toplevel`
 

doc/build/glossary.rst

 
 .. glossary::
 
+
     descriptor
         In Python, a descriptor is an object attribute with “binding behavior”, one whose attribute access has been overridden by methods in the `descriptor protocol <http://docs.python.org/howto/descriptor.html>`_.
         Those methods are __get__(), __set__(), and __delete__(). If any of those methods are defined
             >>> inspect(m1).attrs.data.history.added
             "some data"
 
+    discriminator
+        A result-set column which is used during :term:`polymorphic` loading
+        to determine what kind of mapped class should be applied to a particular
+        incoming result row.   In SQLAlchemy, the classes are always part
+        of a hierarchy mapping using inheritance mapping.
+
+        .. seealso::
+
+            :ref:`inheritance_toplevel`
+
     instrumentation
     instrumented
         Instrumentation refers to the process of augmenting the functionality
         class which each represent a particular database column
         or relationship to a related class.
 
+    lazy load
+        In object relational mapping, a "lazy load" refers to an
+        attribute that does not contain its database-side value
+        for some period of time, typically when the object is
+        first loaded.  Instead, the attribute receives a
+        *memoization* that causes it to go out to the database
+        and load its data when it's first used.   Using this pattern,
+        the complexity and time spent within object fetches can
+        sometimes be reduced, in that
+        attributes for related tables don't need to be addressed
+        immediately.
+
+        .. seealso::
+
+            `Lazy Load (on Martin Fowler) <http://martinfowler.com/eaaCatalog/lazyLoad.html>`_
+
+            :term:`N plus one problem`
+
+            :doc:`orm/loading`
+
     mapping
     mapped
         We say a class is "mapped" when it has been passed through the
         using a :class:`.Session` as well as loaded using a
         :class:`.Query`.
 
+    N plus one problem
+        The N plus one problem is a common side effect of the
+        :term:`lazy load` pattern, whereby an application wishes
+        to iterate through a related attribute or collection on
+        each member of a result set of objects, where that
+        attribute or collection is set to be loaded via the lazy
+        load pattern.   The net result is that a SELECT statement
+        is emitted to load the initial result set of parent objects;
+        then, as the application iterates through each member,
+        an additional SELECT statement is emitted for each member
+        in order to load the related attribute or collection for
+        that member.  The end result is that for a result set of
+        N parent objects, there will be N + 1 SELECT statements emitted.
+
+        The N plus one problem is alleviated using :term:`eager loading`.
+
+        .. seealso::
+
+            :doc:`orm/loading`
+
+    polymorphic
+    polymorphically
+        Refers to a function that handles several types at once.  In SQLAlchemy,
+        the term is usually applied to the concept of an ORM mapped class
+        whereby a query operation will return different subclasses
+        based on information in the result set, typically by checking the
+        value of a particular column in the result known as the :term:`discriminator`.
+
+        Polymorphic loading in SQLAlchemy implies that a one or a
+        combination of three different schemes are used to map a hierarchy
+        of classes; "joined", "single", and "concrete".   The section
+        :ref:`inheritance_toplevel` describes inheritance mapping fully.
+
+
     release
     releases
     released
 
         	:ref:`pooling_toplevel`
 
+
+    unit of work
+        This pattern is where the system transparently keeps
+        track of changes to objects and periodically flushes all those
+        pending changes out to the database. SQLAlchemy's Session
+        implements this pattern fully in a manner similar to that of
+        Hibernate.
+
+        .. seealso::
+
+            `Unit of Work by Martin Fowler <http://martinfowler.com/eaaCatalog/unitOfWork.html>`_
+
+            :doc:`orm/session`

doc/build/orm/inheritance.rst

 concrete inheritance presents more configurational challenges.
 
 When mappers are configured in an inheritance relationship, SQLAlchemy has the
-ability to load elements "polymorphically", meaning that a single query can
+ability to load elements :term:`polymorphically`, meaning that a single query can
 return objects of multiple types.
 
 Joined Table Inheritance

doc/build/orm/loading.rst

-.. _loading_toplevel:
-
 .. currentmodule:: sqlalchemy.orm
 
 Relationship Loading Techniques
 .. sourcecode:: python+sql
 
     {sql}>>> jack.addresses
-    SELECT addresses.id AS addresses_id, addresses.email_address AS addresses_email_address, 
+    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 one case where SQL is not emitted is for a simple many-to-one relationship, when 
+The one case where SQL is not emitted is for a simple many-to-one relationship, when
 the related object can be identified by its primary key alone and that object is already
 present in the current :class:`.Session`.
 
 
     {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 
+    ... 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 
+    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',)
 
 .. versionadded:: 0.7.5
     Default loader strategies as a new feature.
 
-Each of :func:`.joinedload`, :func:`.subqueryload`, :func:`.lazyload`, 
+Each of :func:`.joinedload`, :func:`.subqueryload`, :func:`.lazyload`,
 and :func:`.noload` can be used to set the default style of
-:func:`.relationship` loading 
+:func:`.relationship` loading
 for a particular query, affecting all :func:`.relationship` -mapped
 attributes not otherwise
 specified in the :class:`.Query`.   This feature is available by passing
 
 Above, the ``lazyload('*')`` option will supercede the ``lazy`` setting
 of all :func:`.relationship` constructs in use for that query,
-except for those which use the ``'dynamic'`` style of loading.   
+except for those which use the ``'dynamic'`` style of loading.
 If some relationships specify
 ``lazy='joined'`` or ``lazy='subquery'``, for example,
 using ``default_strategy(lazy='select')`` will unilaterally
 cause all those relationships to use ``'select'`` loading.
 
 The option does not supercede loader options stated in the
-query, such as :func:`.eagerload`, 
+query, such as :func:`.eagerload`,
 :func:`.subqueryload`, etc.  The query below will still use joined loading
 for the ``widget`` relationship::
 
     session.query(MyClass).options(
-                                lazyload('*'), 
+                                lazyload('*'),
                                 joinedload(MyClass.widget)
                             )
 
 -------------------------
 
 The philosophy behind loader strategies is that any set of loading schemes can be
-applied to a particular query, and *the results don't change* - only the number 
+applied to a particular query, and *the results don't change* - only the number
 of SQL statements required to fully load related objects and collections changes. A particular
 query might start out using all lazy loads.   After using it in context, it might be revealed
 that particular attributes or collections are always accessed, and that it would be more
     >>> jack = session.query(User).\
     ... options(joinedload(User.addresses)).\
     ... filter(User.name=='jack').\
-    ... order_by(Address.email_address).all() 
+    ... order_by(Address.email_address).all()
     {opensql}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
     WHERE users.name = ? ORDER BY addresses.email_address   <-- this part is wrong !
     ['jack']
 
-Above, ``ORDER BY addresses.email_address`` is not valid since ``addresses`` is not in the 
+Above, ``ORDER BY addresses.email_address`` is not valid since ``addresses`` is not in the
 FROM list.   The correct way to load the ``User`` records and order by email
 address is to use :meth:`.Query.join`:
 
     >>> jack = session.query(User).\
     ... join(User.addresses).\
     ... filter(User.name=='jack').\
-    ... order_by(Address.email_address).all() 
+    ... order_by(Address.email_address).all()
     {opensql}
     SELECT users.id AS users_id, users.name AS users_name,
     users.fullname AS users_fullname, users.password AS users_password
 
 The statement above is of course not the same as the previous one, in that the columns from ``addresses``
 are not included in the result at all.   We can add :func:`.joinedload` back in, so that
-there are two joins - one is that which we are ordering on, the other is used anonymously to 
+there are two joins - one is that which we are ordering on, the other is used anonymously to
 load the contents of the ``User.addresses`` collection:
 
 .. sourcecode:: python+sql
     ... join(User.addresses).\
     ... options(joinedload(User.addresses)).\
     ... filter(User.name=='jack').\
-    ... order_by(Address.email_address).all() 
+    ... order_by(Address.email_address).all()
     {opensql}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
 to use in subsequent query criterion, whereas our usage of :func:`.joinedload` only concerns
 itself with the loading of the ``User.addresses`` collection, for each ``User`` in the result.
 In this case, the two joins most probably appear redundant - which they are.  If we
-wanted to use just one JOIN for collection loading as well as ordering, we use the 
-:func:`.contains_eager` option, described in :ref:`contains_eager` below.   But 
+wanted to use just one JOIN for collection loading as well as ordering, we use the
+:func:`.contains_eager` option, described in :ref:`contains_eager` below.   But
 to see why :func:`joinedload` does what it does, consider if we were **filtering** on a
 particular ``Address``:
 
     ... options(joinedload(User.addresses)).\
     ... filter(User.name=='jack').\
     ... filter(Address.email_address=='someaddress@foo.com').\
-    ... all() 
+    ... all()
     {opensql}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
     ... options(subqueryload(User.addresses)).\
     ... filter(User.name=='jack').\
     ... filter(Address.email_address=='someaddress@foo.com').\
-    ... all() 
+    ... all()
     {opensql}SELECT users.id AS users_id, users.name AS users_name,
     users.fullname AS users_fullname, users.password AS users_password
     FROM users JOIN addresses ON users.id = addresses.user_id
     WHERE users.name = ? AND addresses.email_address = ?
     ['jack', 'someaddress@foo.com']
 
-    # ... subqueryload() emits a SELECT in order 
+    # ... subqueryload() emits a SELECT in order
     # to load all address records ...
 
 When using joined eager loading, if the
 externally to the joins, such as when using DISTINCT, LIMIT, OFFSET
 or equivalent, the completed statement is first
 wrapped inside a subquery, and the joins used specifically for joined eager
-loading are applied to the subquery.   SQLAlchemy's 
-joined eager loading goes the extra mile, and then ten miles further, to 
+loading are applied to the subquery.   SQLAlchemy's
+joined eager loading goes the extra mile, and then ten miles further, to
 absolutely ensure that it does not affect the end result of the query, only
 the way collections and related objects are loaded, no matter what the format of the query is.
 
    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 
+   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 
+   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 
+   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-
  * 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 :meth:`.Query.get`.  So here, 
+   reference will be checked first in the current identity map using :meth:`.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.
 ------------------------------------------------------------------
 
 The behavior of :func:`~sqlalchemy.orm.joinedload()` is such that joins are
-created automatically, using anonymous aliases as targets, the results of which 
+created automatically, using anonymous aliases as targets, 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

doc/build/orm/session.rst

-.. _session_toplevel:
-
 =================
 Using the Session
 =================

doc/build/orm/tutorial.rst

 of the database and DBAPI in use.  In this case the SQLite dialect will interpret instructions
 to the Python built-in ``sqlite3`` module.
 
-The :class:`.Engine` has not actually tried to connect to the database yet; that happens 
+The :class:`.Engine` has not actually tried to connect to the database yet; that happens
 only the first time it is asked to perform a task against the database.   We can illustrate
 this by asking it to perform a simple SELECT statement:
 
     ()
     {stop}1
 
-As the :meth:`.Engine.execute` method is called, the :class:`.Engine` establishes a connection to the 
-SQLite database, which is then used to emit the SQL.   The connection is then returned to an internal 
+As the :meth:`.Engine.execute` method is called, the :class:`.Engine` establishes a connection to the
+SQLite database, which is then used to emit the SQL.   The connection is then returned to an internal
 connection pool where it will be reused on subsequent statement executions.  While we illustrate direct usage of the
 :class:`.Engine` here, this isn't typically necessary when using the ORM, where the :class:`.Engine`,
 once created, is used behind the scenes by the ORM as we'll see shortly.
 
 When using the ORM, the configurational process starts by describing the database
 tables we'll be dealing with, and then by defining our own classes which will
-be mapped to those tables.   In modern SQLAlchemy, 
+be mapped to those tables.   In modern SQLAlchemy,
 these two tasks are usually performed together,
 using a system known as :ref:`declarative_toplevel`, which allows us to create
 classes that include directives to describe the actual database table they will
 be mapped to.
 
-Classes mapped using the Declarative system are defined in terms of a base class which 
+Classes mapped using the Declarative system are defined in terms of a base class which
 maintains a catalog of classes and
 tables relative to that base - this is known as the **declarative base class**.  Our
 application will usually have just one instance of this base in a commonly
 Now that we have a "base", we can define any number of mapped classes in terms
 of it.  We will start with just a single table called ``users``, which will store
 records for the end-users using our application.
-A new class called ``User`` will be the class to which we map this table.  The 
+A new class called ``User`` will be the class to which we map this table.  The
 imports we'll need to accomplish this include objects that represent the components
-of our table, including the :class:`.Column` class which represents a database column, 
-as well as the :class:`.Integer` and :class:`.String` classes that 
+of our table, including the :class:`.Column` class which represents a database column,
+as well as the :class:`.Integer` and :class:`.String` classes that
 represent basic datatypes used in columns::
 
     >>> from sqlalchemy import Column, Integer, String
 are of course entirely feasible as well.
 
 We define a constructor via ``__init__()`` and also a ``__repr__()`` method - both are optional.  The
-class of course can have any number of other methods and attributes as required by the application, 
+class of course can have any number of other methods and attributes as required by the application,
 as it's basically just a plain Python class.   Inheriting from ``Base`` is also only a requirement
-of the declarative configurational system, which itself is optional and relatively open ended; at its 
-core, the SQLAlchemy ORM only requires that a class be a so-called "new style class", that is, it inherits 
+of the declarative configurational system, which itself is optional and relatively open ended; at its
+core, the SQLAlchemy ORM only requires that a class be a so-called "new style class", that is, it inherits
 from ``object`` in Python 2, in order to be mapped.   All classes in Python 3 are "new style" classes.
 
 .. topic:: The Non Opinionated Philosophy
 
     In our ``User`` mapping example, it was required that we identify the name of the table
-    in use, as well as the names and characteristics of all columns which we care about, 
+    in use, as well as the names and characteristics of all columns which we care about,
     including which column or columns
     represent the primary key, as well as some basic information about the types in use.
     SQLAlchemy never makes assumptions about these decisions - the developer must
 
 With our ``User`` class constructed via the Declarative system, we have defined information about
 our table, known as **table metadata**, as well as a user-defined class which is linked to this
-table, known as a **mapped class**.   Declarative has provided for us a shorthand system for what in SQLAlchemy is 
+table, known as a **mapped class**.   Declarative has provided for us a shorthand system for what in SQLAlchemy is
 called a "Classical Mapping", which specifies these two units separately and is discussed
-in :ref:`classical_mapping`.   The table 
+in :ref:`classical_mapping`.   The table
 is actually represented by a datastructure known as :class:`.Table`, and the mapping represented
-by a :class:`.Mapper` object generated by a function called :func:`.mapper`.  Declarative performs both of 
+by a :class:`.Mapper` object generated by a function called :func:`.mapper`.  Declarative performs both of
 these steps for us, making available the
 :class:`.Table` it has created via the ``__table__`` attribute::
 
     >>> User.__table__ # doctest: +NORMALIZE_WHITESPACE
     Table('users', MetaData(None),
-                Column('id', Integer(), table=<users>, primary_key=True, nullable=False), 
-                Column('name', String(), table=<users>), 
-                Column('fullname', String(), table=<users>), 
+                Column('id', Integer(), table=<users>, primary_key=True, nullable=False),
+                Column('name', String(), table=<users>),
+                Column('fullname', String(), table=<users>),
                 Column('password', String(), table=<users>), schema=None)
 
 and while rarely needed, making available the :class:`.Mapper` object via the ``__mapper__`` attribute::
 the :class:`.MetaData` object offers is the ability to issue CREATE TABLE statements
 to the database for all tables that don't yet exist.   We illustrate this
 by calling the :meth:`.MetaData.create_all` method, passing in our :class:`.Engine`
-as a source of database connectivity.  We will see that special commands are 
+as a source of database connectivity.  We will see that special commands are
 first emitted to check for the presence of the ``users`` table, and following that
 the actual ``CREATE TABLE`` statement:
 
         from sqlalchemy import Sequence
         Column(Integer, Sequence('user_id_seq'), primary_key=True)
 
-    A full, foolproof :class:`~sqlalchemy.schema.Table` generated via our declarative 
+    A full, foolproof :class:`~sqlalchemy.schema.Table` generated via our declarative
     mapping is therefore::
 
         class User(Base):
     'None'
 
 The ``id`` attribute, which while not defined by our ``__init__()`` method,
-exists with a value of ``None`` on our ``User`` instance due to the ``id`` 
+exists with a value of ``None`` on our ``User`` instance due to the ``id``
 column we declared in our mapping.  By
 default, the ORM creates class attributes for all columns present
 in the table being mapped.   These class attributes exist as
-`Python descriptors <http://docs.python.org/howto/descriptor.html>`_, and 
+`Python descriptors <http://docs.python.org/howto/descriptor.html>`_, and
 define **instrumentation** for the mapped class. The
 functionality of this instrumentation includes the ability to fire on change
 events, track modifications, and to automatically load new data from the database when
    The business of acquiring a :class:`.Session` has a good deal of variety based
    on the variety of types of applications and frameworks out there.
    Keep in mind the :class:`.Session` is just a workspace for your objects,
-   local to a particular database connection - if you think of 
+   local to a particular database connection - if you think of
    an application thread as a guest at a dinner party, the :class:`.Session`
-   is the guest's plate and the objects it holds are the food 
-   (and the database...the kitchen?)!   Hints on 
+   is the guest's plate and the objects it holds are the food
+   (and the database...the kitchen?)!   Hints on
    how :class:`.Session` is integrated into an application are at
    :ref:`session_faq`.
 
     BEGIN (implicit)
     INSERT INTO users (name, fullname, password) VALUES (?, ?, ?)
     ('ed', 'Ed Jones', 'edspassword')
-    SELECT users.id AS users_id, 
-            users.name AS users_name, 
-            users.fullname AS users_fullname, 
+    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 = ?
     >>> ed_user is our_user
     True
 
-The ORM concept at work here is known as an `identity map <http://martinfowler.com/eaaCatalog/identityMap.html>`_ 
+The ORM concept at work here is known as an `identity map <http://martinfowler.com/eaaCatalog/identityMap.html>`_
 and ensures that
 all operations upon a particular row within a
 :class:`~sqlalchemy.orm.session.Session` operate upon the same set of data.
 
     {sql}>>> ed_user.id # doctest: +NORMALIZE_WHITESPACE
     BEGIN (implicit)
-    SELECT users.id AS users_id, 
-            users.name AS users_name, 
-            users.fullname AS users_fullname, 
+    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.id = ?
 because a new transaction was begun after we issued :meth:`~.Session.commit`. SQLAlchemy
 by default refreshes data from a previous transaction the first time it's
 accessed within a new transaction, so that the most recent state is available.
-The level of reloading is configurable as is described in :ref:`session_toplevel`.
+The level of reloading is configurable as is described in :doc:`/orm/session`.
 
 .. topic:: Session Object States
 
    inside the :class:`.Session` without a primary key, to actually being
    inserted, it moved between three out of four
    available "object states" - **transient**, **pending**, and **persistent**.
-   Being aware of these states and what they mean is always a good idea - 
+   Being aware of these states and what they mean is always a good idea -
    be sure to read :ref:`session_object_states` for a quick overview.
 
 Rolling Back
     ('Edwardo', 1)
     INSERT INTO users (name, fullname, password) VALUES (?, ?, ?)
     ('fakeuser', 'Invalid', '12345')
-    SELECT users.id AS users_id, 
-            users.name AS users_name, 
-            users.fullname AS users_fullname, 
+    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 IN (?, ?)
 
     {sql}>>> ed_user.name #doctest: +NORMALIZE_WHITESPACE
     BEGIN (implicit)
-    SELECT users.id AS users_id, 
-            users.name AS users_name, 
-            users.fullname AS users_fullname, 
+    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.id = ?
 .. sourcecode:: python+sql
 
     {sql}>>> session.query(User).filter(User.name.in_(['ed', 'fakeuser'])).all() #doctest: +NORMALIZE_WHITESPACE
-    SELECT users.id AS users_id, 
-            users.name AS users_name, 
-            users.fullname AS users_fullname, 
+    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 IN (?, ?)
 
     {sql}>>> for instance in session.query(User).order_by(User.id): # doctest: +NORMALIZE_WHITESPACE
     ...     print instance.name, instance.fullname
-    SELECT users.id AS users_id, 
+    SELECT users.id AS users_id,
             users.name AS users_name,
-            users.fullname AS users_fullname, 
+            users.fullname AS users_fullname,
             users.password AS users_password
     FROM users ORDER BY users.id
     ()
 
     {sql}>>> for name, fullname in session.query(User.name, User.fullname): # doctest: +NORMALIZE_WHITESPACE
     ...     print name, fullname
-    SELECT users.name AS users_name, 
+    SELECT users.name AS users_name,
             users.fullname AS users_fullname
     FROM users
     ()
 
     {sql}>>> for row in session.query(User, User.name).all(): #doctest: +NORMALIZE_WHITESPACE
     ...    print row.User, row.name
-    SELECT users.id AS users_id, 
-            users.name AS users_name, 
-            users.fullname AS users_fullname, 
+    SELECT users.id AS users_id,
+            users.name AS users_name,
+            users.fullname AS users_fullname,
             users.password AS users_password
     FROM users
     ()
 
     {sql}>>> for row in session.query(user_alias, user_alias.name).all(): #doctest: +NORMALIZE_WHITESPACE
     ...    print row.user_alias
-    SELECT user_alias.id AS user_alias_id, 
-            user_alias.name AS user_alias_name, 
-            user_alias.fullname AS user_alias_fullname, 
+    SELECT user_alias.id AS user_alias_id,
+            user_alias.name AS user_alias_name,
+            user_alias.fullname AS user_alias_fullname,
             user_alias.password AS user_alias_password
     FROM users AS user_alias
     (){stop}
 
     {sql}>>> for u in session.query(User).order_by(User.id)[1:3]: #doctest: +NORMALIZE_WHITESPACE
     ...    print u
-    SELECT users.id AS users_id, 
-            users.name AS users_name, 
-            users.fullname AS users_fullname, 
+    SELECT users.id AS users_id,
+            users.name AS users_name,
+            users.fullname AS users_fullname,
             users.password AS users_password
     FROM users ORDER BY users.id
     LIMIT ? OFFSET ?
     ...          filter(User.name=='ed').\
     ...          filter(User.fullname=='Ed Jones'): # doctest: +NORMALIZE_WHITESPACE
     ...    print user
-    SELECT users.id AS users_id, 
-            users.name AS users_name, 
-            users.fullname AS users_fullname, 
+    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 = ? AND users.fullname = ?
 
     >>> query = session.query(User).filter(User.name.like('%ed')).order_by(User.id)
     {sql}>>> query.all() #doctest: +NORMALIZE_WHITESPACE
-    SELECT users.id AS users_id, 
-            users.name AS users_name, 
-            users.fullname AS users_fullname, 
+    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 LIKE ? ORDER BY users.id
 .. sourcecode:: python+sql
 
     {sql}>>> query.first() #doctest: +NORMALIZE_WHITESPACE
-    SELECT users.id AS users_id, 
-            users.name AS users_name, 
-            users.fullname AS users_fullname, 
+    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 LIKE ? ORDER BY users.id
     ...     user = query.one()
     ... except MultipleResultsFound, e:
     ...     print e
-    SELECT users.id AS users_id, 
-            users.name AS users_name, 
-            users.fullname AS users_fullname, 
+    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 LIKE ? ORDER BY users.id
     ...     user = query.filter(User.id == 99).one()
     ... except NoResultFound, e:
     ...     print e
-    SELECT users.id AS users_id, 
-            users.name AS users_name, 
-            users.fullname AS users_fullname, 
+    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 LIKE ? AND users.id = ? ORDER BY users.id
     ...             filter("id<224").\
     ...             order_by("id").all(): #doctest: +NORMALIZE_WHITESPACE
     ...     print user.name
-    SELECT users.id AS users_id, 
-            users.name AS users_name, 
-            users.fullname AS users_fullname, 
+    SELECT users.id AS users_id,
+            users.name AS users_name,
+            users.fullname AS users_fullname,
             users.password AS users_password
     FROM users
     WHERE id<224 ORDER BY id
 
     {sql}>>> session.query(User).filter("id<:value and name=:name").\
     ...     params(value=224, name='fred').order_by(User.id).one() # doctest: +NORMALIZE_WHITESPACE
-    SELECT users.id AS users_id, 
-            users.name AS users_name, 
-            users.fullname AS users_fullname, 
+    SELECT users.id AS users_id,
+            users.name AS users_name,
+            users.fullname AS users_fullname,
             users.password AS users_password
     FROM users
     WHERE id<? and name=? ORDER BY users.id
 
    :class:`.Query` is constructed like the rest of SQLAlchemy, in that it tries
    to always allow "falling back" to a less automated, lower level approach to things.
-   Accepting strings for all SQL fragments is a big part of that, so that 
+   Accepting strings for all SQL fragments is a big part of that, so that
    you can bypass the need to organize SQL constructs if you know specifically
    what string output you'd like.
    But when using literal strings, the :class:`.Query` no longer knows anything about
-   that part of the SQL construct being emitted, and has no ability to 
+   that part of the SQL construct being emitted, and has no ability to
    **transform** it to adapt to new contexts.
 
    For example, suppose we selected ``User`` objects and ordered by the ``name``
 
        >>> q = session.query(User.id, User.name)
        {sql}>>> q.order_by("name").all()
-       SELECT users.id AS users_id, users.name AS users_name 
+       SELECT users.id AS users_id, users.name AS users_name
        FROM users ORDER BY name
        ()
        {stop}[(1, u'ed'), (4, u'fred'), (3, u'mary'), (2, u'wendy')]
    Perfectly fine.  But suppose, before we got a hold of the :class:`.Query`,
    some sophisticated transformations were applied to it, such as below
    where we use :meth:`~.Query.from_self`, a particularly advanced
-   method, to retrieve pairs of user names with 
+   method, to retrieve pairs of user names with
    different numbers of characters::
 
         >>> from sqlalchemy import func
         ...     filter(User.name < ua.name).\
         ...     filter(func.length(ua.name) != func.length(User.name))
 
-   The :class:`.Query` now represents a select from a subquery, where 
+   The :class:`.Query` now represents a select from a subquery, where
    ``User`` is represented twice both inside and outside of the subquery.
    Telling the :class:`.Query` to order by "name" doesn't really give
-   us much guarantee which "name" it's going to order on.  In this 
+   us much guarantee which "name" it's going to order on.  In this
    case it assumes "name" is against the outer "aliased" ``User`` construct:
 
    .. sourcecode:: python+sql
 
        {sql}>>> q.order_by("name").all() #doctest: +NORMALIZE_WHITESPACE
-       SELECT anon_1.users_id AS anon_1_users_id, 
-                anon_1.users_name AS anon_1_users_name, 
-                users_1.name AS users_1_name 
-       FROM (SELECT users.id AS users_id, users.name AS users_name 
-            FROM users) AS anon_1, users AS users_1 
-       WHERE anon_1.users_name < users_1.name 
-            AND length(users_1.name) != length(anon_1.users_name) 
+       SELECT anon_1.users_id AS anon_1_users_id,
+                anon_1.users_name AS anon_1_users_name,
+                users_1.name AS users_1_name
+       FROM (SELECT users.id AS users_id, users.name AS users_name
+            FROM users) AS anon_1, users AS users_1
+       WHERE anon_1.users_name < users_1.name
+            AND length(users_1.name) != length(anon_1.users_name)
        ORDER BY name
        ()
        {stop}[(1, u'ed', u'fred'), (1, u'ed', u'mary'), (1, u'ed', u'wendy'), (3, u'mary', u'wendy'), (4, u'fred', u'wendy')]
 
    Only if we use the SQL element directly, in this case ``User.name``
-   or ``ua.name``, do we give :class:`.Query` enough information to know 
+   or ``ua.name``, do we give :class:`.Query` enough information to know
    for sure which "name" we'd like to order on, where we can see we get different results
    for each:
 
    .. sourcecode:: python+sql
 
        {sql}>>> q.order_by(ua.name).all() #doctest: +NORMALIZE_WHITESPACE
-       SELECT anon_1.users_id AS anon_1_users_id, 
-                anon_1.users_name AS anon_1_users_name, 
-                users_1.name AS users_1_name 
-       FROM (SELECT users.id AS users_id, users.name AS users_name 
-            FROM users) AS anon_1, users AS users_1 
-       WHERE anon_1.users_name < users_1.name 
-            AND length(users_1.name) != length(anon_1.users_name) 
+       SELECT anon_1.users_id AS anon_1_users_id,
+                anon_1.users_name AS anon_1_users_name,
+                users_1.name AS users_1_name
+       FROM (SELECT users.id AS users_id, users.name AS users_name
+            FROM users) AS anon_1, users AS users_1
+       WHERE anon_1.users_name < users_1.name
+            AND length(users_1.name) != length(anon_1.users_name)
        ORDER BY users_1.name
        ()
        {stop}[(1, u'ed', u'fred'), (1, u'ed', u'mary'), (1, u'ed', u'wendy'), (3, u'mary', u'wendy'), (4, u'fred', u'wendy')]
 
        {sql}>>> q.order_by(User.name).all() #doctest: +NORMALIZE_WHITESPACE
-       SELECT anon_1.users_id AS anon_1_users_id, 
-                anon_1.users_name AS anon_1_users_name, 
-                users_1.name AS users_1_name 
-       FROM (SELECT users.id AS users_id, users.name AS users_name 
-            FROM users) AS anon_1, users AS users_1 
-       WHERE anon_1.users_name < users_1.name 
-            AND length(users_1.name) != length(anon_1.users_name) 
+       SELECT anon_1.users_id AS anon_1_users_id,
+                anon_1.users_name AS anon_1_users_name,
+                users_1.name AS users_1_name
+       FROM (SELECT users.id AS users_id, users.name AS users_name
+            FROM users) AS anon_1, users AS users_1
+       WHERE anon_1.users_name < users_1.name
+            AND length(users_1.name) != length(anon_1.users_name)
        ORDER BY anon_1.users_name
        ()
        {stop}[(1, u'ed', u'wendy'), (1, u'ed', u'mary'), (1, u'ed', u'fred'), (4, u'fred', u'wendy'), (3, u'mary', u'wendy')]
 .. sourcecode:: python+sql
 
     {sql}>>> session.query(User).filter(User.name.like('%ed')).count() #doctest: +NORMALIZE_WHITESPACE
-    SELECT count(*) AS count_1 
-    FROM (SELECT users.id AS users_id, 
-                    users.name AS users_name, 
-                    users.fullname AS users_fullname, 
-                    users.password AS users_password 
-    FROM users 
+    SELECT count(*) AS count_1
+    FROM (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 LIKE ?) AS anon_1
     ('%ed',)
     {stop}2
 
 The :meth:`~.Query.count()` method is used to determine
 how many rows the SQL statement would return.   Looking
-at the generated SQL above, SQLAlchemy always places whatever it is we are 
+at the generated SQL above, SQLAlchemy always places whatever it is we are
 querying into a subquery, then counts the rows from that.   In some cases
 this can be reduced to a simpler ``SELECT count(*) FROM table``, however
 modern versions of SQLAlchemy don't try to guess when this is appropriate,
 as the exact SQL can be emitted using more explicit means.
 
 For situations where the "thing to be counted" needs
-to be indicated specifically, we can specify the "count" function 
+to be indicated specifically, we can specify the "count" function
 directly using the expression ``func.count()``, available from the
 :attr:`~sqlalchemy.sql.expression.func` construct.  Below we
 use it to return the count of each distinct user name:
 .. sourcecode:: python+sql
 
     {sql}>>> session.query(func.count('*')).select_from(User).scalar()
-    SELECT count(?) AS count_1 
+    SELECT count(?) AS count_1
     FROM users
     ('*',)
     {stop}4
 =======================
 
 Let's consider how a second table, related to ``User``, can be mapped and
-queried.  Users in our system 
+queried.  Users in our system
 can store any number of email addresses associated with their username. This
 implies a basic one to many association from the ``users`` to a new
 table which stores email addresses, which we will call ``addresses``. Using
 
 A second directive, known as :func:`.relationship`,
 tells the ORM that the ``Address`` class itself should be linked
-to the ``User`` class, using the attribute ``Address.user``.   
+to the ``User`` class, using the attribute ``Address.user``.
 :func:`.relationship` uses the foreign key
 relationships between the two tables to determine the nature of
 this linkage, determining that ``Address.user`` will be **many-to-one**.
 feature of the SQLAlchemy ORM.   The section :ref:`relationships_backref`
 discusses the "backref" feature in detail.
 
-Arguments to :func:`.relationship` which concern the remote class 
-can be specified using strings, assuming the Declarative system is in 
+Arguments to :func:`.relationship` which concern the remote class
+can be specified using strings, assuming the Declarative system is in
 use.   Once all mappings are complete, these strings are evaluated
-as Python expressions in order to produce the actual argument, in the 
-above case the ``User`` class.   The names which are allowed during 
+as Python expressions in order to produce the actual argument, in the
+above case the ``User`` class.   The names which are allowed during
 this evaluation include, among other things, the names of all classes
 which have been created in terms of the declared base.  Below we illustrate creation
-of the same "addresses/user" bidirectional relationship in terms of ``User`` instead of 
+of the same "addresses/user" bidirectional relationship in terms of ``User`` instead of
 ``Address``::
 
     class User(Base):
 
 .. topic:: Did you know ?
 
-    * a FOREIGN KEY constraint in most (though not all) relational databases can 
+    * a FOREIGN KEY constraint in most (though not all) relational databases can
       only link to a primary key column, or a column that has a UNIQUE constraint.
     * a FOREIGN KEY constraint that refers to a multiple column primary key, and itself
       has multiple columns, is known as a "composite foreign key".  It can also
 .. sourcecode:: python+sql
 
     >>> jack.addresses = [
-    ...                 Address(email_address='jack@google.com'), 
+    ...                 Address(email_address='jack@google.com'),
     ...                 Address(email_address='j25@yahoo.com')]
 
 When using a bidirectional relationship, elements added in one direction
     {sql}>>> jack = session.query(User).\
     ... filter_by(name='jack').one() #doctest: +NORMALIZE_WHITESPACE
     BEGIN (implicit)
-    SELECT users.id AS users_id, 
-            users.name AS users_name, 
-            users.fullname AS users_fullname, 
+    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 = ?
 .. sourcecode:: python+sql
 
     {sql}>>> jack.addresses #doctest: +NORMALIZE_WHITESPACE
-    SELECT addresses.id AS addresses_id, 
-            addresses.email_address AS 
-            addresses_email_address, 
+    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 ORDER BY addresses.id
 
 Now that we have two tables, we can show some more features of :class:`.Query`,
 specifically how to create queries that deal with both tables at the same time.
-The `Wikipedia page on SQL JOIN 
-<http://en.wikipedia.org/wiki/Join_%28SQL%29>`_ offers a good introduction to 
+The `Wikipedia page on SQL JOIN
+<http://en.wikipedia.org/wiki/Join_%28SQL%29>`_ offers a good introduction to
 join techniques, several of which we'll illustrate here.
 
 To construct a simple implicit join between ``User`` and ``Address``,
-we can use :meth:`.Query.filter()` to equate their related columns together. 
+we can use :meth:`.Query.filter()` to equate their related columns together.
 Below we load the ``User`` and ``Address`` entities at once using this method:
 
 .. sourcecode:: python+sql
     ...                     filter(Address.email_address=='jack@google.com').\
     ...                     all():   # doctest: +NORMALIZE_WHITESPACE
     ...     print u, a
-    SELECT users.id AS users_id, 
-            users.name AS users_name, 
+    SELECT users.id AS users_id,
+            users.name AS users_name,
             users.fullname AS users_fullname,
-            users.password AS users_password, 
+            users.password AS users_password,
             addresses.id AS addresses_id,
-            addresses.email_address AS addresses_email_address, 
+            addresses.email_address AS addresses_email_address,
             addresses.user_id AS addresses_user_id
     FROM users, addresses
-    WHERE users.id = addresses.user_id 
+    WHERE users.id = addresses.user_id
             AND addresses.email_address = ?
     ('jack@google.com',)
     {stop}<User('jack','Jack Bean', 'gjffdd')> <Address('jack@google.com')>
     {sql}>>> session.query(User).join(Address).\
     ...         filter(Address.email_address=='jack@google.com').\
     ...         all() #doctest: +NORMALIZE_WHITESPACE
-    SELECT users.id AS users_id, 
-            users.name AS users_name, 
-            users.fullname AS users_fullname, 
+    SELECT users.id AS users_id,
+            users.name AS users_name,
+            users.fullname AS users_fullname,
             users.password AS users_password
     FROM users JOIN addresses ON users.id = addresses.user_id
     WHERE addresses.email_address = ?
     query.join(Address, User.addresses)              # same, with explicit target
     query.join('addresses')                          # same, using a string
 
-As you would expect, the same idea is used for "outer" joins, using the 
+As you would expect, the same idea is used for "outer" joins, using the
 :meth:`~.Query.outerjoin` function::
 
     query.outerjoin(User.addresses)   # LEFT OUTER JOIN
     ...     filter(adalias1.email_address=='jack@google.com').\
     ...     filter(adalias2.email_address=='j25@yahoo.com'):
     ...     print username, email1, email2      # doctest: +NORMALIZE_WHITESPACE
-    SELECT users.name AS users_name, 
+    SELECT users.name AS users_name,
             addresses_1.email_address AS addresses_1_email_address,
             addresses_2.email_address AS addresses_2_email_address
-    FROM users JOIN addresses AS addresses_1 
+    FROM users JOIN addresses AS addresses_1
             ON users.id = addresses_1.user_id
-    JOIN addresses AS addresses_2 
+    JOIN addresses AS addresses_2
             ON users.id = addresses_2.user_id
-    WHERE addresses_1.email_address = ? 
+    WHERE addresses_1.email_address = ?
             AND addresses_2.email_address = ?
     ('jack@google.com', 'j25@yahoo.com')
     {stop}jack jack@google.com j25@yahoo.com
 get rows back for those users who don't have any addresses, e.g.::
 
     SELECT users.*, adr_count.address_count FROM users LEFT OUTER JOIN
-        (SELECT user_id, count(*) AS address_count 
+        (SELECT user_id, count(*) AS address_count
             FROM addresses GROUP BY user_id) AS adr_count
         ON users.id=adr_count.user_id
 
     {sql}>>> for u, count in session.query(User, stmt.c.address_count).\
     ...     outerjoin(stmt, User.id==stmt.c.user_id).order_by(User.id): # doctest: +NORMALIZE_WHITESPACE
     ...     print u, count
-    SELECT users.id AS users_id, 
+    SELECT users.id AS users_id,
             users.name AS users_name,
-            users.fullname AS users_fullname, 
+            users.fullname AS users_fullname,
             users.password AS users_password,
             anon_1.address_count AS anon_1_address_count
-    FROM users LEFT OUTER JOIN 
+    FROM users LEFT OUTER JOIN
         (SELECT addresses.user_id AS user_id, count(?) AS address_count
-        FROM addresses GROUP BY addresses.user_id) AS anon_1 
+        FROM addresses GROUP BY addresses.user_id) AS anon_1
         ON users.id = anon_1.user_id
     ORDER BY users.id
     ('*',)
     >>> for user, address in session.query(User, adalias).\
     ...         join(adalias, User.addresses): # doctest: +NORMALIZE_WHITESPACE
     ...     print user, address
-    SELECT users.id AS users_id, 
-                users.name AS users_name, 
+    SELECT users.id AS users_id,
+                users.name AS users_name,
                 users.fullname AS users_fullname,
-                users.password AS users_password, 
+                users.password AS users_password,
                 anon_1.id AS anon_1_id,
-                anon_1.email_address AS anon_1_email_address, 
+                anon_1.email_address AS anon_1_email_address,
                 anon_1.user_id AS anon_1_user_id
-    FROM users JOIN 
-        (SELECT addresses.id AS id, 
-                addresses.email_address AS email_address, 
+    FROM users JOIN
+        (SELECT addresses.id AS id,
+                addresses.email_address AS email_address,
                 addresses.user_id AS user_id
         FROM addresses
-        WHERE addresses.email_address != ?) AS anon_1 
+        WHERE addresses.email_address != ?) AS anon_1
         ON users.id = anon_1.user_id
     ('j25@yahoo.com',)
     {stop}<User('jack','Jack Bean', 'gjffdd')> <Address('jack@google.com')>
 
     {sql}>>> session.query(Address).\
     ...         filter(~Address.user.has(User.name=='jack')).all() # doctest: +NORMALIZE_WHITESPACE
-    SELECT addresses.id AS addresses_id, 
+    SELECT addresses.id AS addresses_id,
             addresses.email_address AS addresses_email_address,
             addresses.user_id AS addresses_user_id
     FROM addresses
 
 Recall earlier that we illustrated a **lazy loading** operation, when
 we accessed the ``User.addresses`` collection of a ``User`` and SQL
-was emitted.  If you want to reduce the number of queries (dramatically, in many cases), 
+was emitted.  If you want to reduce the number of queries (dramatically, in many cases),
 we can apply an **eager load** to the query operation.   SQLAlchemy
 offers three types of eager loading, two of which are automatic, and a third
 which involves custom criterion.   All three are usually invoked via functions known
 A good choice for loading a set of objects as well as their related collections
 is the :func:`.orm.subqueryload` option, which emits a second SELECT statement
 that fully loads the collections associated with the results just loaded.
-The name "subquery" originates from the fact that the SELECT statement 
+The name "subquery" originates from the fact that the SELECT statement
 constructed directly via the :class:`.Query` is re-used, embedded as a subquery
-into a SELECT against the related table.   This is a little elaborate but 
+into a SELECT against the related table.   This is a little elaborate but
 very easy to use:
 
 .. sourcecode:: python+sql
     {sql}>>> jack = session.query(User).\
     ...                 options(subqueryload(User.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 
-    FROM users 
+    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 
+    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',)
     {stop}>>> jack
 a LEFT OUTER JOIN, so that the lead object as well as the related object
 or collection is loaded in one step.   We illustrate loading the same
 ``addresses`` collection in this way - note that even though the ``User.addresses``
-collection on ``jack`` is actually populated right now, the query 
+collection on ``jack`` is actually populated right now, the query
 will emit the extra join regardless:
 
 .. sourcecode:: python+sql
     {sql}>>> jack = session.query(User).\
     ...                        options(joinedload(User.addresses)).\
     ...                        filter_by(name='jack').one() #doctest: +NORMALIZE_WHITESPACE
-    SELECT users.id AS users_id, 
-            users.name AS users_name, 
+    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 AS addresses_1_email_address, 
+            users.password AS users_password,
+            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
-    FROM users 
+    FROM users
         LEFT OUTER JOIN addresses AS addresses_1 ON users.id = addresses_1.user_id
     WHERE users.name = ? ORDER BY addresses_1.id
     ('jack',)
    The join created by :func:`.joinedload` is anonymously aliased such that
    it **does not affect the query results**.   An :meth:`.Query.order_by`
    or :meth:`.Query.filter` call **cannot** reference these aliased
-   tables - so-called "user space" joins are constructed using 
+   tables - so-called "user space" joins are constructed using
    :meth:`.Query.join`.   The rationale for this is that :func:`.joinedload` is only
    applied in order to affect how related objects or collections are loaded
    as an optimizing detail - it can be added or removed with no impact
-   on actual results.   See the section :ref:`zen_of_eager_loading` for 
+   on actual results.   See the section :ref:`zen_of_eager_loading` for
    a detailed description of how this is used.
 
 Explicit Join + Eagerload
     ...                             filter(User.name=='jack').\
     ...                             options(contains_eager(Address.user)).\
     ...                             all() #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.id AS addresses_id, 
-            addresses.email_address AS addresses_email_address, 
-            addresses.user_id AS addresses_user_id 
-    FROM addresses JOIN users ON users.id = addresses.user_id 
+    SELECT users.id AS users_id,
+            users.name AS users_name,
+            users.fullname AS users_fullname,
+            users.password AS users_password,
+            addresses.id AS addresses_id,
+            addresses.email_address AS addresses_email_address,
+            addresses.user_id AS addresses_user_id
+    FROM addresses JOIN users ON users.id = addresses.user_id
     WHERE users.name = ?
     ('jack',)
 
     <User('jack','Jack Bean', 'gjffdd')>
 
 For more information on eager loading, including how to configure various forms
-of loading by default, see the section :ref:`loading_toplevel`.
+of loading by default, see the section :doc:`/orm/loading`.
 
 Deleting
 ========
     (None, 2)
     DELETE FROM users WHERE users.id = ?
     (5,)
-    SELECT count(*) AS count_1 
-    FROM (SELECT users.id AS users_id, 
-            users.name AS users_name, 
-            users.fullname AS users_fullname, 
-            users.password AS users_password 
-    FROM users 
+    SELECT count(*) AS count_1
+    FROM (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 = ?) AS anon_1
     ('jack',)
     {stop}0
     {sql}>>> session.query(Address).filter(
     ...     Address.email_address.in_(['jack@google.com', 'j25@yahoo.com'])
     ...  ).count() # doctest: +NORMALIZE_WHITESPACE
-    SELECT count(*) AS count_1 
-    FROM (SELECT addresses.id AS addresses_id, 
-                    addresses.email_address AS addresses_email_address, 
-                    addresses.user_id AS addresses_user_id 
-    FROM addresses 
+    SELECT count(*) AS count_1
+    FROM (SELECT addresses.id AS addresses_id,
+                    addresses.email_address AS addresses_email_address,
+                    addresses.user_id AS addresses_user_id
+    FROM addresses
     WHERE addresses.email_address IN (?, ?)) AS anon_1
     ('jack@google.com', 'j25@yahoo.com')
     {stop}2
     # load Jack by primary key
     {sql}>>> jack = session.query(User).get(5)    #doctest: +NORMALIZE_WHITESPACE
     BEGIN (implicit)
-    SELECT users.id AS users_id, 
-            users.name AS users_name, 
-            users.fullname AS users_fullname, 
+    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.id = ?
 
     # remove one Address (lazy load fires off)
     {sql}>>> del jack.addresses[1] #doctest: +NORMALIZE_WHITESPACE
-    SELECT addresses.id AS addresses_id, 
-            addresses.email_address AS addresses_email_address, 
+    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
     ... ).count() # doctest: +NORMALIZE_WHITESPACE
     DELETE FROM addresses WHERE addresses.id = ?
     (2,)
-    SELECT count(*) AS count_1 
-    FROM (SELECT addresses.id AS addresses_id, 
-                    addresses.email_address AS addresses_email_address, 
-                    addresses.user_id AS addresses_user_id 
-    FROM addresses 
+    SELECT count(*) AS count_1
+    FROM (SELECT addresses.id AS addresses_id,
+                    addresses.email_address AS addresses_email_address,
+                    addresses.user_id AS addresses_user_id
+    FROM addresses
     WHERE addresses.email_address IN (?, ?)) AS anon_1
     ('jack@google.com', 'j25@yahoo.com')
     {stop}1
     (1,)
     DELETE FROM users WHERE users.id = ?
     (5,)
-    SELECT count(*) AS count_1 
-    FROM (SELECT users.id AS users_id, 
-                    users.name AS users_name, 
-                    users.fullname AS users_fullname, 
-                    users.password AS users_password 
-    FROM users 
+    SELECT count(*) AS count_1
+    FROM (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 = ?) AS anon_1
     ('jack',)
     {stop}0
     {sql}>>> session.query(Address).filter(
     ...    Address.email_address.in_(['jack@google.com', 'j25@yahoo.com'])
     ... ).count() # doctest: +NORMALIZE_WHITESPACE
-    SELECT count(*) AS count_1 
-    FROM (SELECT addresses.id AS addresses_id, 
-                    addresses.email_address AS addresses_email_address, 
-                    addresses.user_id AS addresses_user_id 
-    FROM addresses 
+    SELECT count(*) AS count_1
+    FROM (SELECT addresses.id AS addresses_id,
+                    addresses.email_address AS addresses_email_address,
+                    addresses.user_id AS addresses_user_id
+    FROM addresses
     WHERE addresses.email_address IN (?, ?)) AS anon_1
     ('jack@google.com', 'j25@yahoo.com')
     {stop}0
 .. topic:: More on Cascades
 
    Further detail on configuration of cascades is at :ref:`unitofwork_cascades`.
-   The cascade functionality can also integrate smoothly with 
+   The cascade functionality can also integrate smoothly with
    the ``ON DELETE CASCADE`` functionality of the relational database.
    See :ref:`passive_deletes` for details.
 
 
 Above, we can see declaring a :class:`.Table` directly is a little different
 than declaring a mapped class.  :class:`.Table` is a constructor function, so
-each individual :class:`.Column` argument is separated by a comma.  The 
+each individual :class:`.Column` argument is separated by a comma.  The
 :class:`.Column` object is also given its name explicitly, rather than it being
 taken from an assigned attribute name.
 
     {sql}>>> wendy = session.query(User).\
     ...                 filter_by(name='wendy').\
     ...                 one() #doctest: +NORMALIZE_WHITESPACE
-    SELECT users.id AS users_id, 
-            users.name AS users_name, 
-            users.fullname AS users_fullname, 
+    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 = ?
     (2, "Wendy's Blog Post", 'This is a test')
     INSERT INTO post_keywords (post_id, keyword_id) VALUES (?, ?)
     ((1, 1), (1, 2))
-    SELECT posts.id AS posts_id, 
-            posts.user_id AS posts_user_id, 
-            posts.headline AS posts_headline, 
+    SELECT posts.id AS posts_id,
+            posts.user_id AS posts_user_id,
+            posts.headline AS posts_headline,
             posts.body AS posts_body
     FROM posts
     WHERE EXISTS (SELECT 1
         FROM post_keywords, keywords
-        WHERE posts.id = post_keywords.post_id 
-            AND keywords.id = post_keywords.keyword_id 
+        WHERE posts.id = post_keywords.post_id
+            AND keywords.id = post_keywords.keyword_id
             AND keywords.keyword = ?)
     ('firstpost',)
     {stop}[BlogPost("Wendy's Blog Post", 'This is a test', <User('wendy','Wendy Williams', 'foobar')>)]
     ...             filter(BlogPost.author==wendy).\
     ...             filter(BlogPost.keywords.any(keyword='firstpost')).\
     ...             all() #doctest: +NORMALIZE_WHITESPACE
-    SELECT posts.id AS posts_id, 
-            posts.user_id AS posts_user_id, 
-            posts.headline AS posts_headline, 
+    SELECT posts.id AS posts_id,
+            posts.user_id AS posts_user_id,
+            posts.headline AS posts_headline,
             posts.body AS posts_body
     FROM posts
     WHERE ? = posts.user_id AND (EXISTS (SELECT 1
         FROM post_keywords, keywords
-        WHERE posts.id = post_keywords.post_id 
-            AND keywords.id = post_keywords.keyword_id 
+        WHERE posts.id = post_keywords.post_id
+            AND keywords.id = post_keywords.keyword_id
             AND keywords.keyword = ?))
     (2, 'firstpost')
     {stop}[BlogPost("Wendy's Blog Post", 'This is a test', <User('wendy','Wendy Williams', 'foobar')>)]
     {sql}>>> wendy.posts.\
     ...         filter(BlogPost.keywords.any(keyword='firstpost')).\
     ...         all() #doctest: +NORMALIZE_WHITESPACE
-    SELECT posts.id AS posts_id, 
-            posts.user_id AS posts_user_id, 
-            posts.headline AS posts_headline, 
+    SELECT posts.id AS posts_id,
+            posts.user_id AS posts_user_id,
+            posts.headline AS posts_headline,
             posts.body AS posts_body
     FROM posts
     WHERE ? = posts.user_id AND (EXISTS (SELECT 1
         FROM post_keywords, keywords
-        WHERE posts.id = post_keywords.post_id 
-            AND keywords.id = post_keywords.keyword_id 
+        WHERE posts.id = post_keywords.post_id
+            AND keywords.id = post_keywords.keyword_id
             AND keywords.keyword = ?))
     (2, 'firstpost')
     {stop}[BlogPost("Wendy's Blog Post", 'This is a test', <User('wendy','Wendy Williams', 'foobar')>)]
 
 Relationship Reference: :ref:`relationship_config_toplevel`
 
-Session Reference: :ref:`session_toplevel`
+Session Reference: :doc:`/orm/session`

lib/sqlalchemy/engine/__init__.py

 
     See also:
 
-    :ref:`engines_toplevel`
+    :doc:`/core/engines`
 
     :ref:`connections_toplevel`
 

lib/sqlalchemy/engine/base.py

 
     See also:
 
-    :ref:`engines_toplevel`
+    :doc:`/core/engines`
 
     :ref:`connections_toplevel`
 

lib/sqlalchemy/orm/__init__.py

 def relationship(argument, secondary=None, **kwargs):
     """Provide a relationship of a primary Mapper to a secondary Mapper.
 
-    .. versionchanged:: 0.6
-        :func:`relationship` is historically known as :func:`relation`.
-
     This corresponds to a parent-child or associative table relationship.  The
     constructed class is an instance of :class:`.RelationshipProperty`.
 
 
       * None - a synonym for 'noload'
 
-      Detailed discussion of loader strategies is at :ref:`loading_toplevel`.
+      Detailed discussion of loader strategies is at :doc:`/orm/loading`.
 
     :param load_on_pending=False:
       Indicates loading behavior for transient or pending parent objects.
       not compile into eager or lazy loaders properly. If this is the
       case, use an alternative method.
 
+    .. versionchanged:: 0.6
+        :func:`relationship` was renamed from its previous name :func:`relation`.
+
     """
     return RelationshipProperty(argument, secondary=secondary, **kwargs)
 

lib/sqlalchemy/orm/query.py

         foreign-key-to-primary-key criterion, will also use an
         operation equivalent to :meth:`~.Query.get` in order to retrieve
         the target value from the local identity map
-        before querying the database.  See :ref:`loading_toplevel`
+        before querying the database.  See :doc:`/orm/loading`
         for further details on relationship loading.
 
         :param ident: A scalar or tuple value representing
 
         Most supplied options regard changing how column- and
         relationship-mapped attributes are loaded. See the sections
-        :ref:`deferred` and :ref:`loading_toplevel` for reference
+        :ref:`deferred` and :doc:`/orm/loading` for reference
         documentation.
 
         """

lib/sqlalchemy/orm/session.py

 class Session(_SessionClassMethods):
     """Manages persistence operations for ORM-mapped objects.
 
-    The Session's usage paradigm is described at :ref:`session_toplevel`.
+    The Session's usage paradigm is described at :doc:`/orm/session`.
 
 
     """

lib/sqlalchemy/orm/util.py

 
     :param base: Base class to be aliased.
 
-    :param cls_or_mappers: a single class or mapper, or list of
+    :param classes: a single class or mapper, or list of
         class/mappers, which inherit from the base class.
         Alternatively, it may also be the string ``'*'``, in which case
         all descending mapped classes will be added to the FROM clause.

lib/sqlalchemy/sql/operators.py

 
          .. versionadded:: 0.8 - added the 'precedence' argument.
 
+        .. seealso::
+
+            :ref:`types_operators`
+
         """
         operator = custom_op(opstring, precedence)
 
     def __radd__(self, other):
         """Implement the ``+`` operator in reverse.
 
-        See :meth:`__add__`.
+        See :meth:`.ColumnOperators.__add__`.
 
         """
         return self.reverse_operate(add, other)
     def __rsub__(self, other):
         """Implement the ``-`` operator in reverse.
 
-        See :meth:`__sub__`.
+        See :meth:`.ColumnOperators.__sub__`.
 
         """
         return self.reverse_operate(sub, other)
     def __rmul__(self, other):
         """Implement the ``*`` operator in reverse.
 
-        See :meth:`__mul__`.
+        See :meth:`.ColumnOperators.__mul__`.
 
         """
         return self.reverse_operate(mul, other)
     def __rdiv__(self, other):
         """Implement the ``/`` operator in reverse.
 
-        See :meth:`__div__`.
+        See :meth:`.ColumnOperators.__div__`.
 
         """
         return self.reverse_operate(div, other)
         if the parent object has non-string affinity.
         If the parent object has a string affinity,
         produces the concatenation operator, ``a || b`` -
-        see :meth:`concat`.
+        see :meth:`.ColumnOperators.concat`.
 
         """
         return self.operate(add, other)
     def __rtruediv__(self, other):
         """Implement the ``//`` operator in reverse.
 
-        See :meth:`__truediv__`.
+        See :meth:`.ColumnOperators.__truediv__`.
 
         """
         return self.reverse_operate(truediv, other)

test/orm/test_of_type.py

                 sess.query(Company).join(
                     Company.employees.of_type(wp)
                 ).order_by(Company.company_id, wp.person_id).\
+                options(contains_eager(Company.employees.of_type(wp))).all(),
+                [self.c1, self.c2]
+            )
+        self.assert_sql_count(testing.db, go, 1)
+
+    def test_with_polymorphic_join_exec_contains_eager_two(self):
+        sess = Session()
+        def go():
+            wp = with_polymorphic(Person, [Engineer, Manager], aliased=True)
+            eq_(
+                sess.query(Company).join(
+                    Company.employees.of_type(wp)
+                ).order_by(Company.company_id, wp.person_id).\
                 options(contains_eager(Company.employees, alias=wp)).all(),
                 [self.c1, self.c2]
             )