]> git.ipfire.org Git - thirdparty/sqlalchemy/sqlalchemy.git/commitdiff
merged trunk r2901-2924
authorMike Bayer <mike_mp@zzzcomputing.com>
Mon, 16 Jul 2007 16:06:16 +0000 (16:06 +0000)
committerMike Bayer <mike_mp@zzzcomputing.com>
Mon, 16 Jul 2007 16:06:16 +0000 (16:06 +0000)
23 files changed:
CHANGES
README
doc/build/content/adv_datamapping.txt
doc/build/content/datamapping.txt
doc/build/content/dbengine.txt
doc/build/content/metadata.txt
doc/build/content/sqlconstruction.txt
doc/build/content/tutorial.txt
doc/build/content/unitofwork.txt
doc/build/read_markdown.py
doc/build/templates/formatting.html
lib/sqlalchemy/ansisql.py
lib/sqlalchemy/databases/oracle.py
lib/sqlalchemy/engine/base.py
lib/sqlalchemy/orm/query.py
lib/sqlalchemy/orm/session.py
lib/sqlalchemy/sql.py
lib/sqlalchemy/types.py
test/engine/reflection.py
test/orm/inheritance/basic.py
test/orm/query.py
test/sql/testtypes.py
test/testbase.py

diff --git a/CHANGES b/CHANGES
index 9e19b5fd85c114a360c359d43ea49c32baa96448..83df609f103ded9d1f626ae36c601dcc75496071 100644 (file)
--- a/CHANGES
+++ b/CHANGES
       join(['a', 'c']).filter(<some other crit>).all()
       in 0.4 all join() calls start from the "root"
     - added synchronization to the mapper() construction step, to avoid
-      thread collections when pre-existing mappers are compiling in a 
+      thread collisions when pre-existing mappers are compiling in a 
       different thread [ticket:613]
     - a warning is issued by Mapper when two primary key columns of the
       same name are munged into a single attribute.  this happens frequently
       this is also mostly to help inheritance scenarios formulate the best 
       choice of primary key columns.  [ticket:185]
     - added 'bind' argument to Sequence.create()/drop(), ColumnDefault.execute()
+    - columns can be overridden in a reflected table with a "key" 
+      attribute different than the column's name, including for primary key
+      columns [ticket:650]
     - fixed "ambiguous column" result detection, when dupe col names exist
       in a result [ticket:657]
     - some enhancements to "column targeting", the ability to match a column
     - fixed catching of some errors that imply a dropped connection [ticket:625]
     - fixed escaping of the modulo operator [ticket:624]
     - added 'fields' to reserved words [ticket:590]
+    - various reflection enhancement/fixes
 - oracle
     - datetime fixes: got subsecond TIMESTAMP to work [ticket:604],
       added OracleDate which supports types.Date with only year/month/day
       LOB objects detected in a result set to be forced into OracleBinary
       so that the LOB is read() automatically, if no typemap was present
       (i.e., if a textual execute() was issued).
+    - mod operator '%' produces MOD [ticket:624]
+    - converts cx_oracle datetime objects to Python datetime.datetime when
+      Python 2.3 used [ticket:542]
+    - fixed unicode conversion in Oracle TEXT type
 - postgres
     - fixed escaping of the modulo operator [ticket:624]
     - added support for reflection of domains [ticket:570]
     - sqlite better handles datetime/date/time objects mixed and matched
       with various Date/Time/DateTime columns
     - string PK column inserts dont get overwritten with OID [ticket:603] 
-
 - mssql
     - fix port option handling for pyodbc [ticket:634]
     - now able to reflect start and increment values for identity columns
diff --git a/README b/README
index f4726e164f09f27a9cd793ce8f5427319a21ff40..881ef4c70937be10690748844c342062e682b644 100644 (file)
--- a/README
+++ b/README
@@ -6,7 +6,7 @@ To install:
 
        python setup.py install
 
-SVN checkouts also inlcude setup.cfg file allowing setuptools to create 
+SVN checkouts also include setup.cfg file allowing setuptools to create 
 an svn-tagged build.  
 
 Documentation is available in HTML format in the ./doc/ directory.
index 3877152faa1cc9925df5a16bb81e6bee01e4f9d5..07815a583db4f59db931de508caafd2045d5cbfa 100644 (file)
@@ -150,6 +150,7 @@ In this example, a class `MyClass` is defined, which is associated with a parent
     myparent.myclasses.append(MyClass('this is myclass'))
     myclass = myparent.myclasses['this is myclass']
 
+Note: SQLAlchemy 0.4 has an overhauled and much improved implementation for custom list classes, with some slight API changes.
 
 #### Custom Join Conditions {@name=customjoin}
 
@@ -253,8 +254,8 @@ You can defer or undefer columns at the `Query` level with the `options` method:
 
     {python}
     query = session.query(Book)
-    query.options(defer('summary')).select()
-    query.options(undefer('excerpt')).select()
+    query.options(defer('summary')).all()
+    query.options(undefer('excerpt')).all()
 
 #### Working with Large Collections
 
@@ -359,45 +360,29 @@ The "order_by" parameter can be sent to a mapper, overriding the per-engine orde
     # order by multiple items
     mapper = mapper(User, users_table, order_by=[users_table.c.user_id, desc(users_table.c.user_name)])
 
-"order_by" can also be specified to an individual `select` method, overriding all other per-engine/per-mapper orderings:
+"order_by" can also be specified with queries, overriding all other per-engine/per-mapper orderings:
 
     {python}
     # order by a column
-    l = mapper.select(users_table.c.user_name=='fred', order_by=users_table.c.user_id)
+    l = query.filter(users_table.c.user_name=='fred').order_by(users_table.c.user_id).all()
     
     # order by multiple criterion
-    l = mapper.select(users_table.c.user_name=='fred', order_by=[users_table.c.user_id, desc(users_table.c.user_name)])
+    l = query.filter(users_table.c.user_name=='fred').order_by([users_table.c.user_id, desc(users_table.c.user_name)])
 
-For relations, the "order_by" property can also be specified to all forms of relation:
+The "order_by" property can also be specified on a `relation()` which will control the ordering of the collection:
 
     {python}
+    mapper(Address, addresses_table)
+    
     # order address objects by address id
