From: Mike Bayer Date: Sun, 7 Aug 2011 05:13:29 +0000 (-0400) Subject: - classical mappings X-Git-Tag: rel_0_7_3~88 X-Git-Url: http://git.ipfire.org/gitweb.cgi?a=commitdiff_plain;h=c4e3cb2bf5a16bfca4f42ed6729ada1040ab8690;p=thirdparty%2Fsqlalchemy%2Fsqlalchemy.git - classical mappings - break out eager loads add contains eager again - edits --- diff --git a/doc/build/orm/inheritance.rst b/doc/build/orm/inheritance.rst index 31c5c8d93d..351a493cca 100644 --- a/doc/build/orm/inheritance.rst +++ b/doc/build/orm/inheritance.rst @@ -15,6 +15,11 @@ When mappers are configured in an inheritance relationship, SQLAlchemy has the ability to load elements "polymorphically", meaning that a single query can return objects of multiple types. +.. note:: This section currently uses classical mappings to illustrate inheritance + configurations, and will soon be updated to standardize on Declarative. + Until then, please refer to :ref:`declarative_inheritance` for information on + how common inheritance mappings are constructed declaratively. + For the following sections, assume this class relationship: .. sourcecode:: python+sql @@ -525,6 +530,7 @@ Upon select, the polymorphic union produces a query like this: For a recipe that sets up concrete inheritance using declarative, see the `DeclarativeAbstractConcreteBase `_ recipe on the wiki. +Other helpers for concrete inheritance with declarative are currently under development. Using Relationships with Inheritance ------------------------------------ diff --git a/doc/build/orm/mapper_config.rst b/doc/build/orm/mapper_config.rst index a88f3d7e75..d4f3ddc0a5 100644 --- a/doc/build/orm/mapper_config.rst +++ b/doc/build/orm/mapper_config.rst @@ -10,9 +10,89 @@ This section describes a variety of configurational patterns that are usable with mappers. It assumes you've worked through :ref:`ormtutorial_toplevel` and know how to construct and use rudimentary mappers and relationships. -Note that all patterns here apply both to the usage of explicit -:func:`~.orm.mapper` and :class:`.Table` objects as well as when using the -:mod:`sqlalchemy.ext.declarative` extension. Any example in this section which +.. _classical_mapping: + +Classical Mappings +================== + +Recall from :ref:`ormtutorial_toplevel` that we normally use the :ref:`declarative_toplevel` +system to define mappings. The "Classical Mapping" refers to the process +of defining :class:`.Table` metadata and mapped class via :func:`.mapper` separately. +While direct usage of :func:`.mapper` is not prominent in modern SQLAlchemy, +the function can be used +to create alternative mappings for an already mapped class, offers greater configurational +flexibility in certain highly circular configurations, and is also at the base of +alternative configurational systems not based upon Declarative. Many SQLAlchemy +applications in fact use classical mappings directly for configuration. + +The ``User`` example in the tutorial, using classical mapping, defines the +:class:`.Table`, class, and :func:`.mapper` of the class separately. Below we illustrate +the full ``User``/``Address`` example using this style:: + + from sqlalchemy import Table, Metadata, Column, ForeignKey, Integer, String + from sqlalchemy.orm import mapper, relationship + + metadata = MetaData() + + users = Table('user', metadata, + Column('id', Integer, primary_key=True), + Column('name', String(50)), + Column('fullname', String(50)), + Column('password', String(12)) + ) + + addresses = Table('address', metadata, + Column('id', Integer, primary_key=True), + Column('user_id', Integer, ForeignKey('user.id')), + Column('email_address', String(50)) + ) + + class User(object): + def __init__(self, name, fullname, password): + self.name = name + self.fullname = fullname + self.password = password + + class Address(object): + def __init__(self, email_address): + self.email_address = email_address + + + mapper(User, users, properties={ + 'addresses':relationship(Address, order_by=addresses.c.id, backref="user") + }) + mapper(Address, addresses) + +When the above is complete we now have a :class:`.Table`/:func:`.mapper` setup the same as that +set up using Declarative in the tutorial. Note that the mappings do not have the +benefit of the instrumented ``User`` and ``Address`` classes available, nor is the "string" argument +system of :func:`.relationship` available, as this is a feature of Declarative. The ``order_by`` +argument of the ``User.addresses`` relationship is defined in terms of the actual ``addresses`` +table instead of the ``Address`` class. + +It's also worth noting that the "Classical" and "Declarative" mapping systems are not in +any way exclusive of each other. The two can be mixed freely - below we can +define a new class ``Order`` using a declarative base, which links back to ``User``- +no problem, except that we can't specify ``User`` as a string since it's not available +in the "base" registry:: + + from sqlalchemy.ext.declarative import declarative_base + + Base = declarative_base() + + class Order(Base): + __tablename__ = 'order' + + id = Column(Integer, primary_key=True) + user_id = Column(ForeignKey('user.id')) + order_number = Column(String(50)) + user = relationship(User, backref="orders") + +This reference document currently uses classical mappings for most examples. However, all +patterns here apply both to the usage of explicit +:func:`~.orm.mapper` and :class:`.Table` objects as well as when using +Declarative, where options that are specific to the :func:`.mapper` +function can be specified with Declarative via the ``__mapper__`` attribute. Any example in this section which takes a form such as:: mapper(User, users_table, primary_key=[users_table.c.id]) @@ -25,8 +105,8 @@ Would translate into declarative as:: 'primary_key':[users_table.c.id] } -Or if using ``__tablename__``, :class:`.Column` objects are declared inline -with the class definition. These are usable as is within ``__mapper_args__``:: +:class:`.Column` objects which are declared inline can also +be used directly in ``__mapper_args__``:: class User(Base): __tablename__ = 'users' @@ -45,48 +125,6 @@ The default behavior of :func:`~.orm.mapper` is to assemble all the columns in the mapped :class:`.Table` into mapped object attributes. This behavior can be modified in several ways, as well as enhanced by SQL expressions. -Mapping a Subset of Table Columns ---------------------------------- - -To reference a subset of columns referenced by a table as mapped attributes, -use the ``include_properties`` or ``exclude_properties`` arguments. For -example:: - - mapper(User, users_table, include_properties=['user_id', 'user_name']) - -...will map the ``User`` class to the ``users_table`` table, only including -the "user_id" and "user_name" columns - the rest are not refererenced. -Similarly:: - - mapper(Address, addresses_table, - exclude_properties=['street', 'city', 'state', 'zip']) - -...will map the ``Address`` class to the ``addresses_table`` table, including -all columns present except "street", "city", "state", and "zip". - -When this mapping is used, the columns that are not included will not be -referenced in any SELECT statements emitted by :class:`.Query`, nor will there -be any mapped attribute on the mapped class which represents the column; -assigning an attribute of that name will have no effect beyond that of -a normal Python attribute assignment. - -In some cases, multiple columns may have the same name, such as when -mapping to a join of two or more tables that share some column name. To -exclude or include individual columns, :class:`.Column` objects -may also be placed within the "include_properties" and "exclude_properties" -collections (new feature as of 0.6.4):: - - mapper(UserAddress, users_table.join(addresses_table), - exclude_properties=[addresses_table.c.id], - primary_key=[users_table.c.id] - ) - -It should be noted that insert and update defaults configured on individal -:class:`.Column` objects, such as those configured by the "default", -"update", "server_default" and "server_onupdate" arguments, will continue -to function normally even if those :class:`.Column` objects are not mapped. -This functionality is part of the SQL expression and execution system and -occurs below the level of the ORM. Attribute Names for Mapped Columns @@ -168,6 +206,49 @@ or with declarative:: Further examples of :func:`.column_property` are at :ref:`mapper_sql_expressions`. +Mapping a Subset of Table Columns +--------------------------------- + +To reference a subset of columns referenced by a table as mapped attributes, +use the ``include_properties`` or ``exclude_properties`` arguments. For +example:: + + mapper(User, users_table, include_properties=['user_id', 'user_name']) + +...will map the ``User`` class to the ``users_table`` table, only including +the "user_id" and "user_name" columns - the rest are not refererenced. +Similarly:: + + mapper(Address, addresses_table, + exclude_properties=['street', 'city', 'state', 'zip']) + +...will map the ``Address`` class to the ``addresses_table`` table, including +all columns present except "street", "city", "state", and "zip". + +When this mapping is used, the columns that are not included will not be +referenced in any SELECT statements emitted by :class:`.Query`, nor will there +be any mapped attribute on the mapped class which represents the column; +assigning an attribute of that name will have no effect beyond that of +a normal Python attribute assignment. + +In some cases, multiple columns may have the same name, such as when +mapping to a join of two or more tables that share some column name. To +exclude or include individual columns, :class:`.Column` objects +may also be placed within the "include_properties" and "exclude_properties" +collections (new feature as of 0.6.4):: + + mapper(UserAddress, users_table.join(addresses_table), + exclude_properties=[addresses_table.c.id], + primary_key=[users_table.c.id] + ) + +It should be noted that insert and update defaults configured on individal +:class:`.Column` objects, such as those configured by the "default", +"update", "server_default" and "server_onupdate" arguments, will continue +to function normally even if those :class:`.Column` objects are not mapped. +This functionality is part of the SQL expression and execution system and +occurs below the level of the ORM. + .. autofunction:: column_property .. _deferred: diff --git a/doc/build/orm/tutorial.rst b/doc/build/orm/tutorial.rst index 6e84752dd9..5924a80d2c 100644 --- a/doc/build/orm/tutorial.rst +++ b/doc/build/orm/tutorial.rst @@ -70,13 +70,11 @@ the SQL behind a popup window so it doesn't get in our way; just click the The return value of :func:`.create_engine` is an instance of :class:`.Engine`, and it represents the core interface to the database, adapted through a **dialect** that handles the details of the database and DBAPI in use. In this case the SQLite dialect will interpret instructions -to the Python built-in ``sqlite3`` module. +to the Python built-in ``sqlite3`` module. The :class:`.Engine` has not actually tried to connect to the database yet; that happens -only the first time it is asked to perform a task against the database. - -Normally, the :class:`.Engine` is passed off to the ORM where it is used behind the scenes. -We can execute SQL directly from it however, as we illustrate here: +only the first time it is asked to perform a task against the database. We can illustrate +this by asking it to perform a simple SELECT statement: .. sourcecode:: python+sql @@ -85,24 +83,28 @@ We can execute SQL directly from it however, as we illustrate here: () {stop}1 -Above, by executing a SQL statement, the :class:`.Engine` creates a connection which -by default remains pooled, and will be re-used for subsequent statement executions. +As the :meth:`.Engine.execute` method is called, the :class:`.Engine` establishes a connection to the +SQLite database, which is then used to emit the SQL. The connection is then returned to an internal +connection pool where it will be reused on subsequent statement executions. While we illustrate direct usage of the +:class:`.Engine` here, this isn't typically necessary when using the ORM, where the :class:`.Engine`, +once created, is used behind the scenes by the ORM as we'll see shortly. Declare a Mapping ================= -When using the ORM, the basic configurational process is to define **table metadata**, -which describes the database tables we'll be dealing with, and then to define -our own classes which will be **mapped** to those tables. In modern SQLAlchemy, +When using the ORM, the configurational process starts by describing the database +tables we'll be dealing with, and then by defining our own classes which will +be mapped to those tables. In modern SQLAlchemy, these two tasks are usually performed together, -using a system known as :ref:`declarative_toplevel`, which allows us to create our own -mapped classes that also define how they will be mapped to an actual database -table. +using a system known as :ref:`declarative_toplevel`, which allows us to create +classes that include directives to describe the actual database table they will +be mapped to. -Mapped classes are defined in terms of a base class which maintains a catalog of classes and +Classes mapped using the Declarative system are defined in terms of a base class which +maintains a catalog of classes and tables relative to that base - this is known as the **declarative base class**. Our application will usually have just one instance of this base in a commonly -imported module. We create this class using the :func:`.declarative_base` +imported module. We create the base class using the :func:`.declarative_base` function, as follows:: >>> from sqlalchemy.ext.declarative import declarative_base @@ -111,11 +113,11 @@ function, as follows:: Now that we have a "base", we can define any number of mapped classes in terms of it. We will start with just a single table called ``users``, which will store -records for the end-users using our application (lets assume it's a website). +records for the end-users using our application. A new class called ``User`` will be the class to which we map this table. The imports we'll need to accomplish this include objects that represent the components -of our table, including the :class:`.Column` class which represents a column -in a table, as well as the :class:`.Integer` and :class:`.String` type objects that +of our table, including the :class:`.Column` class which represents a database column, +as well as the :class:`.Integer` and :class:`.String` classes that represent basic datatypes used in columns:: >>> from sqlalchemy import Column, Integer, String @@ -135,26 +137,43 @@ represent basic datatypes used in columns:: ... def __repr__(self): ... return "" % (self.name, self.fullname, self.password) +The above ``User`` class establishes details about the table being mapped, including the name of the table denoted +by the ``__tablename__`` attribute, a set of columns ``id``, ``name``, ``fullname`` and ``password``, +where the ``id`` column will also be the primary key of the table. While its certainly possible +that some database tables don't have primary key columns (as is also the case with views, which can +also be mapped), the ORM in order to actually map to a particular table needs there +to be at least one column denoted as a primary key column; multiple-column, i.e. composite, primary keys +are of course entirely feasible as well. + +We define a constructor via ``__init__()`` and also a ``__repr__()`` method - both are optional. The +class of course can have any number of other methods and attributes as required by the application, +as it's basically just a plain Python class. Inheriting from ``Base`` is also only a requirement +of the declarative configurational system, which itself is optional and relatively open ended; at it's +core, the SQLAlchemy ORM only requires that a class be a so-called "new style class", that is, it inherits +from ``object`` in Python 2, in order to be mapped. All classes in Python 3 are "new style" classes. + .. topic:: The Non Opinionated Philosophy - Above, the ``__tablename__`` attribute represents the name of the table in the - database to which we are mapping, and the :class:`.Column` object referenced by the - name ``id`` defines the **primary key** column of our table; the usage of :class:`.Integer` - states that it should be of type ``INT``. SQLAlchemy never makes - assumptions about decisions like these - the developer using SQLAlchemy must - always decide on the specific conventions to be used. However, that doesn't mean the + In our ``User`` mapping example, it was required that we identify the name of the table + in use, as well as the names and characteristics of all columns which we care about, + including which column or columns + represent the primary key, as well as some basic information about the types in use. + SQLAlchemy never makes assumptions about these decisions - the developer must + always be explicit about specific conventions in use. However, that doesn't mean the task can't be automated. While this tutorial will keep things explicit, developers are - encouraged to make usage of helper functions as well as "Declarative Mixins" to + encouraged to make use of helper functions as well as "Declarative Mixins" to automate their tasks in large scale applications. The section :ref:`declarative_mixins` introduces many of these techniques. -With our ``User`` class constructed via the Declarative system, we have defined **table metadata** as well as a -**mapped class**. This configuration is shorthand for what in SQLAlchemy is -called a "classical mapping", -which would have us first create an object representing the 'users' table using a class known as -:class:`.Table`, and then creating a mapping to this table through the usage of a function called -:func:`.mapper`. Declarative instead performs these steps for us, making available the -:class:`.Table` it has created for us via the ``__table__`` attribute:: +With our ``User`` class constructed via the Declarative system, we have defined information about +our table, known as **table metadata**, as well as a user-defined class which is linked to this +table, known as a **mapped class**. Declarative has provided for us a shorthand system for what in SQLAlchemy is +called a "Classical Mapping", which specifies these two units separately and is discussed +in :ref:`classical_mapping`. The table +is actually represented by a datastructure known as :class:`.Table`, and the mapping represented +by a "mapper" object generated by a function called :func:`.mapper`. Declarative performs both of +these steps for us, making available the +:class:`.Table` it has created via the ``__table__`` attribute:: >>> User.__table__ # doctest: +NORMALIZE_WHITESPACE Table('users', MetaData(None), @@ -175,12 +194,14 @@ new tables that have yet to be created in our SQLite database, so one helpful fe the :class:`.MetaData` object offers is the ability to issue CREATE TABLE statements to the database for all tables that don't yet exist. We illustrate this by calling the :meth:`.MetaData.create_all` method, passing in our :class:`.Engine` -as a source of database connectivity: +as a source of database connectivity. We will see that special commands are +first emitted to check for the presence of the ``users`` table, and following that +the actual ``CREATE TABLE`` statement: .. sourcecode:: python+sql - {sql}>>> Base.metadata.create_all(engine) # doctest:+ELLIPSIS,+NORMALIZE_WHITESPACE - PRAGMA table_info("users") + >>> Base.metadata.create_all(engine) # doctest:+ELLIPSIS,+NORMALIZE_WHITESPACE + {opensql}PRAGMA table_info("users") () CREATE TABLE users ( id INTEGER NOT NULL, @@ -241,8 +262,7 @@ as a source of database connectivity: Create an Instance of the Mapped Class ====================================== -With our fully specified ``User`` class and associated table metadata, -let's now create and inspect a ``User`` object:: +With mappings complete, let's now create and inspect a ``User`` object:: >>> ed_user = User('ed', 'Ed Jones', 'edspassword') >>> ed_user.name @@ -259,8 +279,8 @@ default, the ORM creates class attributes for all columns present in the table being mapped. 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 +functionality of this instrumentation includes the ability to fire on change +events, track modifications, and to automatically load new data from the database when needed. Since we have not yet told SQLAlchemy to persist ``Ed Jones`` within the @@ -271,9 +291,10 @@ will be populated with a newly generated value. Note that in our ``User`` example we supplied an ``__init__()`` method, which receives ``name``, ``fullname`` and ``password`` as positional arguments. - The declarative system supplies for us a default constructor if one is + The Declarative system supplies for us a default constructor if one is not already present, which accepts keyword arguments of the same name - as that of the mapped attributes:: + as that of the mapped attributes. Below we define ``User`` without + specifying a constructor:: class User(Base): __tablename__ = 'users' @@ -335,7 +356,7 @@ To persist our ``User`` object, we :meth:`~.Session.add` it to our :class:`~sqla >>> ed_user = User('ed', 'Ed Jones', 'edspassword') >>> session.add(ed_user) -At this point, we say that the instance is *pending*; no SQL has yet been issued +At this point, we say that the instance is **pending**; no SQL has yet been issued and the object is not yet represented by a row in the database. The :class:`~sqlalchemy.orm.session.Session` will issue the SQL to persist ``Ed Jones`` as soon as is needed, using a process known as a **flush**. If we @@ -454,8 +475,7 @@ load-on-first-access. In this case, the entire row was re-loaded on access because a new transaction was begun after we issued :meth:`~.Session.commit`. SQLAlchemy by default refreshes data from a previous transaction the first time it's accessed within a new transaction, so that the most recent state is available. -The level of reloading is configurable as is described in the chapter on -Sessions. +The level of reloading is configurable as is described in :ref:`session_toplevel`. Rolling Back ============ @@ -641,7 +661,7 @@ operators with the class-level attributes on your mapped class: ('Ed Jones',) {stop}ed -The :class:`~sqlalchemy.orm.query.Query` object is fully *generative*, meaning +The :class:`~sqlalchemy.orm.query.Query` object is fully **generative**, meaning that most method calls return a new :class:`~sqlalchemy.orm.query.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 @@ -922,7 +942,7 @@ declarative, we define this table along with its mapped class, ``Address``: ... email_address = Column(String, nullable=False) ... user_id = Column(Integer, ForeignKey('users.id')) ... - ... user = relationship(User, backref=backref('addresses', order_by=id)) + ... user = relationship("User", backref=backref('addresses', order_by=id)) ... ... def __init__(self, email_address): ... self.email_address = email_address @@ -930,48 +950,36 @@ declarative, we define this table along with its mapped class, ``Address``: ... def __repr__(self): ... return "" % self.email_address -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 +The above class introduces a **foreign key** constraint, applied to the +:class:`.Column` named ``user_id``, using the :class:`.ForeignKey` construct. +This configuration defines the relationship between the two tables +as it exists in the database. The relationship between the ``User`` and +``Address`` classes themselves is defined separately using the :func:`~sqlalchemy.orm.relationship()` 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 relationship 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. - -.. note:: The :func:`~sqlalchemy.orm.relationship()` function has historically - been known as :func:`~sqlalchemy.orm.relation()` in the 0.5 series of - SQLAlchemy and earlier. - -The :func:`~sqlalchemy.orm.relationship()` function is extremely flexible, and -could just have easily been defined on the ``User`` class: - -.. sourcecode:: python+sql +``user`` to be placed on the ``Address`` class which links to ``User``, as well as an ``addresses`` +collection to be placed on the ``User`` class, defined by the :func:`.backref` +class as a configuration nested inside the relationship. Such a relationship is known as +a **bidirectional** relationship, in that each of ``User`` and ``Address`` contains +an explicit link to the other. + +Based on the placement of the :class:`.ForeignKey`, :func:`.relationship` and +:func:`.backref` know that the ``User.addresses`` relationship is **one to many** +and that the ``Address.user`` relationship is **many to one** - this is established +strictly based on the geometry of the tables involved. + +Arguments to :func:`.relationship` which concern the remote class +can be specified using strings, which are evaluated once all mappings are complete +as Python expressions. The names which are allowed include, among other things, the names of all classes +which have been created in terms of the declared base. Below we illustrate creating +the same "addresses/user" bidirectional relationship in terms of ``User`` instead of +``Address``:: class User(Base): # .... - addresses = relationship(Address, order_by=Address.id, backref="user") - -We are also free to not define a backref, and to define the -:func:`~sqlalchemy.orm.relationship()` only on one class and not the other. It -is also possible to define two separate :func:`~sqlalchemy.orm.relationship()` -constructs for either direction, which is generally safe for many-to-one and -one-to-many relationships, but not for many-to-many relationships. - -When using the :mod:`~.sqlalchemy.ext.declarative` extension, -:func:`~sqlalchemy.orm.relationship()` gives us the option to use strings for -most arguments that concern the target class, in the case that the target -class has not yet been defined: - -.. sourcecode:: python+sql - - class User(Base): - .... addresses = relationship("Address", order_by="Address.id", backref="user") +See the docstring for :func:`.relationship` for more detail on argument style. + 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: @@ -1015,9 +1023,9 @@ just assign a full list directly: >>> 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: +automatically become visible in the other direction. This behavior occurs +based on attribute on-change events and is evaluated in Python, without +using any SQL: .. sourcecode:: python+sql @@ -1083,8 +1091,9 @@ Querying with Joins Now that we have two tables, we can show some more features of :class:`.Query`, specifically how to create queries that deal with both tables at the same time. -For those needing to brush up on this topic, the `Wikipedia page on SQL JOIN -`_ is a good place check. +The `Wikipedia page on SQL JOIN +`_ offers a good introduction to +join techniques, several of which we'll illustrate here. To construct a simple implicit join between ``User`` and ``Address``, we can use :meth:`.Query.filter()` to equate their related columns together. @@ -1107,8 +1116,8 @@ Below we load the ``User`` and ``Address`` entities at once using this method: ('jack@google.com',) {stop} -Next, to construct the actual SQL JOIN syntax, the most common way is -to use :meth:`.Query.join`: +The actual SQL JOIN syntax, on the other hand, is most easily achieved using the :meth:`.Query.join` +method: .. sourcecode:: python+sql @@ -1139,7 +1148,8 @@ As you would expect, the same idea is used for "outer" joins, using the query.outerjoin(User.addresses) # LEFT OUTER JOIN The reference documentation for :meth:`~.Query.join` contains detailed information -and examples of the calling styles accepted. +and examples of the calling styles accepted by this method; :meth:`~.Query.join` +is an important method at the center of usage for any SQL-fluent application. Using Aliases ------------- @@ -1148,7 +1158,7 @@ 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 occurrences of that table. The :class:`~sqlalchemy.orm.query.Query` supports this most -explicitly using the ``aliased`` construct. Below we join to the ``Address`` +explicitly using the :attr:`~sqlalchemy.orm.aliased` construct. Below we join to the ``Address`` entity twice, to locate a user who has two distinct email addresses at the same time: @@ -1361,12 +1371,15 @@ Recall earlier that we illustrated a **lazy loading** operation, when we accessed the ``User.addresses`` collection of a ``User`` and SQL was emitted. If you want to reduce the number of queries (dramatically, in many cases), we can apply an **eager load** to the query operation. SQLAlchemy -offers two types of eager loading, available via the :func:`.orm.joinedload` -and :func:`.orm.subqueryload` functions. These functions are **query options** -that give additional instructions to the :class:`.Query` on how -we would like various attributes to be loaded. +offers three types of eager loading, two of which are automatic, and a third +which involves custom criterion. All three are usually invoked via functions known +as **query options** which give additional instructions to the :class:`.Query` on how +we would like various attributes to be loaded, via the :meth:`.Query.options` method. + +Subquery Load +------------- -In this case we'd like to indicate that ``User.addresses`` should load eagerly. +In this case we'd like to indicate that ``User.addresses`` should load eagerly. A good choice for loading a set of objects as well as their related collections is the :func:`.orm.subqueryload` option, which emits a second SELECT statement that fully loads the collections associated with the results just loaded. @@ -1403,7 +1416,10 @@ very easy to use: >>> jack.addresses [, ] -The other eager loading function is more well known and is called +Joined Load +------------- + +The other automatic eager loading function is more well known and is called :func:`.orm.joinedload`. This style of loading emits a JOIN, by default a LEFT OUTER JOIN, so that the lead object as well as the related object or collection is loaded in one step. We illustrate loading the same @@ -1445,31 +1461,7 @@ While :func:`.joinedload` has been around for a long time, :func:`.subqueryload` is a newer form of eager loading. :func:`.subqueryload` tends to be more appropriate for loading related collections while :func:`.joinedload` tends to be better suited for many-to-one relationships, due to the fact that only one row is loaded -for both the lead and the related object. Below we illustrate loading an ``Address`` -row, using :func:`.joinedload` to load the corresponding ``Address.user`` datamember: - -.. sourcecode:: python+sql - - {sql}>>> jacks_address = session.query(Address).\ - ... options(joinedload(Address.user)).\ - ... filter_by(email_address='j25@yahoo.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, - users_1.id AS users_1_id, - users_1.name AS users_1_name, - users_1.fullname AS users_1_fullname, - users_1.password AS users_1_password - FROM addresses LEFT OUTER JOIN users AS users_1 ON users_1.id = addresses.user_id - WHERE addresses.email_address = ? - ('j25@yahoo.com',) - - {stop}>>> jacks_address - - - >>> jacks_address.user - +for both the lead and the related object. .. topic:: ``joinedload()`` is not a replacement for ``join()`` @@ -1483,8 +1475,46 @@ row, using :func:`.joinedload` to load the corresponding ``Address.user`` datame on actual results. See the section :ref:`zen_of_eager_loading` for a detailed description of how this is used. -Full information on built in eager loading, as well as how to construct joined eager -loads explicitly using :meth:`.Query.join`, is available at :ref:`loading_toplevel`. +Explicit Join + Eagerload +-------------------------- + +A third style of eager loading is when we are constructing a JOIN explicitly in +order to locate the primary rows, and would like to additionally apply the extra +table to a related object or collection on the primary object. This feature +is supplied via the :func:`.orm.contains_eager` function, and is most +typically useful for pre-loading the many-to-one object on a query that needs +to filter on that same object. Below we illustrate loading an ``Address`` +row as well as the related ``User`` object, filtering on the ``User`` named +"jack" and using :func:`.orm.contains_eager` to apply the "user" columns to the ``Address.user`` +attribute: + +.. sourcecode:: python+sql + + >>> from sqlalchemy.orm import contains_eager + {sql}>>> jacks_addresses = session.query(Address).\ + ... join(Address.user).\ + ... filter(User.name=='jack').\ + ... options(contains_eager(Address.user)).\ + ... all() #doctest: +NORMALIZE_WHITESPACE + SELECT users.id AS users_id, + users.name AS users_name, + users.fullname AS users_fullname, + users.password AS users_password, + addresses.id AS addresses_id, + addresses.email_address AS addresses_email_address, + addresses.user_id AS addresses_user_id + FROM addresses JOIN users ON users.id = addresses.user_id + WHERE users.name = ? + ('jack',) + + {stop}>>> jacks_addresses + [, ] + + >>> jacks_addresses[0].user + + +For more information on eager loading, including how to configure various forms +of loading by default, see the section :ref:`loading_toplevel`. Deleting ======== @@ -1502,9 +1532,13 @@ the session, then we'll issue a ``count`` query to see that no rows remain: (None, 2) DELETE FROM users WHERE users.id = ? (5,) - SELECT count(1) AS count_1 - FROM users - WHERE users.name = ? + SELECT count(*) AS count_1 + FROM (SELECT users.id AS users_id, + users.name AS users_name, + users.fullname AS users_fullname, + users.password AS users_password + FROM users + WHERE users.name = ?) AS anon_1 ('jack',) {stop}0 @@ -1515,9 +1549,12 @@ 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(1) AS count_1 - FROM addresses - WHERE addresses.email_address IN (?, ?) + SELECT count(*) AS count_1 + FROM (SELECT addresses.id AS addresses_id, + addresses.email_address AS addresses_email_address, + addresses.user_id AS addresses_user_id + FROM addresses + WHERE addresses.email_address IN (?, ?)) AS anon_1 ('jack@google.com', 'j25@yahoo.com') {stop}2 @@ -1598,9 +1635,12 @@ removing an address from his ``addresses`` collection will result in that ... ).count() # doctest: +NORMALIZE_WHITESPACE DELETE FROM addresses WHERE addresses.id = ? (2,) - SELECT count(1) AS count_1 - FROM addresses - WHERE addresses.email_address IN (?, ?) + SELECT count(*) AS count_1 + FROM (SELECT addresses.id AS addresses_id, + addresses.email_address AS addresses_email_address, + addresses.user_id AS addresses_user_id + FROM addresses + WHERE addresses.email_address IN (?, ?)) AS anon_1 ('jack@google.com', 'j25@yahoo.com') {stop}1 @@ -1615,18 +1655,25 @@ Deleting Jack will delete both Jack and his remaining ``Address``: (1,) DELETE FROM users WHERE users.id = ? (5,) - SELECT count(1) AS count_1 - FROM users - WHERE users.name = ? + SELECT count(*) AS count_1 + FROM (SELECT users.id AS users_id, + users.name AS users_name, + users.fullname AS users_fullname, + users.password AS users_password + FROM users + WHERE users.name = ?) AS anon_1 ('jack',) {stop}0 {sql}>>> session.query(Address).filter( ... Address.email_address.in_(['jack@google.com', 'j25@yahoo.com']) ... ).count() # doctest: +NORMALIZE_WHITESPACE - SELECT count(1) AS count_1 - FROM addresses - WHERE addresses.email_address IN (?, ?) + SELECT count(*) AS count_1 + FROM (SELECT addresses.id AS addresses_id, + addresses.email_address AS addresses_email_address, + addresses.user_id AS addresses_user_id + FROM addresses + WHERE addresses.email_address IN (?, ?)) AS anon_1 ('jack@google.com', 'j25@yahoo.com') {stop}0 diff --git a/lib/sqlalchemy/engine/base.py b/lib/sqlalchemy/engine/base.py index a5f5ae223f..3139904d24 100644 --- a/lib/sqlalchemy/engine/base.py +++ b/lib/sqlalchemy/engine/base.py @@ -2200,7 +2200,7 @@ class Engine(Connectable, log.Identified): @property @util.deprecated("0.7", - "Use :attr:`.expression.func` to create function constructs.") + "Use :attr:`~sqlalchemy.sql.expression.func` to create function constructs.") def func(self): return expression._FunctionGenerator(bind=self) diff --git a/lib/sqlalchemy/orm/query.py b/lib/sqlalchemy/orm/query.py index cd958d502d..6ef6c3b573 100644 --- a/lib/sqlalchemy/orm/query.py +++ b/lib/sqlalchemy/orm/query.py @@ -2336,7 +2336,7 @@ class Query(object): to count, to skip the usage of a subquery or otherwise control of the FROM clause, or to use other aggregate functions, - use :attr:`.func` expressions in conjunction + use :attr:`~sqlalchemy.sql.expression.func` expressions in conjunction with :meth:`~.Session.query`, i.e.:: from sqlalchemy import func