summaryrefslogtreecommitdiff
path: root/doc
diff options
context:
space:
mode:
authorMike Bayer <mike_mp@zzzcomputing.com>2008-05-09 16:34:10 +0000
committerMike Bayer <mike_mp@zzzcomputing.com>2008-05-09 16:34:10 +0000
commit4a6afd469fad170868554bf28578849bf3dfd5dd (patch)
treeb396edc33d567ae19dd244e87137296450467725 /doc
parent46b7c9dc57a38d5b9e44a4723dad2ad8ec57baca (diff)
downloadsqlalchemy-4a6afd469fad170868554bf28578849bf3dfd5dd.tar.gz
r4695 merged to trunk; trunk now becomes 0.5.
0.4 development continues at /sqlalchemy/branches/rel_0_4
Diffstat (limited to 'doc')
-rw-r--r--doc/build/content/intro.txt97
-rw-r--r--doc/build/content/mappers.txt48
-rw-r--r--doc/build/content/ormtutorial.txt984
-rw-r--r--doc/build/content/session.txt482
-rw-r--r--doc/build/content/sqlexpression.txt61
-rw-r--r--doc/build/gen_docstrings.py3
-rw-r--r--doc/build/genhtml.py2
-rw-r--r--doc/build/testdocs.py2
8 files changed, 704 insertions, 975 deletions
diff --git a/doc/build/content/intro.txt b/doc/build/content/intro.txt
index d6ded5bdf..c45577d80 100644
--- a/doc/build/content/intro.txt
+++ b/doc/build/content/intro.txt
@@ -64,103 +64,22 @@ SQLAlchemy is designed to operate with a [DB-API](http://www.python.org/doc/peps
* SQLite: [pysqlite](http://initd.org/tracker/pysqlite), [sqlite3](http://docs.python.org/lib/module-sqlite3.html) (included with Python 2.5 or greater)
* MySQL: [MySQLdb](http://sourceforge.net/projects/mysql-python)
* Oracle: [cx_Oracle](http://www.cxtools.net/default.aspx?nav=home)
-* MS-SQL: [pyodbc](http://pyodbc.sourceforge.net/) (recommended), [adodbapi](http://adodbapi.sourceforge.net/) or [pymssql](http://pymssql.sourceforge.net/)
+* MS-SQL, MSAccess: [pyodbc](http://pyodbc.sourceforge.net/) (recommended), [adodbapi](http://adodbapi.sourceforge.net/) or [pymssql](http://pymssql.sourceforge.net/)
* Firebird: [kinterbasdb](http://kinterbasdb.sourceforge.net/)
* Informix: [informixdb](http://informixdb.sourceforge.net/)
+* DB2/Informix IDS: [ibm-db](http://code.google.com/p/ibm-db/)
+* Sybase: TODO
+* MAXDB: TODO
### Checking the Installed SQLAlchemy Version
-This documentation covers SQLAlchemy version 0.4. If you're working on a system that already has SQLAlchemy installed, check the version from your Python prompt like this:
+This documentation covers SQLAlchemy version 0.5. If you're working on a system that already has SQLAlchemy installed, check the version from your Python prompt like this:
{python}
>>> import sqlalchemy
>>> sqlalchemy.__version__ # doctest: +SKIP
- 0.4.0
+ 0.5.0
-## 0.3 to 0.4 Migration {@name=migration}
+## 0.4 to 0.5 Migration {@name=migration}
-From version 0.3 to version 0.4 of SQLAlchemy, some conventions have changed. Most of these conventions are available in the most recent releases of the 0.3 series starting with version 0.3.9, so that you can make a 0.3 application compatible with 0.4 in most cases.
-
-This section will detail only those things that have changed in a backwards-incompatible manner. For a full overview of everything that's new and changed, see [WhatsNewIn04](http://www.sqlalchemy.org/trac/wiki/WhatsNewIn04).
-
-### ORM Package is now sqlalchemy.orm {@name=imports}
-
-All symbols related to the SQLAlchemy Object Relational Mapper, i.e. names like `mapper()`, `relation()`, `backref()`, `create_session()` `synonym()`, `eagerload()`, etc. are now only in the `sqlalchemy.orm` package, and **not** in `sqlalchemy`. So if you were previously importing everything on an asterisk:
-
- {python}
- from sqlalchemy import *
-
-You should now import separately from orm:
-
- {python}
- from sqlalchemy import *
- from sqlalchemy.orm import *
-
-Or more commonly, just pull in the names you'll need:
-
- {python}
- from sqlalchemy import create_engine, MetaData, Table, Column, types
- from sqlalchemy.orm import mapper, relation, backref, create_session
-
-### BoundMetaData is now MetaData {@name=metadata}
-
-The `BoundMetaData` name is removed. Now, you just use `MetaData`. Additionally, the `engine` parameter/attribute is now called `bind`, and `connect()` is deprecated:
-
- {python}
- # plain metadata
- meta = MetaData()
-
- # metadata bound to an engine
- meta = MetaData(engine)
-
- # bind metadata to an engine later
- meta.bind = engine
-
-Additionally, `DynamicMetaData` is now known as `ThreadLocalMetaData`.
-
-### "Magic" Global MetaData removed {@name=global}
-
-There was an old way to specify `Table` objects using an implicit, global `MetaData` object. To do this you'd omit the second positional argument, and specify `Table('tablename', Column(...))`. This no longer exists in 0.4 and the second `MetaData` positional argument is required, i.e. `Table('tablename', meta, Column(...))`.
-
-### Some existing select() methods become generative {@name=generative}
-
-The methods `correlate()`, `order_by()`, and `group_by()` on the `select()` construct now return a **new** select object, and do not change the original one. Additionally, the generative methods `where()`, `column()`, `distinct()`, and several others have been added:
-
- {python}
- s = table.select().order_by(table.c.id).where(table.c.x==7)
- result = engine.execute(s)
-
-### collection_class behavior is changed {@name=collection}
-
-If you've been using the `collection_class` option on `mapper()`, the requirements for instrumented collections have changed. For an overview, see [advdatamapping_relation_collections](rel:advdatamapping_relation_collections).
-
-### All "engine", "bind_to", "connectable" Keyword Arguments Changed to "bind" {@name=bind}
-
-This is for create/drop statements, sessions, SQL constructs, metadatas:
-
- {python}
- myengine = create_engine('sqlite://')
-
- meta = MetaData(myengine)
-
- meta2 = MetaData()
- meta2.bind = myengine
-
- session = create_session(bind=myengine)
-
- statement = select([table], bind=myengine)
-
- meta.create_all(bind=myengine)
-
-### All "type" Keyword Arguments Changed to "type_" {@name=type}
-
-This mostly applies to SQL constructs where you pass a type in:
-
- {python}
- s = select([mytable], mytable.c.x=bindparam(y, type_=DateTime))
-
- func.now(type_=DateTime)
-
-### Mapper Extensions must return EXT_CONTINUE to continue execution to the next mapper
-
-If you extend the mapper, the methods in your mapper extension must return EXT_CONTINUE to continue executing additional mappers.
+Notes on what's changed from 0.4 to 0.5 is available on the SQLAlchemy wiki at [05Migration](http://www.sqlalchemy.org/trac/wiki/05Migration).
diff --git a/doc/build/content/mappers.txt b/doc/build/content/mappers.txt
index fca2076bc..1035b89df 100644
--- a/doc/build/content/mappers.txt
+++ b/doc/build/content/mappers.txt
@@ -1105,9 +1105,9 @@ When using `primaryjoin` and `secondaryjoin`, SQLAlchemy also needs to be aware
{python}
mapper(Address, addresses_table)
mapper(User, users_table, properties={
- 'addresses' : relation(Address,
- primaryjoin=users_table.c.user_id==addresses_table.c.user_id,
- foreign_keys=[addresses_table.c.user_id])
+ 'addresses' : relation(Address, primaryjoin=
+ users_table.c.user_id==addresses_table.c.user_id,
+ foreign_keys=[addresses_table.c.user_id])
})
##### Building Query-Enabled Properties {@name=properties}
@@ -1361,9 +1361,11 @@ or more simply just use `eagerload_all()`:
There are two other loader strategies available, **dynamic loading** and **no loading**; these are described in [advdatamapping_relation_largecollections](rel:advdatamapping_relation_largecollections).
-##### Combining Eager Loads with Statement/Result Set Queries
+##### Routing Explicit Joins/Statements into Eagerly Loaded Collections {@name=containseager}
-When full statement or result-set 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.
+When full statement loads are used with `Query`, the user defined SQL is used verbatim and the `Query` does not play any role in generating it. In this scenario, if eager loading is desired, the `Query` should be informed as to what collections should also be loaded from the result set. Similarly, Queries which compile their statement in the usual way may also have user-defined joins built in which are synonymous with what eager loading would normally produce, and it improves performance to utilize those same JOINs for both purposes, instead of allowing the eager load mechanism to generate essentially the same JOIN redundantly. Yet another use case for such a feature is a Query which returns instances with a filtered view of their collections loaded, in which case the default eager load mechanisms need to be bypassed.
+
+The single option `Query` provides to control this is the `contains_eager()` option, which specifies the path of a single relationship to be eagerly loaded. Like all relation-oriented options, it takes a string or Python descriptor as an argument. Below it's used with a `from_statement` load:
{python}
# mapping is the users->addresses mapping
@@ -1372,7 +1374,7 @@ When full statement or result-set loads are used with `Query`, SQLAlchemy does n
})
# define a query on USERS with an outer join to ADDRESSES
- statement = users_table.outerjoin(addresses_table).select(use_labels=True)
+ statement = users_table.outerjoin(addresses_table).select().apply_labels()
# construct a Query object which expects the "addresses" results
query = session.query(User).options(contains_eager('addresses'))
@@ -1380,36 +1382,48 @@ When full statement or result-set loads are used with `Query`, SQLAlchemy does n
# get results normally
r = query.from_statement(statement)
+It works just as well with an inline `Query.join()` or `Query.outerjoin()`:
+
+ {python}
+ session.query(User).outerjoin(User.addresses).options(contains_eager(User.addresses)).all()
+
If the "eager" portion of the statement is "aliased", 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
- adalias = addresses_table.alias('adalias')
+ # use an alias of the Address entity
+ adalias = aliased(Address)
- # define a query on USERS with an outer join to adalias
- statement = users_table.outerjoin(adalias).select(use_labels=True)
-
# construct a Query object which expects the "addresses" results
- query = session.query(User).options(contains_eager('addresses', alias=adalias))
+ query = session.query(User).outerjoin((adalias, User.addresses)).options(contains_eager(User.addresses, alias=adalias))
# get results normally
- {sql}r = query.from_statement(statement).all()
+ {sql}r = 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
+ FROM users LEFT OUTER JOIN email_addresses AS email_addresses_1 ON users.user_id = email_addresses_1.user_id
+
+The path given as the argument to `contains_eager()` needs to be a full path from the starting entity. For example if we were loading `Users->orders->Order->items->Item`, the string version would look like:
-In the case that the main table itself is also aliased, the `contains_alias()` option can be used:
+ {python}
+ query(User).options(contains_eager('orders', 'items'))
+
+The descriptor version like:
+
+ {python}
+ query(User).options(contains_eager(User.orders, Order.items))
+
+A variant on `contains_eager()` is the `contains_alias()` option, which is used in the rare case that the parent object is loaded from an alias within a user-defined SELECT statement:
{python}
# define an aliased UNION called 'ulist'
statement = users.select(users.c.user_id==7).union(users.select(users.c.user_id>7)).alias('ulist')
# add on an eager load of "addresses"
- statement = statement.outerjoin(addresses).select(use_labels=True)
+ statement = statement.outerjoin(addresses).select().apply_labels()
# create query, indicating "ulist" is an alias for the main table, "addresses" property should
# be eager loaded
- query = create_session().query(User).options(contains_alias('ulist'), contains_eager('addresses'))
+ query = session.query(User).options(contains_alias('ulist'), contains_eager('addresses'))
# results
r = query.from_statement(statement)
diff --git a/doc/build/content/ormtutorial.txt b/doc/build/content/ormtutorial.txt
index 85ffa0ff9..f784d57cf 100644
--- a/doc/build/content/ormtutorial.txt
+++ b/doc/build/content/ormtutorial.txt
@@ -1,23 +1,20 @@
-[alpha_api]: javascript:alphaApi()
-[alpha_implementation]: javascript:alphaImplementation()
-
Object Relational Tutorial {@name=datamapping}
============
-In this tutorial we will cover a basic SQLAlchemy object-relational mapping scenario, where we store and retrieve Python objects from a database representation. The database schema will begin with one table, and will later develop into several. The tutorial is in doctest format, meaning each `>>>` line represents something you can type at a Python command prompt, and the following text represents the expected return value. The tutorial has no prerequisites.
+In this tutorial we will cover a basic SQLAlchemy object-relational mapping scenario, where we store and retrieve Python objects from a database representation. The tutorial is in doctest format, meaning each `>>>` line represents something you can type at a Python command prompt, and the following text represents the expected return value.
## Version Check
-A quick check to verify that we are on at least **version 0.4** of SQLAlchemy:
+A quick check to verify that we are on at least **version 0.5** of SQLAlchemy:
{python}
>>> import sqlalchemy
>>> sqlalchemy.__version__ # doctest:+SKIP
- 0.4.0
+ 0.5.0
## Connecting
-For this tutorial we will use an in-memory-only SQLite database. This is an easy way to test things without needing to have an actual database defined anywhere. To connect we use `create_engine()`:
+For this tutorial we will use an in-memory-only SQLite database. To connect we use `create_engine()`:
{python}
>>> from sqlalchemy import create_engine
@@ -27,21 +24,21 @@ The `echo` flag is a shortcut to setting up SQLAlchemy logging, which is accompl
## Define and Create a Table {@name=tables}
-Next we want to tell SQLAlchemy about our tables. We will start with just a single table called `users`, which will store records for the end-users using our application (lets assume it's a website). We define our tables all within a catalog called `MetaData`, using the `Table` construct, which resembles regular SQL CREATE TABLE syntax:
+Next we want to tell SQLAlchemy about our tables. We will start with just a single table called `users`, which will store records for the end-users using our application (lets assume it's a website). We define our tables within a catalog called `MetaData`, using the `Table` construct, which is used in a manner similar to SQL's CREATE TABLE syntax:
{python}
>>> from sqlalchemy import Table, Column, Integer, String, MetaData, ForeignKey
>>> metadata = MetaData()
>>> users_table = Table('users', metadata,
... Column('id', Integer, primary_key=True),
- ... Column('name', String(40)),
- ... Column('fullname', String(100)),
- ... Column('password', String(15))
+ ... Column('name', String),
+ ... Column('fullname', String),
+ ... Column('password', String)
... )
-All about how to define `Table` objects, as well as how to create them from an existing database automatically, is described in [metadata](rel:metadata).
+All about how to define `Table` objects, as well as how to load their definition from an existing database (known as **reflection**), is described in [metadata](rel:metadata).
-Next, to tell the `MetaData` we'd actually like to create our `users_table` for real inside the SQLite database, we use `create_all()`, passing it the `engine` instance which points to our database. This will check for the presence of a table first before creating, so it's safe to call multiple times:
+Next, we can issue CREATE TABLE statements derived from our table metadata, by calling `create_all()` and passing it the `engine` instance which points to our database. This will check for the presence of a table first before creating, so it's safe to call multiple times:
{python}
{sql}>>> metadata.create_all(engine) # doctest:+ELLIPSIS,+NORMALIZE_WHITESPACE
@@ -49,19 +46,24 @@ Next, to tell the `MetaData` we'd actually like to create our `users_table` for
{}
CREATE TABLE users (
id INTEGER NOT NULL,
- name VARCHAR(40),
- fullname VARCHAR(100),
- password VARCHAR(15),
+ name VARCHAR,
+ fullname VARCHAR,
+ password VARCHAR,
PRIMARY KEY (id)
)
{}
COMMIT
-So now our database is created, our initial schema is present, and our SQLAlchemy application knows all about the tables and columns in the database; this information is to be re-used by the Object Relational Mapper, as we'll see now.
-
+Users familiar with the syntax of CREATE TABLE may notice that the VARCHAR columns were generated without a length; on SQLite, this is a valid datatype, but on most databases it's not allowed. So if running this tutorial on a database such as Postgres or MySQL, and you wish to use SQLAlchemy to generate the tables, a "length" may be provided to the `String` type as below:
+
+ {python}
+ Column('name', String(50))
+
+The length field on `String`, as well as similar precision/scale fields available on `Integer`, `Numeric`, etc. are not referenced by SQLAlchemy other than when creating tables.
+
## Define a Python Class to be Mapped {@name=mapping}
-So lets create a rudimentary `User` object to be mapped in the database. This object will for starters have three attributes, `name`, `fullname` and `password`. It only need subclass Python's built-in `object` class (i.e. it's a new style class). We will give it a constructor so that it may conveniently be instantiated with its attributes at once, as well as a `__repr__` method so that we can get a nice string representation of it:
+While the `Table` object defines information about our database, it does not say anything about the definition or behavior of the business objects used by our application; SQLAlchemy views this as a separate concern. To correspond to our `users` table, let's create a rudimentary `User` class. It only need subclass Python's built-in `object` class (i.e. it's a new style class):
{python}
>>> class User(object):
@@ -73,6 +75,8 @@ So lets create a rudimentary `User` object to be mapped in the database. This o
... def __repr__(self):
... return "<User('%s','%s', '%s')>" % (self.name, self.fullname, self.password)
+The class has an `__init__()` and a `__repr__()` method for convenience. These methods are both entirely optional, and can be of any form. SQLAlchemy never calls `__init__()` directly.
+
## Setting up the Mapping
With our `users_table` and `User` class, we now want to map the two together. That's where the SQLAlchemy ORM package comes in. We'll use the `mapper` function to create a **mapping** between `users_table` and `User`:
@@ -80,9 +84,9 @@ With our `users_table` and `User` class, we now want to map the two together. T
{python}
>>> from sqlalchemy.orm import mapper
>>> mapper(User, users_table) # doctest:+ELLIPSIS,+NORMALIZE_WHITESPACE
- <sqlalchemy.orm.mapper.Mapper object at 0x...>
+ <Mapper at 0x...; User>
-The `mapper()` function creates a new `Mapper` object and stores it away for future reference. It also **instruments** the attributes on our `User` class, corresponding to the `users_table` table. The `id`, `name`, `fullname`, and `password` columns in our `users_table` are now instrumented upon our `User` class, meaning it will keep track of all changes to these attributes, and can save and load their values to/from the database. Lets create our first user, 'Ed Jones', and ensure that the object has all three of these attributes:
+The `mapper()` function creates a new `Mapper` object and stores it away for future reference, associated with our class. Let's now create and inspect a `User` object:
{python}
>>> ed_user = User('ed', 'Ed Jones', 'edspassword')
@@ -93,53 +97,87 @@ The `mapper()` function creates a new `Mapper` object and stores it away for fut
>>> str(ed_user.id)
'None'
-What was that last `id` attribute? That was placed there by the `Mapper`, to track the value of the `id` column in the `users_table`. Since our `User` doesn't exist in the database, its id is `None`. When we save the object, it will get populated automatically with its new id.
+The `id` attribute, which while not defined by our `__init__()` method, exists due to the `id` column present within the `users_table` object. By default, the `mapper` creates class attributes for all columns present within the `Table`. These class attributes exist as Python descriptors, and define **instrumentation** for the mapped class. The functionality of this instrumentation is very rich and includes the ability to track modifications and automatically load new data from the database when needed.
-## Too Verbose ? There are alternatives
+Since we have not yet told SQLAlchemy to persist `Ed Jones` within the database, its id is `None`. When we persist the object later, this attribute will be populated with a newly generated value.
-The full set of steps to map a class, which are to define a `Table`, define a class, and then define a `mapper()`, are fairly verbose and for simple cases may appear overly disjoint. Most popular object relational products use the so-called "active record" approach, where the table definition and its class mapping are all defined at once. With SQLAlchemy, there are two excellent alternatives to its usual configuration which provide this approach:
+## Creating Table, Class and Mapper All at Once Declaratively {@name=declarative}
- * [Elixir](http://elixir.ematia.de/) is a "sister" product to SQLAlchemy, which is a full "declarative" layer built on top of SQLAlchemy. It has existed almost as long as SA itself and defines a rich featureset on top of SA's normal configuration, adding many new capabilities such as plugins, automatic generation of table and column names based on configurations, and an intuitive system of defining relations.
- * [declarative](rel:plugins_declarative) is a so-called "micro-declarative" plugin included with SQLAlchemy 0.4.4 and above. In contrast to Elixir, it maintains the use of the same configurational constructs outlined in this tutorial, except it allows the `Column`, `relation()`, and other constructs to be defined "inline" with the mapped class itself, so that explicit calls to `Table` and `mapper()` are not needed in most cases.
+The preceding approach to configuration involving a `Table`, user-defined class, and `mapper()` call illustrate classical SQLAlchemy usage, which values the highest separation of concerns possible. A large number of applications don't require this degree of separation, and for those SQLAlchemy offers an alternate "shorthand" configurational style called **declarative**. For many applications, this is the only style of configuration needed. Our above example using this style is as follows:
-With either declarative layer it's a good idea to be familiar with SQLAlchemy's "base" configurational style in any case. But now that we have our configuration started, we're ready to look at how to build sessions and query the database; this process is the same regardless of configurational style.
+ {python}
+ >>> from sqlalchemy.ext.declarative import declarative_base
+
+ >>> Base = declarative_base()
+ >>> class User(Base):
+ ... __tablename__ = 'users'
+ ...
+ ... id = Column(Integer, primary_key=True)
+ ... name = Column(String)
+ ... fullname = Column(String)
+ ... password = Column(String)
+ ...
+ ... def __init__(self, name, fullname, password):
+ ... self.name = name
+ ... self.fullname = fullname
+ ... self.password = password
+ ...
+ ... def __repr__(self):
+ ... return "<User('%s','%s', '%s')>" % (self.name, self.fullname, self.password)
-## Creating a Session
+Above, the `declarative_base()` function defines a new class which we name `Base`, from which all of our ORM-enabled classes will derive. Note that we define `Column` objects with no "name" field, since it's inferred from the given attribute name.
+
+The underlying `Table` object created by our `declarative_base()` version of `User` is accessible via the `__table__` attribute:
-We're now ready to start talking to the database. The ORM's "handle" to the database is the `Session`. When we first set up the application, at the same level as our `create_engine()` statement, we define a second object called `Session` (or whatever you want to call it, `create_session`, etc.) which is configured by the `sessionmaker()` function. This function is configurational and need only be called once.
+ {python}
+ >>> users_table = User.__table__
+and the owning `MetaData` object is available as well:
+
+ {python}
+ >>> metadata = Base.metadata
+
+Yet another "declarative" method is available for SQLAlchemy as a third party library called [Elixir](http://elixir.ematia.de/). This is a full-featured configurational product which also includes many higher level mapping configurations built in. Like declarative, once classes and mappings are defined, ORM usage is the same as with a classical SQLAlchemy configuration.
+
+## Creating a Session
+
+We're now ready to start talking to the database. The ORM's "handle" to the database is the `Session`. When we first set up the application, at the same level as our `create_engine()` statement, we define a `Session` class which will serve as a factory for new `Session` objects:
+
{python}
>>> from sqlalchemy.orm import sessionmaker
- >>> Session = sessionmaker(bind=engine, autoflush=True, transactional=True)
+ >>> Session = sessionmaker(bind=engine)
In the case where your application does not yet have an `Engine` when you define your module-level objects, just set it up like this:
{python}
- >>> Session = sessionmaker(autoflush=True, transactional=True)
+ >>> Session = sessionmaker()
Later, when you create your engine with `create_engine()`, connect it to the `Session` using `configure()`:
{python}
>>> Session.configure(bind=engine) # once engine is available
-This `Session` class will create new `Session` objects which are bound to our database and have the transactional characteristics we've configured. Whenever you need to have a conversation with the database, you instantiate a `Session`:
+This custom-made `Session` class will create new `Session` objects which are bound to our database. Other transactional characteristics may be defined when calling `sessionmaker()` as well; these are described in a later chapter. Then, whenever you need to have a conversation with the database, you instantiate a `Session`:
{python}
>>> session = Session()
-The above `Session` is associated with our SQLite `engine`, but it hasn't opened any connections yet. When it's first used, it retrieves a connection from a pool of connections maintained by the `engine`, and holds onto it until we commit all changes and/or close the session object. Because we configured `transactional=True`, there's also a transaction in progress (one notable exception to this is MySQL, when you use its default table style of MyISAM). There's options available to modify this behavior but we'll go with this straightforward version to start.
+The above `Session` is associated with our SQLite `engine`, but it hasn't opened any connections yet. When it's first used, it retrieves a connection from a pool of connections maintained by the `engine`, and holds onto it until we commit all changes and/or close the session object.
-## Saving Objects
+## Adding new Objects
-So saving our `User` is as easy as issuing `save()`:
+To persist our `User` object, we `add()` it to our `Session`:
{python}
- >>> session.save(ed_user)
+ >>> ed_user = User('ed', 'Ed Jones', 'edspassword')
+ >>> session.add(ed_user)
-But you'll notice nothing has happened yet. Well, lets pretend something did, and try to query for our user. This is done using the `query()` method on `Session`. We create a new query representing the set of all `User` objects first. Then we narrow the results by "filtering" down to the user we want; that is, the user whose `name` attribute is `"ed"`. Finally we call `first()` which tells `Query`, "we'd like the first result in this list".
+At this point, the instance is **pending**; no SQL has yet been issued. The `Session` will issue the SQL to persist `Ed Jones` as soon as is needed, using a process known as a **flush**. If we query the database for `Ed Jones`, all pending information will first be flushed, and the query is issued afterwards.
+
+For example, below we create a new `Query` object which loads instances of `User`. We "filter by" the `name` attribute of `ed`, and indicate that we'd like only the first result in the full list of rows. A `User` instance is returned which is equivalent to that which we've added:
{python}
- {sql}>>> session.query(User).filter_by(name='ed').first() # doctest:+ELLIPSIS,+NORMALIZE_WHITESPACE
+ {sql}>>> our_user = session.query(User).filter_by(name='ed').first() # doctest:+ELLIPSIS,+NORMALIZE_WHITESPACE
BEGIN
INSERT INTO users (name, fullname, password) VALUES (?, ?, ?)
['ed', 'Ed Jones', 'edspassword']
@@ -148,23 +186,45 @@ But you'll notice nothing has happened yet. Well, lets pretend something did, a
WHERE users.name = ? ORDER BY users.oid
LIMIT 1 OFFSET 0
['ed']
- {stop}<User('ed','Ed Jones', 'edspassword')>
+ {stop}>>> our_user
+ <User('ed','Ed Jones', 'edspassword')>
+
+In fact, the `Session` has identified that the row returned is the **same** row as one already represented within its internal map of objects, so we actually got back the identical instance as that which we just added:
+
+ {python}
+ >>> ed_user is our_user
+ True
-And we get back our new user. If you view the generated SQL, you'll see that the `Session` issued an `INSERT` statement before querying. The `Session` stores whatever you put into it in memory, and at certain points it issues a **flush**, which issues SQL to the database to store all pending new objects and changes to existing objects. You can manually invoke the flush operation using `flush()`; however when the `Session` is configured to `autoflush`, it's usually not needed.
+The ORM concept at work here is known as an **identity map** and ensures that all operations upon a particular row within a `Session` operate upon the same set of data. Once an object with a particular primary key is present in the `Session`, all SQL queries on that `Session` will always return the same Python object for that particular primary key; it also will raise an error if an attempt is made to place a second, already-persisted object with the same primary key within the session.
-OK, let's do some more operations. We'll create and save three more users:
+We can add more `User` objects at once using `add_all()`:
{python}
- >>> session.save(User('wendy', 'Wendy Williams', 'foobar'))
- >>> session.save(User('mary', 'Mary Contrary', 'xxg527'))
- >>> session.save(User('fred', 'Fred Flinstone', 'blah'))
+ >>> session.add_all([
+ ... User('wendy', 'Wendy Williams', 'foobar'),
+ ... User('mary', 'Mary Contrary', 'xxg527'),
+ ... User('fred', 'Fred Flinstone', 'blah')])
Also, Ed has already decided his password isn't too secure, so lets change it:
{python}
>>> ed_user.password = 'f8s7ccs'
+
+The `Session` is paying attention. It knows, for example, that `Ed Jones` has been modified:
+
+ {python}
+ >>> session.dirty
+ IdentitySet([<User('ed','Ed Jones', 'f8s7ccs')>])
-Then we'll permanently store everything thats been changed and added to the database. We do this via `commit()`:
+and that three new `User` objects are pending:
+
+ {python}
+ >>> session.new # doctest: +NORMALIZE_WHITESPACE
+ IdentitySet([<User('wendy','Wendy Williams', 'foobar')>,
+ <User('mary','Mary Contrary', 'xxg527')>,
+ <User('fred','Fred Flinstone', 'blah')>])
+
+We tell the `Session` that we'd like to issue all remaining changes to the database and commit the transaction, which has been in progress throughout. We do this via `commit()`:
{python}
{sql}>>> session.commit()
@@ -183,114 +243,139 @@ Then we'll permanently store everything thats been changed and added to the data
If we look at Ed's `id` attribute, which earlier was `None`, it now has a value:
{python}
- >>> ed_user.id
- 1
-
-After each `INSERT` operation, the `Session` assigns all newly generated ids and column defaults to the mapped object instance. For column defaults which are database-generated and are not part of the table's primary key, they'll be loaded when you first reference the attribute on the instance.
-
-One crucial thing to note about the `Session` is that each object instance is cached within the Session, based on its primary key identifier. The reason for this cache is not as much for performance as it is for maintaining an **identity map** of instances. This map guarantees that whenever you work with a particular `User` object in a session, **you always get the same instance back**. As below, reloading Ed gives us the same instance back:
-
- {python}
- {sql}>>> ed_user is session.query(User).filter_by(name='ed').one() # doctest: +NORMALIZE_WHITESPACE
+ {sql}>>> ed_user.id # doctest: +NORMALIZE_WHITESPACE
BEGIN
SELECT users.id AS users_id, users.name AS users_name, users.fullname AS users_fullname, users.password AS users_password
FROM users
- WHERE users.name = ? ORDER BY users.oid
- LIMIT 2 OFFSET 0
- ['ed']
- {stop}True
+ WHERE users.id = ?
+ [1]
+ {stop}1
-The `get()` method, which queries based on primary key, will not issue any SQL to the database if the given key is already present:
+After the `Session` inserts new rows in the database, all newly generated identifiers and database-generated defaults become available on the instance, either immediately or via load-on-first-access. In this case, the entire row was re-loaded on access because a new transaction was begun after we issued `commit()`. SQLAlchemy by default refreshes data from a previous transaction the first time it's accessed within a new transaction, so that the most recent state is available. The level of reloading is configurable as is described in the chapter on Sessions.
- {python}
- >>> ed_user is session.query(User).get(ed_user.id)
- True
-
## Querying
-A whirlwind tour through querying.
-
-A `Query` is created from the `Session`, relative to a particular class we wish to load.
+A `Query` is created using the `query()` function on `Session`. This function takes a variable number of arguments, which can be any combination of classes and class-instrumented descriptors. Below, we indicate a `Query` which loads `User` instances. When evaluated in an iterative context, the list of `User` objects present is returned:
{python}
- >>> query = session.query(User)
+ {sql}>>> for instance in session.query(User): # doctest: +NORMALIZE_WHITESPACE
+ ... print instance.name, instance.fullname
+ SELECT users.id AS users_id, users.name AS users_name,
+ users.fullname AS users_fullname, users.password AS users_password
+ FROM users ORDER BY users.oid
+ []
+ {stop}ed Ed Jones
+ wendy Wendy Williams
+ mary Mary Contrary
+ fred Fred Flinstone
-Once we have a query, we can start loading objects. The Query object, when first created, represents all the instances of its main class. You can iterate through it directly:
+The `Query` also accepts ORM-instrumented descriptors as arguments. Any time multiple class entities or column-based entities are expressed as arguments to the `query()` function, the return result is expressed as tuples:
{python}
- {sql}>>> for user in session.query(User):
- ... print user.name
- SELECT users.id AS users_id, users.name AS users_name, users.fullname AS users_fullname, users.password AS users_password
- FROM users ORDER BY users.oid
+ {sql}>>> for name, fullname in session.query(User.name, User.fullname): # doctest: +NORMALIZE_WHITESPACE
+ ... print name, fullname
+ SELECT users.name AS users_name, users.fullname AS users_fullname
+ FROM users
[]
- {stop}ed
- wendy
- mary
- fred
+ {stop}ed Ed Jones
+ wendy Wendy Williams
+ mary Mary Contrary
+ fred Fred Flinstone
-...and the SQL will be issued at the point where the query is evaluated as a list. If you apply array slices before iterating, LIMIT and OFFSET are applied to the query:
+Basic operations with `Query` include issuing LIMIT and OFFSET, most conveniently using Python array slices and typically in conjunction with ORDER BY:
{python}
- {sql}>>> for u in session.query(User)[1:3]: #doctest: +NORMALIZE_WHITESPACE
+ {sql}>>> for u in session.query(User).order_by(User.id)[1:3]: #doctest: +NORMALIZE_WHITESPACE
... print u
SELECT users.id AS users_id, users.name AS users_name, users.fullname AS users_fullname, users.password AS users_password
- FROM users ORDER BY users.oid
+ FROM users ORDER BY users.id
LIMIT 2 OFFSET 1
[]
{stop}<User('wendy','Wendy Williams', 'foobar')>
<User('mary','Mary Contrary', 'xxg527')>
-Narrowing the results down is accomplished either with `filter_by()`, which uses keyword arguments:
+and filtering results, which is accomplished either with `filter_by()`, which uses keyword arguments:
{python}
- {sql}>>> for user in session.query(User).filter_by(name='ed', fullname='Ed Jones'):
- ... print user
- SELECT users.id AS users_id, users.name AS users_name, users.fullname AS users_fullname, users.password AS users_password
- FROM users
- WHERE users.fullname = ? AND users.name = ? ORDER BY users.oid
- ['Ed Jones', 'ed']
- {stop}<User('ed','Ed Jones', 'f8s7ccs')>
+ {sql}>>> for name, in session.query(User.name).filter_by(fullname='Ed Jones'): # doctest: +NORMALIZE_WHITESPACE
+ ... print name
+ SELECT users.name AS users_name FROM users
+ WHERE users.fullname = ?
+ ['Ed Jones']
+ {stop}ed
-...or `filter()`, which uses SQL expression language constructs. These allow you to use regular Python operators with the class-level attributes on your mapped class:
+...or `filter()`, which uses more flexible SQL expression language constructs. These allow you to use regular Python operators with the class-level attributes on your mapped class:
{python}
- {sql}>>> for user in session.query(User).filter(User.name=='ed'):
- ... print user
- SELECT users.id AS users_id, users.name AS users_name, users.fullname AS users_fullname, users.password AS users_password
- FROM users
- WHERE users.name = ? ORDER BY users.oid
- ['ed']
- {stop}<User('ed','Ed Jones', 'f8s7ccs')>
+ {sql}>>> for name, in session.query(User.name).filter(User.fullname=='Ed Jones'): # doctest: +NORMALIZE_WHITESPACE
+ ... print name
+ SELECT users.name AS users_name FROM users
+ WHERE users.fullname = ?
+ ['Ed Jones']
+ {stop}ed
-You can also use the `Column` constructs attached to the `users_table` object to construct SQL expressions:
+The `Query` object is fully *generative*, meaning that most method calls return a new `Query` object upon which further criteria may be added. For example, to query for users named "ed" with a full name of "Ed Jones", you can call `filter()` twice, which joins criteria using `AND`:
{python}
- {sql}>>> for user in session.query(User).filter(users_table.c.name=='ed'):
+ {sql}>>> for user in session.query(User).filter(User.name=='ed').filter(User.fullname=='Ed Jones'): # doctest: +NORMALIZE_WHITESPACE
... print user
SELECT users.id AS users_id, users.name AS users_name, users.fullname AS users_fullname, users.password AS users_password
FROM users
- WHERE users.name = ? ORDER BY users.oid
- ['ed']
+ WHERE users.name = ? AND users.fullname = ? ORDER BY users.oid
+ ['ed', 'Ed Jones']
{stop}<User('ed','Ed Jones', 'f8s7ccs')>
-Most common SQL operators are available, such as `LIKE`:
- {python}
- {sql}>>> session.query(User).filter(User.name.like('%ed'))[1] # doctest: +NORMALIZE_WHITESPACE
- SELECT users.id AS users_id, users.name AS users_name, users.fullname AS users_fullname, users.password AS users_password
- FROM users
- WHERE users.name LIKE ? ORDER BY users.oid
- LIMIT 1 OFFSET 1
- ['%ed']
- {stop}<User('fred','Fred Flinstone', 'blah')>
+### Common Filter Operators
-Note above our array index of `1` placed the appropriate LIMIT/OFFSET and returned a scalar result immediately.
+Here's a rundown of some of the most common operators used in `filter()`:
-The `all()`, `one()`, and `first()` methods immediately issue SQL without using an iterative context or array index. `all()` returns a list:
+ * equals
+
+ {python}
+ query.filter(User.name == 'ed')
+
+ * not equals
+
+ {python}
+ query.filter(User.name != 'ed')
+
+ * LIKE
+
+ {python}
+ query.filter(User.name.like('%ed%'))
+
+ * IN
+
+ {python}
+ query.filter(User.name.in_(['ed', 'wendy', 'jack']))
+
+ * IS NULL
+
+ {python}
+ filter(User.name == None)
+
+ * AND
+
+ {python}
+ from sqlalchemy import and_
+ filter(and_(User.name == 'ed', User.fullname == 'Ed Jones'))
+
+ # or call filter()/filter_by() multiple times
+ filter(User.name == 'ed').filter(User.fullname == 'Ed Jones')
+
+ * OR
+
+ {python}
+ from sqlalchemy import or_
+ filter(or_(User.name == 'ed', User.name == 'wendy'))
+
+### Returning Lists and Scalars {@name=scalars}
+
+The `all()`, `one()`, and `first()` methods of `Query` immediately issue SQL and return a non-iterator value. `all()` returns a list:
{python}
>>> query = session.query(User).filter(User.name.like('%ed'))
-
{sql}>>> query.all()
SELECT users.id AS users_id, users.name AS users_name, users.fullname AS users_fullname, users.password AS users_password
FROM users
@@ -309,7 +394,7 @@ The `all()`, `one()`, and `first()` methods immediately issue SQL without using
['%ed']
{stop}<User('ed','Ed Jones', 'f8s7ccs')>
-and `one()`, applies a limit of *two*, and if not exactly one row returned (no more, no less), raises an error:
+`one()`, applies a limit of *two*, and if not exactly one row returned, raises an error:
{python}
{sql}>>> try:
@@ -323,32 +408,9 @@ and `one()`, applies a limit of *two*, and if not exactly one row returned (no m
['%ed']
{stop}Multiple rows returned for one()
-All `Query` methods that don't return a result instead return a new `Query` object, with modifications applied. Therefore you can call many query methods successively to build up the criterion you want:
-
- {python}
- {sql}>>> session.query(User).filter(User.id<2).filter_by(name='ed').\
- ... filter(User.fullname=='Ed Jones').all()
- SELECT users.id AS users_id, users.name AS users_name, users.fullname AS users_fullname, users.password AS users_password
- FROM users
- WHERE users.id < ? AND users.name = ? AND users.fullname = ? ORDER BY users.oid
- [2, 'ed', 'Ed Jones']
- {stop}[<User('ed','Ed Jones', 'f8s7ccs')>]
-
-If you need to use other conjunctions besides `AND`, all SQL conjunctions are available explicitly within expressions, such as `and_()` and `or_()`, when using `filter()`:
+### Using Literal SQL {@naqme=literal}
- {python}
- >>> from sqlalchemy import and_, or_
-
- {sql}>>> session.query(User).filter(
- ... and_(User.id<224, or_(User.name=='ed', User.name=='wendy'))
- ... ).all()
- SELECT users.id AS users_id, users.name AS users_name, users.fullname AS users_fullname, users.password AS users_password
- FROM users
- WHERE users.id < ? AND (users.name = ? OR users.name = ?) ORDER BY users.oid
- [224, 'ed', 'wendy']
- {stop}[<User('ed','Ed Jones', 'f8s7ccs')>, <User('wendy','Wendy Williams', 'foobar')>]
-
-You also have full ability to use literal strings to construct SQL. For a single criterion, use a string with `filter()`:
+Literal strings can be used flexibly with `Query`. Most methods accept strings in addition to SQLAlchemy clause constructs. For example, `filter()`:
{python}
{sql}>>> for user in session.query(User).filter("id<224").all():
@@ -374,8 +436,6 @@ Bind parameters can be specified with string-based SQL, using a colon. To speci
[224, 'fred']
{stop}<User('fred','Fred Flinstone', 'blah')>
-Note that when we use constructed SQL expressions, bind parameters are generated for us automatically; we don't need to worry about them.
-
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 (below illustrated using an asterisk):
{python}
@@ -384,51 +444,39 @@ To use an entirely string-based statement, using `from_statement()`; just ensure
['ed']
{stop}[<User('ed','Ed Jones', 'f8s7ccs')>]
-`from_statement()` can also accomodate full `select()` constructs. These are described in the [sql](rel:sql):
+## Building a Relation {@name=relation}
- {python}
- >>> from sqlalchemy import select, func
-
- {sql}>>> session.query(User).from_statement(
- ... select(
- ... [users_table],
- ... select([func.max(users_table.c.name)]).label('maxuser')==users_table.c.name)
- ... ).all() # doctest: +NORMALIZE_WHITESPACE
- SELECT users.id AS users_id, users.name AS users_name, users.fullname AS users_fullname, users.password AS users_password
- FROM users
- WHERE (SELECT max(users.name) AS max_1
- FROM users) = users.name
- []
- {stop}[<User('wendy','Wendy Williams', 'foobar')>]
-
-There's 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:
+Now let's consider a second table to be dealt with. Users in our system also can store any number of email addresses associated with their username. This implies a basic one to many association from the `users_table` to a new table which stores email addresses, which we will call `addresses`. Using declarative, we define this table along with its mapped class, `Address`:
{python}
- {sql}>>> for r in session.query(User).\
- ... add_column(select([func.max(users_table.c.name)]).label('maxuser')):
- ... print r # doctest: +NORMALIZE_WHITESPACE
- SELECT users.id AS users_id, users.name AS users_name, users.fullname AS users_fullname, users.password AS users_password, (SELECT max(users.name) AS max_1
- FROM users) AS maxuser
- FROM users ORDER BY users.oid
- []
- {stop}(<User('ed','Ed Jones', 'f8s7ccs')>, u'wendy')
- (<User('wendy','Wendy Williams', 'foobar')>, u'wendy')
- (<User('mary','Mary Contrary', 'xxg527')>, u'wendy')
- (<User('fred','Fred Flinstone', 'blah')>, u'wendy')
+ >>> from sqlalchemy import ForeignKey
+ >>> from sqlalchemy.orm import relation
+ >>> class Address(Base):
+ ... __tablename__ = 'addresses'
+ ... id = Column(Integer, primary_key=True)
+ ... email_address = Column(String, nullable=False)
+ ... user_id = Column(Integer, ForeignKey('users.id'))
+ ...
+ ... user = relation(User, backref='addresses')
+ ...
+ ... def __init__(self, email_address):
+ ... self.email_address = email_address
+ ...
+ ... def __repr__(self):
+ ... return "<Address('%s')>" % self.email_address
-## Building a One-to-Many Relation {@name=onetomany}
+The above class introduces a **foreign key** constraint which references the `users` table. This defines for SQLAlchemy the relationship between the two tables at the database level. The relationship between the `User` and `Address` classes is defined separately using the `relation()` function, which defines an attribute `user` to be placed on the `Address` class, as well as an `addresses` collection to be placed on the `User` class. Such a relation is known as a **bidirectional** relationship. Because of the placement of the foreign key, from `Address` to `User` it is **many to one**, and from `User` to `Address` it is **one to many**. SQLAlchemy is automatically aware of many-to-one/one-to-many based on foreign keys.
-We've spent a lot of time dealing with just one class, and one table. Let's now look at how SQLAlchemy deals with two tables, which have a relationship to each other. Let's say that the users in our system also can store any number of email addresses associated with their username. This implies a basic one to many association from the `users_table` to a new table which stores email addresses, which we will call `addresses`. We will also create a relationship between this new table to the users table, using a `ForeignKey`:
+The `relation()` function is extremely flexible, and could just have easily been defined on the `User` class:
{python}
- >>> from sqlalchemy import ForeignKey
-
- >>> addresses_table = Table('addresses', metadata,
- ... Column('id', Integer, primary_key=True),
- ... Column('email_address', String(100), nullable=False),
- ... Column('user_id', Integer, ForeignKey('users.id')))
-
-Another call to `create_all()` will skip over our `users` table and build just the new `addresses` table:
+ class User(Base):
+ ....
+ addresses = relation("Address", backref="user")
+
+Where above we used the string name `"Addresses"` in the event that the `Address` class was not yet defined. We are also free to not define a backref, and to define the `relation()` only on one class and not the other. It is also possible to define two separate `relation()`s for either direction, which is generally safe for many-to-one and one-to-many relations, but not for many-to-many relations.
+
+We'll need to create the `addresses` table in the database, so we will issue another CREATE from our metadata, which will skip over tables which have already been created:
{python}
{sql}>>> metadata.create_all(engine) # doctest: +NORMALIZE_WHITESPACE
@@ -438,7 +486,7 @@ Another call to `create_all()` will skip over our `users` table and build just t
{}
CREATE TABLE addresses (
id INTEGER NOT NULL,
- email_address VARCHAR(100) NOT NULL,
+ email_address VARCHAR NOT NULL,
user_id INTEGER,
PRIMARY KEY (id),
FOREIGN KEY(user_id) REFERENCES users (id)
@@ -446,54 +494,21 @@ Another call to `create_all()` will skip over our `users` table and build just t
{}
COMMIT
-For our ORM setup, we're going to start all over again. We will first close out our `Session` and clear all `Mapper` objects:
-
- {python}
- >>> from sqlalchemy.orm import clear_mappers
- >>> session.close()
- >>> clear_mappers()
-
-Our `User` class, still around, reverts to being just a plain old class. Lets create an `Address` class to represent a user's email address:
-
- {python}
- >>> class Address(object):
- ... def __init__(self, email_address):
- ... self.email_address = email_address
- ...
- ... def __repr__(self):
- ... return "<Address('%s')>" % self.email_address
-
-Now comes the fun part. We define a mapper for each class, and associate them using a function called `relation()`. We can define each mapper in any order we want:
-
- {python}
- >>> from sqlalchemy.orm import relation
-
- >>> mapper(User, users_table, properties={ # doctest: +ELLIPSIS
- ... 'addresses':relation(Address, backref='user')
- ... })
- <sqlalchemy.orm.mapper.Mapper object at 0x...>
-
- >>> mapper(Address, addresses_table) # doctest: +ELLIPSIS
- <sqlalchemy.orm.mapper.Mapper object at 0x...>
-
-Above, the new thing we see is that `User` has defined a relation named `addresses`, which will reference a list of `Address` objects. How does it know it's a list ? SQLAlchemy figures it out for you, based on the foreign key relationship between `users_table` and `addresses_table`.
+## Working with Related Objects {@name=related_objects}
-## Working with Related Objects and Backreferences {@name=relation_backref}
-
-Now when we create a `User`, it automatically has this collection present:
+Now when we create a `User`, a blank `addresses` collection will be present. By default, the collection is a Python list. Other collection types, such as sets and dictionaries, are available as well:
{python}
>>> jack = User('jack', 'Jack Bean', 'gjffdd')
>>> jack.addresses
[]
-We are free to add `Address` objects, and the `session` will take care of everything for us.
+We are free to add `Address` objects on our `User` object. In this case we just assign a full list directly:
{python}
- >>> jack.addresses.append(Address(email_address='jack@google.com'))
- >>> jack.addresses.append(Address(email_address='j25@yahoo.com'))
-
-Before we save into the `Session`, lets examine one other thing that's happened here. The `addresses` collection is present on our `User` because we added a `relation()` with that name. But also within the `relation()` function is the keyword `backref`. This keyword indicates that we wish to make a **bi-directional relationship**. What this basically means is that not only did we generate a one-to-many relationship called `addresses` on the `User` class, we also generated a **many-to-one** relationship on the `Address` class. This relationship is self-updating, without any data being flushed to the database, as we can see on one of Jack's addresses:
+ >>> jack.addresses = [Address(email_address='jack@google.com'), Address(email_address='j25@yahoo.com')]
+
+When using a bidirectional relationship, elements added in one direction automatically become visible in the other direction. This is the basic behavior of the **backref** keyword, which maintains the relationship purely in memory, without using any SQL:
{python}
>>> jack.addresses[1]
@@ -501,13 +516,12 @@ Before we save into the `Session`, lets examine one other thing that's happened
>>> jack.addresses[1].user
<User('jack','Jack Bean', 'gjffdd')>
-
-Let's save into the session, then close out the session and create a new one...so that we can see how `Jack` and his email addresses come back to us:
+
+Let's add and commit `Jack Bean` to the database. `jack` as well as the two `Address` members in his `addresses` collection are both added to the session at once, using a process known as **cascading**:
{python}
- >>> session.save(jack)
+ >>> session.add(jack)
{sql}>>> session.commit()
- BEGIN
INSERT INTO users (name, fullname, password) VALUES (?, ?, ?)
['jack', 'Jack Bean', 'gjffdd']
INSERT INTO addresses (email_address, user_id) VALUES (?, ?)
@@ -516,8 +530,6 @@ Let's save into the session, then close out the session and create a new one...s
['j25@yahoo.com', 5]
COMMIT
- >>> session = Session()
-
Querying for Jack, we get just Jack back. No SQL is yet issued for for Jack's addresses:
{python}
@@ -542,14 +554,9 @@ Let's look at the `addresses` collection. Watch the SQL:
[5]
{stop}[<Address('jack@google.com')>, <Address('j25@yahoo.com')>]
-When we accessed the `addresses` collection, SQL was suddenly issued. This is an example of a **lazy loading relation**.
-
-If you want to reduce the number of queries (dramatically, in many cases), we can apply an **eager load** to the query operation. We clear out the session to ensure that a full reload occurs:
-
- {python}
- >>> session.clear()
+When we accessed the `addresses` collection, SQL was suddenly issued. This is an example of a **lazy loading relation**. The `addresses` collection is now loaded and behaves just like an ordinary list.
-Then apply an **option** to the query, indicating that we'd like `addresses` to load "eagerly". SQLAlchemy then constructs a join between the `users` and `addresses` tables:
+If you want to reduce the number of queries (dramatically, in many cases), we can apply an **eager load** to the query operation. With the same query, we may apply an **option** to the query, indicating that we'd like `addresses` to load "eagerly". SQLAlchemy then constructs an outer join between the `users` and `addresses` tables, and loads them at once, populating the `addresses` collection on each `User` object if it's not already populated:
{python}
>>> from sqlalchemy.orm import eagerload
@@ -572,38 +579,30 @@ Then apply an **option** to the query, indicating that we'd like `addresses` to
>>> jack.addresses
[<Address('jack@google.com')>, <Address('j25@yahoo.com')>]
-
-If you think that query is elaborate, it is ! But SQLAlchemy is just getting started. Note that when using eager loading, *nothing* changes as far as the ultimate results returned. The "loading strategy", as it's called, is designed to be completely transparent in all cases, and is for optimization purposes only. Any query criterion you use to load objects, including ordering, limiting, other joins, etc., should return identical results regardless of the combination of lazily- and eagerly- loaded relationships present.
-An eagerload targeting across multiple relations can use dot separated names:
+SQLAlchemy has the ability to control exactly which attributes and how many levels deep should be joined together in a single SQL query. More information on this feature is available in [advdatamapping_relation](rel:advdatamapping_relation).
- {python}
- query.options(eagerload('orders'), eagerload('orders.items'), eagerload('orders.items.keywords'))
-
-To roll up the above three individual `eagerload()` calls into one, use `eagerload_all()`:
-
- {python}
- query.options(eagerload_all('orders.items.keywords'))
-
## Querying with Joins {@name=joins}
-Which brings us to the next big topic. What if we want to create joins that *do* change the results ? For that, another `Query` tornado is coming....
-
-One way to join two tables together is just to compose a SQL expression. Below we make one up using the `id` and `user_id` attributes on our mapped classes:
+While the eager load created a JOIN specifically to populate a collection, we can also work explicitly with joins in many ways. For example, to construct a simple inner join between `User` and `Address`, we can just `filter()` their related columns together. Below we load the `User` and `Address` entities at once using this method:
{python}
- {sql}>>> session.query(User).filter(User.id==Address.user_id).\
- ... filter(Address.email_address=='jack@google.com').all()
- SELECT users.id AS users_id, users.name AS users_name, users.fullname AS users_fullname, users.password AS users_password
+ {sql}>>> for u, a in session.query(User, Address).filter(User.id==Address.user_id).\
+ ... filter(Address.email_address=='jack@google.com').all(): # doctest: +NORMALIZE_WHITESPACE
+ ... print u, a
+ SELECT users.id AS users_id, users.name AS users_name, users.fullname AS users_fullname,
+ users.password AS users_password, addresses.id AS addresses_id,
+ addresses.email_address AS addresses_email_address, addresses.user_id AS addresses_user_id
FROM users, addresses
WHERE users.id = addresses.user_id AND addresses.email_address = ? ORDER BY users.oid
['jack@google.com']
- {stop}[<User('jack','Jack Bean', 'gjffdd')>]
+ {stop}<User('jack','Jack Bean', 'gjffdd')> <Address('jack@google.com')>
-Or we can make a real JOIN construct; below we use the `join()` function available on `Table` to create a `Join` object, then tell the `Query` to use it as our FROM clause:
+Or we can make a real JOIN construct; one way to do so is to use the ORM `join()` function, and tell `Query` to "select from" this join:
{python}
- {sql}>>> session.query(User).select_from(users_table.join(addresses_table)).\
+ >>> from sqlalchemy.orm import join
+ {sql}>>> session.query(User).select_from(join(User, Address)).\
... filter(Address.email_address=='jack@google.com').all()
SELECT users.id AS users_id, users.name AS users_name, users.fullname AS users_fullname, users.password AS users_password
FROM users JOIN addresses ON users.id = addresses.user_id
@@ -611,12 +610,17 @@ Or we can make a real JOIN construct; below we use the `join()` function availab
['jack@google.com']
{stop}[<User('jack','Jack Bean', 'gjffdd')>]
-Note that the `join()` construct has no problem figuring out the correct join condition between `users_table` and `addresses_table`..the `ForeignKey` we constructed says it all.
+`join()` knows how to join between `User` and `Address` because there's only one foreign key between them. If there were no foreign keys, or several, `join()` would require a third argument indicating the ON clause of the join, in one of the following forms:
-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}
+ join(User, Address, User.id==Address.user_id) # explicit condition
+ join(User, Address, User.addresses) # specify relation from left to right
+ join(User, Address, 'addresses') # same, using a string
+
+The functionality of `join()` is also available generatively from `Query` itself using `Query.join`. This is most easily used with just the "ON" clause portion of the join, such as:
{python}
- {sql}>>> session.query(User).join('addresses').\
+ {sql}>>> session.query(User).join(User.addresses).\
... filter(Address.email_address=='jack@google.com').all()
SELECT users.id AS users_id, users.name AS users_name, users.fullname AS users_fullname, users.password AS users_password
FROM users JOIN addresses ON users.id = addresses.user_id
@@ -624,238 +628,173 @@ The easiest way to join is automatically, using the `join()` method on `Query`.
['jack@google.com']
{stop}[<User('jack','Jack Bean', 'gjffdd')>]
-By "A to B", we mean a single relation name or a path of relations. In our case we only have `User->addresses->Address` configured, but if we had a setup like `A->bars->B->bats->C->widgets->D`, a join along all four entities would look like:
+To explicitly specify the target of the join, use tuples to form an argument list similar to the standalone join. This becomes more important when using aliases and similar constructs:
{python}
- session.query(Foo).join(['bars', 'bats', 'widgets']).filter(...)
+ session.query(User).join((Address, User.addresses))
-Each time `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. When you `join()` again, the joinpoint starts back from the root. We can also backtrack to the beginning explicitly using `reset_joinpoint()`. This instruction will place the joinpoint back at the root `users` table, where subsequent `filter_by()` criterion are again against `users`:
+Multiple joins can be created by passing a list of arguments:
{python}
- {sql}>>> session.query(User).join('addresses').\
- ... filter_by(email_address='jack@google.com').\
- ... reset_joinpoint().filter_by(name='jack').all()
- SELECT users.id AS users_id, users.name AS users_name, users.fullname AS users_fullname, users.password AS users_password
- FROM users JOIN addresses ON users.id = addresses.user_id
- WHERE addresses.email_address = ? AND users.name = ? ORDER BY users.oid
- ['jack@google.com', 'jack']
- {stop}[<User('jack','Jack Bean', 'gjffdd')>]
-
-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}>>> session.query(User).add_entity(Address).join('addresses').\
- ... filter(Address.email_address=='jack@google.com').all()
- SELECT users.id AS users_id, users.name AS users_name, users.fullname AS users_fullname, users.password AS users_password, addresses.id AS addresses_id, addresses.email_address AS addresses_email_address, addresses.user_id AS addresses_user_id
- FROM users JOIN addresses ON users.id = addresses.user_id
- WHERE addresses.email_address = ? ORDER BY users.oid
- ['jack@google.com']
- {stop}[(<User('jack','Jack Bean', 'gjffdd')>, <Address('jack@google.com')>)]
+ session.query(Foo).join(Foo.bars, Bar.bats, (Bat, 'widgets'))
+
+The above would produce SQL something like `foo JOIN bars ON <onclause> JOIN bats ON <onclause> JOIN widgets ON <onclause>`.
+
+### Using Aliases {@name=aliases}
-Another common scenario is the need to join on the same table more than once. For example, if we want to find a `User` who has two distinct email addresses, both `jack@google.com` as well as `j25@yahoo.com`, we need to join to the `Addresses` table twice. SQLAlchemy does provide `Alias` objects which can accomplish this; but far easier is just to tell `join()` to alias for you:
+When querying across multiple tables, if the same table needs to be referenced more than once, SQL typically requires that the table be *aliased* with another name, so that it can be distinguished against other occurences of that table. The `Query` supports this most expicitly using the `aliased` construct. Below we join to the `Address` entity twice, to locate a user who has two distinct email addresses at the same time:
{python}
- {sql}>>> session.query(User).\
- ... join('addresses', aliased=True).filter(Address.email_address=='jack@google.com').\
- ... join('addresses', aliased=True).filter(Address.email_address=='j25@yahoo.com').all()
- SELECT users.id AS users_id, users.name AS users_name, users.fullname AS users_fullname, users.password AS users_password
- FROM users JOIN addresses AS addresses_1 ON users.id = addresses_1.user_id JOIN addresses AS addresses_2 ON users.id = addresses_2.user_id
- WHERE addresses_1.email_address = ? AND addresses_2.email_address = ? ORDER BY users.oid
+ >>> from sqlalchemy.orm import aliased
+ >>> adalias1 = aliased(Address)
+ >>> adalias2 = aliased(Address)
+ {sql}>>> for username, email1, email2 in session.query(User.name, adalias1.email_address, adalias2.email_address).\
+ ... join((adalias1, User.addresses), (adalias2, User.addresses)).\
+ ... filter(adalias1.email_address=='jack@google.com').\
+ ... filter(adalias2.email_address=='j25@yahoo.com'):
+ ... print username, email1, email2 # doctest: +NORMALIZE_WHITESPACE
+ SELECT users.name AS users_name, addresses_1.email_address AS addresses_1_email_address,
+ addresses_2.email_address AS addresses_2_email_address
+ FROM users JOIN addresses AS addresses_1 ON users.id = addresses_1.user_id
+ JOIN addresses AS addresses_2 ON users.id = addresses_2.user_id
+ WHERE addresses_1.email_address = ? AND addresses_2.email_address = ?
['jack@google.com', 'j25@yahoo.com']
- {stop}[<User('jack','Jack Bean', 'gjffdd')>]
+ {stop}jack jack@google.com j25@yahoo.com
-The key thing which occurred above is that our SQL criterion were **aliased** as appropriate corresponding to the alias generated in the most recent `join()` call.
+### Using Subqueries {@name=subqueries}
-The next section describes some "higher level" operators, including `any()` and `has()`, which make patterns like joining to multiple aliases unnecessary in most cases.
+The `Query` is suitable for generating statements which can be used as subqueries. Suppose we wanted to load `User` objects along with a count of how many `Address` records each user has. The best way to generate SQL like this is to get the count of addresses grouped by user ids, and JOIN to the parent. In this case we use a LEFT OUTER JOIN so that we get rows back for those users who don't have any addresses, e.g.:
-### Relation Operators
+ {code}
+ SELECT users.*, adr_count.address_count FROM users LEFT OUTER JOIN
+ (SELECT user_id, count(*) AS address_count FROM addresses GROUP BY user_id) AS adr_count
+ ON users.id=adr_count.user_id
-A summary of all operators usable on relations:
+Using the `Query`, we build a statement like this from the inside out. The `statement` accessor returns a SQL expression representing the statement generated by a particular `Query` - this is an instance of a `select()` construct, which are described in [sql](rel:sql):
+
+ {python}
+ >>> from sqlalchemy.sql import func
+ >>> stmt = session.query(Address.user_id, func.count('*').label('address_count')).group_by(Address.user_id).statement.alias()
+
+The `func` keyword generates SQL functions, and the `alias()` method on `Select` (the return value of `query.statement`) creates a SQL alias, in this case an anonymous one which will have a generated name.
-* Filter on explicit column criterion, combined with a join. Column criterion can make usage of all supported SQL operators and expression constructs:
+Once we have our statement, it behaves like a `Table` construct, which we created for `users` at the top of this tutorial. The columns on the statement are accessible through an attribute called `c`:
- {python}
- {sql}>>> session.query(User).join('addresses').\
- ... filter(Address.email_address=='jack@google.com').all()
- SELECT users.id AS users_id, users.name AS users_name, users.fullname AS users_fullname, users.password AS users_password
- FROM users JOIN addresses ON users.id = addresses.user_id
- WHERE addresses.email_address = ? ORDER BY users.oid
- ['jack@google.com']
- {stop}[<User('jack','Jack Bean', 'gjffdd')>]
+ {python}
+ {sql}>>> for u, count in session.query(User, stmt.c.address_count).outerjoin((stmt, User.id==stmt.c.user_id)): # doctest: +NORMALIZE_WHITESPACE
+ ... print u, count
+ SELECT users.id AS users_id, users.name AS users_name,
+ users.fullname AS users_fullname, users.password AS users_password,
+ anon_1.address_count AS anon_1_address_count
+ FROM users LEFT OUTER JOIN (SELECT addresses.user_id AS user_id, count(?) AS address_count
+ FROM addresses GROUP BY addresses.user_id) AS anon_1 ON users.id = anon_1.user_id
+ ORDER BY users.oid
+ ['*']
+ {stop}<User('ed','Ed Jones', 'f8s7ccs')> None
+ <User('wendy','Wendy Williams', 'foobar')> None
+ <User('mary','Mary Contrary', 'xxg527')> None
+ <User('fred','Fred Flinstone', 'blah')> None
+ <User('jack','Jack Bean', 'gjffdd')> 2
- Criterion placed in `filter()` usually correspond to the last `join()` call; if the join was specified with `aliased=True`, class-level criterion against the join's target (or targets) will be appropriately aliased as well.
+### Using EXISTS
- {python}
- {sql}>>> session.query(User).join('addresses', aliased=True).\
- ... filter(Address.email_address=='jack@google.com').all()
- SELECT users.id AS users_id, users.name AS users_name, users.fullname AS users_fullname, users.password AS users_password
- FROM users JOIN addresses AS addresses_1 ON users.id = addresses_1.user_id
- WHERE addresses_1.email_address = ? ORDER BY users.oid
- ['jack@google.com']
- {stop}[<User('jack','Jack Bean', 'gjffdd')>]
+The EXISTS keyword in SQL is a boolean operator which returns True if the given expression contains any rows. It may be used in many scenarios in place of joins, and is also useful for locating rows which do not have a corresponding row in a related table.
-* Filter_by on key=value criterion, combined with a join. Same as `filter()` on column criterion except keyword arguments are used.
+There is an explicit EXISTS construct, which looks like this:
- {python}
- {sql}>>> session.query(User).join('addresses').\
- ... filter_by(email_address='jack@google.com').all()
- SELECT users.id AS users_id, users.name AS users_name, users.fullname AS users_fullname, users.password AS users_password
- FROM users JOIN addresses ON users.id = addresses.user_id
- WHERE addresses.email_address = ? ORDER BY users.oid
- ['jack@google.com']
- {stop}[<User('jack','Jack Bean', 'gjffdd')>]
-
-* Filter on explicit column criterion using `any()` (for collections) or `has()` (for scalar relations). This is a more succinct method than joining, as an `EXISTS` subquery is generated automatically. `any()` means, "find all parent items where any child item of its collection meets this criterion":
+ {python}
+ >>> from sqlalchemy.sql import exists
+ >>> stmt = exists().where(Address.user_id==User.id)
+ {sql}>>> for name, in session.query(User.name).filter(stmt): # doctest: +NORMALIZE_WHITESPACE
+ ... print name
+ SELECT users.name AS users_name
+ FROM users
+ WHERE EXISTS (SELECT *
+ FROM addresses
+ WHERE addresses.user_id = users.id)
+ []
+ {stop}jack
- {python}
- {sql}>>> session.query(User).\
- ... filter(User.addresses.any(Address.email_address=='jack@google.com')).all()
- SELECT users.id AS users_id, users.name AS users_name, users.fullname AS users_fullname, users.password AS users_password
- FROM users
- WHERE EXISTS (SELECT 1
- FROM addresses
- WHERE users.id = addresses.user_id AND addresses.email_address = ?) ORDER BY users.oid
- ['jack@google.com']
- {stop}[<User('jack','Jack Bean', 'gjffdd')>]
+The `Query` features several operators which make usage of EXISTS automatically. Above, the statement can be expressed along the `User.addresses` relation using `any()`:
+
+ {python}
+ {sql}>>> for name, in session.query(User.name).filter(User.addresses.any()): # doctest: +NORMALIZE_WHITESPACE
+ ... print name
+ SELECT users.name AS users_name
+ FROM users
+ WHERE EXISTS (SELECT 1
+ FROM addresses
+ WHERE users.id = addresses.user_id)
+ []
+ {stop}jack
- `has()` means, "find all parent items where the child item meets this criterion":
+`any()` takes criterion as well, to limit the rows matched:
- {python}
- {sql}>>> session.query(Address).\
- ... filter(Address.user.has(User.name=='jack')).all()
- SELECT addresses.id AS addresses_id, addresses.email_address AS addresses_email_address, addresses.user_id AS addresses_user_id
- FROM addresses
- WHERE EXISTS (SELECT 1
- FROM users
- WHERE users.id = addresses.user_id AND users.name = ?) ORDER BY addresses.oid
- ['jack']
- {stop}[<Address('jack@google.com')>, <Address('j25@yahoo.com')>]
+ {python}
+ {sql}>>> for name, in session.query(User.name).filter(User.addresses.any(Address.email_address.like('%google%'))): # doctest: +NORMALIZE_WHITESPACE
+ ... print name
+ SELECT users.name AS users_name
+ FROM users
+ WHERE EXISTS (SELECT 1
+ FROM addresses
+ WHERE users.id = addresses.user_id AND addresses.email_address LIKE ?)
+ ['%google%']
+ {stop}jack
- Both `has()` and `any()` also accept keyword arguments which are interpreted against the child classes' attributes:
+`has()` is the same operator as `any()` for many-to-one relations (note the `~` operator here too, which means "NOT"):
- {python}
- {sql}>>> session.query(User).\
- ... filter(User.addresses.any(email_address='jack@google.com')).all()
- SELECT users.id AS users_id, users.name AS users_name, users.fullname AS users_fullname, users.password AS users_password
- FROM users
- WHERE EXISTS (SELECT 1
- FROM addresses
- WHERE users.id = addresses.user_id AND addresses.email_address = ?) ORDER BY users.oid
- ['jack@google.com']
- {stop}[<User('jack','Jack Bean', 'gjffdd')>]
+ {python}
+ {sql}>>> session.query(Address).filter(~Address.user.has(User.name=='jack')).all() # doctest: +NORMALIZE_WHITESPACE
+ SELECT addresses.id AS addresses_id, addresses.email_address AS addresses_email_address,
+ addresses.user_id AS addresses_user_id
+ FROM addresses
+ WHERE NOT (EXISTS (SELECT 1
+ FROM users
+ WHERE users.id = addresses.user_id AND users.name = ?)) ORDER BY addresses.oid
+ ['jack']
+ {stop}[]
-* Filter_by on instance identity criterion. When comparing to a related instance, `filter_by()` will in most cases not need to reference the child table, since a child instance already contains enough information with which to generate criterion against the parent table. `filter_by()` uses an equality comparison for all relationship types. For many-to-one and one-to-one, this represents all objects which reference the given child object:
-
- {python}
- # locate a user
- {sql}>>> user = session.query(User).filter(User.name=='jack').one() #doctest: +NORMALIZE_WHITESPACE
- SELECT users.id AS users_id, users.name AS users_name, users.fullname AS users_fullname, users.password AS users_password
- FROM users
- WHERE users.name = ? ORDER BY users.oid
- LIMIT 2 OFFSET 0
- ['jack']
- {stop}
-
- # use the user in a filter_by() expression
- {sql}>>> session.query(Address).filter_by(user=user).all()
- SELECT addresses.id AS addresses_id, addresses.email_address AS addresses_email_address, addresses.user_id AS addresses_user_id
- FROM addresses
- WHERE ? = addresses.user_id ORDER BY addresses.oid
- [5]
- {stop}[<Address('jack@google.com')>, <Address('j25@yahoo.com')>]
+### Common Relation Operators {@name=relationop}
- For one-to-many and many-to-many, it represents all objects which contain the given child object in the related collection:
+Here's all the operators which build on relations:
+ * equals (used for many-to-one)
+
{python}
- # locate an address
- {sql}>>> address = session.query(Address).\
- ... filter(Address.email_address=='jack@google.com').one() #doctest: +NORMALIZE_WHITESPACE
- SELECT addresses.id AS addresses_id, addresses.email_address AS addresses_email_address, addresses.user_id AS addresses_user_id
- FROM addresses
- WHERE addresses.email_address = ? ORDER BY addresses.oid
- LIMIT 2 OFFSET 0
- {stop}['jack@google.com']
-
- # use the address in a filter_by expression
- {sql}>>> session.query(User).filter_by(addresses=address).all()
- SELECT users.id AS users_id, users.name AS users_name, users.fullname AS users_fullname, users.password AS users_password
- FROM users
- WHERE users.id = ? ORDER BY users.oid
- [5]
- {stop}[<User('jack','Jack Bean', 'gjffdd')>]
-
-* Select instances with a particular parent. This is the "reverse" operation of filtering by instance identity criterion; the criterion is against a relation pointing *to* the desired class, instead of one pointing *from* it. This will utilize the same "optimized" query criterion, usually not requiring any joins:
+ query.filter(Address.user == someuser)
+
+ * not equals (used for many-to-one)
{python}
- {sql}>>> session.query(Address).with_parent(user, property='addresses').all()
- SELECT addresses.id AS addresses_id, addresses.email_address AS addresses_email_address, addresses.user_id AS addresses_user_id
- FROM addresses
- WHERE ? = addresses.user_id ORDER BY addresses.oid
- [5]
- {stop}[<Address('jack@google.com')>, <Address('j25@yahoo.com')>]
-
-* Filter on a many-to-one/one-to-one instance identity criterion. The class-level `==` operator will act the same as `filter_by()` for a scalar relation:
+ query.filter(Address.user != someuser)
+ * IS NULL (used for many-to-one)
+
{python}
- {sql}>>> session.query(Address).filter(Address.user==user).all()
- SELECT addresses.id AS addresses_id, addresses.email_address AS addresses_email_address, addresses.user_id AS addresses_user_id
- FROM addresses
- WHERE ? = addresses.user_id ORDER BY addresses.oid
- [5]
- {stop}[<Address('jack@google.com')>, <Address('j25@yahoo.com')>]
-
- whereas the `!=` operator will generate a negated EXISTS clause:
-
+ query.filter(Address.user == None)
+
+ * contains (used for one-to-many and many-to-many collections)
+
{python}
- {sql}>>> session.query(Address).filter(Address.user!=user).all()
- SELECT addresses.id AS addresses_id, addresses.email_address AS addresses_email_address, addresses.user_id AS addresses_user_id
- FROM addresses
- WHERE NOT (EXISTS (SELECT 1
- FROM users
- WHERE users.id = addresses.user_id AND users.id = ?)) ORDER BY addresses.oid
- [5]
- {stop}[]
-
- a comparison to `None` also generates an IS NULL clause for a many-to-one relation:
-
+ query.filter(User.addresses.contains(someaddress))
+
+ * any (used for one-to-many and many-to-many collections)
+
{python}
- {sql}>>> session.query(Address).filter(Address.user==None).all()
- SELECT addresses.id AS addresses_id, addresses.email_address AS addresses_email_address, addresses.user_id AS addresses_user_id
- FROM addresses
- WHERE addresses.user_id IS NULL ORDER BY addresses.oid
- []
- {stop}[]
-
-* Filter on a one-to-many instance identity criterion. The `contains()` operator returns all parent objects which contain the given object as one of its collection members:
-
+ query.filter(User.addresses.any(Address.email_address == 'bar'))
+
+ # also takes keyword arguments:
+ query.filter(User.addresses.any(email_address='bar'))
+
+ * has (used for many-to-one)
+
{python}
- {sql}>>> session.query(User).filter(User.addresses.contains(address)).all()
- SELECT users.id AS users_id, users.name AS users_name, users.fullname AS users_fullname, users.password AS users_password
- FROM users
- WHERE users.id = ? ORDER BY users.oid
- [5]
- {stop}[<User('jack','Jack Bean', 'gjffdd')>]
-
-* Filter on a multiple one-to-many instance identity criterion. The `==` operator can be used with a collection-based attribute against a list of items, which will generate multiple `EXISTS` clauses:
-
+ query.filter(Address.user.has(name='ed'))
+
+ * with_parent (used for any relation)
+
{python}
- {sql}>>> addresses = session.query(Address).filter(Address.user==user).all()
- SELECT addresses.id AS addresses_id, addresses.email_address AS addresses_email_address, addresses.user_id AS addresses_user_id
- FROM addresses
- WHERE ? = addresses.user_id ORDER BY addresses.oid
- [5]
- {stop}
-
- {sql}>>> session.query(User).filter(User.addresses == addresses).all()
- SELECT users.id AS users_id, users.name AS users_name, users.fullname AS users_fullname, users.password AS users_password
- FROM users
- WHERE (EXISTS (SELECT 1
- FROM addresses
- WHERE users.id = addresses.user_id AND addresses.id = ?)) AND (EXISTS (SELECT 1
- FROM addresses
- WHERE users.id = addresses.user_id AND addresses.id = ?)) ORDER BY users.oid
- [1, 2]
- {stop}[<User('jack','Jack Bean', 'gjffdd')>]
+ session.query(Address).with_parent(someuser, 'addresses')
## Deleting
@@ -870,7 +809,7 @@ Let's try to delete `jack` and see how that goes. We'll mark as deleted in the
[None, 2]
DELETE FROM users WHERE users.id = ?
[5]
- SELECT count(users.id) AS count_1
+ SELECT count(1) AS count_1
FROM users
WHERE users.name = ?
['jack']
@@ -882,39 +821,42 @@ So far, so good. How about Jack's `Address` objects ?
{sql}>>> session.query(Address).filter(
... Address.email_address.in_(['jack@google.com', 'j25@yahoo.com'])
... ).count() # doctest: +NORMALIZE_WHITESPACE
- SELECT count(addresses.id) AS count_1
+ SELECT count(1) AS count_1
FROM addresses
WHERE addresses.email_address IN (?, ?)
['jack@google.com', 'j25@yahoo.com']
{stop}2
-Uh oh, they're still there ! Analyzing the flush SQL, we can see that the `user_id` column of each address was set to NULL, but the rows weren't deleted. SQLAlchemy doesn't assume that deletes cascade, you have to tell it so.
+Uh oh, they're still there ! Analyzing the flush SQL, we can see that the `user_id` column of each address was set to NULL, but the rows weren't deleted. SQLAlchemy doesn't assume that deletes cascade, you have to tell it to do so.
-So let's rollback our work, and start fresh with new mappers that express the relationship the way we want:
+### Configuring delete/delete-orphan Cascade {@name=cascade}
+
+We will configure **cascade** options on the `User.addresses` relation to change the behavior. While SQLAlchemy allows you to add new attributes and relations to mappings at any point in time, in this case the existing relation needs to be removed, so we need to tear down the mappings completely and start again. This is not a typical operation and is here just for illustrative purposes.
+
+Removing all ORM state is as follows:
{python}
- {sql}>>> session.rollback() # roll back the transaction
- ROLLBACK
-
- >>> session.clear() # clear the session
+ >>> session.close() # roll back and close the transaction
+ >>> from sqlalchemy.orm import clear_mappers
>>> clear_mappers() # clear mappers
-We need to tell the `addresses` relation on `User` that we'd like session.delete() operations to cascade down to the child `Address` objects. Further, we also want `Address` objects which get detached from their parent `User`, whether or not the parent is deleted, to be deleted. For these behaviors we use two **cascade options** `delete` and `delete-orphan`, using the string-based `cascade` option to the `relation()` function:
+Below, we use `mapper()` to reconfigure an ORM mapping for `User` and `Address`, on our existing but currently un-mapped classes. The `User.addresses` relation now has `delete, delete-orphan` cascade on it, which indicates that DELETE operations will cascade to attached `Address` objects as well as `Address` objects which are removed from their parent:
{python}
>>> mapper(User, users_table, properties={ # doctest: +ELLIPSIS
... 'addresses':relation(Address, backref='user', cascade="all, delete, delete-orphan")
... })
- <sqlalchemy.orm.mapper.Mapper object at 0x...>
+ <Mapper at 0x...; User>
+ >>> addresses_table = Address.__table__
>>> mapper(Address, addresses_table) # doctest: +ELLIPSIS
- <sqlalchemy.orm.mapper.Mapper object at 0x...>
+ <Mapper at 0x...; Address>
-Now when we load Jack, removing an address from his `addresses` collection will result in that `Address` being deleted:
+Now when we load Jack (below using `get()`, which loads by primary key), removing an address from his `addresses` collection will result in that `Address` being deleted:
{python}
# load Jack by primary key
- {sql}>>> jack = session.query(User).get(jack.id) #doctest: +NORMALIZE_WHITESPACE
+ {sql}>>> jack = session.query(User).get(5) #doctest: +NORMALIZE_WHITESPACE
BEGIN
SELECT users.id AS users_id, users.name AS users_name, users.fullname AS users_fullname, users.password AS users_password
FROM users
@@ -929,14 +871,14 @@ Now when we load Jack, removing an address from his `addresses` collection will
WHERE ? = addresses.user_id ORDER BY addresses.oid
[5]
{stop}
-
+
# only one address remains
{sql}>>> session.query(Address).filter(
... Address.email_address.in_(['jack@google.com', 'j25@yahoo.com'])
... ).count() # doctest: +NORMALIZE_WHITESPACE
DELETE FROM addresses WHERE addresses.id = ?
[2]
- SELECT count(addresses.id) AS count_1
+ SELECT count(1) AS count_1
FROM addresses
WHERE addresses.email_address IN (?, ?)
['jack@google.com', 'j25@yahoo.com']
@@ -947,17 +889,12 @@ Deleting Jack will delete both Jack and his remaining `Address`:
{python}
>>> session.delete(jack)
- {sql}>>> session.commit()
+ {sql}>>> session.query(User).filter_by(name='jack').count() # doctest: +NORMALIZE_WHITESPACE
DELETE FROM addresses WHERE addresses.id = ?
[1]
DELETE FROM users WHERE users.id = ?
[5]
- COMMIT
- {stop}
-
- {sql}>>> session.query(User).filter_by(name='jack').count() # doctest: +NORMALIZE_WHITESPACE
- BEGIN
- SELECT count(users.id) AS count_1
+ SELECT count(1) AS count_1
FROM users
WHERE users.name = ?
['jack']
@@ -966,7 +903,7 @@ Deleting Jack will delete both Jack and his remaining `Address`:
{sql}>>> session.query(Address).filter(
... Address.email_address.in_(['jack@google.com', 'j25@yahoo.com'])
... ).count() # doctest: +NORMALIZE_WHITESPACE
- SELECT count(addresses.id) AS count_1
+ SELECT count(1) AS count_1
FROM addresses
WHERE addresses.email_address IN (?, ?)
['jack@google.com', 'j25@yahoo.com']
@@ -976,25 +913,59 @@ Deleting Jack will delete both Jack and his remaining `Address`:
We're moving into the bonus round here, but lets show off a many-to-many relationship. We'll sneak in some other features too, just to take a tour. We'll make our application a blog application, where users can write `BlogPost`s, which have `Keywords` associated with them.
-First some new tables:
+The declarative setup is as follows:
{python}
>>> from sqlalchemy import Text
- >>> post_table = Table('posts', metadata,
- ... Column('id', Integer, primary_key=True),
- ... Column('user_id', Integer, ForeignKey('users.id')),
- ... Column('headline', String(255), nullable=False),
- ... Column('body', Text)
- ... )
-
+
+ >>> # association table
>>> post_keywords = Table('post_keywords', metadata,
- ... Column('post_id', Integer, ForeignKey('posts.id')),
- ... Column('keyword_id', Integer, ForeignKey('keywords.id')))
-
- >>> keywords_table = Table('keywords', metadata,
- ... Column('id', Integer, primary_key=True),
- ... Column('keyword', String(50), nullable=False, unique=True))
+ ... Column('post_id', Integer, ForeignKey('posts.id')),
+ ... Column('keyword_id', Integer, ForeignKey('keywords.id'))
+ ... )
+
+ >>> class BlogPost(Base):
+ ... __tablename__ = 'posts'
+ ...
+ ... id = Column(Integer, primary_key=True)
+ ... user_id = Column(Integer, ForeignKey('users.id'))
+ ... headline = Column(String(255), nullable=False)
+ ... body = Column(Text)
+ ...
+ ... # many to many BlogPost<->Keyword
+ ... keywords = relation('Keyword', secondary=post_keywords, backref='posts')
+ ...
+ ... def __init__(self, headline, body, author):
+ ... self.author = author
+ ... self.headline = headline
+ ... self.body = body
+ ...
+ ... def __repr__(self):
+ ... return "BlogPost(%r, %r, %r)" % (self.headline, self.body, self.author)
+
+ >>> class Keyword(Base):
+ ... __tablename__ = 'keywords'
+ ...
+ ... id = Column(Integer, primary_key=True)
+ ... keyword = Column(String(50), nullable=False, unique=True)
+ ...
+ ... def __init__(self, keyword):
+ ... self.keyword = keyword
+
+Above, the many-to-many relation above is `BlogPost.keywords`. The defining feature of a many to many relation is the `secondary` keyword argument which references a `Table` object representing the association table. This table only contains columns which reference the two sides of the relation; if it has *any* other columns, such as its own primary key, or foreign keys to other tables, SQLAlchemy requires a different usage pattern called the "association object", described at [advdatamapping_relation_patterns_association](rel:advdatamapping_relation_patterns_association).
+
+The many-to-many relation is also bi-directional using the `backref` keyword. This is the one case where usage of `backref` is generally required, since if a separate `posts` relation were added to the `Keyword` entity, both relations would independently add and remove rows from the `post_keywords` table and produce conflicts.
+
+We would also like our `BlogPost` class to have an `author` field. We will add this as another bidirectional relationship, except one issue we'll have is that a single user might have lots of blog posts. When we access `User.posts`, we'd like to be able to filter results further so as not to load the entire collection. For this we use a setting accepted by `relation()` called `lazy='dynamic'`, which configures an alternate **loader strategy** on the attribute. To use it on the "reverse" side of a `relation()`, we use the `backref()` function:
+
+ {python}
+ >>> from sqlalchemy.orm import backref
+ >>> # "dynamic" loading relation to User
+ >>> BlogPost.author = relation(User, backref=backref('posts', lazy='dynamic'))
+
+Create new tables:
+ {python}
{sql}>>> metadata.create_all(engine) # doctest: +NORMALIZE_WHITESPACE
PRAGMA table_info("users")
{}
@@ -1033,41 +1004,6 @@ First some new tables:
{}
COMMIT
-Then some classes:
-
- {python}
- >>> class BlogPost(object):
- ... def __init__(self, headline, body, author):
- ... self.author = author
- ... self.headline = headline
- ... self.body = body
- ... def __repr__(self):
- ... return "BlogPost(%r, %r, %r)" % (self.headline, self.body, self.author)
-
- >>> class Keyword(object):
- ... def __init__(self, keyword):
- ... self.keyword = keyword
-
-And the mappers. `BlogPost` will reference `User` via its `author` attribute:
-
- {python}
- >>> from sqlalchemy.orm import backref
-
- >>> mapper(Keyword, keywords_table) # doctest: +ELLIPSIS
- <sqlalchemy.orm.mapper.Mapper object at 0x...>
-
- >>> mapper(BlogPost, post_table, properties={ # doctest: +ELLIPSIS
- ... 'author':relation(User, backref=backref('posts', lazy='dynamic')),
- ... 'keywords':relation(Keyword, secondary=post_keywords)
- ... })
- <sqlalchemy.orm.mapper.Mapper object at 0x...>
-
-There's three new things in the above mapper:
-
- * the `User` relation has a backref, like we've used before, except this time it references a function called `backref()`. This function is used when yo'd like to specify keyword options for the backwards relationship.
- * the keyword option we specified to `backref()` is `lazy="dynamic"`. This sets a default **loader strategy** on the attribute, in this case a special strategy that allows partial loading of results.
- * The `keywords` relation uses a keyword argument `secondary` to indicate the **association table** for the many to many relationship from `BlogPost` to `Keyword`.
-
Usage is not too different from what we've been doing. Let's give Wendy some blog posts:
{python}
@@ -1079,7 +1015,7 @@ Usage is not too different from what we've been doing. Let's give Wendy some bl
['wendy']
>>> post = BlogPost("Wendy's Blog Post", "This is a test", wendy)
- >>> session.save(post)
+ >>> session.add(post)
We're storing keywords uniquely in the database, but we know that we don't have any yet, so we can just create them:
@@ -1087,7 +1023,7 @@ We're storing keywords uniquely in the database, but we know that we don't have
>>> post.keywords.append(Keyword('wendy'))
>>> post.keywords.append(Keyword('firstpost'))
-We can now look up all blog posts with the keyword 'firstpost'. We'll use a special collection operator `any` to locate "blog posts where any of its keywords has the keyword string 'firstpost'":
+We can now look up all blog posts with the keyword 'firstpost'. We'll use the `any` operator to locate "blog posts where any of its keywords has the keyword string 'firstpost'":
{python}
{sql}>>> session.query(BlogPost).filter(BlogPost.keywords.any(keyword='firstpost')).all()
@@ -1110,7 +1046,7 @@ We can now look up all blog posts with the keyword 'firstpost'. We'll use a sp
If we want to look up just Wendy's posts, we can tell the query to narrow down to her as a parent:
{python}
- {sql}>>> session.query(BlogPost).with_parent(wendy).\
+ {sql}>>> session.query(BlogPost).filter(BlogPost.author==wendy).\
... filter(BlogPost.keywords.any(keyword='firstpost')).all()
SELECT posts.id AS posts_id, posts.user_id AS posts_user_id, posts.headline AS posts_headline, posts.body AS posts_body
FROM posts
diff --git a/doc/build/content/session.txt b/doc/build/content/session.txt
index 0e94cef24..f9d8f4ed9 100644
--- a/doc/build/content/session.txt
+++ b/doc/build/content/session.txt
@@ -1,4 +1,4 @@
- Using the Session {@name=unitofwork}
+Using the Session {@name=unitofwork}
============
The [Mapper](rel:advdatamapping) is the entrypoint to the configurational API of the SQLAlchemy object relational mapper. But the primary object one works with when using the ORM is the [Session](rel:docstrings_sqlalchemy.orm.session_Session).
@@ -7,11 +7,11 @@ The [Mapper](rel:advdatamapping) is the entrypoint to the configurational API of
In the most general sense, the `Session` establishes all conversations with the database and represents a "holding zone" for all the mapped instances which you've loaded or created during its lifespan. It implements the [Unit of Work](http://martinfowler.com/eaaCatalog/unitOfWork.html) pattern, which means it keeps track of all changes which occur, and is capable of **flushing** those changes to the database as appropriate. Another important facet of the `Session` is that it's also maintaining **unique** copies of each instance, where "unique" means "only one object with a particular primary key" - this pattern is called the [Identity Map](http://martinfowler.com/eaaCatalog/identityMap.html).
-Beyond that, the `Session` implements an interface which let's you move objects in or out of the session in a variety of ways, it provides the entryway to a `Query` object which is used to query the database for data, it is commonly used to provide transactional boundaries (though this is optional), and it also can serve as a configurational "home base" for one or more `Engine` objects, which allows various vertical and horizontal partitioning strategies to be achieved.
+Beyond that, the `Session` implements an interface which let's you move objects in or out of the session in a variety of ways, it provides the entryway to a `Query` object which is used to query the database for data, and it also provides a transactional context for SQL operations which rides on top of the transactional capabilities of `Engine` and `Connection` objects.
## Getting a Session
-The `Session` object exists just as a regular Python object, which can be directly instantiated. However, it takes a fair amount of keyword options, several of which you probably want to set explicitly. It's fairly inconvenient to deal with the "configuration" of a session every time you want to create one. Therefore, SQLAlchemy recommends the usage of a helper function called `sessionmaker()`, which typically you call only once for the lifespan of an application. This function creates a customized `Session` subclass for you, with your desired configurational arguments pre-loaded. Then, whenever you need a new `Session`, you use your custom `Session` class with no arguments to create the session.
+`Session` is a regular Python class which can be directly instantiated. However, to standardize how sessions are configured and acquired, the `sessionmaker()` function is normally used to create a top level `Session` configuration which can then be used throughout an application without the need to repeat the configurational arguments.
### Using a sessionmaker() Configuration {@name=sessionmaker}
@@ -21,43 +21,30 @@ The usage of `sessionmaker()` is illustrated below:
from sqlalchemy.orm import sessionmaker
# create a configured "Session" class
- Session = sessionmaker(autoflush=True, transactional=True)
+ Session = sessionmaker(bind=some_engine)
# create a Session
sess = Session()
# work with sess
- sess.save(x)
+ myobject = MyObject('foo', 'bar')
+ sess.add(myobject)
sess.commit()
# close when finished
sess.close()
-Above, the `sessionmaker` call creates a class for us, which we assign to the name `Session`. This class is a subclass of the actual `sqlalchemy.orm.session.Session` class, which will instantiate with the arguments of `autoflush=True` and `transactional=True`.
+Above, the `sessionmaker` call creates a class for us, which we assign to the name `Session`. This class is a subclass of the actual `sqlalchemy.orm.session.Session` class, which will instantiate with a particular bound engine.
When you write your application, place the call to `sessionmaker()` somewhere global, and then make your new `Session` class available to the rest of your application.
-### Binding Session to an Engine or Connection {@name=binding}
+### Binding Session to an Engine {@name=binding}
-In our previous example regarding `sessionmaker()`, nowhere did we specify how our session would connect to our database. When the session is configured in this manner, it will look for a database engine to connect with via the `Table` objects that it works with - the chapter called [metadata_tables_binding](rel:metadata_tables_binding) describes how to associate `Table` objects directly with a source of database connections.
-
-However, it is often more straightforward to explicitly tell the session what database engine (or engines) you'd like it to communicate with. This is particularly handy with multiple-database scenarios where the session can be used as the central point of configuration. To achieve this, the constructor keyword `bind` is used for a basic single-database configuration:
-
- {python}
- # create engine
- engine = create_engine('postgres://...')
-
- # bind custom Session class to the engine
- Session = sessionmaker(bind=engine, autoflush=True, transactional=True)
-
- # work with the session
- sess = Session()
-
-One common issue with the above scenario is that an application will often organize its global imports before it ever connects to a database. Since the `Session` class created by `sessionmaker()` is meant to be a global application object (note we are saying the session *class*, not a session *instance*), we may not have a `bind` argument available. For this, the `Session` class returned by `sessionmaker()` supports post-configuration of all options, through its method `configure()`:
+In our previous example regarding `sessionmaker()`, we specified a `bind` for a particular `Engine`. If we'd like to construct a `sessionmaker()` without an engine available and bind it later on, or to specify other options to an existing `sessionmaker()`, we may use the `configure()` method:
{python}
# configure Session class with desired options
- Session = sessionmaker(autoflush=True, transactional=True)
+ Session = sessionmaker()
# later, we create the engine
engine = create_engine('postgres://...')
@@ -68,16 +55,17 @@ One common issue with the above scenario is that an application will often organ
# work with the session
sess = Session()
-The `Session` also has the ability to be bound to multiple engines. Descriptions of these scenarios are described in [unitofwork_partitioning](rel:unitofwork_partitioning).
+It's actually entirely optional to bind a Session to an engine. If the underlying mapped `Table` objects use "bound" metadata, the `Session` will make use of the bound engine instead (or will even use multiple engines if multiple binds are present within the mapped tables). "Bound" metadata is described at [metadata_tables_binding](rel:metadata_tables_binding).
+The `Session` also has the ability to be bound to multiple engines explicitly. Descriptions of these scenarios are described in [unitofwork_partitioning](rel:unitofwork_partitioning).
-#### Binding Session to a Connection {@name=connection}
+### Binding Session to a Connection {@name=connection}
-The examples involving `bind` so far are dealing with the `Engine` object, which is, like the `Session` class itself, a global configurational object. The `Session` can also be bound to an individual database `Connection`. The reason you might want to do this is if your application controls the boundaries of transactions using distinct `Transaction` objects (these objects are described in [dbengine_transactions](rel:dbengine_transactions)). You'd have a transactional `Connection`, and then you'd want to work with an ORM-level `Session` which participates in that transaction. Since `Connection` is definitely not a globally-scoped object in all but the most rudimental commandline applications, you can bind an individual `Session()` instance to a particular `Connection` not at class configuration time, but at session instance construction time:
+The `Session` can also be explicitly bound to an individual database `Connection`. Reasons for doing this may include to join a `Session` with an ongoing transaction local to a specific `Connection` object, or to bypass connection pooling by just having connections persistently checked out and associated with distinct, long running sessions:
{python}
# global application scope. create Session class, engine
- Session = sessionmaker(autoflush=True, transactional=True)
+ Session = sessionmaker()
engine = create_engine('postgres://...')
@@ -93,44 +81,24 @@ The examples involving `bind` so far are dealing with the `Engine` object, which
### Using create_session() {@name=createsession}
-As an alternative to `sessionmaker()`, `create_session()` exists literally as a function which calls the normal `Session` constructor directly. All arguments are passed through and the new `Session` object is returned:
+As an alternative to `sessionmaker()`, `create_session()` is a function which calls the normal `Session` constructor directly. All arguments are passed through and the new `Session` object is returned:
{python}
- session = create_session(bind=myengine)
-
-The `create_session()` function doesn't add any functionality to the regular `Session`, it just sets up a default argument set of `autoflush=False, transactional=False`. But also, by calling `create_session()` instead of instantiating `Session` directly, you leave room in your application to change the type of session which the function creates. For example, an application which is calling `create_session()` in many places, which is typical for a pre-0.4 application, can be changed to use a `sessionmaker()` by just assigning the return of `sessionmaker()` to the `create_session` name:
+ session = create_session(bind=myengine, autocommit=True, autoflush=False)
- {python}
- # change from:
- from sqlalchemy.orm import create_session
+### Configurational Arguments {@name=configuration}
- # to:
- create_session = sessionmaker()
+Configurational arguments accepted by `sessionmaker()` and `create_session()` are the same as that of the `Session` class itself, and are described at [docstrings_sqlalchemy.orm_modfunc_sessionmaker](rel:docstrings_sqlalchemy.orm_modfunc_sessionmaker).
## Using the Session
-A typical session conversation starts with creating a new session, or acquiring one from an ongoing context. You save new objects and load existing ones, make changes, mark some as deleted, and then persist your changes to the database. If your session is transactional, you use `commit()` to persist any remaining changes and to commit the transaction. If not, you call `flush()` which will flush any remaining data to the database.
-
-Below, we open a new `Session` using a configured `sessionmaker()`, make some changes, and commit:
-
- {python}
- # configured Session class
- Session = sessionmaker(autoflush=True, transactional=True)
-
- sess = Session()
- d = Data(value=10)
- sess.save(d)
- d2 = sess.query(Data).filter(Data.value==15).one()
- d2.value = 19
- sess.commit()
-
### Quickie Intro to Object States {@name=states}
It's helpful to know the states which an instance can have within a session:
* *Transient* - an instance that's not in a session, and is not saved to the database; i.e. it has no database identity. The only relationship such an object has to the ORM is that its class has a `mapper()` associated with it.
-* *Pending* - when you `save()` a transient instance, it becomes pending. It still wasn't actually flushed to the database yet, but it will be when the next flush occurs.
+* *Pending* - when you `add()` a transient instance, it becomes pending. It still wasn't actually flushed to the database yet, but it will be when the next flush occurs.
* *Persistent* - An instance which is present in the session and has a record in the database. You get persistent instances by either flushing so that the pending instances become persistent, or by querying the database for existing instances (or moving persistent instances from other sessions into your local session).
@@ -152,7 +120,7 @@ Knowing these states is important, since the `Session` tries to be strict about
You typically invoke `Session()` when you first need to talk to your database, and want to save some objects or load some existing ones. Then, you work with it, save your changes, and then dispose of it....or at the very least `close()` it. It's not a "global" kind of object, and should be handled more like a "local variable", as it's generally **not** safe to use with concurrent threads. Sessions are very inexpensive to make, and don't use any resources whatsoever until they are first used...so create some !
- There is also a pattern whereby you're using a **contextual session**, this is described later in [unitofwork_contextual](rel:unitofwork_contextual). In this pattern, a helper object is maintaining a `Session` for you, most commonly one that is local to the current thread (and sometimes also local to an application instance). SQLAlchemy 0.4 has worked this pattern out such that it still *looks* like you're creating a new session as you need one...so in that case, it's still a guaranteed win to just say `Session()` whenever you want a session.
+ There is also a pattern whereby you're using a **contextual session**, this is described later in [unitofwork_contextual](rel:unitofwork_contextual). In this pattern, a helper object is maintaining a `Session` for you, most commonly one that is local to the current thread (and sometimes also local to an application instance). SQLAlchemy has worked this pattern out such that it still *looks* like you're creating a new session as you need one...so in that case, it's still a guaranteed win to just say `Session()` whenever you want a session.
* Is the Session a cache ?
@@ -175,121 +143,66 @@ Knowing these states is important, since the `Session` tries to be strict about
But the bigger point here is, you should not *want* to use the session with multiple concurrent threads. That would be like having everyone at a restaurant all eat from the same plate. The session is a local "workspace" that you use for a specific set of tasks; you don't want to, or need to, share that session with other threads who are doing some other task. If, on the other hand, there are other threads participating in the same task you are, such as in a desktop graphical application, then you would be sharing the session with those threads, but you also will have implemented a proper locking scheme (or your graphical framework does) so that those threads do not collide.
-### Session Attributes {@name=attributes}
-
-The session provides a set of attributes and collection-oriented methods which allow you to view the current state of the session.
-
-The **identity map** is accessed by the `identity_map` attribute, which provides a dictionary interface. The keys are "identity keys", which are attached to all persistent objects by the attribute `_instance_key`:
-
- {python}
- >>> myobject._instance_key
- (<class 'test.tables.User'>, (7,))
-
- >>> myobject._instance_key in session.identity_map
- True
-
- >>> session.identity_map.values()
- [<__main__.User object at 0x712630>, <__main__.Address object at 0x712a70>]
-
-The identity map is a weak-referencing dictionary by default. This means that objects which are dereferenced on the outside will be removed from the session automatically. Note that objects which are marked as "dirty" will not fall out of scope until after changes on them have been flushed; special logic kicks in at the point of auto-removal which ensures that no pending changes remain on the object, else a temporary strong reference is created to the object.
-
-Some people prefer objects to stay in the session until explicitly removed in all cases; for this, you can specify the flag `weak_identity_map=False` to the `create_session` or `sessionmaker` functions so that the `Session` will use a regular dictionary.
-
-While the `identity_map` accessor is currently the actual dictionary used by the `Session` to store instances, you should not add or remove items from this dictionary. Use the session methods `save_or_update()` and `expunge()` to add or remove items.
-
-The Session also supports an iterator interface in order to see all objects in the identity map:
-
- {python}
- for obj in session:
- print obj
-
-As well as `__contains__()`:
-
- {python}
- if obj in session:
- print "Object is present"
-
-The session is also keeping track of all newly created (i.e. pending) objects, all objects which have had changes since they were last loaded or saved (i.e. "dirty"), and everything that's been marked as deleted.
-
- {python}
- # pending objects recently added to the Session
- session.new
-
- # persistent objects which currently have changes detected
- # (this collection is now created on the fly each time the property is called)
- session.dirty
-
- # persistent objects that have been marked as deleted via session.delete(obj)
- session.deleted
-
### Querying
-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()`.
+The `query()` function takes one or more *entities* and returns a new `Query` object which will issue mapper queries within the context of this Session. An entity is defined as a mapped class, a `Mapper` object, an orm-enabled *descriptor*, or an `AliasedClass` object (a future release will also include an `Entity` object for use with entity_name mappers).
{python}
# query from a class
session.query(User).filter_by(name='ed').all()
# query with multiple classes, returns tuples
- session.query(User).add_entity(Address).join('addresses').filter_by(name='ed').all()
+ session.query(User, Address).join('addresses').filter_by(name='ed').all()
+
+ # query using orm-enabled descriptors
+ session.query(User.name, User.fullname).all()
# query from a mapper
- query = session.query(usermapper)
- x = query.get(1)
-
- # query from a class mapped with entity name 'alt_users'
- q = session.query(User, entity_name='alt_users')
- 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.
+ user_mapper = class_mapper(User)
+ session.query(user_mapper)
-All instances retrieved by the returned `Query` object will be stored as persistent instances within the originating `Session`.
+When `Query` returns results, each object instantiated is stored within the identity map. When a row matches an object which is already present, the same object is returned. In the latter case, whether or not the row is populated onto an existing object depends upon whether the attributes of the instance have been *expired* or not. As of 0.5, a default-configured `Session` automatically expires all instances along transaction boundaries, so that with a normally isolated transaction, there shouldn't be any issue of instances representing data which is stale with regards to the current transaction.
-### Saving New Instances
+### Adding New or Existing Items
-`save()` is called with a single transient instance as an argument, which is then added to the Session and becomes pending. When the session is next flushed, the instance will be saved to the database. If the given instance is not transient, meaning it is either attached to an existing Session or it has a database identity, an exception is raised.
+`add()` is used to place instances in the session. For *transient* (i.e. brand new) instances, this will have the effect of an INSERT taking place for those instances upon the next flush. For instances which are *persistent* (i.e. were loaded by this session), they are already present and do not need to be added. Instances which are *detached* (i.e. have been removed from a session) may be re-associated with a session using this method:
{python}
user1 = User(name='user1')
user2 = User(name='user2')
- session.save(user1)
- session.save(user2)
+ session.add(user1)
+ session.add(user2)
session.commit() # write changes to the database
-There's also other ways to have objects saved to the session automatically; one is by using cascade rules, and the other is by using a contextual session. Both of these are described later.
+To add a list of items to the session at once, use `add_all()`:
-### Updating/Merging Existing Instances
+ {python}
+ session.add_all([item1, item2, item3])
-The `update()` method is used when you have a detached instance, and you want to put it back into a `Session`. Recall that "detached" means the object has a database identity.
+The `add()` operation **cascades** along the `save-update` cascade. For more details see the section [unitofwork_cascades](rel:unitofwork_cascades).
-Since `update()` is a little picky that way, most people use `save_or_update()`, which checks for an `_instance_key` attribute, and based on whether it's there or not, calls either `save()` or `update()`:
+### Merging
- {python}
- # load user1 using session 1
- user1 = sess1.query(User).get(5)
-
- # remove it from session 1
- sess1.expunge(user1)
-
- # move it into session 2
- sess2.save_or_update(user1)
+`merge()` reconciles the current state of an instance and its associated children with existing data in the database, and returns a copy of the instance associated with the session. Usage is as follows:
-`update()` is also an operation that can happen automatically using cascade rules, just like `save()`.
+ {python}
+ merged_object = session.merge(existing_object)
-`merge()` on the other hand is a little like `update()`, except it creates a **copy** of the given instance in the session, and returns to you that instance; the instance you send it never goes into the session. `merge()` is much fancier than `update()`; it will actually look to see if an object with the same primary key is already present in the session, and if not will load it by primary key. Then, it will merge the attributes of the given object into the one which it just located.
+When given an instance, it follows these steps:
-This method is useful for bringing in objects which may have been restored from a serialization, such as those stored in an HTTP session, where the object may be present in the session already:
+ * It examines the primary key of the instance. If it's present, it attempts to load an instance with that primary key (or pulls from the local identity map).
+ * If there's no primary key on the given instance, or the given primary key does not exist in the database, a new instance is created.
+ * The state of the given instance is then copied onto the located/newly created instance.
+ * The operation is cascaded to associated child items along the `merge` cascade. Note that all changes present on the given instance, including changes to collections, are merged.
+ * The new instance is returned.
- {python}
- # deserialize an object
- myobj = pickle.loads(mystring)
+With `merge()`, the given instance is not placed within the session, and can be associated with a different session or detached. `merge()` is very useful for taking the state of any kind of object structure without regard for its origins or current session associations and placing that state within a session. Here's two examples:
- # "merge" it. if the session already had this object in the
- # identity map, then you get back the one from the current session.
- myobj = session.merge(myobj)
+ * An application which reads an object structure from a file and wishes to save it to the database might parse the file, build up the structure, and then use `merge()` to save it to the database, ensuring that the data within the file is used to formulate the primary key of each element of the structure. Later, when the file has changed, the same process can be re-run, producing a slightly different object structure, which can then be `merged()` in again, and the `Session` will automatically update the database to reflect those changes.
+ * A web application stores mapped entities within an HTTP session object. When each request starts up, the serialized data can be merged into the session, so that the original entity may be safely shared among requests and threads.
-`merge()` includes an important option called `dont_load`. When this boolean flag is set to `True`, the merge of a detached object will not force a `get()` of that object from the database. Normally, `merge()` issues a `get()` for every existing object so that it can load the most recent state of the object, which is then modified according to the state of the given object. With `dont_load=True`, the `get()` is skipped and `merge()` places an exact copy of the given object in the session. This allows objects which were retrieved from a caching system to be copied back into a session without any SQL overhead being added.
+`merge()` is frequently used by applications which implement their own second level caches. This refers to an application which uses an in memory dictionary, or an tool like Memcached to store objects over long running spans of time. When such an object needs to exist within a `Session`, `merge()` is a good choice since it leaves the original cached object untouched. For this use case, merge provides a keyword option called `dont_load=True`. When this boolean flag is set to `True`, `merge()` will not issue any SQL to reconcile the given object against the current state of the database, thereby reducing query overhead. The limitation is that the given object and all of its children may not contain any pending changes, and it's also of course possible that newer information in the database will not be present on the merged object, since no load is issued.
### Deleting
@@ -323,74 +236,58 @@ The solution is to use proper cascading:
### Flushing
-This is the main gateway to what the `Session` does best, which is save everything ! It should be clear by now what a flush looks like:
+When the `Session` is used with its default configuration, the flush step is nearly always done transparently. Specifically, the flush occurs before any individual `Query` is issued, as well as within the `commit()` call before the transaction is committed. This behavior can be disabled by constructing `sessionmaker()` with the flag `autoflush=False`.
+
+Regardless of the autoflush setting, a flush can always be forced by issing `flush()`:
{python}
session.flush()
-It also can be called with a list of objects; in this form, the flush operation will be limited only to the objects specified in the list:
+`flush()` also supports the ability to flush a subset of objects which are present in the session, by passing a list of objects:
{python}
# saves only user1 and address2. all other modified
# objects remain present in the session.
session.flush([user1, address2])
-This second form of flush should be used carefully as it will not necessarily locate other dependent objects within the session, whose database representation may have foreign constraint relationships with the objects being operated upon.
-
-Theres also a way to have `flush()` called automatically before each query; this is called "autoflush" and is described below.
-
-Note that when using a `Session` that has been placed into a transaction, the `commit()` method will also `flush()` the `Session` unconditionally before committing the transaction.
+This second form of flush should be used carefully as it currently does not cascade, meaning that it will not necessarily affect other objects directly associated with the objects given.
-Note that flush **does not change** the state of any collections or entity relationships in memory; for example, if you set a foreign key attribute `b_id` on object `A` with the identifier `B.id`, the change will be flushed to the database, but `A` will not have `B` added to its collection. If you want to manipulate foreign key attributes directly, `refresh()` or `expire()` the objects whose state needs to be refreshed subsequent to flushing.
+The flush process *always* occurs within a transaction, even if the `Session` has been configured with `autocommit=True`, a setting that disables the session's persistent transactional state. If no transaction is present, `flush()` creates its own transaction and commits it. Any failures during flush will always result in a rollback of whatever transaction is present.
-### Autoflush
+### Committing
-A session can be configured to issue `flush()` calls before each query. This allows you to immediately have DB access to whatever has been saved to the session. It's recommended to use autoflush with `transactional=True`, that way an unexpected flush call won't permanently save to the database:
+`commit()` is used to commit the current transaction. It always issues `flush()` beforehand to flush any remaining state to the database; this is independent of the "autoflush" setting. If no transaction is present, it raises an error. Note that the default behavior of the `Session` is that a transaction is always present; this behavior can be disabled by setting `autocommit=True`. In autocommit mode, a transaction can be initiated by calling the `begin()` method.
- {python}
- Session = sessionmaker(autoflush=True, transactional=True)
- sess = Session()
- u1 = User(name='jack')
- sess.save(u1)
-
- # reload user1
- u2 = sess.query(User).filter_by(name='jack').one()
- assert u2 is u1
+Another behavior of `commit()` is that by default it expires the state of all instances present after the commit is complete. This is so that when the instances are next accessed, either through attribute access or by them being present in a `Query` result set, they receive the most recent state. To disable this behavior, configure `sessionmaker()` with `autoexpire=False`.
- # commit session, flushes whatever is remaining
- sess.commit()
+Normally, instances loaded into the `Session` are never changed by subsequent queries; the assumption is that the current transaction is isolated so the state most recently loaded is correct as long as the transaction continues. Setting `autocommit=True` works against this model to some degree since the `Session` behaves in exactly the same way with regard to attribute state, except no transaction is present.
-Autoflush is particularly handy when using "dynamic" mapper relations, so that changes to the underlying collection are immediately available via its query interface.
+### Rolling Back
-### Committing
+`rollback()` rolls back the current transaction. With a default configured session, the post-rollback state of the session is as follows:
-The `commit()` method on `Session` is used specifically when the `Session` is in a transactional state. The two ways that a session may be placed in a transactional state are to create it using the `transactional=True` option, or to call the `begin()` method.
+ * All connections are rolled back and returned to the connection pool, unless the Session was bound directly to
+ a Connection, in which case the connection is still maintained (but still rolled back).
+ * Objects which were initially in the *pending* state when they were added to the `Session` within the lifespan of the transaction are expunged, corresponding to their INSERT statement being rolled back. The state of their attributes remains unchanged.
+ * Objects which were marked as *deleted* within the lifespan of the transaction are promoted back to the *persistent* state, corresponding to their DELETE statement being rolled back. Note that if those objects were first *pending* within the transaction, that operation takes precedence instead.
+ * All objects not expunged are fully expired. This aspect of the behavior may be disabled by configuring `sessionmaker()` with `autoexpire=False`.
-`commit()` serves **two** purposes; it issues a `flush()` unconditionally to persist any remaining pending changes, and it issues a commit to all currently managed database connections. In the typical case this is just a single connection. After the commit, connection resources which were allocated by the `Session` are released. This holds true even for a `Session` which specifies `transactional=True`; when such a session is committed, the next transaction is not "begun" until the next database operation occurs.
+With that state understood, the `Session` may safely continue usage after a rollback occurs (note that this is a new feature as of version 0.5).
-See the section below on "Managing Transactions" for further detail.
+When a `flush()` fails, typically for reasons like primary key, foreign key, or "not nullable" constraint violations, a `rollback()` is issued automatically (it's currently not possible for a flush to continue after a partial failure). However, the flush process always uses its own transactional demarcator called a *subtransaction*, which is described more fully in the docstrings for `Session`. What it means here is that even though the database transaction has been rolled back, the end user must still issue `rollback()` to fully reset the state of the `Session`.
-### Expunge / Clear
+### Expunging
Expunge removes an object from the Session, sending persistent instances to the detached state, and pending instances to the transient state:
{python}
session.expunge(obj1)
-Use `expunge` when you'd like to remove an object altogether from memory, such as before calling `del` on it, which will prevent any "ghost" operations occurring when the session is flushed.
-
-This `clear()` method is equivalent to `expunge()`-ing everything from the Session:
-
- {python}
- session.clear()
-
-However note that the `clear()` method does not reset any transactional state or connection resources; therefore what you usually want to call instead of `clear()` is `close()`.
+To remove all items, call `session.expunge_all()`.
### Closing
-The `close()` method issues a `clear()`, and releases any transactional/connection resources. When connections are returned to the connection pool, whatever transactional state exists is rolled back.
-
-When `close()` is called, the `Session` is in the same state as when it was first created, and is safe to be used again. `close()` is especially important when using a contextual session, which remains in memory after usage. By issuing `close()`, the session will be clean for the next request that makes use of it.
+The `close()` method issues a `expunge_alll()`, and releases any transactional/connection resources. When connections are returned to the connection pool, transactional state is rolled back as well.
### Refreshing / Expiring
@@ -418,6 +315,42 @@ To assist with the Session's "sticky" behavior of instances which are present, i
session.expire(obj1, ['hello', 'world'])
session.expire(obj2, ['hello', 'world'])
+The full contents of the session may be expired at once using `expire_all()`:
+
+ {python}
+ session.expire_all()
+
+`refresh()` and `expire()` are usually not needed when working with a default-configured `Session`. The usual need is when an UPDATE or DELETE has been issued manually within the transaction using `Session.execute()`.
+
+### Session Attributes {@name=attributes}
+
+The `Session` itself acts somewhat like a set-like collection. All items present may be accessed using the iterator interface:
+
+ {python}
+ for obj in session:
+ print obj
+
+And presence may be tested for using regular "contains" semantics:
+
+ {python}
+ if obj in session:
+ print "Object is present"
+
+The session is also keeping track of all newly created (i.e. pending) objects, all objects which have had changes since they were last loaded or saved (i.e. "dirty"), and everything that's been marked as deleted.
+
+ {python}
+ # pending objects recently added to the Session
+ session.new
+
+ # persistent objects which currently have changes detected
+ # (this collection is now created on the fly each time the property is called)
+ session.dirty
+
+ # persistent objects that have been marked as deleted via session.delete(obj)
+ session.deleted
+
+Note that objects within the session are by default *weakly referenced*. This means that when they are dereferenced in the outside application, they fall out of scope from within the `Session` as well and are subject to garbage collection by the Python interpreter. The exceptions to this include objects which are pending, objects which are marked as deleted, or persistent objects which have pending changes on them. After a full flush, these collections are all empty, and all objects are again weakly referenced. To disable the weak referencing behavior and force all objects within the session to remain until explicitly expunged, configure `sessionmaker()` with the `weak_identity_map=False` setting.
+
## Cascades
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`.
@@ -430,25 +363,20 @@ Cascading is configured by setting the `cascade` keyword argument on a `relation
'customer' : relation(User, users_table, user_orders_table, cascade="save-update"),
})
-The above mapper specifies two relations, `items` and `customer`. The `items` relationship specifies "all, delete-orphan" as its `cascade` value, indicating that all `save`, `update`, `merge`, `expunge`, `refresh` `delete` and `expire` operations performed on a parent `Order` instance should also be performed on the child `Item` instances attached to it (`save` and `update` are cascaded using the `save_or_update()` method, so that the database identity of the instance doesn't matter). The `delete-orphan` cascade value additionally indicates that if an `Item` instance is no longer associated with an `Order`, it should also be deleted. The "all, delete-orphan" cascade argument allows a so-called *lifecycle* relationship between an `Order` and an `Item` object.
-
-The `customer` relationship specifies only the "save-update" cascade value, indicating most operations will not be cascaded from a parent `Order` instance to a child `User` instance, except for if the `Order` is attached with a particular session, either via the `save()`, `update()`, or `save-update()` method.
-
-Additionally, when a child item is attached to a parent item that specifies the "save-update" cascade value on the relationship, the child is automatically passed to `save_or_update()` (and the operation is further cascaded to the child item).
+The above mapper specifies two relations, `items` and `customer`. The `items` relationship specifies "all, delete-orphan" as its `cascade` value, indicating that all `add`, `merge`, `expunge`, `refresh` `delete` and `expire` operations performed on a parent `Order` instance should also be performed on the child `Item` instances attached to it. The `delete-orphan` cascade value additionally indicates that if an `Item` instance is no longer associated with an `Order`, it should also be deleted. The "all, delete-orphan" cascade argument allows a so-called *lifecycle* relationship between an `Order` and an `Item` object.
-Note that cascading doesn't do anything that isn't possible by manually calling Session methods on individual instances within a hierarchy, it merely automates common operations on a group of associated instances.
+The `customer` relationship specifies only the "save-update" cascade value, indicating most operations will not be cascaded from a parent `Order` instance to a child `User` instance except for the `add()` operation. "save-update" cascade indicates that an `add()` on the parent will casade to all child items, and also that items added to a parent which is already present in the sessio will also be added.
The default value for `cascade` on `relation()`s is `save-update, merge`.
## Managing Transactions
-The Session can manage transactions automatically, including across multiple engines. When the Session is in a transaction, as it receives requests to execute SQL statements, it adds each individual Connection/Engine encountered to its transactional state. At commit time, all unflushed data is flushed, and each individual transaction is committed. If the underlying databases support two-phase semantics, this may be used by the Session as well if two-phase transactions are enabled.
+The `Session` manages transactions across all engines associated with it. As the `Session` receives requests to execute SQL statements using a particular `Engine` or `Connection`, it adds each individual `Engine` encountered to its transactional state and maintains an open connection for each one (note that a simple application normally has just one `Engine`). At commit time, all unflushed data is flushed, and each individual transaction is committed. If the underlying databases support two-phase semantics, this may be used by the Session as well if two-phase transactions are enabled.
-The easiest way to use a Session with transactions is just to declare it as transactional. The session will remain in a transaction at all times:
+Normal operation ends the transactional state using the `rolback()` or `commit()` methods. After either is called, the `Session` starts a new transaction.
{python}
- # transactional session
- Session = sessionmaker(transactional=True)
+ Session = sessionmaker()
sess = Session()
try:
item1 = sess.query(Item).get(1)
@@ -462,16 +390,10 @@ The easiest way to use a Session with transactions is just to declare it as tran
# rollback - will immediately go into a new transaction afterwards.
sess.rollback()
-Things to note above:
-
- * When using a transactional session, either a `rollback()` or a `close()` call **is required** when an error is raised by `flush()` or `commit()`. The `flush()` error condition will issue a ROLLBACK to the database automatically, but the state of the `Session` itself remains in an "undefined" state until the user decides whether to rollback or close.
- * The `commit()` call unconditionally issues a `flush()`. Particularly when using `transactional=True` in conjunction with `autoflush=True`, explicit `flush()` calls are usually not needed.
-
-Alternatively, a transaction can be begun explicitly using `begin()`:
+A session which is configured with `autocommit=True` may be placed into a transaction using `begin()`. With an `autocommit=True` session that's been placed into a transaction using `begin()`, the session releases all connection resources after a `commit()` or `rollback()` and remains transaction-less (with the exception of flushes) until the next `begin()` call:
{python}
- # non transactional session
- Session = sessionmaker(transactional=False)
+ Session = sessionmaker(autocommit=True)
sess = Session()
sess.begin()
try:
@@ -484,12 +406,10 @@ Alternatively, a transaction can be begun explicitly using `begin()`:
sess.rollback()
raise
-Like the `transactional` example, the same rules apply; an explicit `rollback()` or `close()` is required when an error occurs, and the `commit()` call issues a `flush()` as well.
-
-Session also supports Python 2.5's with statement so that the example above can be written as:
+The `begin()` method also returns a transactional token which is compatible with the Python 2.6 `with` statement:
{python}
- Session = sessionmaker(transactional=False)
+ Session = sessionmaker(autocommit=True)
sess = Session()
with sess.begin():
item1 = sess.query(Item).get(1)
@@ -497,29 +417,31 @@ Session also supports Python 2.5's with statement so that the example above can
item1.foo = 'bar'
item2.bar = 'foo'
-Subtransactions can be created by calling the `begin()` method repeatedly. For each transaction you `begin()` you must always call either `commit()` or `rollback()`. Note that this includes the implicit transaction created by the transactional session. When a subtransaction is created the current transaction of the session is set to that transaction. Commiting the subtransaction will return you to the next outer transaction. Rolling it back will also return you to the next outer transaction, but in addition it will roll back database state to the innermost transaction that supports rolling back to. Usually this means the root transaction, unless you use the nested transaction functionality via the `begin_nested()` method. MySQL and Postgres (and soon Oracle) support using "nested" transactions by creating SAVEPOINTs, :
+SAVEPOINT transactions, if supported by the underlying engine, may be delineated using the `begin_nested()` method:
{python}
- Session = sessionmaker(transactional=False)
+ Session = sessionmaker()
sess = Session()
- sess.begin()
- sess.save(u1)
- sess.save(u2)
- sess.flush()
+ sess.add(u1)
+ sess.add(u2)
sess.begin_nested() # establish a savepoint
- sess.save(u3)
+ sess.add(u3)
sess.rollback() # rolls back u3, keeps u1 and u2
sess.commit() # commits u1 and u2
+`begin_nested()` may be called any number of times, which will issue a new SAVEPOINT with a unique identifier for each call. For each `begin_nested()` call, a corresponding `rollback()` or `commit()` must be issued.
+
+When `begin_nested()` is called, a `flush()` is unconditionally issued (regardless of the `autoflush` setting). This is so that when a `rollback()` occurs, the full state of the session is expired, thus causing all subsequent attribute/instance access to reference the full state of the `Session` right before `begin_nested()` was called.
+
Finally, for MySQL, Postgres, and soon Oracle as well, the session can be instructed to use two-phase commit semantics. This will coordinate the commiting of transactions across databases so that the transaction is either committed or rolled back in all databases. You can also `prepare()` the session for interacting with transactions not managed by SQLAlchemy. To use two phase transactions set the flag `twophase=True` on the session:
{python}
engine1 = create_engine('postgres://db1')
engine2 = create_engine('postgres://db2')
- Session = sessionmaker(twophase=True, transactional=True)
+ Session = sessionmaker(twophase=True)
# bind User operations to engine 1, Account operations to engine 2
Session.configure(binds={User:engine1, Account:engine2})
@@ -532,8 +454,6 @@ Finally, for MySQL, Postgres, and soon Oracle as well, the session can be instru
# before committing both transactions
sess.commit()
-Be aware that when a crash occurs in one of the databases while the the transactions are prepared you have to manually commit or rollback the prepared transactions in your database as appropriate.
-
## Embedding SQL Insert/Update Expressions into a Flush {@name=flushsql}
This feature allows the value of a database column to be set to a SQL expression instead of a literal value. It's especially useful for atomic updates, calling stored procedures, etc. All you do is assign an expression to an attribute:
@@ -551,61 +471,64 @@ This feature allows the value of a database column to be set to a SQL expression
# issues "UPDATE some_table SET value=value+1"
session.commit()
-This works both for INSERT and UPDATE statements. After the flush/commit operation, the `value` attribute on `someobject` gets "deferred", so that when you again access it the newly generated value will be loaded from the database. This is the same mechanism at work when database-side column defaults fire off.
+This technique works both for INSERT and UPDATE statements. After the flush/commit operation, the `value` attribute on `someobject` above is expired, so that when next accessed the newly generated value will be loaded from the database.
## Using SQL Expressions with Sessions {@name=sql}
-SQL constructs and string statements can be executed via the `Session`. You'd want to do this normally when your `Session` is transactional and you'd like your free-standing SQL statements to participate in the same transaction.
-
-The two ways to do this are to use the connection/execution services of the Session, or to have your Session participate in a regular SQL transaction.
-
-First, a Session thats associated with an Engine or Connection can execute statements immediately (whether or not it's transactional):
+SQL expressions and strings can be executed via the `Session` within its transactional context. This is most easily accomplished using the `execute()` method, which returns a `ResultProxy` in the same manner as an `Engine` or `Connection`:
{python}
- Session = sessionmaker(bind=engine, transactional=True)
+ Session = sessionmaker(bind=engine)
sess = Session()
+
+ # execute a string statement
result = sess.execute("select * from table where id=:id", {'id':7})
- result2 = sess.execute(select([mytable], mytable.c.id==7))
+
+ # execute a SQL expression construct
+ result = sess.execute(select([mytable]).where(mytable.c.id==7))
-To get at the current connection used by the session, which will be part of the current transaction if one is in progress, use `connection()`:
+The current `Connection` held by the `Session` is accessible using the `connection()` method:
{python}
connection = sess.connection()
-
-A second scenario is that of a Session which is not directly bound to a connectable. This session executes statements relative to a particular `Mapper`, since the mappers are bound to tables which are in turn bound to connectables via their `MetaData` (either the session or the mapped tables need to be bound). In this case, the Session can conceivably be associated with multiple databases through different mappers; so it wants you to send along a `mapper` argument, which can be any mapped class or mapper instance:
+The examples above deal with a `Session` that's bound to a single `Engine` or `Connection`. To execute statements using a `Session` which is bound either to multiple engines, or none at all (i.e. relies upon bound metadata), both `execute()` and `connection()` accept a `mapper` keyword argument, which is passed a mapped class or `Mapper` instance, which is used to locate the proper context for the desired engine:
+
{python}
- # session is *not* bound to an engine or connection
- Session = sessionmaker(transactional=True)
+ Session = sessionmaker()
sess = Session()
# need to specify mapper or class when executing
result = sess.execute("select * from table where id=:id", {'id':7}, mapper=MyMappedClass)
- result2 = sess.execute(select([mytable], mytable.c.id==7), mapper=MyMappedClass)
- # need to specify mapper or class when you call connection()
+ result = sess.execute(select([mytable], mytable.c.id==7), mapper=MyMappedClass)
+
connection = sess.connection(MyMappedClass)
-The third scenario is when you are using `Connection` and `Transaction` yourself, and want the `Session` to participate. This is easy, as you just bind the `Session` to the connection:
+## Joining a Session into an External Transaction {@name=joining}
+
+If a `Connection` is being used which is already in a transactional state (i.e. has a `Transaction`), a `Session` can be made to participate within that transaction by just binding the `Session` to that `Connection`:
{python}
- # non-transactional session
- Session = sessionmaker(transactional=False)
+ Session = sessionmaker()
# non-ORM connection + transaction
conn = engine.connect()
trans = conn.begin()
- # bind the Session *instance* to the connection
+ # create a Session, bind to the connection
sess = Session(bind=conn)
- # ... etc
+ # ... work with session
- trans.commit()
+ sess.commit() # commit the session
+ sess.close() # close it out, prohibit further actions
-It's safe to use a `Session` which is transactional or autoflushing, as well as to call `begin()`/`commit()` on the session too; the outermost Transaction object, the one we declared explicitly, controls the scope of the transaction.
+ trans.commit() # commit the actual transaction
-When using the `threadlocal` engine context, things are that much easier; the `Session` uses the same connection/transaction as everyone else in the current thread, whether or not you explicitly bind it:
+Note that above, we issue a `commit()` both on the `Session` as well as the `Transaction`. This is an example of where we take advantage of `Connection`'s ability to maintain *subtransactions*, or nested begin/commit pairs. The `Session` is used exactly as though it were managing the transaction on its own; its `commit()` method issues its `flush()`, and commits the subtransaction. The subsequent transaction the `Session` starts after commit will not begin until it's next used. Above we issue a `close()` to prevent this from occuring. Finally, the actual transaction is committed using `Transaction.commit()`.
+
+When using the `threadlocal` engine context, the process above is simplified; the `Session` uses the same connection/transaction as everyone else in the current thread, whether or not you explicitly bind it:
{python}
engine = create_engine('postgres://mydb', strategy="threadlocal")
@@ -627,7 +550,7 @@ The `scoped_session()` function wraps around the `sessionmaker()` function, and
{python}
from sqlalchemy.orm import scoped_session, sessionmaker
- Session = scoped_session(sessionmaker(autoflush=True, transactional=True))
+ Session = scoped_session(sessionmaker())
However, when you instantiate this `Session` "class", in reality the object is pulled from a threadlocal variable, or if it doesn't exist yet, it's created using the underlying class generated by `sessionmaker()`:
@@ -650,38 +573,22 @@ Since the `Session()` constructor now returns the same `Session` object every ti
u2 = User()
# save to the contextual session, without instantiating
- Session.save(u1)
- Session.save(u2)
+ Session.add(u1)
+ Session.add(u2)
# view the "new" attribute
assert u1 in Session.new
- # flush changes (if not using autoflush)
- Session.flush()
-
- # commit transaction (if using a transactional session)
+ # commit changes
Session.commit()
-To "dispose" of the `Session`, there's two general approaches. One is to close out the current session, but to leave it assigned to the current context. This allows the same object to be re-used on another operation. This may be called from a current, instantiated `Session`:
-
- {python}
- sess.close()
-
-Or, when using `scoped_session()`, the `close()` method may also be called as a classmethod on the `Session` "class":
-
- {python}
- Session.close()
-
-When the `Session` is closed, it remains attached, but clears all of its contents and releases any ongoing transactional resources, including rolling back any remaining transactional state. The `Session` can then be used again.
-
-The other method is to remove the current session from the current context altogether. This is accomplished using the classmethod `remove()`:
+The contextual session may be disposed of by calling `Session.remove()`:
{python}
+ # remove current contextual session
Session.remove()
-
-After `remove()` is called, the next call to `Session()` will create a *new* `Session` object which then becomes the contextual session.
-That, in a nutshell, is all there really is to it. Now for all the extra things one should know.
+After `remove()` is called, the next operation with the contextual session will start a new `Session` for the current thread.
### Lifespan of a Contextual Session {@name=lifespan}
@@ -701,7 +608,7 @@ A (really, really) common question is when does the contextual session get creat
# some other code calls Session, it's the
# same contextual session as "sess"
sess2 = Session()
- sess2.save(foo)
+ sess2.add(foo)
sess2.commit()
# generate content to be returned
@@ -709,65 +616,14 @@ A (really, really) common question is when does the contextual session get creat
Session.remove() <-
web response <-
-Above, we illustrate a *typical* organization of duties, where the "Web Framework" layer has some integration built-in to manage the span of ORM sessions. Upon the initial handling of an incoming web request, the framework passes control to a controller. The controller then calls `Session()` when it wishes to work with the ORM; this method establishes the contextual Session which will remain until it's removed. Disparate parts of the controller code may all call `Session()` and will get the same session object. Then, when the controller has completed and the response is to be sent to the web server, the framework **closes out** the current contextual session, above using the `remove()` method which removes the session from the context altogether.
-
-As an alternative, the "finalization" step can also call `Session.close()`, which will leave the same session object in place. Which one is better ? For a web framework which runs from a fixed pool of threads, it doesn't matter much. For a framework which runs a **variable** number of threads, or which **creates and disposes** of a thread for each request, `remove()` is better, since it leaves no resources associated with the thread which might not exist.
-
-* Why close out the session at all ? Why not just leave it going so the next request doesn't have to do as many queries ?
-
- There are some cases where you may actually want to do this. However, this is a special case where you are dealing with data which **does not change** very often, or you don't care about the "freshness" of the data. In reality, a single thread of a web server may, on a slow day, sit around for many minutes or even hours without being accessed. When it's next accessed, if data from the previous request still exists in the session, that data may be very stale indeed. So it's generally better to have an empty session at the start of a web request.
-
-### Associating Classes and Mappers with a Contextual Session {@name=associating}
-
-Another luxury we gain, when we've established a `Session()` that can be globally accessed, is the ability for mapped classes and objects to provide us with session-oriented functionality automatically. When using the `scoped_session()` function, we access this feature using the `mapper` attribute on the object in place of the normal `sqlalchemy.orm.mapper` function:
-
- {python}
- # "contextual" mapper function
- mapper = Session.mapper
-
- # use normally
- mapper(User, users_table, properties={
- relation(Address)
- })
- mapper(Address, addresses_table)
-
-When we use the contextual `mapper()` function, our `User` and `Address` now gain a new attribute `query`, which will create a `Query` object for us against the contextual session:
-
- {python}
- wendy = User.query.filter_by(name='wendy').one()
-
-#### Auto-Save Behavior with Contextual Session's Mapper {@name=autosave}
-
-By default, when using Session.mapper, **new instances are saved into the contextual session automatically upon construction;** there is no longer a need to call `save()`:
-
- {python}
- >>> newuser = User(name='ed')
- >>> assert newuser in Session.new
- True
-
-The auto-save functionality can cause problems, namely that any `flush()` which occurs before a newly constructed object is fully populated will result in that object being INSERTed without all of its attributes completed. As a `flush()` is more frequent when using sessions with `autoflush=True`, **the auto-save behavior can be disabled**, using the `save_on_init=False` flag:
-
- {python}
- # "contextual" mapper function
- mapper = Session.mapper
+The above example illustrates an explicit call to `Session.remove()`. This has the effect such that each web request starts fresh with a brand new session. When integrating with a web framework, there's actually many options on how to proceed for this step, particularly as of version 0.5:
- # use normally, specify no save on init:
- mapper(User, users_table, properties={
- relation(Address)
- }, save_on_init=False)
- mapper(Address, addresses_table, save_on_init=False)
+ * Session.remove() - this is the most cut and dry approach; the `Session` is thrown away, all of its transactional/connection resources are closed out, everything within it is explicitly gone. A new `Session` will be used on the next request.
+ * Session.close() - Similar to calling `remove()`, in that all objects are explicitly expunged and all transactional/connection resources closed, except the actual `Session` object hangs around. It doesn't make too much difference here unless the start of the web request would like to pass specific options to the initial construction of `Session()`, such as a specific `Engine` to bind to.
+ * Session.commit() - In this case, the behavior is that any remaining changes pending are flushed, and the transaction is committed. The full state of the session is expired, so that when the next web request is started, all data will be reloaded. In reality, the contents of the `Session` are weakly referenced anyway so its likely that it will be empty on the next request in any case.
+ * Session.rollback() - Similar to calling commit, except we assume that the user would have called commit explicitly if that was desired; the `rollback()` ensures that no transactional state remains and expires all data, in the case that the request was aborted and did not roll back itself.
+ * do nothing - this is a valid option as well. The controller code is responsible for doing one of the above steps at the end of the request.
- # objects now again require explicit "save"
- >>> newuser = User(name='ed')
- >>> assert newuser in Session.new
- False
-
- >>> Session.save(newuser)
- >>> assert newuser in Session.new
- True
-
-The functionality of `Session.mapper` is an updated version of what used to be accomplished by the `assignmapper()` SQLAlchemy extension.
-
[Generated docstrings for scoped_session()](rel:docstrings_sqlalchemy.orm_modfunc_scoped_session)
## Partitioning Strategies
@@ -782,7 +638,7 @@ Vertical partitioning places different kinds of objects, or different tables, ac
engine1 = create_engine('postgres://db1')
engine2 = create_engine('postgres://db2')
- Session = sessionmaker(twophase=True, transactional=True)
+ Session = sessionmaker(twophase=True)
# bind User operations to engine 1, Account operations to engine 2
Session.configure(binds={User:engine1, Account:engine2})
diff --git a/doc/build/content/sqlexpression.txt b/doc/build/content/sqlexpression.txt
index ec7e92c24..bc46607d7 100644
--- a/doc/build/content/sqlexpression.txt
+++ b/doc/build/content/sqlexpression.txt
@@ -5,12 +5,12 @@ This tutorial will cover SQLAlchemy SQL Expressions, which are Python constructs
## Version Check
-A quick check to verify that we are on at least **version 0.4** of SQLAlchemy:
+A quick check to verify that we are on at least **version 0.5** of SQLAlchemy:
{python}
>>> import sqlalchemy
>>> sqlalchemy.__version__ # doctest:+SKIP
- 0.4.0
+ 0.5.0
## Connecting
@@ -33,14 +33,14 @@ We define our tables all within a catalog called `MetaData`, using the `Table` c
>>> metadata = MetaData()
>>> users = Table('users', metadata,
... Column('id', Integer, primary_key=True),
- ... Column('name', String(40)),
- ... Column('fullname', String(100)),
+ ... Column('name', String),
+ ... Column('fullname', String),
... )
>>> addresses = Table('addresses', metadata,
... Column('id', Integer, primary_key=True),
... Column('user_id', None, ForeignKey('users.id')),
- ... Column('email_address', String(50), nullable=False)
+ ... Column('email_address', String, nullable=False)
... )
All about how to define `Table` objects, as well as how to create them from an existing database automatically, is described in [metadata](rel:metadata).
@@ -55,8 +55,8 @@ Next, to tell the `MetaData` we'd actually like to create our selection of table
{}
CREATE TABLE users (
id INTEGER NOT NULL,
- name VARCHAR(40),
- fullname VARCHAR(100),
+ name VARCHAR,
+ fullname VARCHAR,
PRIMARY KEY (id)
)
{}
@@ -64,13 +64,20 @@ Next, to tell the `MetaData` we'd actually like to create our selection of table
CREATE TABLE addresses (
id INTEGER NOT NULL,
user_id INTEGER,
- email_address VARCHAR(50) NOT NULL,
+ email_address VARCHAR NOT NULL,
PRIMARY KEY (id),
FOREIGN KEY(user_id) REFERENCES users (id)
)
{}
COMMIT
+Users familiar with the syntax of CREATE TABLE may notice that the VARCHAR columns were generated without a length; on SQLite, this is a valid datatype, but on most databases it's not allowed. So if running this tutorial on a database such as Postgres or MySQL, and you wish to use SQLAlchemy to generate the tables, a "length" may be provided to the `String` type as below:
+
+ {python}
+ Column('name', String(50))
+
+The length field on `String`, as well as similar fields available on `Integer`, `Numeric`, etc. are not referenced by SQLAlchemy other than when creating tables.
+
## Insert Expressions
The first SQL expression we'll create is the `Insert` construct, which represents an INSERT statement. This is typically created relative to its target table:
@@ -327,19 +334,19 @@ If we use a literal value (a literal meaning, not a SQLAlchemy clause object), w
{python}
>>> print users.c.id==7
- users.id = :users_id_1
+ users.id = :id_1
The `7` literal is embedded in `ClauseElement`; we can use the same trick we did with the `Insert` object to see it:
{python}
>>> (users.c.id==7).compile().params
- {'users_id_1': 7}
+ {'id_1': 7}
Most Python operators, as it turns out, produce a SQL expression here, like equals, not equals, etc.:
{python}
>>> print users.c.id != 7
- users.id != :users_id_1
+ users.id != :id_1
>>> # None converts to IS NULL
>>> print users.c.name == None
@@ -347,7 +354,7 @@ Most Python operators, as it turns out, produce a SQL expression here, like equa
>>> # reverse works too
>>> print 'fred' > users.c.name
- users.name < :users_name_1
+ users.name < :name_1
If we add two integer columns together, we get an addition expression:
@@ -373,7 +380,7 @@ If you have come across an operator which really isn't available, you can always
{python}
>>> print users.c.name.op('tiddlywinks')('foo')
- users.name tiddlywinks :users_name_1
+ users.name tiddlywinks :name_1
## Conjunctions {@name=conjunctions}
@@ -384,9 +391,9 @@ We'd like to show off some of our operators inside of `select()` constructs. Bu
>>> print and_(users.c.name.like('j%'), users.c.id==addresses.c.user_id, #doctest: +NORMALIZE_WHITESPACE
... or_(addresses.c.email_address=='wendy@aol.com', addresses.c.email_address=='jack@yahoo.com'),
... not_(users.c.id>5))
- users.name LIKE :users_name_1 AND users.id = addresses.user_id AND
- (addresses.email_address = :addresses_email_address_1 OR addresses.email_address = :addresses_email_address_2)
- AND users.id <= :users_id_1
+ users.name LIKE :name_1 AND users.id = addresses.user_id AND
+ (addresses.email_address = :email_address_1 OR addresses.email_address = :email_address_2)
+ AND users.id <= :id_1
And you can also use the re-jiggered bitwise AND, OR and NOT operators, although because of Python operator precedence you have to watch your parenthesis:
@@ -394,9 +401,9 @@ And you can also use the re-jiggered bitwise AND, OR and NOT operators, although
>>> print users.c.name.like('j%') & (users.c.id==addresses.c.user_id) & \
... ((addresses.c.email_address=='wendy@aol.com') | (addresses.c.email_address=='jack@yahoo.com')) \
... & ~(users.c.id>5) # doctest: +NORMALIZE_WHITESPACE
- users.name LIKE :users_name_1 AND users.id = addresses.user_id AND
- (addresses.email_address = :addresses_email_address_1 OR addresses.email_address = :addresses_email_address_2)
- AND users.id <= :users_id_1
+ users.name LIKE :name_1 AND users.id = addresses.user_id AND
+ (addresses.email_address = :email_address_1 OR addresses.email_address = :email_address_2)
+ AND users.id <= :id_1
So with all of this vocabulary, let's select all users who have an email address at AOL or MSN, whose name starts with a letter between "m" and "z", and we'll also generate a column containing their full name combined with their email address. We will add two new constructs to this statement, `between()` and `label()`. `between()` produces a BETWEEN clause, and `label()` is used in a column expression to produce labels using the `AS` keyword; it's recommended when selecting from expressions that otherwise would not have a name:
@@ -528,7 +535,7 @@ Of course you can join on whatever expression you want, such as if we want to jo
{python}
>>> print users.join(addresses, addresses.c.email_address.like(users.c.name + '%'))
- users JOIN addresses ON addresses.email_address LIKE users.name || :users_name_1
+ users JOIN addresses ON addresses.email_address LIKE users.name || :name_1
When we create a `select()` construct, SQLAlchemy looks around at the tables we've mentioned and then places them in the FROM clause of the statement. When we use JOINs however, we know what FROM clause we want, so here we make usage of the `from_obj` keyword argument:
@@ -617,9 +624,9 @@ So we started small, added one little thing at a time, and at the end we have a
>>> print query
{opensql}SELECT users.id AS users_id, users.name AS users_name, users.fullname AS users_fullname, addresses_1.id AS addresses_1_id, addresses_1.user_id AS addresses_1_user_id, addresses_1.email_address AS addresses_1_email_address
FROM users LEFT OUTER JOIN addresses AS addresses_1 ON users.id = addresses_1.user_id
- WHERE users.name = :users_name_1 AND (EXISTS (SELECT addresses_1.id
+ WHERE users.name = :name_1 AND (EXISTS (SELECT addresses_1.id
FROM addresses AS addresses_1
- WHERE addresses_1.user_id = users.id AND addresses_1.email_address LIKE :addresses_email_address_1)) ORDER BY users.fullname DESC
+ WHERE addresses_1.user_id = users.id AND addresses_1.email_address LIKE :email_address_1)) ORDER BY users.fullname DESC
One more thing though, with automatic labeling applied as well as anonymous aliasing, how do we retrieve the columns from the rows for this thing ? The label for the `email_addresses` column is now the generated name `addresses_1_email_address`; and in another statement might be something different ! This is where accessing by result columns by `Column` object becomes very useful:
@@ -783,11 +790,11 @@ Also available, though not supported on all databases, are `intersect()`, `inter
To embed a SELECT in a column expression, use `as_scalar()`:
{python}
- {sql}>>> print conn.execute(select([
+ {sql}>>> print conn.execute(select([ # doctest: +NORMALIZE_WHITESPACE
... users.c.name,
... select([func.count(addresses.c.id)], users.c.id==addresses.c.user_id).as_scalar()
... ])).fetchall()
- SELECT users.name, (SELECT count(addresses.id)
+ SELECT users.name, (SELECT count(addresses.id) AS count_1
FROM addresses
WHERE users.id = addresses.user_id) AS anon_1
FROM users
@@ -797,11 +804,11 @@ To embed a SELECT in a column expression, use `as_scalar()`:
Alternatively, applying a `label()` to a select evaluates it as a scalar as well:
{python}
- {sql}>>> print conn.execute(select([
+ {sql}>>> print conn.execute(select([ # doctest: +NORMALIZE_WHITESPACE
... users.c.name,
... select([func.count(addresses.c.id)], users.c.id==addresses.c.user_id).label('address_count')
... ])).fetchall()
- SELECT users.name, (SELECT count(addresses.id)
+ SELECT users.name, (SELECT count(addresses.id) AS count_1
FROM addresses
WHERE users.id = addresses.user_id) AS address_count
FROM users
@@ -839,7 +846,7 @@ The `select()` function can take keyword arguments `order_by`, `group_by` (as we
>>> s = select([addresses.c.user_id, func.count(addresses.c.id)]).\
... group_by(addresses.c.user_id).having(func.count(addresses.c.id)>1)
{opensql}>>> print conn.execute(s).fetchall()
- SELECT addresses.user_id, count(addresses.id)
+ SELECT addresses.user_id, count(addresses.id) AS count_1
FROM addresses GROUP BY addresses.user_id
HAVING count(addresses.id) > ?
[1]
diff --git a/doc/build/gen_docstrings.py b/doc/build/gen_docstrings.py
index d9bad1384..8ea4c7652 100644
--- a/doc/build/gen_docstrings.py
+++ b/doc/build/gen_docstrings.py
@@ -6,11 +6,8 @@ from sqlalchemy import schema, types, engine, sql, pool, orm, exceptions, databa
from sqlalchemy.sql import compiler, expression
from sqlalchemy.engine import default, strategies, threadlocal, url
import sqlalchemy.orm.shard
-import sqlalchemy.ext.sessioncontext as sessioncontext
-import sqlalchemy.ext.selectresults as selectresults
import sqlalchemy.ext.orderinglist as orderinglist
import sqlalchemy.ext.associationproxy as associationproxy
-import sqlalchemy.ext.assignmapper as assignmapper
import sqlalchemy.ext.sqlsoup as sqlsoup
import sqlalchemy.ext.declarative as declarative
diff --git a/doc/build/genhtml.py b/doc/build/genhtml.py
index e28f86609..f53332e64 100644
--- a/doc/build/genhtml.py
+++ b/doc/build/genhtml.py
@@ -47,7 +47,7 @@ if options.file:
else:
to_gen = files + post_files
-title='SQLAlchemy 0.4 Documentation'
+title='SQLAlchemy 0.5 Documentation'
version = options.version
diff --git a/doc/build/testdocs.py b/doc/build/testdocs.py
index 998320fb0..cb6499d5f 100644
--- a/doc/build/testdocs.py
+++ b/doc/build/testdocs.py
@@ -5,7 +5,7 @@ import os
import re
import doctest
import sqlalchemy.util as util
-import sqlalchemy.logging as salog
+import sqlalchemy.log as salog
import logging
salog.default_enabled=True