-    mapper = mapper(User, users_table, properties = {
-        'addresses' : relation(mapper(Address, addresses_table), order_by=addresses_table.c.address_id)
+    mapper(User, users_table, properties = {
+        'addresses' : relation(Address, order_by=addresses_table.c.address_id)
     })
     
-    # eager load with ordering - the ORDER BY clauses of parent/child will be organized properly
-    mapper = mapper(User, users_table, properties = {
-        'addresses' : relation(mapper(Address, addresses_table), order_by=desc(addresses_table.c.email_address), lazy=False)
-    }, order_by=users_table.c.user_id)
     
-### Limiting Rows {@name=limits}
+### Limiting Rows Combined with Eager Loads {@name=limits}
 
-You can limit rows in a regular SQL query by specifying `limit` and `offset`.  A Mapper can handle the same concepts:
-
-    {python}
-    class User(object):
-        pass
-    
-    mapper(User, users_table)
-    {sql}r = session.query(User).select(limit=20, offset=10)
-    SELECT users.user_id AS users_user_id, 
-    users.user_name AS users_user_name, users.password AS users_password 
-    FROM users ORDER BY users.oid 
-    LIMIT 20 OFFSET 10
-    {}
-
-However, things get tricky when dealing with eager relationships, since a straight LIMIT of rows does not represent the count of items when joining against other tables to load related items as well.  So here is what SQLAlchemy will do when you use limit or offset with an eager relationship:
+As indicated in the docs on `Query`, you can limit rows using `limit()` and `offset()`.  However, things get tricky when dealing with eager relationships, since a straight LIMIT of rows will interfere with the eagerly-loaded rows.  So here is what SQLAlchemy will do when you use limit or offset with an eager relationship:
 
     {python}
     class User(object):
@@ -407,7 +392,7 @@ However, things get tricky when dealing with eager relationships, since a straig
         mapper(User, users_table, properties={
         'addresses' : relation(mapper(Address, addresses_table), lazy=False)
     })
-    r = session.query(User).select(User.c.user_name.like('F%'), limit=20, offset=10)
+    r = session.query(User).filter(User.c.user_name.like('F%')).limit(20).offset(10).all()
     {opensql}SELECT users.user_id AS users_user_id, users.user_name AS users_user_name, 
     users.password AS users_password, addresses.address_id AS addresses_address_id, 
     addresses.user_id AS addresses_user_id, addresses.street AS addresses_street, 
@@ -420,17 +405,15 @@ However, things get tricky when dealing with eager relationships, since a straig
     WHERE rowcount.user_id = users.user_id ORDER BY users.oid, addresses.oid
     {'users_user_name': 'F%'}
     
-The main WHERE clause as well as the limiting clauses are coerced into a subquery; this subquery represents the desired result of objects.  A containing query, which handles the eager relationships, is joined against the subquery to produce the result.
+The main WHERE clause as well as the limiting clauses are coerced into a subquery; this subquery represents the desired result of objects.  A containing query, which handles the eager relationships, is joined against the subquery to produce the result.  This is something to keep in mind as it's a complex query which may be problematic on databases with poor support for LIMIT, such as Oracle which does not support it natively.
 
 ### Mapping a Class with Table Inheritance {@name=inheritance}
 
-Feature Status: [Alpha Implementation][alpha_implementation] 
-
-Inheritance in databases comes in three forms:  *single table inheritance*, where several types of classes are stored in one table, *concrete table inheritance*, where each type of class is stored in its own table, and *multiple table inheritance*, where the parent/child classes are stored in their own tables that are joined together in a select.
+Inheritance in databases comes in three forms:  *single table inheritance*, where several types of classes are stored in one table, *concrete table inheritance*, where each type of class is stored in its own table, and *joined table inheritance*, where the parent/child classes are stored in their own tables that are joined together in a select.
 
-There is also a concept of `polymorphic` loading, which indicates if multiple kinds of classes can be loaded in one pass.
+There is also the ability to load "polymorphically", which is that a single query loads objects of multiple types at once.
 
-SQLAlchemy supports all three kinds of inheritance.  Additionally, true `polymorphic` loading is supported in a straightfoward way for single table inheritance, and has some more manually-configured features that can make it happen for concrete and multiple table inheritance.
+SQLAlchemy supports all three kinds of inheritance.  Additionally, true "polymorphic" loading is supported in a straightfoward way for single table inheritance, and has some more manually-configured features that can make it happen for concrete and multiple table inheritance. 
 
 Working examples of polymorphic inheritance come with the distribution in the directory `examples/polymorphic`.
 
@@ -508,9 +491,7 @@ With polymorphic loading, the SQL query to do the actual polymorphic load must b
     manager_mapper = mapper(Manager, managers_table, inherits=employee_mapper, concrete=True, polymorphic_identity='manager')
     engineer_mapper = mapper(Engineer, engineers_table, inherits=employee_mapper, concrete=True, polymorphic_identity='engineer')
 
-A future release of SQLALchemy might better merge the generated UNION into the mapper construction phase.    
-
-#### Multiple Table Inheritance
+#### Joined Table Inheritance
 
 Like concrete table inheritance, this can be done non-polymorphically, or with a little more complexity, polymorphically:
 
@@ -534,22 +515,18 @@ Like concrete table inheritance, this can be done non-polymorphically, or with a
     mapper(Engineer, engineers, inherits=person_mapper)
     mapper(Manager, managers, inherits=person_mapper)
 
-Polymorphic:
+Polymorphically, joined-table inheritance is easier than concrete, as a simple outer join can usually work:
 
-    {python title="Multiple Table Inheritance, Polymorphic"}
-    person_join = polymorphic_union(
-        {
-            'engineer':employees.join(engineers),
-            'manager':employees.join(managers),
-            'person':employees.select(employees.c.type=='person'),
-        }, None, 'pjoin')
+    {python title="Joined Table Inheritance, Polymorphic"}
+    person_join = people.outerjoin(engineers).outerjoin(managers)
 
-    person_mapper = mapper(Employee, employees, select_table=person_join, polymorphic_on=person_join.c.type, polymorphic_identity='person')
+    person_mapper = mapper(Person, people, select_table=person_join,polymorphic_on=people.c.type, polymorphic_identity='person')
     mapper(Engineer, engineers, inherits=person_mapper, polymorphic_identity='engineer')
     mapper(Manager, managers, inherits=person_mapper, polymorphic_identity='manager')
 
+In SQLAlchemy 0.4, the above mapper setup can load polymorphically *without* the join as well, by issuing distinct queries for each subclasses' table.
 
-The join condition in a multiple table inheritance relationship can be specified explicitly, using `inherit_condition`:
+The join condition in a joined table inheritance structure can be specified explicitly, using `inherit_condition`:
 
     {python}
     AddressUser.mapper = mapper(
@@ -723,49 +700,52 @@ A self-referential mapper where there is more than one relationship on the table
 The "root" property on a TreeNode is a many-to-one relationship.  By default, a self-referential mapper declares relationships as one-to-many, so the extra parameter `remote_side`, pointing to a column or list of columns on the remote side of a relationship, is needed to indicate a "many-to-one" self-referring relationship (note the previous keyword argument `foreignkey` is deprecated).
 Both TreeNode examples above are available in functional form in the `examples/adjacencytree` directory of the distribution.    
 
-### Result-Set Mapping {@name=resultset}
+### Statement and Result-Set ORM Queries {@name=resultset}
 
-Take any result set and feed it into a Query to produce objects.  Multiple mappers can be combined to retrieve unrelated objects from the same row in one step.  The `instances` method on Query takes a ResultProxy object, which is the result type generated from SQLEngine, and delivers object instances.  (note: this method has been moved off of Mapper, where it is deprecated).
+Take any textual statement, constructed statement or result set and feed it into a Query to produce objects.  Below, we define two class/mapper combinations, issue a SELECT statement, and send the result object to the method `instances()` method on `Query`:
 
     {python}
     class User(object):
         pass
 
-    mapper(User, users_table)
-    
-    # select users
-    c = users_table.select().execute()
-
-    # get objects
-    userlist = session.query(User).instances(c)
-    
-    {python}
-    # define a second class/mapper
     class Address(object):
         pass
-        
+
+    mapper(User, users_table)
+    
     mapper(Address, addresses_table)
 
     # select users and addresses in one query
     # use_labels is so that the user_id column in both tables are distinguished
     s = select([users_table, addresses_table], users_table.c.user_id==addresses_table.c.user_id, use_labels=True)
 
-    # execute it, and process the results with the User mapper, chained to the Address mapper
-    r = session.query(User).instances(s.execute(), class_mapper(Address))
+    # execute it, and process the results, asking for both User and Address objects
+    r = session.query(User, Address).instances(s.execute())
     
-    # result rows are an array of objects, one for each mapper used
+    # result rows come back as tuples
+    for entry in r:
+        user = r[0]
+        address = r[1]
+
+Alternatively, the `from_statement()` method may be used with either a textual string or SQL construct:
+
+    {python}
+    s = select([users_table, addresses_table], users_table.c.user_id==addresses_table.c.user_id, use_labels=True)
+
+    r = session.query(User, Address).from_statement(s).all()
+
     for entry in r:
         user = r[0]
         address = r[1]
 
-#### Combining Eager Loads with Result Set Mappings
+#### Combining Eager Loads with Statement/Result Set Queries
 
-When result-set mapping is used with a particular Mapper, SQLAlchemy has no access to the query being used and therefore has no way of tacking on its own `LEFT OUTER JOIN` conditions that are normally used to eager load relationships.  If the query being constructed is created in such a way that it returns rows not just from a parent table (or tables) but also returns rows from child tables, the result-set mapping can be notified as to which additional properties are contained within the result set.  This is done using the `contains_eager()` query option, which specifies the name of the relationship to be eagerly loaded, and optionally a **decorator function** that can translate aliased column names when results are received.
+When full statement/result loads are used with `Query`, SQLAlchemy does not affect the SQL query itself, and therefore has no way of tacking on its own `LEFT [OUTER] JOIN` conditions that are normally used to eager load relationships.  If the query being constructed is created in such a way that it returns rows not just from a parent table (or tables) but also returns rows from child tables, the result-set mapping can be notified as to which additional properties are contained within the result set.  This is done using the `contains_eager()` query option, which specifies the name of the relationship to be eagerly loaded.
 
     {python}
     # mapping is the users->addresses mapping
     mapper(User, users_table, properties={
-        'addresses':relation(Address, addresses_table, lazy=False)
+        'addresses':relation(Address, addresses_table)
     })
     
     # define a query on USERS with an outer join to ADDRESSES
@@ -777,7 +757,7 @@ When result-set mapping is used with a particular Mapper, SQLAlchemy has no acce
     # get results normally
     r = query.instances(statement.execute())
 
-It is often the case with large queries that some of the tables within the query need to be aliased in order to distinguish them from other occurences of the same table within the query.  A query that attempts to add eagerly loaded child items will often have this condition.  The `contains_eager()` function takes a keyword argument `alias` which can either be the string name of an alias, or an actual `Alias` construct used in constructing the query, which will target the eager loading towards the columns of that alias (new in version 0.3.5):
+If the "eager" portion of the statement is "alisaed", the `alias` keyword argument to `contains_eager()` may be used to indicate it.  This is a string alias name or reference to an actual `Alias` object:
 
     {python}
     # use an alias of the addresses table
@@ -790,12 +770,12 @@ It is often the case with large queries that some of the tables within the query
     query = session.query(User).options(contains_eager('addresses', alias=adalias))
 
     # get results normally
-    {sql}r = query.instances(statement.execute())
+    {sql}r = query.from_statement(query).all()
     SELECT users.user_id AS users_user_id, users.user_name AS users_user_name, adalias.address_id AS adalias_address_id, 
     adalias.user_id AS adalias_user_id, adalias.email_address AS adalias_email_address, (...other columns...)
     FROM users LEFT OUTER JOIN email_addresses AS adalias ON users.user_id = adalias.user_id
 
-In the case that the main table itself is also aliased, the `contains_alias()` option can be used (new in version 0.3.5):
+In the case that the main table itself is also aliased, the `contains_alias()` option can be used:
 
     {python}
     # define an aliased UNION called 'ulist'
@@ -812,9 +792,9 @@ In the case that the main table itself is also aliased, the `contains_alias()` o
     r = query.instances(statement.execute())
     
 
-### Mapper Options {@name=mapperoptions}
+### Mapper Keyword Arguments {@name=mapperoptions}
 
-Options which can be sent to the `mapper()` function.  For arguments to `relation()`, see [advdatamapping_properties_relationoptions](rel:advdatamapping_properties_relationoptions).
+Keyword arguments which can be used with the `mapper()` function.  For arguments to `relation()`, see [advdatamapping_properties_relationoptions](rel:advdatamapping_properties_relationoptions).
 
 * **allow_column_override** - if True, allows the usage of a `relation()` which has the same name as a column in the mapped table. The table column will no longer be mapped.
 * **allow_null_pks=False** - indicates that composite primary keys where one or more (but not all) columns contain NULL is a valid primary key. Primary keys which contain NULL values usually indicate that a result row does not contain an entity and should be skipped.
index d91cc3cab07dfc96b6779c3070f5b7701155f14a..d35fecfb34b2a0259fa1bd2c079d44a7498622fe 100644 (file)
@@ -6,19 +6,9 @@ Data Mapping {@name=datamapping}
 
 ### Basic Data Mapping {@name=datamapping}
 
-Data mapping describes the process of defining *Mapper* objects, which associate table metadata with user-defined classes.
+Data mapping describes the process of defining `Mapper` objects, which associate table metadata with user-defined classes.
 
-The `Mapper`'s role is to perform SQL operations upon the database, associating individual table rows with instances of those classes, and individual database columns with properties upon those instances, to transparently associate in-memory objects with a persistent database representation. 
-
-When a `Mapper` is created to associate a `Table` object with a class, all of the columns defined in the `Table` object are associated with the class via property accessors, which add overriding functionality to the normal process of setting and getting object attributes.  These property accessors keep track of changes to object attributes; these changes will be stored to the database when the application "flushes" the current state of objects (known as a *Unit of Work*).
-
-Two objects provide the primary interface for interacting with Mappers and the "unit of work", which are the `Query` object and the `Session` object.  `Query` deals with selecting objects from the database, whereas `Session` provides a context for loaded objects and the ability to communicate changes on those objects back to the database.
-
-The primary method on `Query` for loading objects is its `select()` method, which has similar arguments to a `sqlalchemy.sql.Select` object.  But this select method executes automatically and returns results, instead of awaiting an execute() call.  Instead of returning a cursor-like object, it returns an array of objects.
-
-The three configurational elements to be defined, i.e. the `Table` metadata, the user-defined class, and the `Mapper`, are typically defined as module-level variables, and may be defined in any fashion suitable to the application, with the only requirement being that the class and table metadata are described before the mapper.  For the sake of example, we will be defining these elements close together, but this should not be construed as a requirement; since SQLAlchemy is not a framework, those decisions are left to the developer or an external framework.
-
-Also, keep in mind that the examples in this section deal with explicit `Session` objects mapped directly to `Engine` objects, which represents the most explicit style of using the ORM.  Options exist for how this is configured, including binding `Table` objects directly to `Engines` (described in [metadata_tables_binding](rel:metadata_tables_binding)), as well as using an auto-generating "contextual" session via the SessionContext plugin (described in [plugins_sessioncontext](rel:plugins_sessioncontext)).
+When a `Mapper` is created to associate a `Table` object with a class, all of the columns defined in the `Table` object are associated with the class via property accessors, which add overriding functionality to the normal process of setting and getting object attributes.  These property accessors keep track of changes to object attributes; these changes will be stored to the database when the application "flushes" the current state of objects.  This pattern is called a *Unit of Work* pattern.
 
 ### Synopsis {@name=synopsis}
 
@@ -52,13 +42,13 @@ Thats all for configuration.  Next, we will create an `Engine` and bind it to a
     engine = create_engine("sqlite://mydb.db")
     
     # session
-    session = create_session(bind_to=engine)
+    session = create_session(bind=engine)
 
-The `session` represents a "workspace" which can load objects and persist changes to the database.  A `Session` [[doc](rel:unitofwork)] [[api](rel:docstrings_sqlalchemy.orm.session_Session)] is best created as local to a particular set of related data operations, such as scoped within a function call, or within a single application request cycle.  Next we illustrate a rudimental query which will load a single object instance.  We will modify one of its attributes and persist the change back to the database.
+The `session` represents a "workspace" which can load objects and persist changes to the database.  Note also that the `bind` parameter is optional; if the underlying `Table` objects are bound as described in [metadata_tables_binding](rel:metadata_tables_binding), it's not needed.  A `Session` [[doc](rel:unitofwork)] [[api](rel:docstrings_sqlalchemy.orm.session_Session)] is best created as local to a particular set of related data operations, such as scoped within a function call, or within a single application request cycle.  Next we illustrate a rudimental query which will load a single object instance.  We will modify one of its attributes and persist the change back to the database.
 
     {python}
     # select
-    {sql}user = session.query(User).selectfirst_by(user_name='fred')
+    {sql}user = session.query(User).filter_by(user_name='fred').first()
     SELECT users.user_id AS users_user_id, users.user_name AS users_user_name, 
     users.fullname AS users_fullname, users.password AS users_password 
     FROM users 
@@ -77,13 +67,13 @@ The `session` represents a "workspace" which can load objects and persist change
     [{'user_name': 'fred jones', 'user_id': 1}]        
     COMMIT
     
-Things to note from the above include that the loaded `User` object has an attribute named `user_name` on it, which corresponds to the `user_name` column in the `users_table`; this attribute was configured at the class level when the `Mapper` which we created first compiled itself.  Our modify operation on this attribute caused the object to be marked as `dirty`, which was picked up automatically within the subsequent `flush()` process.  The `flush()` is the point at which all changes to objects within the `Session` are persisted to the database, and the `User` object is no longer marked as `dirty` until it is again modified.
+Things to note from the above include that the loaded `User` object has an attribute named `user_name` on it, which corresponds to the `user_name` column in  `users_table`; this attribute was configured at the class level by the `Mapper`, as part of it's post-initialization process (this process occurs normally when the mapper is first used).  Our modify operation on this attribute caused the object to be marked as "dirty", which was picked up automatically within the subsequent `flush()` process.  The `flush()` is the point at which all changes to objects within the `Session` are persisted to the database, and the `User` object is no longer marked as "dirty" until it is again modified.
 
 ### The Query Object {@name=query}
 
-The method `session.query(class_or_mapper)` returns a `Query` object [[api](rel:docstrings_sqlalchemy.orm.query_Query)].  `Query` implements a large set of methods which are used to produce and execute select statements tailored for loading object instances.  It returns objects in most cases either as lists of objects or as single instances, depending on the type of query issued.
+The method `session.query(class_or_mapper)` returns a `Query` object [[api](rel:docstrings_sqlalchemy.orm.query_Query)].  `Query` implements methods which are used to produce and execute select statements tailored for loading object instances.  It returns object instances in all cases; usually as a list, but in some cases scalar objects, or lists of tuples which contain multiple kinds of objects and sometimes individual scalar values.
 
-A `Query` is always initially generated starting with the `Session` that we're working with, and is always created relative to a particular class, which is the primary class we wish to load.
+A `Query` is created from the `Session`, relative to a particular class we wish to load.
 
     {python}
     # get a query from a Session based on class:
@@ -95,23 +85,20 @@ Alternatively, an actual `Mapper` instance can be specified instead of a class:
     # locate the mapper corresponding to the User class
     usermapper = class_mapper(User)
 
-    # create query for the User
+    # create query against the User mapper
     query = session.query(usermapper)
-    
-Dealing with mappers explicitly as above is usually not needed except for more advanced patterns where a class may have multiple mappers associated with it.
 
-Once we have a query, we can start loading objects.  The two most rudimental and general purpose methods are `select()` and `select_by()`.  `select()` is oriented towards query criterion constructed as a `ClauseElement` object, which is the kind of object generated when constructing SQL expressions as described in the [SQL](rel:sql) section.  `select_by()` can also accomodate `ClauseElement` objects but is generally more oriented towards keyword arguments which correspond to mapped attribute names.  In both cases, the criterion specified is used to construct the `WHERE` criterion of the generated SQL.  The `Query` object will use this criterion to **compile** the full SQL query issued to the database, combining the `WHERE` condition with the appropriate column selection clauses and `FROM` criterion, incuding whatever modifications are required to control ordering, number of rows returned, joins for loading related objects, etc.
-
-The general form of `select_by()` is:
+A query which joins across multiple tables may also be used to request multiple entities, such as:
 
     {python}
-    def select_by(self, *clause_elements, **keyword_criterion)
+    query = session.query(User, Address)
+    
+Once we have a query, we can start loading objects.  The methods `filter()` and `filter_by()` handle narrowing results, and the methods `all()`, `one()`, and `first()` exist to return all, exactly one, or the first result of the total set of results.   Note that all methods are *generative*, meaning that on each call that doesn't return results, you get a **new** `Query` instance.
 
-Where `*clause_elements` is a set of zero or more `ClauseElement` objects, and `**keyword_criterion` are key/value pairs each of which correspond to a simple equality comparison.  The full set of clause elements and key/value pairs are joined together in the resulting SQL statement via `AND`.
+The `filter_by()` method works with keyword arguments, which are combined together via AND:
 
     {python}
-    # using select_by with keyword arguments
-    {sql}result = query.select_by(name='john', fullname='John Smith')
+    {sql}result = session.query(User).filter_by(name='john', fullname='John Smith').all()
     SELECT users.user_id AS users_user_id, users.user_name AS users_user_name, 
     users.fullname AS users_fullname, users.password AS users_password 
     FROM users 
@@ -119,85 +106,155 @@ Where `*clause_elements` is a set of zero or more `ClauseElement` objects, and `
     ORDER BY users.oid
     {'users_user_name': 'john', 'users_fullname': 'John Smith'}
 
-    # using select_by with preceding ClauseElements followed by keywords
-    result = query.select_by(users_table.c.user_id>224, 
-    {sql}        users_table.c.user_name=='john', fullname='John Smith')
+Whereas `filter()` works with constructed SQL expressions, i.e. those described in [sql](rel:sql):
+
+    {python}
+    {sql}result = session.query(User).filter(users_table.c.name=='john').all()
     SELECT users.user_id AS users_user_id, users.user_name AS users_user_name, 
     users.fullname AS users_fullname, users.password AS users_password 
     FROM users 
-    WHERE users.user_id>:users_user_id AND users.user_name = :users_user_name 
-    AND users.fullname = :users_fullname
+    WHERE users.user_name = :users_user_name 
     ORDER BY users.oid
-    {'users_user_name': 'john', 'users_fullname': 'John Smith', 'users_user_id': 224}
+    {'users_user_name': 'john'}
+
+Sometimes, constructing SQL via expressions can be cumbersome.  For quick SQL expression, the `filter()` method can also accomodate straight text:
+
+    {python}
+    {sql}result = session.query(User).filter("user_id>224").all()
+    SELECT users.user_id AS users_user_id, users.user_name AS users_user_name, 
+    users.fullname AS users_fullname, users.password AS users_password 
+    FROM users 
+    WHERE users.user_id>224
+    ORDER BY users.oid
+    {}
+
+When using text, bind parameters can be specified the same way as in a `text()` clause, using a colon.  To specify the bind parameter values, use the `params()` method:
 
-Note that a `ClauseElement` is generated by each boolean operator (i.e. `==`, `>`) that's performed against a `Column` object - recall that this operator is overloaded to return a binary clause construct.  But the `fullname="John Smith"` argument is not using any kind of overloading, and is a regular Python keyword argument.   Additionally, while `ClauseElements` are constructed against the `Column` elements configured on the mapped `Table`, the keyword-based criterion is constructed against the class-level attribute names which were configured by the mapper.  While they are the same name as their columns in this particular example, they can be configured to have names distinct from their columns.  So it follows that using `ClauseElements` for criterion are closer to the relational side of things, and using keyword arguments are closer towards the domain object side of things.
+    {python}
+    {sql}result = session.query(User).filter("user_id>:value").params(value=224).all()
+    SELECT users.user_id AS users_user_id, users.user_name AS users_user_name, 
+    users.fullname AS users_fullname, users.password AS users_password 
+    FROM users 
+    WHERE users.user_id>:value
+    ORDER BY users.oid
+    {'value': 224}
 
-The `select()` method, unlike the `select_by()` method, is purely `ClauseElement`/relationally oriented and has no domain-level awareness.  Its basic argument signature:
+Multiple `filter()` and `filter_by()` expressions may be combined together.  The resulting statement groups them using AND.
 
     {python}
-    def select(self, clause_element, **additional_options)
+    result = session.query(User).filter(users_table.c.user_id>224).filter_by(name='john').
+    {sql}            filter(users.c.fullname=='John Smith').all()
+    SELECT users.user_id AS users_user_id, users.user_name AS users_user_name, 
+    users.fullname AS users_fullname, users.password AS users_password 
+    FROM users 
+    WHERE users.user_id>:users_user_id AND users.user_name = :users_user_name 
+    AND users.fullname = :users_fullname
+    ORDER BY users.oid
+    {'users_user_name': 'john', 'users_fullname': 'John Smith', 'users_user_id': 224}
 
-The `clause_element` argument again represents a `ClauseElement` which corresponds to the `WHERE` criterion of a `SELECT` statement.  Here, there is only a single `ClauseElement` and it will be used to form the entire set of criterion.  A basic `select()` operation looks like this:
+`filter_by()`'s keyword arguments can also take mapped object instances as comparison arguments.  We'll illustrate this later when we talk about object relationships.
+
+Note that all conjunctions are available explicitly, such as `and_()` and `or_()`, when using `filter()`:
 
     {python}
-    result = query.select(users_table.c.user_name=='john')
+    result = session.query(User).filter(
+        and_(users_table.c.user_id>224, or_(users_table.c.name=='john', users_table.c.name=='ed'))
+        ).all()
 
-To generate `AND` criterion the way `select_by()` does, you use the `and_()` construct from the sql construction system, which generates just another `ClauseElement` containing two sub-`ClauseElement`s:
+Its also straightforward to use an entirely string-based statement, using `from_statement()`; just ensure that the columns clause of the statement contains the column names normally used by the mapper (here illustrated using an asterisk):
 
     {python}
-    result = query.select(and_(users_table.c.user_name=='john', 
-        users_table.c.fullname=='John Smith'))
+    {sql}result = session.query(User).from_statement("SELECT * FROM users").all()
+    SELECT * FROM users
+    {}
 
-From this, one can see that `select()` is stricter in its operation and does not make any kind of assumptions about how to join multiple criterion together.  Of course in all cases where `ClauseElement` is used, any expression can be created using combinations of `and_()`, `or_()`, `not_()`, etc.
+`from_statement()` can also accomodate `select()` constructs:
 
     {python}
-    result = query.select(
-        or_(users_table.c.user_name == 'john', users_table.c.user_name=='fred')
-    {sql})
+    result = session.query(User).from_statement(
+        select([users], users.c.name<'e', having=users.c.name==func.max(users.c.name), group_by=[c for c in users.c])
+    {sql}    ).all()
     SELECT users.user_id AS users_user_id, users.user_name AS users_user_name, 
     users.fullname AS users_fullname, users.password AS users_password 
     FROM users 
-    WHERE users.user_name = :users_user_name  OR users.user_name = :users_user_name_1
+    WHERE users.user_name>:users_user_name HAVING users.user_name == max(users.user_name)
+    GROUP BY users.user_id, users.user_name, users.fullname, users.password
     ORDER BY users.oid
-    {'users_user_name': 'john', 'users_user_name_1': 'fred'}
+    {'users_user_name': 'e'}
+    
+Any set of filtered criterion (or no criterion) can be distilled into a count of rows using `count()`:
+
+    {python}
+    {sql}num = session.query(Users).filter(users_table.c.user_id>224).count()
+    SELECT count(users.id) FROM users WHERE users.user_id>:users_user_id
+    {'users_user_id': 224}
     
-The keyword portion of `select()` is used to indicate further modifications to the generated SQL, including common arguments like ordering, limits and offsets:
+Rows are limited and offset using `limit()` and `offset()`:
 
     {python}
-    result = query.select(users_table.c.user_name=='john', 
-    {sql}            order_by=[users_table.c.fullname], limit=10, offset=12)
+    {sql}result = session.query(User).limit(20).offset(5).all()
     SELECT users.user_id AS users_user_id, users.user_name AS users_user_name, 
     users.fullname AS users_fullname, users.password AS users_password 
-    FROM users 
-    WHERE users.user_name = :users_user_name 
-    ORDER BY users.fullname LIMIT 10 OFFSET 12
-    {'users_user_name': 'john'}
+    FROM users ORDER BY users.oid
+    LIMIT 20 OFFSET 5
+    {}
+
+And ordering is applied, using `Column` objects and related SQL constructs, with `order_by()`:
+
+    {python}
+    {sql}result = session.query(User).order_by(desc(users_table.c.user_name)).all()
+    SELECT users.user_id AS users_user_id, users.user_name AS users_user_name, 
+    users.fullname AS users_fullname, users.password AS users_password 
+    FROM users ORDER BY users.user_name DESC
+    {}
+    
+The `first()` and `one()` methods will also limit rows, and both will return a single object, instead of a list.  In the case of `first()`, rows are limited to just one, and the result is returned as a scalar.  In the case of `one()`, rows are limited to *two*; however, only one is returned.  If two rows are matched, an exception is raised.
 
-#### Issuing Full SELECT statements {@name=fullselect}
+    {python}
+    # load the first result
+    user = session.query(User).first()
+    
+    # load exactly *one* result - if more than one result matches, an exception is raised
+    user = session.query(User).filter_by(name='jack').one()
 
-`select()` also provides an additional calling style, which is to pass a fully constructed `Select` construct to it.
+The `Query`, when evaluated as an iterator, executes results immediately, using whatever state has been built up:
 
     {python}
-    # using a full select object
-    {sql}result = query.select(users_table.select(users_table.c.user_name=='john'))
+    {sql}result = list(session.query(User))
     SELECT users.user_id AS users_user_id, users.user_name AS users_user_name, 
     users.fullname AS users_fullname, users.password AS users_password 
-    FROM users 
-    WHERE users.user_name = :users_user_name
-    {'users_user_name': 'john'}
+    FROM users ORDER BY users.oid
+    {}
+    
+Array indexes and slices work too, adding the corresponding LIMIT and OFFSET clauses:
 
-When a full select is passed, the `Query` object does not generate its own SQL.  Instead, the select construct passed in is used without modification, except that its `use_labels` flag is switched on to prevent column name collisions.  Therefore its expected that the construct will include the proper column clause as appropriate to the mapped class being loaded.
+    {python}
+    {sql}result = list(session.query(User)[1:3])
+    SELECT users.user_id AS users_user_id, users.user_name AS users_user_name, 
+    users.fullname AS users_fullname, users.password AS users_password 
+    FROM users ORDER BY users.oid
+    LIMIT 2 OFFSET 1
+    {}
 
-The techniques used when querying with a full select construct are similar to another query method called `instances()`, which is described in [advdatamapping_resultset](rel:advdatamapping_resultset).
+A scalar index returns a scalar result immediately:
 
-#### Two General Calling Styles {@name=callingstyles}
+    {python}
+    {sql}user = session.query(User)[2]
+    SELECT users.user_id AS users_user_id, users.user_name AS users_user_name, 
+    users.fullname AS users_fullname, users.password AS users_password 
+    FROM users ORDER BY users.oid
+    LIMIT 1 OFFSET 2
+    {}
+
+Theres also a way to combine scalar results with objects, using `add_column()`.  This is often used for functions and aggregates.  When `add_column()` (or its cousin `add_entity()`, described later) is used, tuples are returned:
 
-The twin calling styles presented by `select()` and `select_by()` are mirrored in several other methods on `Query`.  For each method that indicates a verb such as `select()` and accepts a single `ClauseElement` followed by keyword-based options, the `_by()` version accepts a list of `ClauseElement` objects followed by keyword-based argument criterion.  These include:
+    {python}
+    result = session.query(User).add_column(func.max(users_table.c.name)).group_by([c for c in users_table.c]).all()
+    for r in result:
+        print "user:", r[0]
+        print "max name:", r[1]
 
-* `selectfirst()` / `selectfirst_by()` - select with LIMIT 1 and return a single object instance
-* `selectone()` / `selectone_by()` - select with LIMIT 2, assert that only one row is present, and return a single object instance
-* `filter()` / `filter_by()` - apply the criterion to the `Query` object generatively, and return a new `Query` object with the criterion built in.
-* `count()` / `count_by()` - return the total number of object instances that would be returned.
+Later in this chapter, we'll discuss how to configure relations between mapped classes.  Once that's done, we'll discuss how to use table joins in [datamapping_joins](rel:datamapping_joins).
 
 #### Loading by Primary Key {@name=primarykey}
 
@@ -207,75 +264,23 @@ The `get()` method loads a single instance, given the primary key value of the d
     # load user with primary key 15
     user = query.get(15)
 
-The `get()` method, because it has the actual primary key value of the instance, can return an already-loaded instance from the `Session` without performing any SQL.  It is the only method on `Query` that does not issue SQL to the database in all cases.
+The `get()` method, because it has the actual primary key value of the instance, can return an already-loaded instance from the `Session` without performing any SQL.  It is the only result-returning method on `Query` that does not issue SQL to the database in all cases.
 
-To issue a composite primary key to `get()`, use a tuple.  The order of the arguments matches that of the table meta data.
+To issue a composite primary key to `get()`, use a tuple.  The order of the arguments matches that of the primary key columns of the table:
 
     {python}
     myobj = query.get((27, 3, 'receipts'))
 
-Another special method on `Query` is `load()`.  This method has the same signature as `get()`, except it always **refreshes** the returned instance with the latest data from the database.  This is in fact a unique behavior, since as we will see in the [unitofwork](rel:unitofwork) chapter, the `Query` usually does not refresh the contents of instances which are already present in the session.
+Another special method on `Query` is `load()`.  This method has the same signature as `get()`, except it always **refreshes** the returned instance with the latest data from the database.  This is in fact a unique behavior, since as we will see in the [unitofwork](rel:unitofwork) chapter, most `Query` methods do not reload the contents of instances which are already present in the session.
 
 #### Column Objects Available via their Mapped Class {@name=columnsonclass}
 
-Some of the above examples above illustrate the usage of the mapper's Table object to provide the columns for a WHERE Clause.  These columns are also accessible off of the mapped class directly.  When a mapper is assigned to a class, it also attaches a special property accessor `c` to the class itself, which can be used just like the table metadata to access the columns of the table:
-
-    {python}
-    userlist = session.query(User).select(User.c.user_id==12)
-
-#### Generative Query Methods {@name=generative}
-
-The `filter()` and `filter_by()` methods on `Query` are known as "generative" methods, in that instead of returning results, they return a newly constructed `Query` object which contains the "filter" criterion as part of its internal state.  This is another method of stringing together criterion via `AND` clauses.  But when using the `Query` generatively, it becomes a much more flexible object, as there are generative methods for adding all kinds of criterion and modifiers, some of which are relationally oriented and others which are domain oriented.  The key behavior of a "generative" method is that calling it produces a **new** `Query` object, which contains all the attributes of the previous `Query` object plus some new modifications.  
-
-    {python}
-    # create a Query
-    query = session.query(User)   
-    
-    # filter by user_name property using filter_by()
-    query = query.filter_by(user_name="john") 
-    
-    # filter by email address column using filter()
-    query = query.filter(User.c.fullname=="John Smith")  
-    
-    # execute - the list() method returns a list.  this is equivalent to just saying query.select()
-    {sql}result = query.list()
-    SELECT users.user_name AS users_user_name, users.fullname as users_fullname,
-    users.password AS users_password, users.user_id AS users_user_id 
-    FROM users 
-    WHERE ((users.user_name = :users_user_name) 
-    AND users.fullname = :users_fullname) ORDER BY users.oid
-
-Other generative behavior includes list-based indexing and slice operations, which translate into `LIMIT` and `OFFSET` criterion:
-
-    {python}
-    {sql}session.query(User).filter(user_table.c.fullname.like('j%'))[20:30]
-    SELECT users.user_name AS users_user_name, users.fullname as users_fullname,
-    users.password AS users_password, users.user_id AS users_user_id 
-    FROM users 
-    WHERE users.fullname LIKE :users_fullname 
-    ORDER BY users.oid LIMIT 10 OFFSET 20
-    {'users_fullname': 'j%'}
-    
-Iterable behavior:
-
-    {python}
-    for user in session.query(User).filter_by(user_name='john'):
-        # etc.
-        
-Applying modifiers generatively:
+Some of the above examples above illustrate the usage of the mapper's Table object to provide the columns for a WHERE Clause.  These columns are also accessible off of the mapped class directly.  When a mapper is assigned to a class, it also attaches a special property accessor `c` to the class itself, which can be used just like that of a `Table` object to access the columns of the table:
 
     {python}
-    {sql}session.query(User).filter(user_table.c.fullname.like('j%')).
-        limit(10).offset(20).order_by(user_table.c.user_id).list()
-    SELECT users.user_name AS users_user_name, users.fullname as users_fullname,
-    users.password AS users_password, users.user_id AS users_user_id 
-    FROM users 
-    WHERE users.fullname LIKE :users_fullname 
-    ORDER BY users.user_id LIMIT 10 OFFSET 20
-    
-There is no restriction to mixing the "generative" methods like `filter()`, `join()`, `order_by()` etc. with the "non-generative" methods like `select()` and `select_by()`.  The only difference is that `select...` methods return results immediately, whereas generative methods return a new `Query` from which results can be retrieved using any number of methods such as `list()`, `select()`, `select_by()`, `selectfirst()`, etc.  Options that are specified to the `select...` methods build upon options that were already built up generatively.
+    userlist = session.query(User).filter(User.c.user_id==12).first()
 
-The `Query` object will be described further in subsequent sections dealing with joins.  Also, be sure to consult the full generated documentation for `Query`: [Query](rel:docstrings_sqlalchemy.orm.query_Query).
+In version 0.4 of SQLAlchemy, the "c" prefix will no longer be needed.
 
 ### Saving Objects {@name=saving}
 
@@ -304,7 +309,7 @@ When a mapper is created, the target class has its mapped properties decorated b
     sess.save(myuser2)
     
     # load a third User from the database            
-    {sql}myuser3 = sess.query(User).select(User.c.user_name=='fred')[0]  
+    {sql}myuser3 = sess.query(User).filter_by(name='fred').all()[0]  
     SELECT users.user_id AS users_user_id, 
     users.user_name AS users_user_name, users.password AS users_password
     FROM users WHERE users.user_name = :users_user_name
@@ -322,8 +327,6 @@ When a mapper is created, the target class has its mapped properties decorated b
     INSERT INTO users (user_name, password) VALUES (:user_name, :password)
     {'password': 'lalalala', 'user_name': 'ed'}
 
-The requirement that new instances be explicitly stored in the `Session` via `save()` operation can be modified by using the [plugins_sessioncontext](rel:plugins_sessioncontext) extension module.
-        
 The mapped class can also specify whatever methods and/or constructor it wants:
 
     {python}
@@ -351,8 +354,6 @@ The mapped class can also specify whatever methods and/or constructor it wants:
 
 Note that the **__init__() method is not called when the instance is loaded**.  This is so that classes can define operations that are specific to their initial construction which are not re-called when the object is restored from the database, and is similar in concept to how Python's `pickle` module calls `__new__()` when deserializing instances.  To allow `__init__()` to be called at object load time, or to define any other sort of on-load operation, create a `MapperExtension` which supplies the `create_instance()` method (see [advdatamapping_extending](rel:advdatamapping_extending), as well as the example in the FAQ).
 
-SQLAlchemy will only place columns into UPDATE statements for which the value of the attribute has changed.  This is to conserve database traffic and also to successfully interact with a "deferred" attribute, which is a mapped object attribute against the mapper's primary table that isnt loaded until referenced by the application.
-
 ### Defining and Using Relationships {@name=relations}
 
 So that covers how to map the columns in a table to an object, how to load objects, create new ones, and save changes.  The next step is how to define an object's relationships to other database-persisted objects.  This is done via the `relation` function [[doc](rel:advdatamapping_properties_relationoptions)][[api](rel:docstrings_sqlalchemy.orm_modfunc_relation)] provided by the `orm` module.
@@ -413,9 +414,11 @@ Lets do some operations with these classes and see what happens:
 
     {python}
     engine = create_engine('sqlite:///mydb.db')
+    
+    # create tables
     metadata.create_all(engine)
     
-    session = create_session(bind_to=engine)
+    session = create_session(bind=engine)
     
     u = User('jane', 'hihilala')
     u.addresses.append(Address('123 anywhere street', 'big city', 'UT', '76543'))
@@ -432,7 +435,7 @@ Lets do some operations with these classes and see what happens:
         
 A lot just happened there!  The `Mapper` figured out how to relate rows in the addresses table to the users table, and also upon flush had to determine the proper order in which to insert rows.  After the insert, all the `User` and `Address` objects have their new primary and foreign key attributes populated.
 
-Also notice that when we created a `Mapper` on the `User` class which defined an `addresses` relation, the newly created `User` instance magically had an "addresses" attribute which behaved like a list.   This list is in reality a Python `property` which will return an instance of `sqlalchemy.orm.attributes.InstrumentedList`.  This is a generic collection-bearing object which can represent lists, sets, dictionaries, or any user-defined collection class which has an `append()` method.  By default it represents a list:
+Also notice that when we created a `Mapper` on the `User` class which defined an `addresses` relation, the newly created `User` instance magically had an "addresses" attribute which behaved like a list.   This list is in reality a Python `property` which will return an instance of `sqlalchemy.orm.attributes.InstrumentedList`.  This is a generic collection-bearing object which can represent lists, sets, dictionaries, or any user-defined collection class.  By default it represents a list:
 
     {python}
     del u.addresses[1]
@@ -518,47 +521,16 @@ The `backref()` function is often used to set up a bi-directional one-to-one rel
     mapper(Address, addresses_table, properties={
         'user' : relation(User, backref=backref('address', uselist=False))
     })
-
-
-### Selecting from Relationships {@name=selectrelations}
-
-We've seen how the `relation` specifier affects the saving of an object and its child items, how does it affect selecting them?  By default, the relation keyword indicates that the related property should be attached a *lazy loader* when instances of the parent object are loaded from the database; this is just a callable function that when accessed will invoke a second SQL query to load the child objects of the parent.
-    
-    {python}
-    # define a mapper
-    mapper(User, users_table, properties = {
-          'addresses' : relation(mapper(Address, addresses_table))
-        })
-    
-    # select users where username is 'jane', get the first element of the list
-    # this will incur a load operation for the parent table
-    {sql}user = session.query(User).select(User.c.user_name=='jane')[0]   
-    SELECT users.user_id AS users_user_id, 
-    users.user_name AS users_user_name, users.password AS users_password
-    FROM users WHERE users.user_name = :users_user_name ORDER BY users.oid
-    {'users_user_name': 'jane'}
-    
-    # iterate through the User object's addresses.  this will incur an
-    # immediate load of those child items
-    {sql}for a in user.addresses:    
-    SELECT addresses.address_id AS addresses_address_id, 
-    addresses.user_id AS addresses_user_id, addresses.street AS addresses_street, 
-    addresses.city AS addresses_city, addresses.state AS addresses_state, 
-    addresses.zip AS addresses_zip FROM addresses
-    WHERE addresses.user_id = :users_user_id ORDER BY addresses.oid
-    {'users_user_id': 1}
         
-        print repr(a)
-            
-#### Selecting With Joins {@name=queryjoins}
+### Querying with Joins {@name=joins}
 
 When using mappers that have relationships to other mappers, the need to specify query criterion across multiple tables arises.  SQLAlchemy provides several core techniques which offer this functionality.
 
-When specifying columns to the `select()` method (including variants like `selectfirst()`, `selectone()`, etc.) or the generative `filter()` method of `Query`, if the columns are attached to a table other than the mapped table, that table is automatically added to the "FROM" clause of the query.  This is the same behavior that occurs when creating a non-ORM `select` object.  Using this feature, joins can be created when querying:
+One way is just to build up the join criterion yourself.  This is easy to do using `filter()`:
 
     {python}
-    {sql}l = session.query(User).select(and_(users.c.user_id==addresses.c.user_id, 
-                 addresses.c.street=='123 Green Street'))
+    {sql}l = session.query(User).filter(users.c.user_id==addresses.c.user_id).
+                 filter(addresses.c.street=='123 Green Street').all()
     SELECT users.user_id AS users_user_id, 
     users.user_name AS users_user_name, users.password AS users_password
     FROM users, addresses 
@@ -567,25 +539,13 @@ When specifying columns to the `select()` method (including variants like `selec
     ORDER BY users.oid
     {'addresses_street', '123 Green Street'}
 
-Above, we specified selection criterion that included columns from both the `users` and the `addresses` table.  Note that in this case, we had to specify not just the matching condition to the `street` column on `addresses`, but also the join condition between the `users` and `addresses` table.  Even though the `User` mapper has a relationship to the `Address` mapper where the join condition is known, **the select method does not assume how you want to construct joins**.  If we did not include the join clause, we would get:
-
-    {python}
-    # this is usually not what you want to do
-    {sql}l = session.query(User).select(addresses.c.street=='123 Green Street')
-    SELECT users.user_id AS users_user_id, 
-    users.user_name AS users_user_name, users.password AS users_password
-    FROM users, addresses 
-    WHERE addresses.street=:addresses_street
-    ORDER BY users.oid
-    {'addresses_street', '123 Green Street'}
+Above, we specified selection criterion that included columns from both the `users` and the `addresses` table.  Note that in this case, we had to specify not just the matching condition to the `street` column on `addresses`, but also the join condition between the `users` and `addresses` table.  If we didn't do that, we'd get a *cartesian product* of both tables.  The `Query` object never "guesses" what kind of join you'd like to use, but makes it easy using the `join()` method which we'll get to in a moment.
     
-The above join will return all rows of the `users` table, even those that do not correspond to the `addresses` table, in a **cartesian product** with the matching rows from the `addresses` table.
-
-Another way to specify joins more explicitly is to use the `from_obj` parameter of `select()`, or the generative `select_from()` method.  These allow you to explicitly place elements in the FROM clause of the query, which could include lists of tables and/or `Join` constructs:
+A way to specify joins very explicitly, using the SQL `join()` construct, is possible via the `select_from()` method on `Query`:
 
     {python}
-    {sql}l = session.query(User).select(addresses_table.c.street=='123 Green Street', 
-                    from_obj=[users_table.join(addresses_table)])
+    {sql}l = session.query(User).select_from(users_table.join(addresses_table)).
+            filter(addresses_table.c.street=='123 Green Street').all()
     SELECT users.user_id AS users_user_id, 
     users.user_name AS users_user_name, users.password AS users_password
     FROM users JOIN addresses ON users.user_id=addresses.user_id
@@ -593,13 +553,11 @@ Another way to specify joins more explicitly is to use the `from_obj` parameter
     ORDER BY users.oid
     {'addresses_street', '123 Green Street'}
 
-In the above example, the `join()` function by default creates a natural join between the two tables, so we were able to avoid having to specify the join condition between `users` and `addresses` explicitly.
-
-Using a generative approach:
+But the easiest way to join is automatically, using the `join()` method on `Query`.  Just give this method the path from A to B, using the name of a mapped relationship directly:
 
     {python}
-    {sql}l = session.query(User).filter(addresses_table.c.street=='123 Green Street').
-                select_from(users_table.join(addresses_table)).list()
+    {sql}l = session.query(User).join('addresses').
+            filter(addresses_table.c.street=='123 Green Street').all()
     SELECT users.user_id AS users_user_id, 
     users.user_name AS users_user_name, users.password AS users_password
     FROM users JOIN addresses ON users.user_id=addresses.user_id
@@ -607,42 +565,44 @@ Using a generative approach:
     ORDER BY users.oid
     {'addresses_street', '123 Green Street'}
 
-Note that `select_from()` takes either a single scalar element or a list of selectables (i.e. `select_from([table1, table2, table3.join(table4), ...])`), which are added to the current list of "from" elements.  As is the behavior of constructed SQL, `join` elements used in the `from_obj` parameter or the `select_from()` method will replace instances of the individual tables they represent.
+Each time the `join()` is called on `Query`, the **joinpoint** of the query is moved to be that of the endpoint of the join.  As above, when we joined from `users_table` to `addresses_table`, all subsequent criterion used by `filter_by()` are against the `addresses` table.  If we wanted to filter back on the starting table again, we can use the `reset_joinpoint()` function:
 
-#### Creating Joins Using filter\_by() and select\_by() {@name=relselectby}
+    {python}
+    l = session.query(User).join('addresses').
+            filter_by(street='123 Green Street').
+            reset_joinpoint().filter_by(user_name='ed').all()
 
-Another way that joins can be created is by using the `select_by` method or the generative `filter_by` methods of `Query`, which have the ability to create joins across relationships automatically.  These methods are in many circumstances more convenient than, but not as flexible as, the more SQL-level approach using the `select()`/`select_from()` methods described in the previous section.
+With `reset_joinpoint()`, we can also issue new `join()`s which will start back from the root table.
 
-To issue a join using `select_by()`, just specify a key in the keyword-based argument list which is not present in the primary mapper's list of properties or columns, but *is* present in the property list of some relationship down the line of objects.  The `Query` object will recursively traverse along the mapped relationships starting with the lead class and descending into child classes, until it finds a property matching the given name.  For each new mapper it encounters along the path to the located property, it constructs a join across that relationship:
+In all cases, we can get the `User` and the matching `Address` objects back at the same time, by telling the session we want both.  This returns the results as a list of tuples:
 
     {python}
-    {sql}l = session.query(User).select_by(street='123 Green Street')
-    SELECT users.user_id AS users_user_id, 
-    users.user_name AS users_user_name, users.password AS users_password
-    FROM users, addresses 
-    WHERE users.user_id=addresses.user_id
-    AND addresses.street=:addresses_street
-    ORDER BY users.oid
-    {'addresses_street', '123 Green Street'}
+    result = session.query(User, Address).join('addresses').
+            filter(addresses_table.c.street=='123 Green Street').all()
+    for r in result:
+        print "User:", r[0]
+        print "Address:", r[1]
+
+The above syntax is shorthand for using the `add_entity()` method:
 
-The above example is shorthand for:
+    {python}
+    session.query(User).add_entity(Address).join('addresses').all()
+    
+To join across multiple relationships, specify them in a list.  Below, we load a `ShoppingCart`, limiting its `cartitems` collection to the single item which has a `price` object whose `amount` column is 47.95:
 
     {python}
-    l = session.query(User).select(and_(
-             Address.c.user_id==User.c.user_id, 
-             Address.c.street=='123 Green Street')
-       )
+    cart = session.query(ShoppingCart).join(['cartitems', 'price']).filter_by(amount=47.95).one()
 
-`select_by()` and its related functions can compare not only column-based attributes to column-based values, but also relations to object instances:
+`filter_by()` can also generate joins in some cases, such as when comparing to an object instance:
 
     {python}
     # get an instance of Address. assume its primary key identity
     # is 12.
-    someaddress = session.query(Address).get_by(street='123 Green Street')
+    someaddress = session.query(Address).filter_by(street='123 Green Street').one()
     
     # look for User instances which have the 
     # "someaddress" instance in their "addresses" collection
-    {sql}l = session.query(User).select_by(addresses=someaddress)
+    {sql}l = session.query(User).filter_by(addresses=someaddress).all()
     SELECT users.user_id AS users_user_id, 
     users.user_name AS users_user_name, users.password AS users_password
     FROM users, addresses 
@@ -651,71 +611,82 @@ The above example is shorthand for:
     ORDER BY users.oid
     {'addresses_addresses_id': 12}
 
-Where above, the comparison denoted by `addresses=someaddress` is constructed by comparing all the primary key columns in the `Address` mapper to each corresponding primary key value in the `someaddress` entity.  In other words, its equivalent to saying `select_by(address_id=someaddress.address_id)`.
+You can also create joins in "reverse", that is, to find an object with a certain parent.  This is accomplished using `with_parent()`:
+    
+    {python}
+    # load a user
+    someuser = session.query(User).get(2)
 
-#### Generating Join Criterion Using join() {@name=jointo}
+    # load an address with that user as a parent and email address foo@bar.com
+    {sql}someaddresses = session.query(Address).with_parent(someuser).
+        filter_by(email_address="foo@bar.com").all()
+    SELECT addresses.address_id AS addresses_address_id, 
+    addresses.user_id AS addresses_user_id, addresses.street AS addresses_street, 
+    addresses.city AS addresses_city, addresses.state AS addresses_state, 
+    addresses.zip AS addresses_zip FROM addresses
+    WHERE addresses.email_address =  :addresses_email_address AND 
+    addresses.user_id = :users_user_id ORDER BY addresses.oid
+    {'users_user_id': 1, 'addresses_email_address': 'foo@bar.com'}
 
-The `join()` method is a generative method which can apply join conditions to a `Query` based on the names of relationships, using a similar mechanism as that of `select_by()` and similar methods.  By specifying the string name of a relation as its only argument, the resulting `Query` will automatically join from the starting class' mapper to the target mapper, indicated by searching for a relationship of that name along the relationship path.
+Sometimes it's necessary to create repeated joins that are independent of each other, even though they reference the same tables.  Using our one-to-many setup, an example is to locate users who have two partcular email addresses.  We can do this using table aliases:
 
     {python}
-    {sql}l = session.query(User).join('addresses').list()
-    SELECT users.user_name AS users_user_name, users.password AS users_password, 
-    users.user_id AS users_user_id 
-    FROM users JOIN addresses ON users.user_id = addresses.user_id 
+    ad1 = addresses_table.alias('ad1')
+    ad2 = addresses_table.alias('ad2')
+    {sql}result = session.query(User).filter(and_(
+        ad1.c.user_id==users.c.user_id,
+        ad1.c.email_address=='foo@bar.com',
+        ad2.c.user_id==users.c.user_id,
+        ad2.c.email_address=='lala@yahoo.com'
+        )).all()
+    SELECT users.user_id AS users_user_id, 
+    users.user_name AS users_user_name, users.password AS users_password
+    FROM users, addresses AS ad1, addresses AS ad2
+    WHERE users.user_id=ad1.user_id
+    AND ad1.email_address=:ad1_email_address
+    AND users.user_id=ad2.user_id
+    AND ad2.email_address=:ad2_email_address
     ORDER BY users.oid
-    
-One drawback of this method of locating a relationship is that its not *deterministic*.   If the same relationship name occurs on more than one traversal path, its only possible to locate one of those relationships.  Similarly, if relationships are added to mapped classes, queries that worked fine may suddenly experience a similar conflict and produce unexpected results.
+    {'ad1_email_address': 'foo@bar.com', 'ad2_email_address': 'lala@yahoo.com'}
 
-So to specify a deterministic path to `join()`, send the relationship name or a path of names as a list.  Such as:
+Version 0.4 of SQLAlchemy will include better ability to issue queries like the above with less verbosity.
 
-    {python}
-    l = session.query(User).join(['addresses']).list()
-
-Where above, if the "addresses" relation is not present directly on the `User` class, an error is raised.
+### Loading Relationships {@name=selectrelations}
 
-To traverse more deeply into relationships, specify multiple relationship names in the order in which they are constructed:
+We've seen how the `relation` specifier affects the saving of an object and its child items, and also how it allows us to build joins.  How to we get the actual related items loaded ?  By default, the `relation()` function indicates that the related property should be attached a *lazy loader* when instances of the parent object are loaded from the database; this is just a callable function that when accessed will invoke a second SQL query to load the child objects of the parent.
 
     {python}
-    orders = session.query(Order).join(['customer', 'addresses']).select_by(email_address="foo@bar.com")
-
-For those familiar with older versions of `Query`, the `join()` method is an easier-to-use version of the `join_by()`, `join_to()` and `join_via()` methods, all of which produce `ClauseElements` that can be constructed into a query criterion.  Consult the generated documentation for information on these methods.
+    # define a user mapper
+    mapper(User, users_table, properties = {
+          'addresses' : relation(Address)
+        })
 
-#### Joining from a Parent Object {@name=joinparent}
+    # define an address mapper
+    mapper(Address, addresses_table)
 
-(new in 0.3.7) To help in navigating collections, the `with_parent()` generative method adds criterion which corresponds to instances which belong to a particular parent.  This method makes use of the same "lazy loading" criterion used to load relationships normally, which means for a typical non-many-to-many relationship it will **not** actually create a join, and instead places bind parameters at the point at which the parent table's columns are normally specified.  This means you get a lighter weight query which also works with self-referential relationships, which otherwise would require an explicit `alias` object in order to create self-joins.  For example, to load all the `Address` objects which belong to a particular `User`:
+    # select users where username is 'jane', get the first element of the list
+    # this will incur a load operation for the parent table
+    {sql}user = session.query(User).filter(User.c.user_name=='jane')[0]   
+    SELECT users.user_id AS users_user_id, 
+    users.user_name AS users_user_name, users.password AS users_password
+    FROM users WHERE users.user_name = :users_user_name ORDER BY users.oid
+    {'users_user_name': 'jane'}
 
-    {python}
-    # load a user
-    someuser = session.query(User).get(2)
-    
-    # load the addresses of that user
-    {sql}addresses = session.query(Address).with_parent(someuser).list()
+    # iterate through the User object's addresses.  this will incur an
+    # immediate load of those child items
+    {sql}for a in user.addresses:    
     SELECT addresses.address_id AS addresses_address_id, 
     addresses.user_id AS addresses_user_id, addresses.street AS addresses_street, 
     addresses.city AS addresses_city, addresses.state AS addresses_state, 
     addresses.zip AS addresses_zip FROM addresses
     WHERE addresses.user_id = :users_user_id ORDER BY addresses.oid
     {'users_user_id': 1}
-    
-    # filter the results
-    {sql}someaddresses = session.query(Address).with_parent(someuser).
-        filter_by(email_address="foo@bar.com").list()
-    SELECT addresses.address_id AS addresses_address_id, 
-    addresses.user_id AS addresses_user_id, addresses.street AS addresses_street, 
-    addresses.city AS addresses_city, addresses.state AS addresses_state, 
-    addresses.zip AS addresses_zip FROM addresses
-    WHERE addresses.email_address =  :addresses_email_address AND 
-    addresses.user_id = :users_user_id ORDER BY addresses.oid
-    {'users_user_id': 1, 'addresses_email_address': 'foo@bar.com'}
-    
 
-#### Eager Loading {@name=eagerload}
+        print repr(a)
 
-Eager Loading describes the loading of parent and child objects across a relation using a single query.  The purpose of eager loading is strictly one of performance enhancement; eager loading has **no impact** on the results of a query, except that when traversing child objects within the results, lazy loaders will not need to issue separate queries to load those child objects.
+#### Eager Loading {@name=eagerload}
 
-Eager Loading is enabled on a per-relationship basis, either as the default
-for a particular relationship, or for a single query using query options,
-described later.
+Eager Loading is another way for relationships to be loaded.  It describes the loading of parent and child objects across a relation using a single query.  The purpose of eager loading is strictly one of performance enhancement; eager loading has **no impact** on the results of a query, except that when traversing child objects within the results, lazy loaders will not need to issue separate queries to load those child objects.
 
 With just a single parameter `lazy=False` specified to the relation object, the parent and child SQL queries can be joined together.
 
@@ -726,7 +697,7 @@ With just a single parameter `lazy=False` specified to the relation object, the
         }
       )
     
-    {sql}users = session.query(User).select(User.c.user_name=='Jane')
+    {sql}users = session.query(User).filter(User.c.user_name=='Jane').all()
     SELECT users.user_name AS users_user_name, users.password AS users_password, 
     users.user_id AS users_user_id, addresses_4fb8.city AS addresses_4fb8_city, 
     addresses_4fb8.address_id AS addresses_4fb8_address_id, addresses_4fb8.user_id AS addresses_4fb8_user_id, 
@@ -743,10 +714,10 @@ With just a single parameter `lazy=False` specified to the relation object, the
 
 Above, a pretty ambitious query is generated just by specifying that the User should be loaded with its child Addresses in one query.  When the mapper processes the results, it uses an *Identity Map* to keep track of objects that were already loaded, based on their primary key identity.  Through this method, the redundant rows produced by the join are organized into the distinct object instances they represent.
         
-Recall that eager loading has no impact on the results of the query.  What if our query included our own join criterion?  The eager loading query accomodates this using aliases, and is immune to the effects of additional joins being specified in the original query.  To use our select_by example above, joining against the "addresses" table to locate users with a certain street results in this behavior:
+Recall that eager loading has no impact on the results of the query.  What if our query included our own join criterion?  The eager loading query accomodates this using aliases, and is immune to the effects of additional joins being specified in the original query.  Joining against the "addresses" table to locate users with a certain street results in this behavior:
 
     {python}
-    {sql}users = session.query(User).select_by(street='123 Green Street')
+    {sql}users = session.query(User).join('addresses').filter_by(street='123 Green Street').all()
     
     SELECT users.user_name AS users_user_name, 
     users.password AS users_password, users.user_id AS users_user_id, 
@@ -755,15 +726,16 @@ Recall that eager loading has no impact on the results of the query.  What if ou
     addresses_6ca7.user_id AS addresses_6ca7_user_id, 
     addresses_6ca7.zip AS addresses_6ca7_zip, addresses_6ca7.state AS addresses_6ca7_state, 
     addresses_6ca7.street AS addresses_6ca7_street 
-    FROM addresses, users LEFT OUTER JOIN addresses AS addresses_6ca7 ON users.user_id = addresses_6ca7.user_id 
-    WHERE addresses.street = :addresses_street AND users.user_id = addresses.user_id ORDER BY users.oid, addresses_6ca7.oid
+    FROM users JOIN addresses on users.user_id = addresses.user_id 
+    LEFT OUTER JOIN addresses AS addresses_6ca7 ON users.user_id = addresses_6ca7.user_id 
+    WHERE addresses.street = :addresses_street ORDER BY users.oid, addresses_6ca7.oid
     {'addresses_street': '123 Green Street'}
                     
-The join implied by passing the "street" parameter is separate from the join produced by the eager join, which is "aliasized" to prevent conflicts.
+The join resulting from `join('addresses')` is separate from the join produced by the eager join, which is "aliasized" to prevent conflicts.
     
 #### Using Options to Change the Loading Strategy {@name=options}
 
-The `options()` method on the `Query` object is a generative method that allows modifications to the underlying querying methodology.  The most common use of this feature is to change the "eager/lazy" loading behavior of a particular mapper, via the functions `eagerload()`, `lazyload()` and `noload()`:
+The `options()` method on the `Query` object is allows modifications to the underlying querying methodology.  The most common use of this feature is to change the "eager/lazy" loading behavior of a particular mapper, via the functions `eagerload()`, `lazyload()` and `noload()`:
     
     {python}
     # user mapper with lazy addresses
@@ -777,7 +749,7 @@ The `options()` method on the `Query` object is a generative method that allows
     
     # make an eager loading query
     eagerquery = query.options(eagerload('addresses'))
-    u = eagerquery.select()
+    u = eagerquery.all()
     
     # make another query that wont load the addresses at all
     plainquery = query.options(noload('addresses'))
@@ -788,9 +760,13 @@ The `options()` method on the `Query` object is a generative method that allows
     # to specify a relation on a relation, separate the property names by a "."
     myquery = oldquery.options(eagerload('orders.items'))
 
-### One to One/Many to One {@name=onetoone}
+### More Relationships {@name=morerelations}
 
-The above examples focused on the "one-to-many" relationship.  To do other forms of relationship is easy, as the `relation` function can usually figure out what you want:
+Previously, we've discussed how to set up a one-to-many relationship.  This section will go over the remaining major types of relationships that can be configured.  More detail on on relationships as well as more advanced patterns can be found in [advdatamapping](rel:advdatamapping).
+
+#### One to One/Many to One {@name=manytoone}
+
+The above examples focused on the "one-to-many" relationship.  To do other forms of relationship is easy, as the `relation()` function can usually figure out what you want:
 
     {python}
     metadata = MetaData()
@@ -828,13 +804,13 @@ The above examples focused on the "one-to-many" relationship.  To do other forms
         
     mapper(UserPrefs, prefs_table)
     
-    mapper(User, users_table, properties = dict(
-        preferences = relation(UserPrefs, lazy=False, cascade="all, delete-orphan"),
-    ))
+    mapper(User, users_table, properties = {
+        'preferences':relation(UserPrefs, lazy=False, cascade="all, delete-orphan"),
+    })
     
     # select
-    session = create_session(bind_to=engine)
-    {sql}user = session.query(User).get_by(user_name='fred')
+    session = create_session(bind=engine)
+    {sql}user = session.query(User).filter_by(user_name='fred').one()
     SELECT users.preference_id AS users_preference_id, users.user_name AS users_user_name, 
     users.password AS users_password, users.user_id AS users_user_id, 
     user_prefs_4eb2.timezone AS user_prefs_4eb2_timezone, user_prefs_4eb2.stylename AS user_prefs_4eb2_stylename, 
@@ -856,9 +832,9 @@ The above examples focused on the "one-to-many" relationship.  To do other forms
     WHERE user_prefs.pref_id = :pref_id
     [{'stylename': 'bluesteel', 'pref_id': 1}]
 
-### Many to Many {@name=manytomany}
+#### Many to Many {@name=manytomany}
 
-The `relation` function handles a basic many-to-many relationship when you specify an association table using the `secondary` argument:
+The `relation()` function handles a basic many-to-many relationship when you specify an association table using the `secondary` argument:
 
     {python}
     metadata = MetaData()
@@ -894,12 +870,12 @@ The `relation` function handles a basic many-to-many relationship when you speci
         
     # define a mapper that does many-to-many on the 'itemkeywords' association 
     # table
-    mapper(Article, articles_table, properties = dict(
-        keywords = relation(Keyword, secondary=itemkeywords_table, lazy=False)
-        )
+    mapper(Article, articles_table, properties = {
+        'keywords':relation(Keyword, secondary=itemkeywords_table, lazy=False)
+        }
     )
     
-    session = create_session(bind_to=engine)
+    session = create_session(bind=engine)
     
     article = Article()
     article.headline = 'a headline'
@@ -918,8 +894,8 @@ The `relation` function handles a basic many-to-many relationship when you speci
     INSERT INTO article_keywords (article_id, keyword_id) VALUES (:article_id, :keyword_id)
     [{'keyword_id': 1, 'article_id': 1}, {'keyword_id': 2, 'article_id': 1}]
     
-    # select articles based on a keyword.  select_by will handle the extra joins.
-    {sql}articles = session.query(Article).select_by(keyword_name='politics')
+    # select articles based on a keyword.  
+    {sql}articles = session.query(Article).join('keywords').filter_by(keyword_name='politics').all()
     SELECT keywords_e2f2.keyword_id AS keywords_e2f2_keyword_id, keywords_e2f2.keyword_name AS keywords_e2f2_keyword_name, 
     articles.headline AS articles_headline, articles.body AS articles_body, articles.article_id AS articles_article_id 
     FROM keywords, article_keywords, articles 
@@ -949,7 +925,7 @@ The `relation` function handles a basic many-to-many relationship when you speci
     INSERT INTO article_keywords (article_id, keyword_id) VALUES (:article_id, :keyword_id)
     [{'keyword_id': 3, 'article_id': 1}, {'keyword_id': 4, 'article_id': 1}]
 
-### Association Object {@name=association}
+#### Association Object {@name=association}
 
 Many to Many can also be done with an association object, that adds additional information about how two items are related.  In this pattern, the "secondary" option to `relation()` is no longer used; instead, the association object becomes a mapped entity itself, mapped to the association table.  If the association table has no explicit primary key columns defined, you also have to tell the mapper what columns will compose its "primary key", which are typically the two (or more) columns involved in the association.  Also, the relation between the parent and association mapping is typically set up with a cascade of `all, delete-orphan`.  This is to ensure that when an association object is removed from its parent collection, it is deleted (otherwise, the unit of work tries to null out one of the foreign key columns, which raises an error condition since that column is also part of its "primary key").
 
@@ -1016,9 +992,9 @@ Many to Many can also be done with an association object, that adds additional i
     # keyword mapper
     mapper(Keyword, keywords_table)
     
-    session = create_session(bind_to=engine)
+    session = create_session(bind=engine)
     # select by keyword
-    {sql}alist = session.query(Article).select_by(keyword_name='jacks_stories')
+    {sql}alist = session.query(Article).join(['keywords', 'keyword']).filter_by(keyword_name='jacks_stories').all()
     SELECT article_keyword_f9af.keyword_id AS article_keyword_f9af_key_b3e1, 
     article_keyword_f9af.attached_by AS article_keyword_f9af_att_95d4, 
     article_keyword_f9af.article_id AS article_keyword_f9af_art_fd49, 
index 67854716678485ec4c9f516bf39a4bff2327eb7e..c03d569374b6f32977b921b1546caefda7d9292e 100644 (file)
@@ -158,7 +158,7 @@ The `execute()` methods on both `Engine` and `Connection` can also receive SQL c
 
 The above SQL construct is known as a `select()`.  The full range of SQL constructs available are described in [sql](rel:sql).
 
-Both `Connection` and `Engine` fulfill an interface known as `Connectable` which specifies common functionality between the two objects, namely being able to call `connect()` to return a `Connection` object (`Connection` just returns itself), and being able to call `execute()` to get a result set.   Following this, most SQLAlchemy functions and objects which accept an `Engine` as a parameter or attribute with which to execute SQL will also accept a `Connection`.  In SQLAlchemy 0.3, this argument is frequently named `connectable` or `engine`.  In the 0.4 series of SQLAlchemy, its consistently named `bind`.
+Both `Connection` and `Engine` fulfill an interface known as `Connectable` which specifies common functionality between the two objects, namely being able to call `connect()` to return a `Connection` object (`Connection` just returns itself), and being able to call `execute()` to get a result set.   Following this, most SQLAlchemy functions and objects which accept an `Engine` as a parameter or attribute with which to execute SQL will also accept a `Connection`.  As of SQLAlchemy 0.3.9, this argument is named `bind`.
 
     {python title="Specify Engine or Connection"}
     engine = create_engine('sqlite:///:memory:')
@@ -168,11 +168,11 @@ Both `Connection` and `Engine` fulfill an interface known as `Connectable` which
     table = Table('sometable', metadata, Column('col1', Integer))
     
     # create the table with the Engine
-    table.create(connectable=engine)
+    table.create(bind=engine)
     
     # drop the table with a Connection off the Engine
     connection = engine.connect()
-    table.drop(connectable=connection)
+    table.drop(bind=connection)
 
 Connection facts:
 
@@ -229,6 +229,7 @@ Note that SQLAlchemy's Object Relational Mapper also provides a way to control t
 Transaction Facts:
 
  * the Transaction object, just like its parent Connection, is **not threadsafe**.
+ * SQLAlchemy 0.4 will feature transactions with two-phase commit capability as well as SAVEPOINT capability.
 
 #### Understanding Autocommit
 
@@ -240,7 +241,7 @@ The above transaction example illustrates how to use `Transaction` so that sever
 
 ### Connectionless Execution, Implicit Execution {@name=implicit}
 
-Recall from the first section we mentioned executing with and without a `Connection`.  `Connectionless` execution refers to calling the `execute()` method on an object which is not a `Connection`, which could be on the the `Engine` itself, or could be a constructed SQL object.  When we say "implicit", we mean that we are calling the `execute()` method on an object which is neither a `Connection` nor an `Engine` object; this can only be used with constructed SQL objects which have their own `execute()` method, and can be "bound" to an `Engine`.  A description of "constructed SQL objects" may be found in [sql](rel:sql).  
+Recall from the first section we mentioned executing with and without a `Connection`.  `Connectionless` execution refers to calling the `execute()` method on an object which is not a `Connection`, which could be on the the `Engine` itself, or could be a constructed SQL object.  When we say "implicit", we mean that we are calling the `execute()` method on an object which is neither a `Connection` nor an `Engine` object; this can only be used with constructed SQL objects which have their own `execute()` method, and can be "bound" to an `Engine`.  A description of "constructed SQL objects" may be found in [sql](rel:sql).
 
 A summary of all three methods follows below.  First, assume the usage of the following `MetaData` and `Table` objects; while we haven't yet introduced these concepts, for now you only need to know that we are representing a database table, and are creating an "executeable" SQL construct which issues a statement to the database.  These objects are described in [metadata](rel:metadata).
 
index 7088a08aeda9b28311d95b13414f3b8885deda5d..ff854bd39c0eaad9565b37b80c5722b028fc753d 100644 (file)
@@ -96,8 +96,8 @@ And `Table` provides an interface to the table's properties as well as that of i
     # access the table's MetaData:
     employees.metadata
     
-    # access the table's Engine, if its MetaData is bound:
-    employees.engine
+    # access the table's bound Engine or Connection, if its MetaData is bound:
+    employees.bind
     
     # access a column's name, type, nullable, primary key, foreign key
     employees.c.employee_id.name
@@ -117,9 +117,9 @@ And `Table` provides an interface to the table's properties as well as that of i
     # get the table related by a foreign key
     fcolumn = employees.c.employee_dept.foreign_key.column.table
 
-#### Binding MetaData to an Engine {@name=binding}
+#### Binding MetaData to an Engine or Connection {@name=binding}
 
-A `MetaData` object can be associated with an `Engine` (or an individual `Connection`); this process is called **binding**.  This allows the `MetaData` and the elements which it contains to perform operations against the database directly, using the connection resources to which it's bound.   Common operations which are made more convenient through binding include being able to generate SQL constructs which know how to execute themselves, creating `Table` objects which query the database for their column and constraint information, and issuing CREATE or DROP statements.
+A `MetaData` object can be associated with an `Engine` or an individual `Connection`; this process is called **binding**.  The term used to describe "an engine or a connection" is often referred to as a **connectable**.  Binding allows the `MetaData` and the elements which it contains to perform operations against the database directly, using the connection resources to which it's bound.   Common operations which are made more convenient through binding include being able to generate SQL constructs which know how to execute themselves, creating `Table` objects which query the database for their column and constraint information, and issuing CREATE or DROP statements.
 
 To bind `MetaData` to an `Engine`, use the `connect()` method:
 
@@ -130,7 +130,7 @@ To bind `MetaData` to an `Engine`, use the `connect()` method:
     meta = MetaData()
 
     # bind to an engine
-    meta.connect(engine)
+    meta.bind = engine
 
 Once this is done, the `MetaData` and its contained `Table` objects can access the database directly:
 
@@ -212,42 +212,6 @@ Within the `MetaData` collection, this table will be identified by the combinati
 
 Note that these clauses are not supported on SQLite, and require `InnoDB` tables when used with MySQL.  They may also not be supported on other databases.
 
-#### Enabling Table / Column Quoting {@name=quoting}
-
-Feature Status: [Alpha Implementation][alpha_implementation] 
-
-Many table, schema, or column names require quoting to be enabled.  Reasons for this include names that are the same as a database reserved word, or for identifiers that use MixedCase, where the database would normally "fold" the case convention into lower or uppercase (such as Postgres).  SQLAlchemy will attempt to automatically determine when quoting should be used.  It will determine a value for every identifier name called `case_sensitive`, which defaults to `False` if the identifer name uses no uppercase letters, or `True` otherwise.  This flag may be explicitly set on any schema item as well (schema items include `Table`, `Column`, `MetaData`, `Sequence`, etc.) to override this default setting, where objects will inherit the setting from an enclosing object if not explicitly overridden.
-
-When `case_sensitive` is `True`, the dialect will do what it has to in order for the database to recognize the casing.  For Postgres and Oracle, this means using quoted identifiers.
-
-Identifiers that match known SQL reserved words (such as "asc", "union", etc.) will also be quoted according to the dialect's quoting convention regardless of the `case_sensitive` setting.
-
-To force quoting for an identifier, set the "quote=True" flag on `Column` or `Table`, as well as the `quote_schema=True` flag for `Table`. 
-
-    {python}
-    table2 = Table('WorstCase2', metadata,
-        # desc is a reserved word, which will be quoted.
-        Column('desc', Integer, primary_key=True),
-
-        # if using a reserved word which SQLAlchemy doesn't know about,
-        # specify quote=True
-        Column('some_reserved_word', Integer, quote=True, primary_key=True),
-
-        # MixedCase uses a mixed case convention. 
-        # it will be automatically quoted since it is case sensitive
-        Column('MixedCase', Integer),
-
-        # Union is both a reserved word and mixed case
-        Column('Union', Integer),
-
-        # normal_column doesnt require quoting
-        Column('normal_column', String(30)))
-
-    # to use tables where case_sensitive is False by default regardless
-    # of idenfifier casings, set "case_sensitive" to false at any level
-    # (or true to force case sensitive for lowercase identifiers as well)
-    lowercase_metadata = MetaData(case_sensitive=False)
-
 #### Other Options {@name=options}
 
 `Tables` may support database-specific options, such as MySQL's `engine` option that can specify "MyISAM", "InnoDB", and other backends for the table:
@@ -262,11 +226,11 @@ To force quoting for an identifier, set the "quote=True" flag on `Column` or `Ta
     
 ### Creating and Dropping Database Tables {@name=creating}    
 
-Creating and dropping individual tables can be done via the `create()` and `drop()` methods of `Table`; these methods take an optional `connectable` parameter which references an `Engine` or a `Connection`.  If not supplied, the `Engine` bound to the `MetaData` will be used, else an error is raised:
+Creating and dropping individual tables can be done via the `create()` and `drop()` methods of `Table`; these methods take an optional `bind` parameter which references an `Engine` or a `Connection`.  If not supplied, the `Engine` bound to the `MetaData` will be used, else an error is raised:
 
     {python}
     meta = MetaData()
-    meta.connect('sqlite:///:memory:')
+    meta.bind = 'sqlite:///:memory:'
 
     employees = Table('employees', meta, 
         Column('employee_id', Integer, primary_key=True),
@@ -284,17 +248,17 @@ Creating and dropping individual tables can be done via the `create()` and `drop
 `drop()` method:
     
     {python}
-    {sql}employees.drop(connectable=e)
+    {sql}employees.drop(bind=e)
     DROP TABLE employees
     {}            
 
 The `create()` and `drop()` methods also support an optional keyword argument `checkfirst` which will issue the database's appropriate pragma statements to check if the table exists before creating or dropping:
 
     {python}
-    employees.create(connectable=e, checkfirst=True)
+    employees.create(bind=e, checkfirst=True)
     employees.drop(checkfirst=False)
     
-Entire groups of Tables can be created and dropped directly from the `MetaData` object with `create_all()` and `drop_all()`.  These methods always check for the existence of each table before creating or dropping.  Each method takes an optional `connectable` keyword argument which can reference an `Engine` or a `Connection`.  If no engine is specified, the underlying bound `Engine`,  if any, is used:
+Entire groups of Tables can be created and dropped directly from the `MetaData` object with `create_all()` and `drop_all()`.  These methods always check for the existence of each table before creating or dropping.  Each method takes an optional `bind` keyword argument which can reference an `Engine` or a `Connection`.  If no engine is specified, the underlying bound `Engine`,  if any, is used:
 
     {python}
     engine = create_engine('sqlite:///:memory:')
@@ -315,7 +279,7 @@ Entire groups of Tables can be created and dropped directly from the `MetaData`
         Column('pref_value', String(100))
     )
     
-    {sql}metadata.create_all(connectable=engine)
+    {sql}metadata.create_all(bind=engine)
     PRAGMA table_info(users){}
     CREATE TABLE users(
             user_id INTEGER NOT NULL PRIMARY KEY, 
@@ -397,7 +361,7 @@ A PassiveDefault indicates an column default that is executed upon INSERT by the
 
     {python}
     t = Table('test', meta, 
-        Column('mycolumn', DateTime, PassiveDefault(text("sysdate")))
+        Column('mycolumn', DateTime, PassiveDefault("sysdate"))
     )
         
 A create call for the above table will produce:
@@ -415,7 +379,7 @@ PassiveDefault also sends a message to the `Engine` that data is available after
         Column('my_id', Integer, primary_key=True),
 
         # an on-insert database-side default
-        Column('data1', Integer, PassiveDefault(text("d1_func()"))),
+        Column('data1', Integer, PassiveDefault("d1_func()")),
     )
     # insert a row
     r = mytable.insert().execute(name='fred')
@@ -444,9 +408,9 @@ A table with a sequence looks like:
         Column("createdate", DateTime())
     )
 
