]> git.ipfire.org Git - thirdparty/sqlalchemy/sqlalchemy.git/commitdiff
- doc updates. generated sql docs are against sql.expression now.
authorMike Bayer <mike_mp@zzzcomputing.com>
Fri, 28 Sep 2007 16:37:52 +0000 (16:37 +0000)
committerMike Bayer <mike_mp@zzzcomputing.com>
Fri, 28 Sep 2007 16:37:52 +0000 (16:37 +0000)
- added SessionExtension docs.
- removed old sqlconstruction doc.
- changed 'copy_collections' flag in Select to '_copy_collections'; its
not really "public".

doc/build/content/intro.txt
doc/build/content/session.txt
doc/build/content/sqlconstruction.txt [deleted file]
doc/build/content/sqlexpression.txt
doc/build/gen_docstrings.py
lib/sqlalchemy/orm/interfaces.py
lib/sqlalchemy/sql/expression.py

index 9ac8f94862e8166326861dfa20a1028be5faa473..9150f1d4ba53ffbee25549921bd0640f43e96b84 100644 (file)
@@ -12,7 +12,7 @@ The SQLAlchemy SQL Toolkit and Object Relational Mapper is a comprehensive set o
                +-----------------------------------------------------------+
                +---------+ +------------------------------------+ +--------+
                |         | |       SQL Expression Language      | |        |
-               |         | |        [[tutorial]](rel:sql)  [[docs]](rel:docstrings_sqlalchemy.sql)          | |        |
+               |         | |        [[tutorial]](rel:sql)  [[docs]](rel:docstrings_sqlalchemy.sql.expression)          | |        |
                |         | +------------------------------------+ |        |
                |         +-----------------------+ +--------------+        |
                |        Dialect/Execution        | |    Schema Management  |