-The Sequence is used with Postgres or Oracle to indicate the name of a database sequence that will be used to create default values for a column.  When a table with a Sequence on a column is created in the database by SQLAlchemy, the database sequence object is also created.   Similarly, the database sequence is dropped when the table is dropped.  Sequences are typically used with primary key columns.  When using Postgres, if an integer primary key column defines no explicit Sequence or other default method, SQLAlchemy will create the column with the SERIAL keyword, and will pre-execute a sequence named "tablename_columnname_seq" in order to retrieve new primary key values, if they were not otherwise explicitly stated.   Oracle, which has no "auto-increment" keyword, requires that a Sequence be created for a table if automatic primary key generation is desired.
+The Sequence is used with Postgres or Oracle to indicate the name of a database sequence that will be used to create default values for a column.  When a table with a Sequence on a column is created in the database by SQLAlchemy, the database sequence object is also created.   Similarly, the database sequence is dropped when the table is dropped.  Sequences are typically used with primary key columns.  When using Postgres, if an integer primary key column defines no explicit Sequence or other default method, SQLAlchemy will create the column with the SERIAL keyword, and will pre-execute a sequence named "tablename_columnname_seq" in order to retrieve new primary key values, if they were not otherwise explicitly stated.   Oracle, which has no "auto-increment" keyword, requires that a Sequence be specified for a table if automatic primary key generation is desired.
 
-A Sequence object can be defined on a Table that is then used for a non-sequence-supporting database.  In that case, the Sequence object is simply ignored.  Note that a Sequence object is **entirely optional for all databases except Oracle**, as other databases offer options for auto-creating primary key values, such as AUTOINCREMENT, SERIAL, etc.  SQLAlchemy will use these default methods for creating primary key values if no Sequence is present on the table metadata.
+A Sequence object can be defined on a Table that is then also used with a non-sequence-supporting database.  In that case, the Sequence object is simply ignored.  Note that a Sequence object is **entirely optional for all databases except Oracle**, as other databases offer options for auto-creating primary key values, such as AUTOINCREMENT, SERIAL, etc.  SQLAlchemy will use these default methods for creating primary key values if no Sequence is present on the table metadata.
 
 A sequence can also be specified with `optional=True` which indicates the Sequence should only be used on a database that requires an explicit sequence, and not those that supply some other method of providing integer values.  At the moment, it essentially means "use this sequence only with Oracle and not Postgres".
 
@@ -527,7 +491,7 @@ The `Index` objects will be created along with the CREATE statements for the tab
     # define an index
     i = Index('someindex', sometable.c.col5)
 
-    # create the index, will use the table's connectable, or specify the connectable keyword argument
+    # create the index, will use the table's bound connectable if the `bind` keyword argument not specified
     i.create()
 
 ### Adapting Tables to Alternate Metadata {@name=adapting}