index 4d92e2f487cb0b1e4224fc7415bc892ab5b51b1e..e25195419190cff5a8f151d6cb5ab35523b9682e 100644 (file)
@@ -738,3 +738,23 @@ Vertical partitioning places different kinds of objects, or different tables, ac
 Horizontal partitioning partitions the rows of a single table (or a set of tables) across multiple databases.
 
 See the "sharding" example in [attribute_shard.py](http://www.sqlalchemy.org/trac/browser/sqlalchemy/trunk/examples/sharding/attribute_shard.py)
+
+## Extending Session
+
+Extending the session can be achieved through subclassing as well as through a simple extension class, which resembles the style of [advdatamapping_mapper_extending](rel:advdatamapping_mapper_extending) called [SessionExtension](rel:docstrings_sqlalchemy.orm.session_SessionExtension).  See the docstrings for more information on this class' methods.
+
+Basic usage is similar to `MapperExtension`:
+
+    {python}
+    class MySessionExtension(SessionExtension):
+        def before_commit(self, session):
+            print "before commit!"
+            
+    Session = sessionmaker(extension=MySessionExtension())
+    
+or with `create_session()`:
+
+    {python}
+    sess = create_session(extension=MySessionExtension())
+    
+The same `SessionExtension` instance can be used with any number of sessions.
\ No newline at end of file
diff --git a/doc/build/content/sqlconstruction.txt b/doc/build/content/sqlconstruction.txt
deleted file mode 100644 (file)
index 5adc00a..0000000
+++ /dev/null
@@ -1,957 +0,0 @@
-SQL Expression Language Tutorial {@name=sql}
-===============================================
-
-**TUTORIAL FORMAT IS TODO**
-
-For this section, we will mostly use the implcit style of execution, meaning the `Table` objects are associated with a bound instance of `MetaData`, and constructed `ClauseElement` objects support self-execution.  Assume the following configuration:
-
-    {python}
-    from sqlalchemy import *
-    metadata = MetaData('sqlite:///mydb.db', echo=True)
-    
-    # a table to store users
-    users = Table('users', metadata,
-        Column('user_id', Integer, primary_key = True),
-        Column('user_name', String(40)),
-        Column('password', String(80))
-    )
-    
-    # a table that stores mailing addresses associated with a specific user
-    addresses = Table('addresses', metadata,
-        Column('address_id', Integer, primary_key = True),
-        Column('user_id', Integer, ForeignKey("users.user_id")),
-        Column('street', String(100)),
-        Column('city', String(80)),
-        Column('state', String(2)),
-        Column('zip', String(10))
-    )
-    
-    # a table that stores keywords
-    keywords = Table('keywords', metadata,
-        Column('keyword_id', Integer, primary_key = True),
-        Column('name', VARCHAR(50))
-    )
-    
-    # a table that associates keywords with users
-    userkeywords = Table('userkeywords', metadata,
-        Column('user_id', INT, ForeignKey("users")),
-        Column('keyword_id', INT, ForeignKey("keywords"))
-    )
-
-### Simple Select {@name=select}
-
-A select is done by constructing a `Select` object with the proper arguments  [[api](rel:docstrings_sqlalchemy.sql_modfunc_select)], adding any extra arguments if desired, then calling its `execute()` method.
-
-    {python title="Basic Select"}
-    from sqlalchemy import *
-    
-    # use the select() function defined in the sql package
-    s = select([users])
-    
-    # or, call the select() method off of a Table object
-    s = users.select()
-    
-    # then, call execute on the Select object:
-    {sql}result = s.execute() 
-    SELECT users.user_id, users.user_name, users.password FROM users
-    {}
-    
-    # the SQL text of any clause object can also be viewed via the str() call:
-    >>> str(s)
-    SELECT users.user_id, users.user_name, users.password FROM users
-
-#### Explicit Execution {@name=explicit}
-
-As mentioned above, `ClauseElement` structures can also be executed with a `Connection` object explicitly:
-
-    {python}
-    engine = create_engine('sqlite:///myfile.db')
-    conn = engine.connect()
-    
-    {sql}result = conn.execute(users.select())
-    SELECT users.user_id, users.user_name, users.password FROM users
-    {}
-    
-    conn.close()
-
-#### Binding ClauseElements to Engines {@name=binding}
-
-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"], bind=myengine).execute()
-    SELECT current_time
-    {}
-    
-    # select a function
-    {sql}select([func.now()], bind=db).execute()
-    SELECT now()
-    {}
-
-#### Getting Results {@name=resultproxy}
-
-The object returned by `execute()` is a `sqlalchemy.engine.ResultProxy` object, which acts much like a DBAPI `cursor` object in the context of a result set, except that the rows returned can address their columns by ordinal position, column name, or even column object:
-
-    {python title="Using the ResultProxy"}
-    # select rows, get resulting ResultProxy object
-    {sql}result = users.select().execute()  
-    SELECT users.user_id, users.user_name, users.password FROM users
-    {}
-    
-    # get one row
-    row = result.fetchone()
-    
-    # get the 'user_id' column via integer index:
-    user_id = row[0]
-    
-    # or column name
-    user_name = row['user_name']
-    
-    # or column object
-    password = row[users.c.password]
-    
-    # or column accessor
-    password = row.password
-    
-    # ResultProxy object also supports fetchall()
-    rows = result.fetchall()
-    
-    # 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 
-    # the connection pool.  de-referencing the result
-    # will also have the same effect.  if an explicit Connection was 
-    # used, then close() just closes the underlying cursor object.
-    result.close()
-
-#### Using Column Labels {@name=labels}
-
-A common need when writing statements that reference multiple tables is to create labels for columns, thereby separating columns from different tables with the same name.  The Select construct supports automatic generation of column labels via the `use_labels=True` parameter:
-
-    {python title="use_labels Flag"}
-    {sql}c = select([users, addresses], 
-    users.c.user_id==addresses.c.address_id, 
-    use_labels=True).execute()  
-    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, 
-    addresses.city AS addresses_city, addresses.state AS addresses_state, 
-    addresses.zip AS addresses_zip
-    FROM users, addresses
-    WHERE users.user_id = addresses.address_id
-    {}
-
-The table name part of the label is affected if you use a construct such as a table alias:
-
-    {python title="use_labels with an Alias"}
-    person = users.alias('person')
-    {sql}c = select([person, addresses], 
-        person.c.user_id==addresses.c.address_id, 
-        use_labels=True).execute()  
-    SELECT person.user_id AS person_user_id, person.user_name AS person_user_name, 
-    person.password AS person_password, 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 users AS person, addresses
-    WHERE person.user_id = addresses.address_id
-
-Labels are also generated in such a way as to never go beyond 30 characters.  Most databases support a limit on the length of symbols, such as Postgres, and particularly Oracle which has a rather short limit of 30:
-
-    {python title="use_labels Generates Abbreviated Labels"}
-    long_named_table = users.alias('this_is_the_person_table')
-    {sql}c = select([long_named_table], use_labels=True).execute()  
-    SELECT this_is_the_person_table.user_id AS this_is_the_person_table_b36c, 
-    this_is_the_person_table.user_name AS this_is_the_person_table_f76a, 
-    this_is_the_person_table.password AS this_is_the_person_table_1e7c
-    FROM users AS this_is_the_person_table
-    {}
-    
-You can also specify custom labels on a per-column basis using the `label()` function:
-
-    {python title="label() Function on Column"}
-    {sql}c = select([users.c.user_id.label('id'), 
-               users.c.user_name.label('name')]).execute()  
-    SELECT users.user_id AS id, users.user_name AS name
-    FROM users
-    {}
-
-#### Table/Column Specification {@name=columns}
-
-Calling `select` off a table automatically generates a column clause which includes all the table's columns, in the order they are specified in the source Table object.
-
-But in addition to selecting all the columns off a single table, any set of columns can be specified, as well as full tables, and any combination of the two:
-
-    {python title="Specify Columns to Select"}
-    # individual columns
-    {sql}c = select([users.c.user_id, users.c.user_name]).execute()  
-    SELECT users.user_id, users.user_name FROM users
-    {}
-    
-    # full tables
-    {sql}c = select([users, addresses]).execute()  
-    SELECT users.user_id, users.user_name, users.password, 
-    addresses.address_id, addresses.user_id, 
-    addresses.street, addresses.city, addresses.state, addresses.zip
-    FROM users, addresses
-    {}
-    
-    # combinations
-    {sql}c = select([users, addresses.c.zip]).execute()  
-    SELECT users.user_id, users.user_name, users.password, 
-    addresses.zip FROM users, addresses
-    {}
-
-### 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:
-
-    {python title="Basic WHERE Clause"}
-    {sql}c = users.select(users.c.user_id == 7).execute()  
-    SELECT users.user_id, users.user_name, users.password, 
-    FROM users WHERE users.user_id = :users_user_id
-    {'users_user_id': 7}                
-
-Notice that the literal value "7" was broken out of the query and placed into a bind parameter.  Databases such as Oracle must parse incoming SQL and create a "plan" when new queries are received, which is an expensive process.  By using bind parameters, the same query with various literal values can have its plan compiled only once, and used repeatedly with less overhead.
-            
-More where clauses:
-
-    {python}
-    # another comparison operator
-    {sql}c = select([users], users.c.user_id>7).execute() 
-    SELECT users.user_id, users.user_name, users.password, 
-    FROM users WHERE users.user_id > :users_user_id
-    {'users_user_id': 7}
-    
-    # OR keyword
-    {sql}c = users.select(or_(users.c.user_name=='jack', users.c.user_name=='ed')).execute()  
-    SELECT users.user_id, users.user_name, users.password 
-    FROM users WHERE users.user_name = :users_user_name 
-    OR users.user_name = :users_user_name_1
-    {'users_user_name_1': 'ed', 'users_user_name': 'jack'}
-    
-    # AND keyword
-    {sql}c = users.select(and_(users.c.user_name=='jack', users.c.password=='dog')).execute()  
-    SELECT users.user_id, users.user_name, users.password
-    FROM users WHERE users.user_name = :users_user_name 
-    AND users.password = :users_password
-    {'users_user_name': 'jack', 'users_password': 'dog'}
-    
-    # NOT keyword
-    {sql}c = users.select(not_(
-            or_(users.c.user_name=='jack', users.c.password=='dog')
-        )).execute()  
-    SELECT users.user_id, users.user_name, users.password 
-    FROM users 
-    WHERE NOT (users.user_name = :users_user_name 
-        OR users.password = :users_password)
-    {'users_user_name': 'jack', 'users_password': 'dog'}
-                
-    # IN clause
-    {sql}c = users.select(users.c.user_name.in_('jack', 'ed', 'fred')).execute()  
-    SELECT users.user_id, users.user_name, users.password
-    FROM users WHERE users.user_name 
-    IN (:users_user_name, :users_user_name_1, :users_user_name_2)
-    {'users_user_name': 'jack', 'users_user_name_1': 'ed', 
-    'users_user_name_2': 'fred'}
-
-                
-    # join users and addresses together
-    {sql}c = select([users, addresses], users.c.user_id==addresses.c.address_id).execute()  
-    SELECT users.user_id, users.user_name, users.password,  
-    addresses.address_id, addresses.user_id, addresses.street, addresses.city, 
-    addresses.state, addresses.zip
-    FROM users, addresses
-    WHERE users.user_id = addresses.address_id
-    {}
-
-                
-    # join users and addresses together, but dont specify "addresses" in the 
-    # selection criterion.  The WHERE criterion adds it to the FROM list 
-    # automatically.
-    {sql}c = select([users], and_(
-                    users.c.user_id==addresses.c.user_id,
-                    users.c.user_name=='fred'
-                )).execute()
-    SELECT users.user_id, users.user_name, users.password
-    FROM users, addresses WHERE users.user_id = addresses.user_id 
-    AND users.user_name = :users_user_name
-    {'users_user_name': 'fred'}                
-            
-
-Select statements can also generate a WHERE clause based on the parameters you give it.  If a given parameter, which matches the name of a column or its "label" (the combined tablename + "_" + column name), and does not already correspond to a bind parameter in the select object, it will be added as a comparison against that column.  This is a shortcut to creating a full WHERE clause:
-
-    {python}
-    # specify a match for the "user_name" column
-    {sql}c = users.select().execute(user_name='ed')
-    SELECT users.user_id, users.user_name, users.password
-    FROM users WHERE users.user_name = :users_user_name
-    {'users_user_name': 'ed'}
-
-    # specify a full where clause for the "user_name" column, as well as a
-    # comparison for the "user_id" column
-    {sql}c = users.select(users.c.user_name=='ed').execute(user_id=10)
-    SELECT users.user_id, users.user_name, users.password
-    FROM users WHERE users.user_name = :users_user_name AND users.user_id = :users_user_id
-    {'users_user_name': 'ed', 'users_user_id': 10}
-
-#### Operators {@name=operators}
-
-Supported column operators so far are all the numerical comparison operators, i.e. '==', '>', '>=', etc., as well as `like()`, `startswith()`, `endswith()`, `between()`, and `in()`.  Boolean operators include `not_()`, `and_()` and `or_()`, which also can be used inline via '~', '&amp;', and '|'.  Math operators are '+', '-', '*', '/'.  Any custom operator can be specified via the `op()` function shown below.
-    {python}
-    # "like" operator
-    users.select(users.c.user_name.like('%ter'))
-    
-    # equality operator
-    users.select(users.c.user_name == 'jane')
-    
-    # in opertator
-    users.select(users.c.user_id.in_(1,2,3))
-    
-    # and_, endswith, equality operators
-    users.select(and_(addresses.c.street.endswith('green street'),
-                    addresses.c.zip=='11234'))
-    
-    # & operator subsituting for 'and_'
-    users.select(addresses.c.street.endswith('green street') & (addresses.c.zip=='11234'))
-    
-    # + concatenation operator
-    select([users.c.user_name + '_name'])
-    
-    # NOT operator
-    users.select(~(addresses.c.street == 'Green Street'))
-    
-    # any custom operator
-    select([users.c.user_name.op('||')('_category')])
-
-    # "null" comparison via == (converts to IS)
-    {sql}users.select(users.c.user_name==None).execute()
-    SELECT users.user_id, users.user_name, users.password 
-    FROM users 
-    WHERE users.user_name IS NULL
-
-    # or via explicit null() construct
-    {sql}users.select(users.c.user_name==null()).execute()
-    SELECT users.user_id, users.user_name, users.password 
-    FROM users 
-    WHERE users.user_name IS NULL
-    
-#### Functions {@name=functions}
-
-Functions can be specified using the `func` keyword:
-
-    {python}
-    {sql}select([func.count(users.c.user_id)]).execute()
-    SELECT count(users.user_id) FROM users
-    
-    {sql}users.select(func.substr(users.c.user_name, 1) == 'J').execute()
-    SELECT users.user_id, users.user_name, users.password FROM users 
-    WHERE substr(users.user_name, :substr) = :substr_1
-    {'substr_1': 'J', 'substr': 1}
-
-Functions also are callable as standalone values:
-
-    {python}
-    # call the "now()" function
-    time = func.now(bind=myengine).scalar()
-
-    # call myfunc(1,2,3)
-    myvalue = func.myfunc(1, 2, 3, bind=db).execute()
-
-    # or call them off the engine
-    db.func.now().scalar()
-
-#### Literals {@name=literals}
-
-You can drop in a literal value anywhere there isnt a column to attach to via the `literal` keyword:
-
-    {python}
-    {sql}select([literal('foo') + literal('bar'), users.c.user_name]).execute()
-    SELECT :literal + :literal_1, users.user_name 
-    FROM users
-    {'literal_1': 'bar', 'literal': 'foo'}
-
-    # literals have all the same comparison functions as columns
-    {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'], bind=e).execute()
-    SELECT ? || ?
-    ['foo', 'bar']
-
-#### Order By {@name=orderby}
-
-The ORDER BY clause of a select statement can be specified as individual columns to order by within an array specified via the `order_by` parameter, and optional usage of the asc() and desc() functions:
-
-    {python}
-    # straight order by
-    {sql}c = users.select(order_by=[users.c.user_name]).execute() 
-    SELECT users.user_id, users.user_name, users.password
-    FROM users ORDER BY users.user_name                
-
-    # descending/ascending order by on multiple columns
-    {sql}c = users.select(
-        users.c.user_name>'J', 
-        order_by=[desc(users.c.user_id), asc(users.c.user_name)]).execute() 
-    SELECT users.user_id, users.user_name, users.password
-    FROM users WHERE users.user_name > :users_user_name 
-    ORDER BY users.user_id DESC, users.user_name ASC
-    {'users_user_name':'J'}
-
-#### DISTINCT, LIMIT and OFFSET {@name=options}
-
-These are specified as keyword arguments:
-
-    {python}
-    {sql}c = select([users.c.user_name], distinct=True).execute()
-    SELECT DISTINCT users.user_name FROM users
-
-    {sql}c = users.select(limit=10, offset=20).execute()
-    SELECT users.user_id, users.user_name, users.password FROM users LIMIT 10 OFFSET 20
-        
-The Oracle driver does not support LIMIT and OFFSET directly, but instead wraps the generated query into a subquery and uses the "rownum" variable to control the rows selected (this is somewhat experimental).  Similarly, the Firebird and MSSQL drivers convert LIMIT into queries using FIRST and TOP, respectively.
-
-### Inner and Outer Joins {@name=join}
-
-As some of the examples indicated above, a regular inner join can be implicitly stated, just like in a SQL expression, by just specifying the tables to be joined as well as their join conditions:
-
-    {python}
-    {sql}addresses.select(addresses.c.user_id==users.c.user_id).execute() 
-    SELECT addresses.address_id, addresses.user_id, addresses.street, 
-    addresses.city, addresses.state, addresses.zip FROM addresses, users
-    WHERE addresses.user_id = users.user_id
-    {}                   
-
-There is also an explicit join constructor, which can be embedded into a select query via the `from_obj` parameter of the select statement:
-
-    {python}
-    {sql}addresses.select(from_obj=[
-        addresses.join(users, addresses.c.user_id==users.c.user_id)
-    ]).execute() 
-    SELECT addresses.address_id, addresses.user_id, addresses.street, addresses.city, 
-    addresses.state, addresses.zip 
-    FROM addresses JOIN users ON addresses.user_id = users.user_id
-    {}                
-
-The join constructor can also be used by itself:
-
-    {python}
-    {sql}join(users, addresses, users.c.user_id==addresses.c.user_id).select().execute()
-    SELECT users.user_id, users.user_name, users.password, 
-    addresses.address_id, addresses.user_id, addresses.street, addresses.city, 
-    addresses.state, addresses.zip 
-    FROM addresses JOIN users ON addresses.user_id = users.user_id
-    {}                
-
-The join criterion in a join() call is optional.  If not specified, the condition will be derived from the foreign key relationships of the two tables.  If no criterion can be constructed, an exception will be raised.
-
-    {python}
-    {sql}join(users, addresses).select().execute()
-    SELECT users.user_id, users.user_name, users.password, 
-    addresses.address_id, addresses.user_id, addresses.street, addresses.city, 
-    addresses.state, addresses.zip 
-    FROM addresses JOIN users ON addresses.user_id = users.user_id
-    {}                
-
-Notice that this is the first example where the FROM criterion of the select statement is explicitly specified.  In most cases, the FROM criterion is automatically determined from the columns requested as well as the WHERE clause.  The `from_obj` keyword argument indicates a list of explicit FROM clauses to be used in the statement.
-
-A join can be created on its own using the `join` or `outerjoin` functions, or can be created off of an existing Table or other selectable unit via the `join` or `outerjoin` methods:
-        
-    {python}
-    {sql}outerjoin(users, addresses, 
-               users.c.user_id==addresses.c.address_id).select().execute()
-    SELECT users.user_id, users.user_name, users.password, addresses.address_id, 
-    addresses.user_id, addresses.street, addresses.city, addresses.state, addresses.zip
-    FROM users LEFT OUTER JOIN addresses ON users.user_id = addresses.address_id
-    {}                
-
-    {sql}users.select(keywords.c.name=='running', from_obj=[
-            users.join(
-                userkeywords, userkeywords.c.user_id==users.c.user_id).join(
-                    keywords, keywords.c.keyword_id==userkeywords.c.keyword_id)
-            ]).execute()   
-    SELECT users.user_id, users.user_name, users.password FROM users 
-    JOIN userkeywords ON userkeywords.user_id = users.user_id 
-    JOIN keywords ON keywords.keyword_id = userkeywords.keyword_id
-    WHERE keywords.name = :keywords_name
-    {'keywords_name': 'running'}                
-
-Joins also provide a keyword argument `fold_equivalents` on the `select()` function which allows the column list of the resulting select to be "folded" to the minimal list of columns, based on those columns that are known to be equivalent from the "onclause" of the join.  This saves the effort of constructing column lists manually in conjunction with databases like Postgres which can be picky about "ambiguous columns".  In this example, only the "users.user_id" column, but not the "addresses.user_id" column, shows up in the column clause of the resulting select:
-
-    {python}
-    {sql}users.join(addresses).select(fold_equivalents=True).execute()
-    SELECT users.user_id, users.user_name, users.password, addresses.address_id, 
-    addresses.street, addresses.city, addresses.state, addresses.zip
-    FROM users JOIN addresses ON users.user_id = addresses.address_id
-    {}                
-    
-The `fold_equivalents` argument will recursively apply to "chained" joins as well, i.e. `a.join(b).join(c)...`.
-
-### Table Aliases {@name=alias}
-
-Aliases are used primarily when you want to use the same table more than once as a FROM expression in a statement:
-    
-    {python}
-    address_b = addresses.alias('addressb')
-    {sql}# select users who have an address on Green street as well as Orange street
-    users.select(and_(
-        users.c.user_id==addresses.c.user_id,
-        addresses.c.street.like('%Green%'),
-        users.c.user_id==address_b.c.user_id,
-        address_b.c.street.like('%Orange%')
-    )).execute()
-    SELECT users.user_id, users.user_name, users.password
-    FROM users, addresses, addresses AS addressb
-    WHERE users.user_id = addresses.user_id 
-    AND addresses.street LIKE :addresses_street 
-    AND users.user_id = addressb.user_id 
-    AND addressb.street LIKE :addressb_street
-    {'addressb_street': '%Orange%', 'addresses_street': '%Green%'}
-
-### Subqueries {@name=subqueries}
-
-SQLAlchemy allows the creation of select statements from not just Table objects, but from a whole class of objects that implement the `Selectable` interface.  This includes Tables, Aliases, Joins and Selects.  Therefore, if you have a Select, you can select from the Select:
-    
-    {python}
-    >>> s = users.select()
-    >>> str(s)
-    SELECT users.user_id, users.user_name, users.password FROM users
-    
-    {python}
-    >>> s = s.select()
-    >>> str(s)
-    SELECT user_id, user_name, password
-    FROM (SELECT users.user_id, users.user_name, users.password FROM users)                
-
-Any Select, Join, or Alias object supports the same column accessors as a Table:
-
-    {python}
-    >>> s = users.select()
-    >>> [c.key for c in s.columns]
-    ['user_id', 'user_name', 'password']            
-
-When you use `use_labels=True` in a Select object, the label version of the column names become the keys of the accessible columns.  In effect you can create your own "view objects":
-        
-    {python}
-    s = select([users, addresses], users.c.user_id==addresses.c.user_id, use_labels=True)
-    {sql}select([
-        s.c.users_user_name, s.c.addresses_street, s.c.addresses_zip
-    ], s.c.addresses_city=='San Francisco').execute()
-    SELECT users_user_name, addresses_street, addresses_zip
-    FROM (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, 
-    addresses.city AS addresses_city, addresses.state AS addresses_state, 
-    addresses.zip AS addresses_zip
-    FROM users, addresses
-    WHERE users.user_id = addresses.user_id)
-    WHERE addresses_city = :addresses_city
-    {'addresses_city': 'San Francisco'}
-
-To specify a SELECT statement as one of the selectable units in a FROM clause, it usually should be given an alias.
-
-    {python}
-    {sql}s = users.select().alias('u')
-    select([addresses, s]).execute()
-    SELECT addresses.address_id, addresses.user_id, addresses.street, addresses.city, 
-    addresses.state, addresses.zip, u.user_id, u.user_name, u.password 
-    FROM addresses, 
-    (SELECT users.user_id, users.user_name, users.password FROM users) AS u
-    {}
-
-Select objects can be used in a WHERE condition, in operators such as IN:
-
-    {python}
-    # select user ids for all users whos name starts with a "p"
-    s = select([users.c.user_id], users.c.user_name.like('p%'))
-    
-    # now select all addresses for those users
-    {sql}addresses.select(addresses.c.user_id.in_(s)).execute()
-    SELECT addresses.address_id, addresses.user_id, addresses.street, 
-    addresses.city, addresses.state, addresses.zip
-    FROM addresses WHERE addresses.address_id IN 
-    (SELECT users.user_id FROM users WHERE users.user_name LIKE :users_user_name)
-    {'users_user_name': 'p%'}
-
-The sql package supports embedding select statements into other select statements as the criterion in a WHERE condition, or as one of the "selectable" objects in the FROM list of the query.  It does not at the moment directly support embedding a SELECT statement as one of the column criterion for a statement, although this can be achieved via direct text insertion, described later.
-        
-#### Scalar Column Subqueries {@name=scalar}
-
-Subqueries can be used in the column clause of a select statement by specifying the `scalar=True` flag:
-
-    {python}
-    {sql}select([table2.c.col1, table2.c.col2, 
-                    select([table1.c.col1], table1.c.col2==7, scalar=True)])
-    SELECT table2.col1, table2.col2, 
-    (SELECT table1.col1 AS col1 FROM table1 WHERE col2=:table1_col2) 
-    FROM table2
-    {'table1_col2': 7}
-
-### Correlated Subqueries {@name=correlated}
-
-When a select object is embedded inside of another select object, and both objects reference the same table, SQLAlchemy makes the assumption that the table should be correlated from the child query to the parent query.  To disable this behavior, specify the flag `correlate=False` to the Select statement.
-
-    {python}
-    # make an alias of a regular select.   
-    s = select([addresses.c.street], addresses.c.user_id==users.c.user_id).alias('s')
-    >>> str(s)
-    SELECT addresses.street FROM addresses, users 
-    WHERE addresses.user_id = users.user_id
-
-    # now embed that select into another one.  the "users" table is removed from
-    # the embedded query's FROM list and is instead correlated to the parent query
-    s2 = select([users, s.c.street])
-    >>> str(s2)
-    SELECT users.user_id, users.user_name, users.password, s.street
-    FROM users, (SELECT addresses.street FROM addresses
-    WHERE addresses.user_id = users.user_id) s
-
-#### EXISTS Clauses {@name=exists}
-
-An EXISTS clause can function as a higher-scaling version of an IN clause, and is usually used in a correlated fashion:
-
-    {python}
-    # find all users who have an address on Green street:
-    {sql}users.select(
-        exists(
-            [addresses.c.address_id], 
-            and_(
-                addresses.c.user_id==users.c.user_id, 
-                addresses.c.street.like('%Green%')
-            )
-        )
-    )
-    SELECT users.user_id, users.user_name, users.password 
-    FROM users WHERE EXISTS (SELECT addresses.address_id 
-    FROM addresses WHERE addresses.user_id = users.user_id 
-    AND addresses.street LIKE :addresses_street)
-    {'addresses_street': '%Green%'}
-
-### Unions {@name=unions}
-
-Unions come in two flavors, UNION and UNION ALL, which are available via module level functions or methods off a Selectable:
-
-    {python}
-    {sql}union(
-        addresses.select(addresses.c.street=='123 Green Street'),
-        addresses.select(addresses.c.street=='44 Park Ave.'),
-        addresses.select(addresses.c.street=='3 Mill Road'),
-        order_by=[addresses.c.street]
-    ).execute()
-    SELECT addresses.address_id, addresses.user_id, addresses.street, 
-    addresses.city, addresses.state, addresses.zip 
-    FROM addresses WHERE addresses.street = :addresses_street 
-    UNION 
-    SELECT addresses.address_id, addresses.user_id, addresses.street, 
-    addresses.city, addresses.state, addresses.zip 
-    FROM addresses WHERE addresses.street = :addresses_street_1 
-    UNION 
-    SELECT addresses.address_id, addresses.user_id, addresses.street, 
-    addresses.city, addresses.state, addresses.zip 
-    FROM addresses WHERE addresses.street = :addresses_street_2 
-    ORDER BY addresses.street
-    {'addresses_street_1': '44 Park Ave.', 
-    'addresses_street': '123 Green Street', 
-    'addresses_street_2': '3 Mill Road'}
-    
-    {sql}users.select(
-        users.c.user_id==7
-      ).union_all(
-          users.select(
-              users.c.user_id==9
-          ), 
-          order_by=[users.c.user_id]   # order_by is an argument to union_all()
-      ).execute() 
-    SELECT users.user_id, users.user_name, users.password 
-    FROM users WHERE users.user_id = :users_user_id 
-    UNION ALL 
-    SELECT users.user_id, users.user_name, users.password 
-    FROM users WHERE users.user_id = :users_user_id_1 
-    ORDER BY users.user_id
-    {'users_user_id_1': 9, 'users_user_id': 7}
-
-### Custom Bind Parameters {@name=bindparams}
-
-Throughout all these examples, SQLAlchemy is busy creating bind parameters wherever literal expressions occur.  You can also specify your own bind parameters with your own names, and use the same statement repeatedly.  The bind parameters, shown here in the "named" format, will be converted to the appropriate named or positional style according to the database implementation being used.
-
-    {python title="Custom Bind Params"}
-    s = users.select(users.c.user_name==bindparam('username'))
-    
-    # execute implicitly
-    {sql}s.execute(username='fred')
-    SELECT users.user_id, users.user_name, users.password 
-    FROM users WHERE users.user_name = :username
-    {'username': 'fred'}
-    
-    # execute explicitly
-    conn = engine.connect()
-    {sql}conn.execute(s, username='fred')
-    SELECT users.user_id, users.user_name, users.password 
-    FROM users WHERE users.user_name = :username
-    {'username': 'fred'}
-    
-
-`executemany()` is also available by supplying multiple dictionary arguments instead of keyword arguments to the `execute()` method of `ClauseElement` or `Connection`.  Examples can be found later in the sections on INSERT/UPDATE/DELETE.
-
-#### Precompiling a Query {@name=precompiling}
-
-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')
-    s.execute(username='jane')
-    s.execute(username='mary')
-
-### 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 Engine to be used for the query has to be explicitly specified:
-
-    {python}
-    # strings as column clauses
-    {sql}select(["user_id", "user_name"], from_obj=[users]).execute()
-    SELECT user_id, user_name FROM users
-    {}
-    
-    # strings for full column lists
-    {sql}select(
-            ["user_id, user_name, password, addresses.*"], 
-            from_obj=[users.alias('u'), addresses]).execute()
-    SELECT u.user_id, u.user_name, u.password, addresses.* 
-    FROM users AS u, addresses
-    {}
-    
-    # functions, etc.
-    {sql}select([users.c.user_id, "process_string(user_name)"]).execute()
-    SELECT users.user_id, process_string(user_name) FROM users
-    {}
-    
-    # where clauses
-    {sql}users.select(and_(users.c.user_id==7, "process_string(user_name)=27")).execute()
-    SELECT users.user_id, users.user_name, users.password FROM users 
-    WHERE users.user_id = :users_user_id AND process_string(user_name)=27
-    {'users_user_id': 7}
-    
-    # subqueries
-    {sql}users.select(
-        "exists (select 1 from addresses where addresses.user_id=users.user_id)").execute()
-    SELECT users.user_id, users.user_name, users.password FROM users 
-    WHERE exists (select 1 from addresses where addresses.user_id=users.user_id)
-    {}
-    
-    # custom FROM objects
-    {sql}select(
-            ["*"], 
-            from_obj=["(select user_id, user_name from users)"], 
-            bind=db).execute()
-    SELECT * FROM (select user_id, user_name from users)
-    {}
-    
-    # a full query
-    {sql}text("select user_name from users", bind=db).execute()
-    SELECT user_name FROM users
-    {}
-    
-
-#### Using Bind Parameters in Text Blocks {@name=textual_binds}
-
-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 = 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 = 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 = text("select foo from mytable where lala=:hoho", 
-            bindparams=[bindparam('hoho', type=types.String)],
-            typemap={'foo':types.DateTime}, bind=engine
-            )
-    r = t.execute(hoho="im hoho")
-    
-    # 'foo' is a datetime
-    year = r.fetchone()['foo'].year
-
-### Building Select Objects {@name=building}
-
-One of the primary motivations for a programmatic SQL library is to allow the piecemeal construction of a SQL statement based on program variables.  All the above examples typically show Select objects being created all at once.  The Select object also includes "builder" methods to allow building up an object.  The below example is a "user search" function, where users can be selected based on primary key, user name, street address, keywords, or any combination:
-
-    {python}
-    def find_users(id=None, name=None, street=None, keywords=None):
-        statement = users.select()
-        if id is not None:
-            statement.append_whereclause(users.c.user_id==id)
-        if name is not None:
-            statement.append_whereclause(users.c.user_name==name)
-        if street is not None:
-            # append_whereclause joins "WHERE" conditions together with AND
-            statement.append_whereclause(users.c.user_id==addresses.c.user_id)
-            statement.append_whereclause(addresses.c.street==street)
-        if keywords is not None:
-            statement.append_from(
-                    users.join(userkeywords, users.c.user_id==userkeywords.c.user_id).join(
-                            keywords, userkeywords.c.keyword_id==keywords.c.keyword_id))
-            statement.append_whereclause(keywords.c.name.in_(keywords))
-            # to avoid multiple repeats, set query to be DISTINCT:
-            statement.distinct=True
-        return statement.execute()
-    
-    {sql}find_users(id=7)
-    SELECT users.user_id, users.user_name, users.password 
-    FROM users 
-    WHERE users.user_id = :users_user_id
-    {'users_user_id': 7}
-
-    {sql}find_users(street='123 Green Street')
-    SELECT users.user_id, users.user_name, users.password 
-    FROM users, addresses 
-    WHERE users.user_id = addresses.user_id AND addresses.street = :addresses_street
-    {'addresses_street': '123 Green Street'}
-
-    {sql}find_users(name='Jack', keywords=['jack','foo'])
-    SELECT DISTINCT users.user_id, users.user_name, users.password 
-    FROM users JOIN userkeywords ON users.user_id = userkeywords.user_id 
-    JOIN keywords ON userkeywords.keyword_id = keywords.keyword_id 
-    WHERE users.user_name = :users_user_name AND keywords.name IN ('jack', 'foo')
-    {'users_user_name': 'Jack'}
-
-### Inserts {@name=insert}
-
-An INSERT involves just one table.  The Insert object is used via the insert() function, and the specified columns determine what columns show up in the generated SQL.  If primary key columns are left out of the criterion, the SQL generator will try to populate them as specified by the particular database engine and sequences, i.e. relying upon an auto-incremented column or explicitly calling a sequence beforehand.  Insert statements, as well as updates and deletes, can also execute multiple parameters in one pass via specifying an array of dictionaries as parameters.
-
-The values to be populated for an INSERT or an UPDATE can be specified to the insert()/update() functions as the `values` named argument, or the query will be compiled based on the values of the parameters sent to the execute() method.
-
-    {python title="Using insert()"}
-    # basic insert
-    {sql}users.insert().execute(user_id=1, user_name='jack', password='asdfdaf')
-    INSERT INTO users (user_id, user_name, password) 
-    VALUES (:user_id, :user_name, :password)
-    {'user_name': 'jack', 'password': 'asdfdaf', 'user_id': 1}
-    
-    # insert just user_name, NULL for others
-    # will auto-populate primary key columns if they are configured
-    # to do so
-    {sql}users.insert().execute(user_name='ed')
-    INSERT INTO users (user_name) VALUES (:user_name)
-    {'user_name': 'ed'}
-    
-    # INSERT with a list:
-    {sql}users.insert(values=(3, 'jane', 'sdfadfas')).execute()
-    INSERT INTO users (user_id, user_name, password) 
-    VALUES (:user_id, :user_name, :password)
-    {'user_id': 3, 'password': 'sdfadfas', 'user_name': 'jane'}
-    
-    # INSERT with user-defined bind parameters
-    i = users.insert(
-        values={'user_name':bindparam('name'), 'password':bindparam('pw')}
-        )
-    {sql}i.execute(name='mary', pw='adas5fs')
-    INSERT INTO users (user_name, password) VALUES (:name, :pw)
-    {'name': 'mary', 'pw': 'adas5fs'}
-    
-    # INSERT many - if no explicit 'values' parameter is sent,
-    # the first parameter list in the list determines
-    # the generated SQL of the insert (i.e. what columns are present)
-    # executemany() is used at the DBAPI level
-    {sql}users.insert().execute(
-        {'user_id':7, 'user_name':'jack', 'password':'asdfasdf'},
-        {'user_id':8, 'user_name':'ed', 'password':'asdffcadf'},
-        {'user_id':9, 'user_name':'fred', 'password':'asttf'},
-    )
-    INSERT INTO users (user_id, user_name, password) 
-    VALUES (:user_id, :user_name, :password)
-    [{'user_name': 'jack', 'password': 'asdfasdf', 'user_id': 7}, 
-    {'user_name': 'ed', 'password': 'asdffcadf', 'user_id': 8}, 
-    {'user_name': 'fred', 'password': 'asttf', 'user_id': 9}]
-
-### Updates {@name=update}
-
-Updates work a lot like INSERTS, except there is an additional WHERE clause that can be specified.
-
-    {python title="Using update()"}
-    # change 'jack' to 'ed'
-    {sql}users.update(users.c.user_name=='jack').execute(user_name='ed')
-                UPDATE users SET user_name=:user_name WHERE users.user_name = :users_user_name
-    {'users_user_name': 'jack', 'user_name': 'ed'}
-    
-    # use bind parameters
-    u = users.update(users.c.user_name==bindparam('name'), 
-                    values={'user_name':bindparam('newname')})
-    {sql}u.execute(name='jack', newname='ed')
-    UPDATE users SET user_name=:newname WHERE users.user_name = :name
-    {'newname': 'ed', 'name': 'jack'}
-    
-    # update a column to another column
-    {sql}users.update(values={users.c.password:users.c.user_name}).execute()
-    UPDATE users SET password=users.user_name
-    {}
-    
-    # expressions OK too
-    {sql}users.update(values={users.c.user_id:users.c.user_id + 17}).execute()
-    UPDATE users SET user_id=users.user_id + :users_user_id
-    {'users_user_id':17}
-    
-    # multi-update
-    {sql}users.update(users.c.user_id==bindparam('id')).execute(
-            {'id':7, 'user_name':'jack', 'password':'fh5jks'},
-            {'id':8, 'user_name':'ed', 'password':'fsr234ks'},
-            {'id':9, 'user_name':'mary', 'password':'7h5jse'},
-        )
-    UPDATE users SET user_name=:user_name, password=:password WHERE users.user_id = :id
-    [{'password': 'fh5jks', 'user_name': 'jack', 'id': 7}, 
-    {'password': 'fsr234ks', 'user_name': 'ed', 'id': 8}, 
-    {'password': '7h5jse', 'user_name': 'mary', 'id': 9}]
-
-#### Correlated Updates {@name=correlated}
-
-A correlated update lets you update a table using selection from another table, or the same table:
-
-    {python}s = select([addresses.c.city], addresses.c.user_id==users.c.user_id)
-    {sql}users.update(
-        and_(users.c.user_id>10, users.c.user_id<20), 
-        values={users.c.user_name:s}
-    ).execute() 
-    UPDATE users SET user_name=(SELECT addresses.city 
-    FROM addresses 
-    WHERE addresses.user_id = users.user_id) 
-    WHERE users.user_id > :users_user_id AND users.user_id < :users_user_id_1
-    {'users_user_id_1': 20, 'users_user_id': 10}
-
-### Deletes {@name=delete}
-
-A delete is formulated like an update, except theres no values:
-
-    {python}users.delete(users.c.user_id==7).execute()
-    users.delete(users.c.user_name.like(bindparam('name'))).execute(
-            {'name':'%Jack%'},
-            {'name':'%Ed%'},
-            {'name':'%Jane%'},
-        )
-    users.delete(exists())
-    
index 4c83f006cf3b22a810d50a834316183dbb6abfeb..d8017b833654e5a635d19ddf0694ffa329c60825 100644 (file)
@@ -920,7 +920,7 @@ Finally, a delete.  Easy enough:
 
 ## Further Reference {@name=reference}
 
-The best place to get every possible name you can use in constructed SQL is the [Generated Documentation](rel:docstrings_sqlalchemy.sql).
+The best place to get every possible name you can use in constructed SQL is the [Generated Documentation](rel:docstrings_sqlalchemy.sql.expression).
 
 Table Metadata Reference: [metadata](rel:metadata)
 
index e998f0d7a5be48865e22163d47e972919603d562..1931e08918070ec3b68e47e8dc0e0cbc2f2354e0 100644 (file)
@@ -43,7 +43,7 @@ def make_all_docs():
         make_doc(obj=orm.mapperlib, classes=[orm.mapperlib.Mapper]),
         make_doc(obj=orm.properties),
         make_doc(obj=orm.query, classes=[orm.query.Query]),
-        make_doc(obj=orm.session, classes=[orm.session.Session]),
+        make_doc(obj=orm.session, classes=[orm.session.Session, orm.session.SessionExtension]),
         make_doc(obj=orm.shard),
         make_doc(obj=associationproxy, classes=[associationproxy.AssociationProxy]),
         make_doc(obj=orderinglist, classes=[orderinglist.OrderingList]),
index df8c205bf02238de2e0641c46b1b13626d596003..cd3dfbec0682c8b9160debb5942adf2a73fbde42 100644 (file)
@@ -197,6 +197,13 @@ class MapperExtension(object):
 
         This is a good place to set up primary key values and such
         that aren't handled otherwise.
+
+        Column-based attributes can be modified within this method which will 
+        result in the new value being inserted.  However *no* changes to the overall 
+        flush plan can be made; this means any collection modification or
+        save() operations which occur within this method will not take effect
+        until the next flush call.
+        
         """
 
         return EXT_CONTINUE
@@ -207,7 +214,29 @@ class MapperExtension(object):
         return EXT_CONTINUE
 
     def before_update(self, mapper, connection, instance):
-        """Receive an object instance before that instance is UPDATEed."""
+        """Receive an object instance before that instance is UPDATEed.
+        
+        Note that this method is called for all instances that are marked as 
+        "dirty", even those which have no net changes to their column-based 
+        attributes.  An object is marked as dirty when any of its column-based
+        attributes have a "set attribute" operation called or when any of its 
+        collections are modified.  If, at update time, no column-based attributes
+        have any net changes, no UPDATE statement will be issued.  This means
+        that an instance being sent to before_update is *not* a guarantee that
+        an UPDATE statement will be issued (although you can affect the outcome
+        here).
+
+        To detect if the column-based attributes on the object have net changes,
+        and will therefore generate an UPDATE statement, use 
+        ``object_session(instance).is_modified(instance, include_collections=False)``.
+        
+        Column-based attributes can be modified within this method which will 
+        result in their being updated.  However *no* changes to the overall 
+        flush plan can be made; this means any collection modification or
+        save() operations which occur within this method will not take effect
+        until the next flush call.
+        
+        """
 
         return EXT_CONTINUE
 
@@ -217,7 +246,14 @@ class MapperExtension(object):
         return EXT_CONTINUE
 
     def before_delete(self, mapper, connection, instance):
-        """Receive an object instance before that instance is DELETEed."""
+        """Receive an object instance before that instance is DELETEed.
+        
+        Note that *no* changes to the overall 
+        flush plan can be made here; this means any collection modification,
+        save() or delete() operations which occur within this method will 
+        not take effect until the next flush call.
+        
+        """
 
         return EXT_CONTINUE
 
index 17fbe88a2c237a54206bdc4812643407b565ba4f..0dbde749fe6271e04742ef100e4143359b134d61 100644 (file)
@@ -8,7 +8,7 @@
 
 
 All components are derived from a common base class
-[sqlalchemy.sql#ClauseElement].  Common behaviors are organized based
+[sqlalchemy.sql.expression#ClauseElement].  Common behaviors are organized based
 on class hierarchies, in some cases via mixins.
 
 All object construction from this package occurs via functions which
@@ -66,10 +66,10 @@ def asc(column):
 def outerjoin(left, right, onclause=None, **kwargs):
     """Return an ``OUTER JOIN`` clause element.
 
-    The returned object is an instance of [sqlalchemy.sql#Join].
+    The returned object is an instance of [sqlalchemy.sql.expression#Join].
 
     Similar functionality is also available via the ``outerjoin()``
-    method on any [sqlalchemy.sql#FromClause].
+    method on any [sqlalchemy.sql.expression#FromClause].
 
     left
       The left side of the join.
@@ -91,10 +91,10 @@ def outerjoin(left, right, onclause=None, **kwargs):
 def join(left, right, onclause=None, **kwargs):
     """Return a ``JOIN`` clause element (regular inner join).
 
-    The returned object is an instance of [sqlalchemy.sql#Join].
+    The returned object is an instance of [sqlalchemy.sql.expression#Join].
 
     Similar functionality is also available via the ``join()`` method
-    on any [sqlalchemy.sql#FromClause].
+    on any [sqlalchemy.sql.expression#FromClause].
 
     left
       The left side of the join.
@@ -117,9 +117,9 @@ def select(columns=None, whereclause=None, from_obj=[], **kwargs):
     """Returns a ``SELECT`` clause element.
 
     Similar functionality is also available via the ``select()``
-    method on any [sqlalchemy.sql#FromClause].
+    method on any [sqlalchemy.sql.expression#FromClause].
 
-    The returned object is an instance of [sqlalchemy.sql#Select].
+    The returned object is an instance of [sqlalchemy.sql.expression#Select].
 
     All arguments which accept ``ClauseElement`` arguments also accept
     string arguments, which will be converted as appropriate into
@@ -236,21 +236,21 @@ def select(columns=None, whereclause=None, from_obj=[], **kwargs):
         return s
 
 def subquery(alias, *args, **kwargs):
-    """Return an [sqlalchemy.sql#Alias] object derived from a [sqlalchemy.sql#Select].
+    """Return an [sqlalchemy.sql.expression#Alias] object derived from a [sqlalchemy.sql.expression#Select].
 
     name
       alias name
 
     \*args, \**kwargs
 
-      all other arguments are delivered to the [sqlalchemy.sql#select()]
+      all other arguments are delivered to the [sqlalchemy.sql.expression#select()]
       function.
     """
 
     return Select(*args, **kwargs).alias(alias)
 
 def insert(table, values=None, inline=False):
-    """Return an [sqlalchemy.sql#Insert] clause element.
+    """Return an [sqlalchemy.sql.expression#Insert] clause element.
 
     Similar functionality is available via the ``insert()`` method on
     [sqlalchemy.schema#Table].
@@ -290,7 +290,7 @@ def insert(table, values=None, inline=False):
     return Insert(table, values, inline=inline)
 
 def update(table, whereclause=None, values=None, inline=False):
-    """Return an [sqlalchemy.sql#Update] clause element.
+    """Return an [sqlalchemy.sql.expression#Update] clause element.
 
     Similar functionality is available via the ``update()`` method on
     [sqlalchemy.schema#Table].
@@ -335,7 +335,7 @@ def update(table, whereclause=None, values=None, inline=False):
     return Update(table, whereclause=whereclause, values=values, inline=inline)
 
 def delete(table, whereclause = None, **kwargs):
-    """Return a [sqlalchemy.sql#Delete] clause element.
+    """Return a [sqlalchemy.sql.expression#Delete] clause element.
 
     Similar functionality is available via the ``delete()`` method on
     [sqlalchemy.schema#Table].
@@ -354,7 +354,7 @@ def and_(*clauses):
     """Join a list of clauses together using the ``AND`` operator.
 
     The ``&`` operator is also overloaded on all
-    [sqlalchemy.sql#_CompareMixin] subclasses to produce the same
+    [sqlalchemy.sql.expression#_CompareMixin] subclasses to produce the same
     result.
     """
     if len(clauses) == 1:
@@ -365,7 +365,7 @@ def or_(*clauses):
     """Join a list of clauses together using the ``OR`` operator.
 
     The ``|`` operator is also overloaded on all
-    [sqlalchemy.sql#_CompareMixin] subclasses to produce the same
+    [sqlalchemy.sql.expression#_CompareMixin] subclasses to produce the same
     result.
     """
 
@@ -377,7 +377,7 @@ def not_(clause):
     """Return a negation of the given clause, i.e. ``NOT(clause)``.
 
     The ``~`` operator is also overloaded on all
-    [sqlalchemy.sql#_CompareMixin] subclasses to produce the same
+    [sqlalchemy.sql.expression#_CompareMixin] subclasses to produce the same
     result.
     """
 
@@ -393,7 +393,7 @@ def between(ctest, cleft, cright):
 
     Equivalent of SQL ``clausetest BETWEEN clauseleft AND clauseright``.
 
-    The ``between()`` method on all [sqlalchemy.sql#_CompareMixin] subclasses
+    The ``between()`` method on all [sqlalchemy.sql.expression#_CompareMixin] subclasses
     provides similar functionality.
     """
 
@@ -448,13 +448,13 @@ def extract(field, expr):
     return func.extract(expr)
 
 def exists(*args, **kwargs):
-    """Return an ``EXISTS`` clause as applied to a [sqlalchemy.sql#Select] object.
+    """Return an ``EXISTS`` clause as applied to a [sqlalchemy.sql.expression#Select] object.
 
-    The resulting [sqlalchemy.sql#_Exists] object can be executed by
+    The resulting [sqlalchemy.sql.expression#_Exists] object can be executed by
     itself or used as a subquery within an enclosing select.
 
     \*args, \**kwargs
-      all arguments are sent directly to the [sqlalchemy.sql#select()]
+      all arguments are sent directly to the [sqlalchemy.sql.expression#select()]
       function to produce a ``SELECT`` statement.
     """
 
@@ -463,17 +463,17 @@ def exists(*args, **kwargs):
 def union(*selects, **kwargs):
     """Return a ``UNION`` of multiple selectables.
 
-    The returned object is an instance of [sqlalchemy.sql#CompoundSelect].
+    The returned object is an instance of [sqlalchemy.sql.expression#CompoundSelect].
 
     A similar ``union()`` method is available on all
-    [sqlalchemy.sql#FromClause] subclasses.
+    [sqlalchemy.sql.expression#FromClause] subclasses.
 
     \*selects
-      a list of [sqlalchemy.sql#Select] instances.
+      a list of [sqlalchemy.sql.expression#Select] instances.
 
     \**kwargs
        available keyword arguments are the same as those of
-       [sqlalchemy.sql#select()].
+       [sqlalchemy.sql.expression#select()].
     """
 
     return _compound_select('UNION', *selects, **kwargs)
@@ -481,17 +481,17 @@ def union(*selects, **kwargs):
 def union_all(*selects, **kwargs):
     """Return a ``UNION ALL`` of multiple selectables.
 
-    The returned object is an instance of [sqlalchemy.sql#CompoundSelect].
+    The returned object is an instance of [sqlalchemy.sql.expression#CompoundSelect].
 
     A similar ``union_all()`` method is available on all
-    [sqlalchemy.sql#FromClause] subclasses.
+    [sqlalchemy.sql.expression#FromClause] subclasses.
 
     \*selects
-      a list of [sqlalchemy.sql#Select] instances.
+      a list of [sqlalchemy.sql.expression#Select] instances.
 
     \**kwargs
       available keyword arguments are the same as those of
-      [sqlalchemy.sql#select()].
+      [sqlalchemy.sql.expression#select()].
     """
 
     return _compound_select('UNION ALL', *selects, **kwargs)
@@ -499,63 +499,63 @@ def union_all(*selects, **kwargs):
 def except_(*selects, **kwargs):
     """Return an ``EXCEPT`` of multiple selectables.
 
-    The returned object is an instance of [sqlalchemy.sql#CompoundSelect].
+    The returned object is an instance of [sqlalchemy.sql.expression#CompoundSelect].
 
     \*selects
-      a list of [sqlalchemy.sql#Select] instances.
+      a list of [sqlalchemy.sql.expression#Select] instances.
 
     \**kwargs
       available keyword arguments are the same as those of
-      [sqlalchemy.sql#select()].
+      [sqlalchemy.sql.expression#select()].
     """
     return _compound_select('EXCEPT', *selects, **kwargs)
 
 def except_all(*selects, **kwargs):
     """Return an ``EXCEPT ALL`` of multiple selectables.
 
-    The returned object is an instance of [sqlalchemy.sql#CompoundSelect].
+    The returned object is an instance of [sqlalchemy.sql.expression#CompoundSelect].
 
     \*selects
-      a list of [sqlalchemy.sql#Select] instances.
+      a list of [sqlalchemy.sql.expression#Select] instances.
 
     \**kwargs
       available keyword arguments are the same as those of
-      [sqlalchemy.sql#select()].
+      [sqlalchemy.sql.expression#select()].
     """
     return _compound_select('EXCEPT ALL', *selects, **kwargs)
 
 def intersect(*selects, **kwargs):
     """Return an ``INTERSECT`` of multiple selectables.
 
-    The returned object is an instance of [sqlalchemy.sql#CompoundSelect].
+    The returned object is an instance of [sqlalchemy.sql.expression#CompoundSelect].
 
     \*selects
-      a list of [sqlalchemy.sql#Select] instances.
+      a list of [sqlalchemy.sql.expression#Select] instances.
 
     \**kwargs
       available keyword arguments are the same as those of
-      [sqlalchemy.sql#select()].
+      [sqlalchemy.sql.expression#select()].
     """
     return _compound_select('INTERSECT', *selects, **kwargs)
 
 def intersect_all(*selects, **kwargs):
     """Return an ``INTERSECT ALL`` of multiple selectables.
 
-    The returned object is an instance of [sqlalchemy.sql#CompoundSelect].
+    The returned object is an instance of [sqlalchemy.sql.expression#CompoundSelect].
 
     \*selects
-      a list of [sqlalchemy.sql#Select] instances.
+      a list of [sqlalchemy.sql.expression#Select] instances.
 
     \**kwargs
       available keyword arguments are the same as those of
-      [sqlalchemy.sql#select()].
+      [sqlalchemy.sql.expression#select()].
     """
     return _compound_select('INTERSECT ALL', *selects, **kwargs)
 
 def alias(selectable, alias=None):
-    """Return an [sqlalchemy.sql#Alias] object.
+    """Return an [sqlalchemy.sql.expression#Alias] object.
 
-    An ``Alias`` represents any [sqlalchemy.sql#FromClause] with
+    An ``Alias`` represents any [sqlalchemy.sql.expression#FromClause] with
     an alternate name assigned within SQL, typically using the ``AS``
     clause when generated, e.g. ``SELECT * FROM table AS aliasname``.
 
@@ -580,10 +580,10 @@ def literal(value, type_=None):
     Literal clauses are created automatically when non-
     ``ClauseElement`` objects (such as strings, ints, dates, etc.) are
     used in a comparison operation with a
-    [sqlalchemy.sql#_CompareMixin] subclass, such as a ``Column``
+    [sqlalchemy.sql.expression#_CompareMixin] subclass, such as a ``Column``
     object.  Use this function to force the generation of a literal
     clause, which will be created as a
-    [sqlalchemy.sql#_BindParamClause] with a bound value.
+    [sqlalchemy.sql.expression#_BindParamClause] with a bound value.
 
     value
       the value to be bound.  Can be any Python object supported by
@@ -598,7 +598,7 @@ def literal(value, type_=None):
     return _BindParamClause('literal', value, type_=type_, unique=True)
 
 def label(name, obj):
-    """Return a [sqlalchemy.sql#_Label] object for the given [sqlalchemy.sql#ColumnElement].
+    """Return a [sqlalchemy.sql.expression#_Label] object for the given [sqlalchemy.sql.expression#ColumnElement].
 
     A label changes the name of an element in the columns clause of a
     ``SELECT`` statement, typically via the ``AS`` SQL keyword.
@@ -618,7 +618,7 @@ def label(name, obj):
 def column(text, type_=None):
     """Return a textual column clause, as would be in the columns clause of a ``SELECT`` statement.
 
-    The object returned is an instance of [sqlalchemy.sql#_ColumnClause],
+    The object returned is an instance of [sqlalchemy.sql.expression#_ColumnClause],
     which represents the "syntactical" portion of the schema-level
     [sqlalchemy.schema#Column] object.
 
@@ -626,7 +626,7 @@ def column(text, type_=None):
       the name of the column.  Quoting rules will be applied to the
       clause like any other column name.  For textual column
       constructs that are not to be quoted, use the
-      [sqlalchemy.sql#literal_column()] function.
+      [sqlalchemy.sql.expression#literal_column()] function.
 
     type\_
       an optional [sqlalchemy.types#TypeEngine] object which will
@@ -639,7 +639,7 @@ def column(text, type_=None):
 def literal_column(text, type_=None):
     """Return a textual column clause, as would be in the columns clause of a ``SELECT`` statement.
 
-    The object returned is an instance of [sqlalchemy.sql#_ColumnClause],
+    The object returned is an instance of [sqlalchemy.sql.expression#_ColumnClause],
     which represents the "syntactical" portion of the schema-level
     [sqlalchemy.schema#Column] object.
 
@@ -647,7 +647,7 @@ def literal_column(text, type_=None):
       the name of the column.  Quoting rules will not be applied to
       the column.  For textual column constructs that should be quoted
       like any other column construct, use the
-      [sqlalchemy.sql#column()] function.
+      [sqlalchemy.sql.expression#column()] function.
 
     type
       an optional [sqlalchemy.types#TypeEngine] object which will
@@ -657,7 +657,7 @@ def literal_column(text, type_=None):
     return _ColumnClause(text, type_=type_, is_literal=True)
 
 def table(name, *columns):
-    """Return a [sqlalchemy.sql#Table] object.
+    """Return a [sqlalchemy.sql.expression#Table] object.
 
     This is a primitive version of the [sqlalchemy.schema#Table] object,
     which is a subclass of this object.
@@ -1329,14 +1329,14 @@ class _CompareMixin(ColumnOperators):
 class Selectable(ClauseElement):
     """Represent a column list-holding object.
 
-    This is the common base class of [sqlalchemy.sql#ColumnElement]
-    and [sqlalchemy.sql#FromClause].  The reason ``ColumnElement`` is
+    This is the common base class of [sqlalchemy.sql.expression#ColumnElement]
+    and [sqlalchemy.sql.expression#FromClause].  The reason ``ColumnElement`` is
     marked as a "list-holding" object is so that it can be treated
     similarly to ``FromClause`` in column-selection scenarios; it
     contains a list of columns consisting of itself.
     """
 
-    columns = util.NotImplProperty("""a [sqlalchemy.sql#ColumnCollection] containing ``ColumnElement`` instances.""")
+    columns = util.NotImplProperty("""a [sqlalchemy.sql.expression#ColumnCollection] containing ``ColumnElement`` instances.""")
 
     def select(self, whereclauses = None, **params):
         return select([self], whereclauses, **params)
@@ -2725,17 +2725,42 @@ class _SelectBaseMixin(object):
         self.append_group_by(*util.to_list(group_by, []))
 
     def as_scalar(self):
+        """return a 'scalar' representation of this selectable, which can be used
+        as a column expression.
+        
+        Typically, a select statement which has only one column in its columns clause
+        is eligible to be used as a scalar expression.
+        
+        The returned object is an instance of [sqlalchemy.sql.expression#_ScalarSelect].
+        """
+        
         return _ScalarSelect(self)
 
     def apply_labels(self):
+        """set the 'labels' flag on this selectable.
+        
+        This will result in column expressions being generated using labels against their table 
+        name, such as "SELECT somecolumn AS tablename_somecolumn".  This allows selectables which 
+        contain multiple FROM clauses to produce a unique set of column names regardless of name conflicts
+        among the individual FROM clauses.
+        """
+        
         s = self._generate()
         s.use_labels = True
         return s
 
     def label(self, name):
+        """return a 'scalar' representation of this selectable, embedded as a subquery
+        with a label.
+        
+        See also ``as_scalar()``.
+        """
+        
         return self.as_scalar().label(name)
 
     def supports_execution(self):
+        """part of the ClauseElement contract; returns ``True`` in all cases for this class."""
+        
         return True
 
     def _generate(self):
@@ -2744,26 +2769,45 @@ class _SelectBaseMixin(object):
         return s
 
     def limit(self, limit):
+        """return a new selectable with the given LIMIT criterion applied."""
+        
         s = self._generate()
         s._limit = limit
         return s
 
     def offset(self, offset):
+        """return a new selectable with the given OFFSET criterion applied."""
+
         s = self._generate()
         s._offset = offset
         return s
 
     def order_by(self, *clauses):
+        """return a new selectable with the given list of ORDER BY criterion applied.
+        
+        The criterion will be appended to any pre-existing ORDER BY criterion.
+        """
+
         s = self._generate()
         s.append_order_by(*clauses)
         return s
 
     def group_by(self, *clauses):
+        """return a new selectable with the given list of GROUP BY criterion applied.
+        
+        The criterion will be appended to any pre-existing GROUP BY criterion.
+        """
+
         s = self._generate()
         s.append_group_by(*clauses)
         return s
 
     def append_order_by(self, *clauses):
+        """Append the given ORDER BY criterion applied to this selectable.
+        
+        The criterion will be appended to any pre-existing ORDER BY criterion.
+        """
+        
         if clauses == [None]:
             self._order_by_clause = ClauseList()
         else:
@@ -2772,6 +2816,11 @@ class _SelectBaseMixin(object):
             self._order_by_clause = ClauseList(*clauses)
 
     def append_group_by(self, *clauses):
+        """Append the given GROUP BY criterion applied to this selectable.
+        
+        The criterion will be appended to any pre-existing GROUP BY criterion.
+        """
+        
         if clauses == [None]:
             self._group_by_clause = ClauseList()
         else:
@@ -2780,6 +2829,11 @@ class _SelectBaseMixin(object):
             self._group_by_clause = ClauseList(*clauses)
 
     def select(self, whereclauses = None, **params):
+        """return a SELECT of this selectable.  
+        
+        This has the effect of embeddeding this select into a subquery that is selected
+        from.
+        """
         return select([self], whereclauses, **params)
 
     def _get_from_objects(self, is_where=False, **modifiers):
@@ -2890,8 +2944,11 @@ class Select(_SelectBaseMixin, FromClause):
         """Construct a Select object.
 
         The public constructor for Select is the
-        [sqlalchemy.sql#select()] function; see that function for
+        [sqlalchemy.sql.expression#select()] function; see that function for
         argument descriptions.
+        
+        Additional generative and mutator methods are available on the 
+        [sqlalchemy.sql.expression#_SelectBaseMixin] superclass.
         """
 
         self._should_correlate = correlate
@@ -2911,11 +2968,11 @@ class Select(_SelectBaseMixin, FromClause):
 
         if columns is not None:
             for c in columns:
-                self.append_column(c, copy_collection=False)
+                self.append_column(c, _copy_collection=False)
 
         if from_obj is not None:
             for f in from_obj:
-                self.append_from(f, copy_collection=False)
+                self.append_from(f, _copy_collection=False)
 
         if whereclause is not None:
             self.append_whereclause(whereclause)
@@ -2925,7 +2982,7 @@ class Select(_SelectBaseMixin, FromClause):
 
         if prefixes is not None:
             for p in prefixes:
-                self.append_prefix(p, copy_collection=False)
+                self.append_prefix(p, _copy_collection=False)
 
         _SelectBaseMixin.__init__(self, **kwargs)
 
@@ -2975,9 +3032,15 @@ class Select(_SelectBaseMixin, FromClause):
 
     froms = property(_get_display_froms, doc="""Return a list of all FromClause elements which will be applied to the FROM clause of the resulting statement.""")
 
-    name = property(lambda self:"Select statement")
+    name = property(lambda self:"Select statement", doc="""Placeholder 'name' attribute to meet the FromClause interface.""")
 
     def locate_all_froms(self):
+        """return a Set of all FromClause elements referenced by this Select.  
+        
+        This set is a superset of that returned by the ``froms`` property, which
+        is specifically for those FromClause elements that would actually be rendered.
+        """
+        
         froms = util.Set()
         for col in self._raw_columns:
             for f in col._get_from_objects():
@@ -3001,7 +3064,7 @@ class Select(_SelectBaseMixin, FromClause):
             else:
                 yield c
 
-    inner_columns = property(_get_inner_columns)
+    inner_columns = property(_get_inner_columns, doc="""a collection of all ColumnElement expressions which would be rendered into the columns clause of the resulting SELECT statement.""")
 
     def _copy_internals(self):
         self._clone_from_clause()
@@ -3012,6 +3075,8 @@ class Select(_SelectBaseMixin, FromClause):
                 setattr(self, attr, getattr(self, attr)._clone())
 
     def get_children(self, column_collections=True, **kwargs):
+        """return child elements as per the ClauseElement specification."""
+        
         return (column_collections and list(self.columns) or []) + \
             list(self.locate_all_froms()) + \
             [x for x in (self._whereclause, self._having, self._order_by_clause, self._group_by_clause) if x is not None]
@@ -3103,15 +3168,15 @@ class Select(_SelectBaseMixin, FromClause):
             s.append_correlation(fromclause)
         return s
 
-    def append_correlation(self, fromclause, copy_collection=True):
+    def append_correlation(self, fromclause, _copy_collection=True):
         """append the given correlation expression to this select() construct."""
         
-        if not copy_collection:
+        if not _copy_collection:
             self.__correlate.add(fromclause)
         else:
             self.__correlate = util.Set(list(self.__correlate) + [fromclause])
 
-    def append_column(self, column, copy_collection=True):
+    def append_column(self, column, _copy_collection=True):
         """append the given column expression to the columns clause of this select() construct."""
         
         column = _literal_as_column(column)
@@ -3119,16 +3184,16 @@ class Select(_SelectBaseMixin, FromClause):
         if isinstance(column, _ScalarSelect):
             column = column.self_group(against=operators.comma_op)
         
-        if not copy_collection:
+        if not _copy_collection:
             self._raw_columns.append(column)
         else:
             self._raw_columns = self._raw_columns + [column]
 
-    def append_prefix(self, clause, copy_collection=True):
+    def append_prefix(self, clause, _copy_collection=True):
         """append the given columns clause prefix expression to this select() construct."""
         
         clause = _literal_as_text(clause)
-        if not copy_collection:
+        if not _copy_collection:
             self._prefixes.append(clause)
         else:
             self._prefixes = self._prefixes + [clause]
@@ -3155,11 +3220,13 @@ class Select(_SelectBaseMixin, FromClause):
         else:
             self._having = _literal_as_text(having)
 
-    def append_from(self, fromclause, copy_collection=True):
+    def append_from(self, fromclause, _copy_collection=True):
+        """append the given FromClause expression to this select() construct's FROM clause."""
+        
         if _is_literal(fromclause):
             fromclause = FromClause(fromclause)
             
-        if not copy_collection:
+        if not _copy_collection:
             self._froms.add(fromclause)
         else:
             self._froms = util.Set(list(self._froms) + [fromclause])
@@ -3174,6 +3241,12 @@ class Select(_SelectBaseMixin, FromClause):
             return column._make_proxy(self)
 
     def self_group(self, against=None):
+        """return a 'grouping' construct as per the ClauseElement specification.
+        
+        This produces an element that can be embedded in an expression.  Note that
+        this method is called automatically as needed when constructing expressions.
+        """
+        
         if isinstance(against, CompoundSelect):
             return self
         return _FromGrouping(self)
@@ -3190,24 +3263,40 @@ class Select(_SelectBaseMixin, FromClause):
                 return oid
         else:
             return None
-    oid_column = property(_locate_oid_column)
+    oid_column = property(_locate_oid_column, doc="""return the 'oid' column, if any, for this select statement.
+    
+    This is part of the FromClause contract.  The column will usually be the 'oid' column of the first ``Table``
+    located within the from clause of this select().
+    """)
 
     def union(self, other, **kwargs):
+        """return a SQL UNION of this select() construct against the given selectable."""
+        
         return union(self, other, **kwargs)
 
     def union_all(self, other, **kwargs):
+        """return a SQL UNION ALL of this select() construct against the given selectable."""
+
         return union_all(self, other, **kwargs)
 
     def except_(self, other, **kwargs):
+        """return a SQL EXCEPT of this select() construct against the given selectable."""
+        
         return except_(self, other, **kwargs)
 
     def except_all(self, other, **kwargs):
+        """return a SQL EXCEPT ALL of this select() construct against the given selectable."""
+
         return except_all(self, other, **kwargs)
 
     def intersect(self, other, **kwargs):
+        """return a SQL INTERSECT of this select() construct against the given selectable."""
+
         return intersect(self, other, **kwargs)
 
     def intersect_all(self, other, **kwargs):
+        """return a SQL INTERSECT ALL of this select() construct against the given selectable."""
+
         return intersect_all(self, other, **kwargs)
 
     def _table_iterator(self):