index e8077f5bd10188808b71a7764889f9852c337368..a672fb5cec650ae79db70c948ab2853be09ed59f 100644 (file)
@@ -74,8 +74,7 @@ As mentioned above, `ClauseElement` structures can also be executed with a `Conn
     engine = create_engine('sqlite:///myfile.db')
     conn = engine.connect()
     
-    s = users.select()
-    {sql}result = conn.execute(s)
+    {sql}result = conn.execute(users.select())
     SELECT users.user_id, users.user_name, users.password FROM users
     {}
     
@@ -83,16 +82,21 @@ As mentioned above, `ClauseElement` structures can also be executed with a `Conn
 
 #### Binding ClauseElements to Engines {@name=binding}
 
-For queries that don't contain any tables, `ClauseElement`s that represent a fully executeable statement support an `engine` keyword parameter which can bind the object to an `Engine`, thereby allowing implicit execution:
+For queries that don't contain any "bound" tables, `ClauseElement`s that represent a fully executeable statement support an `bind` keyword parameter which can bind the object to an `Engine` or `Connection`, thereby allowing implicit execution:
 
     {python}
+    # select using a table
+    {sql}select([users], bind=myengine).execute()
+    SELECT users.user_id, users.user_name, users.password FROM users
+    {}
+
     # select a literal
-    {sql}select(["current_time"], engine=myengine).execute()
+    {sql}select(["current_time"], bind=myengine).execute()
     SELECT current_time
     {}
     
     # select a function
-    {sql}select([func.now()], engine=db).execute()
+    {sql}select([func.now()], bind=db).execute()
     SELECT now()
     {}
 
@@ -127,6 +131,11 @@ The object returned by `execute()` is a `sqlalchemy.engine.ResultProxy` object,
     # or get the underlying DBAPI cursor object
     cursor = result.cursor
     
+    # after an INSERT, return the last inserted primary key value
+    # returned as a list of primary key values for *one* row 
+    # (a list since primary keys can be composite)
+    id = result.last_inserted_ids()
+    
     # close the result.  If the statement was implicitly executed 
     # (i.e. without an explicit Connection), this will
     # return the underlying connection resources back to 
@@ -213,7 +222,7 @@ But in addition to selecting all the columns off a single table, any set of colu
     {}
 
 ### WHERE Clause {@name=whereclause}
-    
+
 The WHERE condition is the named keyword argument `whereclause`, or the second positional argument to the `select()` constructor and the first positional argument to the `select()` method of `Table`.
 
 WHERE conditions are constructed using column objects, literal values, and functions defined in the `sqlalchemy.sql` module.  Column objects override the standard Python operators to provide clause compositional objects, which compile down to SQL operations:
@@ -366,10 +375,10 @@ Functions also are callable as standalone values:
 
     {python}
     # call the "now()" function
-    time = func.now(engine=myengine).scalar()
+    time = func.now(bind=myengine).scalar()
 
     # call myfunc(1,2,3)
-    myvalue = func.myfunc(1, 2, 3, engine=db).execute()
+    myvalue = func.myfunc(1, 2, 3, bind=db).execute()
 
     # or call them off the engine
     db.func.now().scalar()
@@ -385,14 +394,14 @@ You can drop in a literal value anywhere there isnt a column to attach to via th
     {'literal_1': 'bar', 'literal': 'foo'}
 
     # literals have all the same comparison functions as columns
-    {sql}select([literal('foo') == literal('bar')], engine=myengine).scalar()
+    {sql}select([literal('foo') == literal('bar')], bind=myengine).scalar()
     SELECT :literal = :literal_1
     {'literal_1': 'bar', 'literal': 'foo'}
 
 Literals also take an optional `type` parameter to give literals a type.  This can sometimes be significant, for example when using the "+" operator with SQLite, the String type is detected and the operator is converted to "||":
 
     {python}
-    {sql}select([literal('foo', type=String) + 'bar'], engine=e).execute()
+    {sql}select([literal('foo', type=String) + 'bar'], bind=e).execute()
     SELECT ? || ?
     ['foo', 'bar']
 
@@ -712,7 +721,7 @@ Throughout all these examples, SQLAlchemy is busy creating bind parameters where
 
 #### Precompiling a Query {@name=precompiling}
 
-By throwing the `compile()` method onto the end of any query object, the query can be "compiled" by the SQLEngine into a `sqlalchemy.sql.Compiled` object just once, and the resulting compiled object reused, which eliminates repeated internal compilation of the SQL string:
+By throwing the `compile()` method onto the end of any query object, the query can be "compiled" by the Engine into a `sqlalchemy.sql.Compiled` object just once, and the resulting compiled object reused, which eliminates repeated internal compilation of the SQL string:
 
     {python}s = users.select(users.c.user_name==bindparam('username')).compile()
     s.execute(username='fred')
@@ -721,7 +730,7 @@ By throwing the `compile()` method onto the end of any query object, the query c
 
 ### Literal Text Blocks {@name=textual}
 
-The sql package tries to allow free textual placement in as many ways as possible.  In the examples below, note that the from_obj parameter is used only when no other information exists within the select object with which to determine table metadata.  Also note that in a query where there isnt even table metadata used, the SQLEngine to be used for the query has to be explicitly specified:
+The sql package tries to allow free textual placement in as many ways as possible.  In the examples below, note that the from_obj parameter is used only when no other information exists within the select object with which to determine table metadata.  Also note that in a query where there isnt even table metadata used, the Engine to be used for the query has to be explicitly specified:
 
     {python}
     # strings as column clauses
@@ -759,12 +768,12 @@ The sql package tries to allow free textual placement in as many ways as possibl
     {sql}select(
             ["*"], 
             from_obj=["(select user_id, user_name from users)"], 
-            engine=db).execute()
+            bind=db).execute()
     SELECT * FROM (select user_id, user_name from users)
     {}
     
     # a full query
-    {sql}text("select user_name from users", engine=db).execute()
+    {sql}text("select user_name from users", bind=db).execute()
     SELECT user_name FROM users
     {}
     
@@ -773,22 +782,22 @@ The sql package tries to allow free textual placement in as many ways as possibl
 
 Use the format `':paramname'` to define bind parameters inside of a text block.  They will be converted to the appropriate format upon compilation:
 
-    {python}t = engine.text("select foo from mytable where lala=:hoho")
+    {python}t = text("select foo from mytable where lala=:hoho", bind=engine)
     r = t.execute(hoho=7)
 
 Bind parameters can also be explicit, which allows typing information to be added.  Just specify them as a list with keys that match those inside the textual statement:
 
-    {python}t = engine.text("select foo from mytable where lala=:hoho", 
-                    bindparams=[bindparam('hoho', type=types.String)])
+    {python}t = text("select foo from mytable where lala=:hoho", 
+                    bindparams=[bindparam('hoho', type=types.String)], bind=engine)
         r = t.execute(hoho="im hoho")
 
 Result-row type processing can be added via the `typemap` argument, which is a dictionary of return columns mapped to types:
 
     {python}# specify DateTime type for the 'foo' column in the result set
     # sqlite, for example, uses result-row post-processing to construct dates
-    t = engine.text("select foo from mytable where lala=:hoho", 
+    t = text("select foo from mytable where lala=:hoho", 
             bindparams=[bindparam('hoho', type=types.String)],
-            typemap={'foo':types.DateTime}
+            typemap={'foo':types.DateTime}, bind=engine
             )
     r = t.execute(hoho="im hoho")
     
index 615f275f9ac41ed67cd29d96b9fef6df45149193..93f3c506458d7a1d7d713d68df50f38dad3b1a72 100644 (file)
@@ -86,16 +86,16 @@ Configuring SQLAlchemy for your database consists of creating objects called `Ta
 
     {python}
     >>> metadata = MetaData()
-    >>> metadata.connect(db)
+    >>> metadata.bind = db
 
-An equivalent operation is to create the `MetaData` object directly with an Engine URL, which calls the `create_engine` call for us:
+An equivalent operation is to create the `MetaData` object directly with the Engine:
 
     {python}
-    >>> metadata = MetaData('sqlite:///tutorial.db')
-
+    >>> metadata = MetaData(db)
+    
 Now, when we tell "metadata" about the tables in our database, we can issue CREATE statements for those tables, as well as execute SQL statements derived from them, without needing to open or close any connections; that will be all done automatically.
 
-Note that SQLALchemy fully supports the usage of explicit Connection objects for all SQL operations, which may be in conjunction with plain `MetaData` objects that are entirely unbound to any Engine, providing a more decoupled pattern that allows finer-grained control of connections than the "bound" approach this tutorial will present.  For the purposes of this tutorial, we will stick with "bound" objects, as it allows us to focus more on SA's general concepts, leaving explicit connection management as a more advanced topic.
+Note that SQLALchemy allows us to use explicit connection objects for everything, if we wanted to, and there are reasons why you might want to do this.  But for the purposes of this tutorial, using `bind` removes the need for us to deal with explicit connections.
 
 ### Creating a Table {@name=table_creating}
 
@@ -206,7 +206,7 @@ Pretty much the full range of standard SQL operations are supported as construct
 
 ### Working with Rows
 
-You can see that when we print out the rows returned by an execution result, it prints the rows as tuples.  These rows in fact support both the list and dictionary interfaces.  The dictionary interface allows the addressing of columns by string column name, or even the original `Column` object:
+You can see that when we print out the rows returned by an execution result, it prints the rows as tuples.  These rows support both the list and dictionary interfaces.  The dictionary interface allows the addressing of columns by string column name, or even the original `Column` object:
 
     {python}
     >>> row.keys()
@@ -271,7 +271,7 @@ With two related tables, we can now construct a join amongst them using the `joi
     >>> print [row for row in r]
     [(1, u'Mary', u'secure', 2, u'mary@mary.com', 1), (2, u'Tom', None, 1, u'tom@tom.com', 2)]
     
-The `join` method is also a standalone function in the `sqlalchemy` namespace.  The join condition is figured out from the foreign keys of the Table objects given.  The condition (also called the "ON clause") can be specified explicitly, such as in this example where we locate all users that used their email address as their password:
+The `join` method is also a standalone function in the `sqlalchemy` namespace.  The join condition is figured out from the foreign keys of the Table objects given.  The condition (also called the "ON clause") can be specified explicitly, such as in this example which creates a join representing all users that used their email address as their password:
 
     {python}
     >>> print join(users_table, email_addresses_table, 
@@ -332,7 +332,7 @@ The Session has all kinds of methods on it to manage and inspect its collection
 
     {python}
     >>> query = session.query(User)
-    >>> print query.select_by(user_name='Harry')
+    >>> print query.filter_by(user_name='Harry').all()
     SELECT users.user_name AS users_user_name, users.password AS users_password, users.user_id AS users_user_id 
     FROM users 
     WHERE users.user_name = ? ORDER BY users.oid
@@ -341,22 +341,22 @@ The Session has all kinds of methods on it to manage and inspect its collection
     
 All querying for objects is performed via an instance of `Query`.  The various `select` methods on an instance of `Mapper` also use an underlying `Query` object to perform the operation.  A `Query` is always bound to a specific `Session`.
 
-Lets turn off the database echoing for a moment, and try out a few methods on `Query`.  Methods that end with the suffix `_by` primarily take keyword arguments which correspond to properties on the object.  Other methods take `ClauseElement` objects, which are constructed by using `Column` objects inside of Python expressions, in the same way as we did with our SQL select example in the previous section of this tutorial.  Using `ClauseElement` structures to query objects is more verbose but more flexible:
+Lets turn off the database echoing for a moment, and try out a few methods on `Query`.  The two methods used to narrow results are `filter()` and `filter_by()`, and the two most common methods used to load results are `all()` and `first()`.   The `get()` method is used for a quick lookup by primary key.  `filter_by()` works with keyword arguments, and `filter()` works with `ClauseElement` objects, which are constructed by using `Column` objects inside of Python expressions, in the same way as we did with our SQL select example in the previous section of this tutorial.  Using `ClauseElement` structures to query objects is more verbose but more flexible:
 
     {python}
     >>> metadata.engine.echo = False
-    >>> print query.select(User.c.user_id==3)
+    >>> print query.filter(User.c.user_id==3).all()
     [User(u'Fred',None)]
     >>> print query.get(2)
     User(u'Tom',None)
-    >>> print query.get_by(user_name='Mary')
+    >>> print query.filter_by(user_name='Mary').first()
     User(u'Mary',u'secure')
-    >>> print query.selectfirst(User.c.password==None)
+    >>> print query.filter(User.c.password==None).first()
     User(u'Tom',None)
     >>> print query.count()
     4
 
-Notice that our `User` class has a special attribute `c` attached to it.  This 'c' represents the columns on the User's mapper's Table object.  Saying `User.c.user_name` is synonymous with saying `users_table.c.user_name`, recalling that `User` is the Python class and `users` is our `Table` object.
+Notice that our `User` class has a special attribute `c` attached to it.  This 'c' represents the columns on the User's mapper's Table object.  Saying `User.c.user_name` is synonymous with saying `users_table.c.user_name`, recalling that `User` is the Python class and `users_table` is our `Table` object.
 
 ### Making Changes {@name=changes}
 
@@ -373,15 +373,15 @@ With a little experience in loading objects, lets see what its like to make chan
 Lets also make a few changes on some of the objects in the database.  We will load them with our `Query` object, and then change some things.
 
     {python}
-    >>> mary = query.get_by(user_name='Mary')
-    >>> harry = query.get_by(user_name='Harry')
+    >>> mary = query.filter_by(user_name='Mary').first()
+    >>> harry = query.filter_by(user_name='Harry').first()
     >>> mary.password = 'marysnewpassword'
     >>> harry.password = 'harrysnewpassword'
     
 At the moment, nothing has been saved to the database; all of our changes are in memory only.  What happens if some other part of the application also tries to load 'Mary' from the database and make some changes before we had a chance to save it ?  Assuming that the same `Session` is used, loading 'Mary' from the database a second time will issue a second query in order locate the primary key of 'Mary', but will *return the same object instance as the one already loaded*.  This behavior is due to an important property of the `Session` known as the **identity map**:
 
     {python}
-    >>> mary2 = query.get_by(user_name='Mary')
+    >>> mary2 = query.filter_by(user_name='Mary').first()
     >>> mary is mary2
     True
     
@@ -394,7 +394,7 @@ As far as the issue of the same object being modified in two different Sessions,
 With a new user "ed" and some changes made on "Mary" and "Harry", lets also mark "Fred" as deleted:
 
     {python}
-    >>> fred = query.get_by(user_name='Fred')
+    >>> fred = query.filter_by(user_name='Fred').first()
     >>> session.delete(fred)
     
 Then to send all of our changes to the database, we `flush()` the Session.  Lets turn echo back on to see this happen!:
@@ -456,7 +456,7 @@ Lets try out this new mapping configuration, and see what we get for the email a
 We can then treat the `addresses` attribute on each `User` object like a regular list:
 
     {python}
-    >>> mary = query.get_by(user_name='Mary') # doctest: +NORMALIZE_WHITESPACE
+    >>> mary = query.filter_by(user_name='Mary').first() # doctest: +NORMALIZE_WHITESPACE
     SELECT users.user_name AS users_user_name, users.password AS users_password, users.user_id AS users_user_id 
     FROM users 
     WHERE users.user_name = ? ORDER BY users.oid 
@@ -490,9 +490,9 @@ You may have noticed from the example above that when we say `session.flush()`,
     {python}
     >>> transaction = session.create_transaction()
     >>> try: # doctest: +NORMALIZE_WHITESPACE
-    ...     (ed, harry, mary) = session.query(User).select(
-    ...         User.c.user_name.in_('Ed', 'Harry', 'Mary'), order_by=User.c.user_name
-    ...     )
+    ...     (ed, harry, mary) = session.query(User).filter(
+    ...         User.c.user_name.in_('Ed', 'Harry', 'Mary')
+    ...     ).order_by(User.c.user_name).all()
     ...     del mary.addresses[1]
     ...     harry.addresses.append(Address('harry2@gmail.com'))
     ...     session.flush()
@@ -528,6 +528,8 @@ You may have noticed from the example above that when we say `session.flush()`,
     ['fredsnewemail@fred.com', 6]
     COMMIT
 
+The `SessionTransaction` process above is due to be greatly simplified in version 0.4 of SQLAlchemy, where the `Session` will be able to wrap its whole lifespan in a transaction automatically.
+
 Main documentation:  [unitofwork](rel:unitofwork)
 
 Next Steps
index 261799e41b43326660e911a72e1b588abd5c8273..4ae2c3c91b7f644145b840e622314a4967de54a0 100644 (file)
@@ -45,13 +45,13 @@ A common option used with `create_session()` is to specify a specific `Engine` o
     
     # create a Session that will use this engine for all operations.
     # it will open and close Connections as needed.
-    session = create_session(bind_to=e)
+    session = create_session(bind=e)
     
     # open a Connection
     conn = e.connect()
     
     # create a Session that will use this specific Connection for all operations
-    session = create_session(bind_to=conn)
+    session = create_session(bind=conn)
     
 
 The session to which an object is attached can be acquired via the `object_session()` function, which returns the appropriate `Session` if the object is pending or persistent, or `None` if the object is transient or detached:
@@ -61,7 +61,7 @@ The session to which an object is attached can be acquired via the `object_sessi
 
 Session Facts:
 
- * the Session object is **not threadsafe**.  For thread-local management of Sessions, the recommended approch is to use the [plugins_sessioncontext](rel:plugins_sessioncontext) extension module.
+ * the Session object is **not threadsafe**.  For thread-local management of Sessions, the recommended approach is to use the [plugins_sessioncontext](rel:plugins_sessioncontext) extension module.
 
 We will now cover some of the key concepts used by Sessions and its underlying Unit of Work.
 
@@ -77,13 +77,13 @@ For example; below, two separate calls to load an instance with database identit
     mymapper = mapper(MyClass, mytable)
     
     session = create_session()
-    obj1 = session.query(MyClass).selectfirst(mytable.c.id==15)
-    obj2 = session.query(MyClass).selectfirst(mytable.c.id==15)
+    obj1 = session.query(MyClass).filter(mytable.c.id==15).first()
+    obj2 = session.query(MyClass).filter(mytable.c.id==15).first()
     
     >>> obj1 is obj2
     True
     
-The Identity Map is an instance of `dict` by default.  (This is new as of version 0.3.2).  As an option, you can specify the flag `weak_identity_map=True` to the `create_session` function so that it will use a `weakref.WeakValueDictionary`, so that when an in-memory object falls out of scope, it will be removed automatically, thereby providing some automatic management of memory.   However, this may not be instant if there are circular references upon the object.  To guarantee that an instance is removed from the identity map before removing references to it, use the `expunge()` method, described later, to remove it.  Additionally, note that an object that has changes marked on it (i.e. "dirty") can still fall out of scope when using `weak_identity_map`.
+The Identity Map is an instance of `dict` by default.  As an option, you can specify the flag `weak_identity_map=True` to the `create_session` function so that it will use a `weakref.WeakValueDictionary`, so that when an in-memory object falls out of scope, it will be removed automatically, thereby providing some automatic management of memory.   However, this may not be instant if there are circular references upon the object.  To guarantee that an instance is removed from the identity map before removing references to it, use the `expunge()` method, described later, to remove it.  Additionally, note that an object that has changes marked on it (i.e. "dirty") can still fall out of scope when using `weak_identity_map`.
 
 The Session supports an iterator interface in order to see all objects in the identity map:
 
@@ -138,11 +138,14 @@ As for objects inside of `new` and `deleted`, if you abandon all references to n
 
 #### query() {@name=query}
 
-The `query()` function takes a class or `Mapper` as an argument, along with an optional `entity_name` parameter, and returns a new `Query` object which will issue mapper queries within the context of this Session.  If a Mapper is passed, then the Query uses that mapper.  Otherwise, if a class is sent, it will locate the primary mapper for that class which is used to construct the Query.  
+The `query()` function takes one or more classes and/or mappers, along with an optional `entity_name` parameter, and returns a new `Query` object which will issue mapper queries within the context of this Session.  For each mapper is passed, the Query uses that mapper.  For each class, the Query will locate the primary mapper for the class using `class_mapper()`.
 
     {python}
     # query from a class
-    session.query(User).select_by(name='ed')
+    session.query(User).filter_by(name='ed').all()
+
+    # query with multiple classes, returns tuples
+    session.query(User, Address).join('addresses').filter_by(name='ed').all()
     
     # query from a mapper
     query = session.query(usermapper)
@@ -150,7 +153,7 @@ The `query()` function takes a class or `Mapper` as an argument, along with an o
     
     # query from a class mapped with entity name 'alt_users'
     q = session.query(User, entity_name='alt_users')
-    y = q.options(eagerload('orders')).select()
+    y = q.options(eagerload('orders')).all()
     
 `entity_name` is an optional keyword argument sent with a class object, in order to further qualify which primary mapper to be used; this only applies if there was a `Mapper` created with that particular class/entity name combination, else an exception is raised.  All of the methods on Session which take a class or mapper argument also take the `entity_name` argument, so that a given class can be properly matched to the desired primary mapper.
 
@@ -291,7 +294,7 @@ Both of these methods receive two arguments; in the case of `bind_mapper()`, it
     sess.bind_mapper(mymapper, sqlite_connection)  # bind mymapper operations to a single SQLite connection
     sess.bind_table(email_addresses_table, engine2) # bind operations with the email_addresses_table to mysql
     
-Normally, when a `Session` is created via `create_session()` with no arguments, the Session has no awareness of individual `Engines`, and when mappers use the `Session` to retrieve connections, the underlying `MetaData` each `Table` is associated with is expected to be "bound" to an `Engine`, else no engine can be located and an exception is raised.  A second form of `create_session()` takes the argument `bind_to=engine_or_connection`, where all SQL operations performed by this `Session` use the single `Engine` or `Connection` (collectively known as a `Connectable`) passed to the constructor.  With `bind_mapper()` and `bind_table()`,  the operations of individual mapper and/or tables are bound to distinct engines or connections, thereby overriding not only the engine which may be "bound" to the underlying `MetaData`, but also the `Engine` or `Connection` which may have been passed to the `create_session()` function.  Configurations which interact with multiple explicit database connections at one time must use either or both of these methods in order to associate `Session` operations with the appropriate connection resource.  
+Normally, when a `Session` is created via `create_session()` with no arguments, the Session has no awareness of individual `Engines`, and when mappers use the `Session` to retrieve connections, the underlying `MetaData` each `Table` is associated with is expected to be "bound" to an `Engine`, else no engine can be located and an exception is raised.  A second form of `create_session()` takes the argument `bind=engine_or_connection`, where all SQL operations performed by this `Session` use the single `Engine` or `Connection` (collectively known as a `Connectable`) passed to the constructor.  With `bind_mapper()` and `bind_table()`,  the operations of individual mapper and/or tables are bound to distinct engines or connections, thereby overriding not only the engine which may be "bound" to the underlying `MetaData`, but also the `Engine` or `Connection` which may have been passed to the `create_session()` function.  Configurations which interact with multiple explicit database connections at one time must use either or both of these methods in order to associate `Session` operations with the appropriate connection resource.  
 
 Binding a `Mapper` to a resource takes precedence over a `Table` bind, meaning if mapper A is associated with table B, and the Session binds mapper A to connection X and table B to connection Y, an operation with mapper A will use connection X, not connection Y.
 
@@ -309,8 +312,6 @@ This method is a combination of the `save()` and `update()` methods, which will
 
 #### merge() {@name=merge}
 
-Feature Status: [Alpha Implementation][alpha_implementation] 
-
 `merge()` is used to return the persistent version of an instance that is not attached to this Session.  When passed an instance, if an instance with its database identity already exists within this Session, it is returned.  If the instance does not exist in this Session, it is loaded from the database and then returned.  
 
 A future version of `merge()` will also update the Session's instance with the state of the given instance (hence the name "merge").
@@ -329,8 +330,6 @@ Note that `merge()` *does not* associate the given instance with the Session; it
 
 ### Cascade rules {@name=cascade}
 
-Feature Status: [Alpha Implementation][alpha_implementation] 
-
 Mappers support the concept of configurable *cascade* behavior on `relation()`s.  This behavior controls how the Session should treat the instances that have a parent-child relationship with another instance that is operated upon by the Session.  Cascade is indicated as a comma-separated list of string keywords, with the possible values `all`, `delete`, `save-update`, `refresh-expire`, `merge`, `expunge`, and `delete-orphan`.
 
 Cascading is configured by setting the `cascade` keyword argument on a `relation()`:
@@ -389,7 +388,8 @@ Note that while SessionTransaction is capable of tracking multiple transactions
 SessionTransaction Facts:
 
  * SessionTransaction, like its parent Session object, is **not threadsafe**.
+ * SessionTransaction will no longer be necessary in SQLAlchemy 0.4, where its functionality is to be merged with the Session itself.
+  
 #### Using SQL with SessionTransaction {@name=sql}
 
 The SessionTransaction can interact with direct SQL queries in two general ways.  Either specific `Connection` objects can be associated with the `SessionTransaction`, which are then useable both for direct SQL as well as within `flush()` operations performed by the `SessionTransaction`, or via accessing the `Connection` object automatically referenced within the `SessionTransaction`.
@@ -430,7 +430,7 @@ The transactions issued by `SessionTransaction` as well as internally by the `Se
 
     {python title="Transactions with Sessions"}
     connection = engine.connect()   # Connection
-    session = create_session(bind_to=connection) # Session bound to the Connection
+    session = create_session(bind=connection) # Session bound to the Connection
     trans = connection.begin()      # start transaction
     try:
         stuff = session.query(MyClass).select()     # Session operation uses connection
@@ -442,33 +442,3 @@ The transactions issued by `SessionTransaction` as well as internally by the `Se
         raise
     trans.commit()      # commit
 
-### Analyzing Object Flushes {@name=logging}    
-
-The session module can log an extensive display of its "flush plans", which is a graph of its internal representation of objects before they are written to the database.  To turn this logging on:
-
-    {python}
-    # make an Session with echo_uow
-    session = create_session(echo_uow=True)
-    
-The `flush()` operation will then dump to the standard output displays like the following:
-
-    {code}
-    Task dump:
-    
-     UOWTask(6034768, 'User/users/None')
-      |
-      |- Save User(6016624)
-      |       |-Process User(6016624).addresses
-      |
-      |- UOWTask(6034832, 'Address/email_addresses/None')
-      |   |- Save Address(6034384)
-      |   |- Save Address(6034256)
-      |   |----
-      | 
-      |----
-    
-The above graph can be read straight downwards to determine the order of operations.  It indicates "save User 6016624, process each element in the 'addresses' list on User 6016624, save Address 6034384, Address 6034256".
-
-Of course, one can also get a good idea of the order of operations just by logging the actual SQL statements executed.
-
-    
index acd23915ee815f499aa9bd1b9f570e95a3c93254..c80589fc2bad434124ae7f81a2d2300869a90a6c 100644 (file)
@@ -143,7 +143,7 @@ def replace_pre_with_mako(tree):
         # syntax highlighter which uses the tokenize module
         text = re.sub(r'>>> ', r'">>>" ', text)
 
-        sqlre = re.compile(r'{sql}(.*?)((?:BEGIN|SELECT|INSERT|DELETE|UPDATE|CREATE|DROP|PRAGMA|DESCRIBE).*?)\n\s*(\n|$)', re.S)
+        sqlre = re.compile(r'{sql}(.*?\n)((?:BEGIN|SELECT|INSERT|DELETE|UPDATE|CREATE|DROP|PRAGMA|DESCRIBE).*?)\n\s*(\n|$)', re.S)
         if sqlre.search(text) is not None:
             use_sliders = False
         else:
@@ -151,7 +151,7 @@ def replace_pre_with_mako(tree):
         
         text = sqlre.sub(r"""${formatting.poplink()}\1\n<%call expr="formatting.codepopper()">\2</%call>\n\n""", text)
 
-        sqlre2 = re.compile(r'{opensql}(.*?)((?:BEGIN|SELECT|INSERT|DELETE|UPDATE|CREATE|DROP).*?)\n\s*(\n|$)', re.S)
+        sqlre2 = re.compile(r'{opensql}(.*?\n)((?:BEGIN|SELECT|INSERT|DELETE|UPDATE|CREATE|DROP).*?)\n\s*(\n|$)', re.S)
         text = sqlre2.sub(r"<%call expr='formatting.poppedcode()' >\1\n\2</%call>\n\n", text)
 
         tag = et.Element("MAKO:formatting.code")
index bb34b173eed2da6e20b94de99ac3d5e951274e1c..4068e3adfa41e307b865cbba4e1943f260956ecc 100644 (file)
         p = re.compile(r'<pre>(.*?)</pre>', re.S)
         def hlight(match):
             return "<pre>" + highlight.highlight(fix_indent(match.group(1)), html_escape = html_escape, syntaxtype = syntaxtype) + "</pre>"
-        content = p.sub(hlight, "<pre>" + capture(caller.body) + "</pre>")
+        try:
+            content = p.sub(hlight, "<pre>" + capture(caller.body) + "</pre>")
+        except:
+            raise "the content is " + str(capture(caller.body))
         
     %>
 
index b1e58dac22dbb1c650fa0837f8040d87bd93d947..7322c50bd910da018280d0a1cc513f9003b8c9ce 100644 (file)
@@ -881,8 +881,8 @@ class ANSISchemaGenerator(ANSISchemaBase):
 
     def get_column_default_string(self, column):
         if isinstance(column.default, schema.PassiveDefault):
-            if isinstance(column.default.arg, str):
-                return repr(column.default.arg)
+            if isinstance(column.default.arg, basestring):
+                return "'%s'" % column.default.arg
             else:
                 return str(self._compile(column.default.arg, None))
         else:
index 39ad51e26595a89651fc3211dd7d3c092030c68e..e276ad31a7db008f15fe5354d4e0046fd35332e5 100644 (file)
@@ -43,6 +43,14 @@ class OracleDate(sqltypes.Date):
 class OracleDateTime(sqltypes.DateTime):
     def get_col_spec(self):
         return "DATE"
+        
+    def convert_result_value(self, value, dialect):
+        if value is None or isinstance(value,datetime.datetime):
+            return value
+        else:
+            # convert cx_oracle datetime object returned pre-python 2.4
+            return datetime.datetime(value.year,value.month,
+                value.day,value.hour, value.minute, value.second)
 
 # Note:
 # Oracle DATE == DATETIME
@@ -57,6 +65,15 @@ class OracleTimestamp(sqltypes.TIMESTAMP):
     def get_dbapi_type(self, dialect):
         return dialect.TIMESTAMP
 
+    def convert_result_value(self, value, dialect):
+        if value is None or isinstance(value,datetime.datetime):
+            return value
+        else:
+            # convert cx_oracle datetime object returned pre-python 2.4
+            return datetime.datetime(value.year,value.month,
+                value.day,value.hour, value.minute, value.second)
+
+
 class OracleString(sqltypes.String):
     def get_col_spec(self):
         return "VARCHAR(%(length)s)" % {'length' : self.length}
@@ -72,7 +89,8 @@ class OracleText(sqltypes.TEXT):
         if value is None:
             return None
         else:
-            return value.read()
+            return super(OracleText, self).convert_result_value(value.read(), dialect)
+
 
 class OracleRaw(sqltypes.Binary):
     def get_col_spec(self):
@@ -553,6 +571,13 @@ class OracleCompiler(ansisql.ANSICompiler):
         else:
             return super(OracleCompiler, self).for_update_clause(select)
 
+    def visit_binary(self, binary):
+        if binary.operator == '%': 
+            self.strings[binary] = ("MOD(%s,%s)"%(self.get_str(binary.left), self.get_str(binary.right)))
+        else:
+            return ansisql.ANSICompiler.visit_binary(self, binary)
+        
+
 class OracleSchemaGenerator(ansisql.ANSISchemaGenerator):
     def get_column_specification(self, column, **kwargs):
         colspec = self.preparer.format_column(column)
index 100acd49a38645007bc57772eb6db5f180b53577..6f4cca2234fee0ed3bd44cd99947466626010b00 100644 (file)
@@ -355,14 +355,11 @@ class ExecutionContext(object):
         """Return the list of the primary key values for the last insert statement executed.
 
         This does not apply to straight textual clauses; only to
-        ``sql.Insert`` objects compiled against a ``schema.Table`` object,
-        which are executed via `execute()`.  The order of
+        ``sql.Insert`` objects compiled against a ``schema.Table`` object.
+        The order of
         items in the list is the same as that of the Table's
         'primary_key' attribute.
 
-        In some cases, this method may invoke a query back to the
-        database to retrieve the data, based on the "lastrowid" value
-        in the cursor.
         """
 
         raise NotImplementedError()
@@ -1106,6 +1103,7 @@ class ResultProxy(object):
         else:
             return self.context.get_rowcount()
     rowcount = property(_get_rowcount)
+    lastrowid = property(lambda s:s.cursor.lastrowid)
     
     def _init_metadata(self):
         if hasattr(self, '_ResultProxy__props'):
index 58827a7b86628b941334123edea627d6cbf601b4..9a4afb9f5b245187f62a1a1518c2a41f8fd903d3 100644 (file)
@@ -7,11 +7,14 @@
 from sqlalchemy import sql, util, exceptions, sql_util, logging, schema
 from sqlalchemy.orm import mapper, class_mapper, object_mapper
 from sqlalchemy.orm.interfaces import OperationContext
+import random
 
 __all__ = ['Query', 'QueryContext', 'SelectionContext']
 
 class Query(object):
-    """Encapsulates the object-fetching operations provided by Mappers."""
+    """Encapsulates the object-fetching operations provided by Mappers.
+    
+    """
 
     def __init__(self, class_or_mapper, session=None, entity_name=None):
         if isinstance(class_or_mapper, type):
@@ -182,9 +185,6 @@ class Query(object):
         of this Query along with the additional entities.  The Query selects
         from all tables with no joining criterion by default.
         
-        When tuple-based results are returned, the 'uniquing' of returned entities
-        is disabled to maintain grouping.
-
             entity
                 a class or mapper which will be added to the results.
                 
@@ -207,15 +207,18 @@ class Query(object):
         table or selectable that is not the primary mapped selectable.  The Query selects
         from all tables with no joining criterion by default.
         
-        When tuple-based results are returned, the 'uniquing' of returned entities
-        is disabled to maintain grouping.
-
             column
                 a string column name or sql.ColumnElement to be added to the results.
                 
         """
         
         q = self._clone()
+
+        # alias non-labeled column elements. 
+        # TODO: make the generation deterministic
+        if isinstance(column, sql.ColumnElement) and not hasattr(column, '_label'):
+            column = column.label("anon_" + hex(random.randint(0, 65535))[2:])
+
         q._entities = q._entities + [column]
         return q
         
@@ -236,7 +239,7 @@ class Query(object):
         q = self._clone()
         q._lockmode = mode
         return q
-    
+
     def params(self, **kwargs):
         """add values for bind parameters which may have been specified in filter()."""
         
@@ -450,11 +453,9 @@ class Query(object):
     def join(self, prop):
         """create a join of this ``Query`` object's criterion
         to a relationship and return the newly resulting ``Query``.
-        
-        'prop' may be a string property name in which it is located
-        in the same manner as keyword arguments in ``select_by``, or
-        it may be a list of strings in which case the property is located
-        by direct traversal of each keyname (i.e. like join_via()).
+
+        'prop' may be a string property name or a list of string
+        property names.
         """
         
         q = self._clone()
@@ -467,10 +468,8 @@ class Query(object):
         """create a left outer join of this ``Query`` object's criterion
         to a relationship and return the newly resulting ``Query``.
         
-        'prop' may be a string property name in which it is located
-        in the same manner as keyword arguments in ``select_by``, or
-        it may be a list of strings in which case the property is located
-        by direct traversal of each keyname (i.e. like join_via()).
+        'prop' may be a string property name or a list of string
+        property names.
         """
         q = self._clone()
         (clause, mapper) = self._join_to(prop, outerjoin=True, start=self.mapper)
@@ -478,6 +477,20 @@ class Query(object):
         q._joinpoint = mapper
         return q
 
+    def reset_joinpoint(self):
+        """return a new Query reset the 'joinpoint' of this Query reset 
+        back to the starting mapper.  Subsequent generative calls will
+        be constructed from the new joinpoint.
+
+        Note that each call to join() or outerjoin() also starts from
+        the root.
+        """
+
+        q = self._clone()
+        q._joinpoint = q.mapper
+        return q
+
+
     def select_from(self, from_obj):
         """Set the `from_obj` parameter of the query and return the newly 
         resulting ``Query``.
@@ -664,7 +677,7 @@ class Query(object):
             session._register_persistent(instance)
 
         if mappers_or_columns:
-            return zip(*([result] + [o[1] for o in process]))
+            return list(util.OrderedSet(zip(*([result] + [o[1] for o in process]))))
         else:
             return result.data
 
@@ -684,7 +697,10 @@ class Query(object):
         params = {}
         
         for i, primary_key in enumerate(self.primary_key_columns):
-            params[primary_key._label] = ident[i]
+            try:
+                params[primary_key._label] = ident[i]
+            except IndexError:
+                raise exceptions.InvalidRequestError("Could not find enough values to formulate primary key for query.get(); primary key columns are %s" % ', '.join(["'%s'" % str(c) for c in self.primary_key_columns]))
         try:
             q = self
             if lockmode is not None:
@@ -695,7 +711,6 @@ class Query(object):
         except IndexError:
             return None
 
-
     def _should_nest(self, querycontext):
         """Return True if the given statement options indicate that we
         should *nest* the generated query as a subquery inside of a
index 17d8feabb14401f0e2dace67fe08d97ce45d3fee..cd7dcc04ee4082ed206573936bc348d5923e9fde 100644 (file)
@@ -274,15 +274,21 @@ class Session(object):
                 raise exceptions.InvalidRequestError("Could not locate any Engine or Connection bound to mapper '%s'" % str(mapper))
             return e
 
-    def query(self, mapper_or_class, entity_name=None, **kwargs):
+    def query(self, mapper_or_class, *addtl_entities, **kwargs):
         """Return a new ``Query`` object corresponding to this ``Session`` and
         the mapper, or the classes' primary mapper.
         """
-
+        
+        entity_name = kwargs.pop('entity_name', None)
+        
         if isinstance(mapper_or_class, type):
-            return query.Query(_class_mapper(mapper_or_class, entity_name=entity_name), self, **kwargs)
+            q = query.Query(_class_mapper(mapper_or_class, entity_name=entity_name), self, **kwargs)
         else:
-            return query.Query(mapper_or_class, self, **kwargs)
+            q = query.Query(mapper_or_class, self, **kwargs)
+            
+        for ent in addtl_entities:
+            q = q.add_entity(ent)
+        return q
 
     def _sql(self):
         class SQLProxy(object):
index c5eeda9c935af0b838c1df6d761e09194a4c4b79..38c1cb13f78b10174c09f6333fbb9d193f3b2208 100644 (file)
@@ -1445,6 +1445,13 @@ class ColumnCollection(util.OrderedProperties):
         The key attribute of the column will be used as the hash key
         for this dictionary.
         """
+
+        # Allow an aliased column to replace an unaliased column of the
+        # same name.
+        if self.has_key(column.name):
+            other = self[column.name]
+            if other.name == other.key:
+                del self[other.name]
         self[column.key] = column
     
     def remove(self, column):
index 9a8e418a7d645694fc85c79d35192b776abb7b3d..ddaf990e7f5f737bc225e863d1891b1b3902533a 100644 (file)
@@ -357,31 +357,33 @@ class Interval(TypeDecorator):
         will be stored as DateTime = '2nd Jan 1970 00:00', see convert_bind_param
         and convert_result_value to actual conversion code
     """
-    impl = None
-    
-    def __init__(self,*args,**kwargs):
-        #avoid of getting instance of None type in __init__ of TypeDecorator
-        pass
+    #Empty useless type, because at the moment of creation of instance we don't
+    #know what type will be decorated - it depends on used dialect.
+    impl = TypeEngine
 
     def load_dialect_impl(self, dialect):
-        import sqlalchemy.databases.postgres as pg
         """Checks if engine has native implementation of timedelta python type,
         if so it returns right class to handle it, if there is no native support, 
         it fallback to engine's DateTime implementation class
         """
-        
+        if not hasattr(self,'__supported'):
+            import sqlalchemy.databases.postgres as pg
+            self.__supported = {pg.PGDialect:pg.PGInterval}
+            del pg
+            
         if self.__hasNativeImpl(dialect):
             #For now, only PostgreSQL has native timedelta types support
-            return pg.PGInterval()
+            return self.__supported[dialect.__class__]()
         else:
             #All others should fallback to DateTime
             return dialect.type_descriptor(DateTime)
         
     def __hasNativeImpl(self,dialect):
-        import sqlalchemy.databases.postgres as pg
-        return dialect.__class__ in [pg.PGDialect]
+        return dialect.__class__ in self.__supported
             
     def convert_bind_param(self, value, dialect):
+        if value is None:
+            return None
         if not self.__hasNativeImpl(dialect):
             tmpval = dt.datetime.utcfromtimestamp(0) + value
             return self.impl.convert_bind_param(tmpval,dialect)
@@ -389,15 +391,13 @@ class Interval(TypeDecorator):
             return self.impl.convert_bind_param(value,dialect)
 
     def convert_result_value(self, value, dialect):
+        if value is None:
+            return None
         retval = self.impl.convert_result_value(value,dialect)
         if not self.__hasNativeImpl(dialect):
             return retval - dt.datetime.utcfromtimestamp(0)
         else:
             return retval
-    
-    def is_mutable(self):
-        #neither datetime, nor PGInterval are mutable types
-        return False
 
 class FLOAT(Float):pass
 class TEXT(String):pass
index 842be682d615f085e6f4a320c8acfba658a71285..9aa07fa4204ba244d3a96ff1e9c79f13fcf00635 100644 (file)
@@ -140,7 +140,24 @@ class ReflectionTest(PersistTest):
                 autoload=True)
             
             assert u3.join(a3).onclause == u3.c.id==a3.c.user_id
-            
+
+            meta4 = MetaData(testbase.db)
+            u4 = Table('users', meta4,
+                       Column('id', Integer, key='u_id', primary_key=True),
+                       autoload=True)
+            a4 = Table('addresses', meta4,
+                       Column('id', Integer, key='street', primary_key=True),
+                       Column('street', String(30), key='user_id'),
+                       Column('user_id', Integer, ForeignKey('users.u_id'),
+                              key='id'),
+                       autoload=True)
+
+            assert u4.join(a4).onclause.compare(u4.c.u_id==a4.c.id)
+            assert list(u4.primary_key) == [u4.c.u_id]
+            assert len(u4.columns) == 2
+            assert len(u4.constraints) == 1
+            assert len(a4.columns) == 3
+            assert len(a4.constraints) == 2
         finally:
             meta.drop_all()
 
index 3f61ec36913ea05f0c98793b6883d9aa19b0df4b..bcf269a8766803958bb4d77746ca19c88814a6f8 100644 (file)
@@ -263,6 +263,9 @@ class DistinctPKTest(testbase.ORMTest):
 
         class Employee(Person): pass
 
+        import warnings
+        warnings.filterwarnings("error", r".*On mapper.*distinct primary key")
+
     def insert_data(self):
         person_insert = person_table.insert()
         person_insert.execute(id=1, name='alice')
index 43ba564e7c6cac2774e883c7b0004f5e942b3a4e..57d533a91c491077d122638561f7e3e9a7f00cbb 100644 (file)
@@ -131,6 +131,13 @@ class FilterTest(QueryTest):
     def test_basic(self):
         assert [User(id=7), User(id=8), User(id=9),User(id=10)] == create_session().query(User).all()
 
+    def test_limit(self):
+        assert [User(id=8), User(id=9)] == create_session().query(User).limit(2).offset(1).all()
+
+        assert [User(id=8), User(id=9)] == list(create_session().query(User)[1:3])
+
+        assert User(id=8) == create_session().query(User)[1]
+        
     def test_onefilter(self):
         assert [User(id=8), User(id=9)] == create_session().query(User).filter(users.c.name.endswith('ed')).all()
 
@@ -140,6 +147,27 @@ class FilterTest(QueryTest):
             assert False
         except exceptions.ArgumentError, e:
             assert str(e) == "filter() argument must be of type sqlalchemy.sql.ClauseElement or string"
+
+class CountTest(QueryTest):
+    def test_basic(self):
+        assert 4 == create_session().query(User).count()
+
+        assert 2 == create_session().query(User).filter(users.c.name.endswith('ed')).count()
+
+class TextTest(QueryTest):
+    def test_fulltext(self):
+        assert [User(id=7), User(id=8), User(id=9),User(id=10)] == create_session().query(User).from_statement("select * from users").all()
+
+    def test_fragment(self):
+        assert [User(id=8), User(id=9)] == create_session().query(User).filter("id in (8, 9)").all()
+
+        assert [User(id=9)] == create_session().query(User).filter("name='fred'").filter("id=9").all()
+
+        assert [User(id=9)] == create_session().query(User).filter("name='fred'").filter(users.c.id==9).all()
+
+    def test_binds(self):
+        assert [User(id=8), User(id=9)] == create_session().query(User).filter("id in (:id1, :id2)").params(id1=8, id2=9).all()
+        
         
 class ParentTest(QueryTest):
     def test_o2m(self):
@@ -184,6 +212,7 @@ class ParentTest(QueryTest):
         
     
 class JoinTest(QueryTest):
+
     def test_overlapping_paths(self):
         # load a user who has an order that contains item id 3 and address id 1 (order 3, owned by jack)
         result = create_session().query(User).join(['orders', 'items']).filter_by(id=3).join(['orders','address']).filter_by(id=1).all()
@@ -192,6 +221,14 @@ class JoinTest(QueryTest):
     def test_overlapping_paths_outerjoin(self):
         result = create_session().query(User).outerjoin(['orders', 'items']).filter_by(id=3).outerjoin(['orders','address']).filter_by(id=1).all()
         assert [User(id=7, name='jack')] == result
+
+    def test_reset_joinpoint(self):
+        # load a user who has an order that contains item id 3 and address id 1 (order 3, owned by jack)
+        result = create_session().query(User).join(['orders', 'items']).filter_by(id=3).reset_joinpoint().join(['orders','address']).filter_by(id=1).all()
+        assert [User(id=7, name='jack')] == result
+
+        result = create_session().query(User).outerjoin(['orders', 'items']).filter_by(id=3).reset_joinpoint().outerjoin(['orders','address']).filter_by(id=1).all()
+        assert [User(id=7, name='jack')] == result
     
     def test_overlap_with_aliases(self):
         oalias = orders.alias('oalias')
@@ -262,14 +299,25 @@ class InstancesTest(QueryTest):
             assert fixtures.user_address_result == l
         self.assert_sql_count(testbase.db, go, 1)
 
+
+        def go():
+            l = q.options(contains_alias('ulist'), contains_eager('addresses')).from_statement(query).all()
+            assert fixtures.user_address_result == l
+        self.assert_sql_count(testbase.db, go, 1)
+
     def test_contains_eager(self):
 
-        selectquery = users.outerjoin(addresses).select(use_labels=True, order_by=[users.c.id, addresses.c.id])
+        selectquery = users.outerjoin(addresses).select(users.c.id<10, use_labels=True, order_by=[users.c.id, addresses.c.id])
         q = create_session().query(User)
 
         def go():
             l = q.options(contains_eager('addresses')).instances(selectquery.execute())
-            assert fixtures.user_address_result == l
+            assert fixtures.user_address_result[0:3] == l
+        self.assert_sql_count(testbase.db, go, 1)
+
+        def go():
+            l = q.options(contains_eager('addresses')).from_statement(selectquery).all()
+            assert fixtures.user_address_result[0:3] == l
         self.assert_sql_count(testbase.db, go, 1)
 
     def test_contains_eager_alias(self):
@@ -328,6 +376,12 @@ class InstancesTest(QueryTest):
         q = sess.query(User).add_entity(Address)
         l = q.join('addresses').filter_by(email_address='ed@bettyboop.com').all()
         assert l == [(user8, address3)]
+        
+        q = sess.query(User, Address).join('addresses').filter_by(email_address='ed@bettyboop.com')
+        assert q.all() == [(user8, address3)]
+
+        q = sess.query(User, Address).join('addresses').options(eagerload('addresses')).filter_by(email_address='ed@bettyboop.com')
+        assert q.all() == [(user8, address3)]
 
     def test_multi_columns(self):
         sess = create_session()
@@ -345,7 +399,7 @@ class InstancesTest(QueryTest):
 
         s = select([users, func.count(addresses.c.id).label('count')]).select_from(users.outerjoin(addresses)).group_by(*[c for c in users.c]).order_by(users.c.id)
         q = sess.query(User)
-        l = q.instances(s.execute(), "count")
+        l = q.add_column("count").from_statement(s).all()
         assert l == expected
 
     @testbase.unsupported('mysql') # only because of "+" operator requiring "concat" in mysql (fix #475)
@@ -360,8 +414,14 @@ class InstancesTest(QueryTest):
 
         s = select([users, func.count(addresses.c.id).label('count'), ("Name:" + users.c.name).label('concat')], from_obj=[users.outerjoin(addresses)], group_by=[c for c in users.c], order_by=[users.c.id])
         q = create_session().query(User)
-        l = q.instances(s.execute(), "count", "concat")
+        l = q.add_column("count").add_column("concat").from_statement(s).all()
         assert l == expected
+        
+        q = create_session().query(User).add_column(func.count(addresses.c.id))\
+            .add_column(("Name:" + users.c.name)).select_from(users.outerjoin(addresses))\
+            .group_by([c for c in users.c]).order_by(users.c.id)
+            
+        assert q.all() == expected
 
 class FilterByTest(QueryTest):
     def test_aliased(self):
index 24fbde3a259ceb9a8552df59ac82232615925de7..59b9da388406f098836b3e451d6cc36fe2890e60 100644 (file)
@@ -68,7 +68,8 @@ class AdaptTest(PersistTest):
         col = Column('', MyDecoratedType)
         dialect_type = col.type.dialect_impl(dialect)
         assert isinstance(dialect_type.impl, oracle.OracleText), repr(dialect_type.impl)
-    
+
+
     def testoracletimestamp(self):
         dialect = oracle.OracleDialect()
         t1 = oracle.OracleTimestamp
@@ -209,6 +210,7 @@ class UnicodeTest(AssertMixin):
         finally:
             db.engine.dialect.convert_unicode = prev_unicode
 
+    @testbase.unsupported('oracle')
     def testlength(self):
         """checks the database correctly understands the length of a unicode string"""
         teststr = u'aaa\x1234'
@@ -355,11 +357,14 @@ class IntervalTest(AssertMixin):
         global interval_table, metadata
         metadata = MetaData(testbase.db)
         interval_table = Table("intervaltable", metadata, 
-            Column("id", Integer, primary_key=True),
+            Column("id", Integer, Sequence('interval_id_seq', optional=True), primary_key=True),
             Column("interval", Interval),
             )
         metadata.create_all()
-        
+    
+    def tearDown(self):
+        interval_table.delete().execute()
+            
     def tearDownAll(self):
         metadata.drop_all()
         
@@ -367,6 +372,10 @@ class IntervalTest(AssertMixin):
         delta = datetime.datetime(2006, 10, 5) - datetime.datetime(2005, 8, 17)
         interval_table.insert().execute(interval=delta)
         assert interval_table.select().execute().fetchone()['interval'] == delta
+
+    def test_null(self):
+        interval_table.insert().execute(id=1, inverval=None)
+        assert interval_table.select().execute().fetchone()['interval'] is None
         
 class BooleanTest(AssertMixin):
     def setUpAll(self):
index d1e901a2e55bace3b65f29a5abc24b26d84bec34..bd1f2cfa98fe4e2313752c52dca43217e0656fe9 100644 (file)
@@ -10,9 +10,6 @@ import sqlalchemy
 from sqlalchemy import sql, schema, engine, pool, MetaData
 from sqlalchemy.orm import clear_mappers
 
-import warnings
-warnings.filterwarnings("error")
-
 db = None
 metadata = None
 db_uri = None