summaryrefslogtreecommitdiff
path: root/doc/build/orm/loading_columns.rst
diff options
context:
space:
mode:
Diffstat (limited to 'doc/build/orm/loading_columns.rst')
-rw-r--r--doc/build/orm/loading_columns.rst254
1 files changed, 194 insertions, 60 deletions
diff --git a/doc/build/orm/loading_columns.rst b/doc/build/orm/loading_columns.rst
index 6b3673dba..c73ab0719 100644
--- a/doc/build/orm/loading_columns.rst
+++ b/doc/build/orm/loading_columns.rst
@@ -15,54 +15,130 @@ Deferred Column Loading
Deferred column loading allows particular columns of a table be loaded only
upon direct access, instead of when the entity is queried using
-:class:`_sql.Select`. This feature is useful when one wants to avoid
+:class:`_sql.Select` or :class:`_orm.Query`. This feature is useful when one wants to avoid
loading a large text or binary field into memory when it's not needed.
-Individual columns can be lazy loaded by themselves or placed into groups that
-lazy-load together, using the :func:`_orm.deferred` function to
-mark them as "deferred". In the example below, we define a mapping that will load each of
+
+Configuring Deferred Loading at Mapper Configuration Time
+---------------------------------------------------------
+
+First introduced at :ref:`orm_declarative_column_options` and
+:ref:`orm_imperative_table_column_options`, the
+:paramref:`_orm.mapped_column.deferred` parameter of :func:`_orm.mapped_column`,
+as well as the :func:`_orm.deferred` ORM function may be used to indicate mapped
+columns as "deferred" at mapper configuration time. With this configuration,
+the target columns will not be loaded in SELECT statements by default, and
+will instead only be loaded "lazily" when their corresponding attribute is
+accessed on a mapped instance. Deferral can be configured for individual
+columns or groups of columns that will load together when any of them
+are accessed.
+
+In the example below, using :ref:`Declarative Table <orm_declarative_table>`
+configuration, we define a mapping that will load each of
``.excerpt`` and ``.photo`` in separate, individual-row SELECT statements when each
attribute is first referenced on the individual object instance::
- from sqlalchemy.orm import deferred
- from sqlalchemy import Integer, String, Text, Binary, Column
+ from sqlalchemy import Text
+ from sqlalchemy.orm import DeclarativeBase
+ from sqlalchemy.orm import Mapped
+ from sqlalchemy.orm import mapped_column
+
+ class Base(DeclarativeBase):
+ pass
class Book(Base):
__tablename__ = 'book'
- book_id = Column(Integer, primary_key=True)
- title = Column(String(200), nullable=False)
- summary = Column(String(2000))
- excerpt = deferred(Column(Text))
- photo = deferred(Column(Binary))
+ book_id: Mapped[int] = mapped_column(primary_key=True)
+ title: Mapped[str]
+ summary: Mapped[str]
+ excerpt: Mapped[str] = mapped_column(Text, deferred=True)
+ photo: Mapped[bytes] = mapped_column(deferred=True)
-Classical mappings as always place the usage of :func:`_orm.deferred` in the
-``properties`` dictionary against the table-bound :class:`_schema.Column`::
+A :func:`_sql.select` construct for the above mapping will not include
+``excerpt`` and ``photo`` by default::
+
+ >>> from sqlalchemy import select
+ >>> print(select(Book))
+ SELECT book.book_id, book.title, book.summary
+ FROM book
+
+When an object of type ``Book`` is loaded by the ORM, accessing the
+``.excerpt`` or ``.photo`` attributes will instead :term:`lazy load` the
+data from each column using a new SQL statement.
+
+When using :ref:`Imperative Table <orm_imperative_table_configuration>`
+or fully :ref:`Imperative <orm_imperative_mapping>` configuration, the
+:func:`_orm.deferred` construct should be used instead, passing the
+target :class:`_schema.Column` object to be mapped as the argument::
+
+ from sqlalchemy import Column, Integer, LargeBinary, String, Table, Text
+ from sqlalchemy.orm import DeclarativeBase
+ from sqlalchemy.orm import deferred
+
+
+ class Base(DeclarativeBase):
+ pass
+
+
+ book = Table(
+ "book",
+ Base.metadata,
+ Column("book_id", Integer, primary_key=True),
+ Column("title", String),
+ Column("summary", String),
+ Column("excerpt", Text),
+ Column("photo", LargeBinary),
+ )
+
+
+ class Book(Base):
+ __table__ = book
+
+ excerpt = deferred(book.c.excerpt)
+ photo = deferred(book.c.photo)
- mapper_registry.map_imperatively(Book, book_table, properties={
- 'photo':deferred(book_table.c.photo)
- })
Deferred columns can be associated with a "group" name, so that they load
-together when any of them are first accessed. The example below defines a
-mapping with a ``photos`` deferred group. When one ``.photo`` is accessed, all three
-photos will be loaded in one SELECT statement. The ``.excerpt`` will be loaded
-separately when it is accessed::
+together when any of them are first accessed. When using
+:func:`_orm.mapped_column`, this group name may be specified using the
+:paramref:`_orm.mapped_column.deferred_group` parameter, which implies
+:paramref:`_orm.mapped_column.deferred` if that parameter is not already
+set. When using :func:`_orm.deferred`, the :paramref:`_orm.deferred.group`
+parameter may be used.
+
+The example below defines a mapping with a ``photos`` deferred group. When
+an attribute within the group ``.photo1``, ``.photo2``, ``.photo3``
+is accessed on an instance of ``Book``, all three columns will be loaded in one SELECT
+statement. The ``.excerpt`` column however will only be loaded when it
+is directly accessed::
+
+ from sqlalchemy import Text
+ from sqlalchemy.orm import DeclarativeBase
+ from sqlalchemy.orm import Mapped
+ from sqlalchemy.orm import mapped_column
+
+ class Base(DeclarativeBase):
+ pass
class Book(Base):
__tablename__ = 'book'
- book_id = Column(Integer, primary_key=True)
- title = Column(String(200), nullable=False)
- summary = Column(String(2000))
- excerpt = deferred(Column(Text))
- photo1 = deferred(Column(Binary), group='photos')
- photo2 = deferred(Column(Binary), group='photos')
- photo3 = deferred(Column(Binary), group='photos')
+ book_id: Mapped[int] = mapped_column(primary_key=True)
+ title: Mapped[str]
+ summary: Mapped[str]
+ excerpt: Mapped[str] = mapped_column(Text, deferred=True)
+ photo1: Mapped[bytes] = mapped_column(deferred_group="photos")
+ photo2: Mapped[bytes] = mapped_column(deferred_group="photos")
+ photo3: Mapped[bytes] = mapped_column(deferred_group="photos")
+
.. _deferred_options:
Deferred Column Loader Query Options
------------------------------------
+At query time, the :func:`_orm.defer`, :func:`_orm.undefer` and
+:func:`_orm.undefer_group` loader options may be used to further control the
+"deferral behavior" of mapped columns.
Columns can be marked as "deferred" or reset to "undeferred" at query time
using options which are passed to the :meth:`_sql.Select.options` method; the most
@@ -74,8 +150,8 @@ basic query options are :func:`_orm.defer` and
from sqlalchemy import select
stmt = select(Book)
- stmt = stmt.options(defer('summary'), undefer('excerpt'))
- session.scalars(stmt).all()
+ stmt = stmt.options(defer(Book.summary), undefer(Book.excerpt))
+ book_objs = session.scalars(stmt).all()
Above, the "summary" column will not load until accessed, and the "excerpt"
@@ -89,7 +165,7 @@ using :func:`_orm.undefer_group`, sending in the group name::
stmt = select(Book)
stmt = stmt.options(undefer_group('photos'))
- session.scalars(stmt).all()
+ book_objs = session.scalars(stmt).all()
.. _deferred_loading_w_multiple:
@@ -97,9 +173,11 @@ using :func:`_orm.undefer_group`, sending in the group name::
Deferred Loading across Multiple Entities
-----------------------------------------
-To specify column deferral for a :class:`_sql.Select` that loads multiple types of
-entities at once, the deferral options may be specified more explicitly using
-class-bound attributes, rather than string names::
+Column deferral may also be used for a statement that loads multiple types of
+entities at once, by referring to the appropriate class bound attribute
+within the :func:`_orm.defer` function. Suppose ``Book`` has a
+relationship ``Book.author`` to a related class ``Author``, we could write
+a query as follows which will defer the ``Author.bio`` column::
from sqlalchemy.orm import defer
from sqlalchemy import select
@@ -107,6 +185,8 @@ class-bound attributes, rather than string names::
stmt = select(Book, Author).join(Book.author)
stmt = stmt.options(defer(Author.bio))
+ book_author_objs = session.execute(stmt).all()
+
Column deferral options may also indicate that they take place along various
relationship paths, which are themselves often :ref:`eagerly loaded
@@ -128,11 +208,12 @@ those explicitly specified::
joinedload(Author.books).load_only(Book.summary, Book.excerpt)
)
+ author_objs = session.scalars(stmt).all()
+
Option structures as above can also be organized in more complex ways, such
as hierarchically using the :meth:`_orm.Load.options`
method, which allows multiple sub-options to be chained to a common parent
-option at once. Any mixture of string names and class-bound attribute objects
-may be used::
+option at once. The example below illustrates a more complex structure::
from sqlalchemy.orm import defer
from sqlalchemy.orm import joinedload
@@ -149,12 +230,9 @@ may be used::
)
)
)
+ author_objs = session.scalars(stmt).all()
-
-.. versionadded:: 1.3.6 Added :meth:`_orm.Load.options` to allow easier
- construction of hierarchies of loader options.
-
Another way to apply options to a path is to use the :func:`_orm.defaultload`
function. This function is used to indicate a particular path within a loader
option structure without actually setting any options at that level, so that further
@@ -171,6 +249,8 @@ to create the same structure as we did above using :meth:`_orm.Load.options` as:
defaultload(Author.book).defaultload(Book.citations).defer(Citation.fulltext)
)
+ author_objs = session.scalars(stmt).all()
+
.. seealso::
:ref:`relationship_loader_options` - targeted towards relationship loading
@@ -188,8 +268,10 @@ the "summary" and "excerpt" columns, we could say::
from sqlalchemy.orm import undefer
from sqlalchemy import select
- select(Book).options(
- defer('*'), undefer("summary"), undefer("excerpt"))
+ stmt = select(Book).options(
+ defer('*'), undefer(Book.summary), undefer(Book.excerpt))
+
+ book_objs = session.scalars(stmt).all()
Above, the :func:`.defer` option is applied using a wildcard to all column
attributes on the ``Book`` class. Then, the :func:`.undefer` option is used
@@ -206,32 +288,36 @@ that are named::
from sqlalchemy.orm import load_only
from sqlalchemy import select
- select(Book).options(load_only(Book.summary, Book.excerpt))
+ stmt = select(Book).options(load_only(Book.summary, Book.excerpt))
+
+ book_objs = session.scalars(stmt).all()
Wildcard and Exclusionary Options with Multiple-Entity Queries
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
Wildcard options and exclusionary options such as :func:`.load_only` may
-only be applied to a single entity at a time within a :class:`_query.Query`. To
-suit the less common case where a :class:`_query.Query` is returning multiple
+only be applied to a single entity at a time within a statement.
+To suit the less common case where a statement is returning multiple
primary entities at once, a special calling style may be required in order
-to apply a wildcard or exclusionary option, which is to use the
+to apply a wildcard or exclusionary option to a specific entity, which is to use the
:class:`_orm.Load` object to indicate the starting entity for a deferral option.
-Such as, if we were loading ``Book`` and ``Author`` at once, the :class:`_query.Query`
+Such as, if we were loading ``Book`` and ``Author`` at once, the ORM
will raise an informative error if we try to apply :func:`.load_only` to
-both at once. Using :class:`_orm.Load` looks like::
+both at once. Instead, we may use :class:`_orm.Load` to apply the option
+to either or both of ``Book`` and ``Author`` individually::
from sqlalchemy.orm import Load
- query = session.query(Book, Author).join(Book.author)
- query = query.options(
+ stmt = select(Book, Author).join(Book.author)
+ stmt = stmt.options(
Load(Book).load_only(Book.summary, Book.excerpt)
)
+ book_author_objs = session.execute(stmt).all()
Above, :class:`_orm.Load` is used in conjunction with the exclusionary option
:func:`.load_only` so that the deferral of all other columns only takes
place for the ``Book`` class and not the ``Author`` class. Again,
-the :class:`_query.Query` object should raise an informative error message when
+the ORM should raise an informative error message when
the above calling style is actually required that describes those cases
where explicit use of :class:`_orm.Load` is needed.
@@ -246,32 +332,80 @@ The :func:`.deferred` loader option and the corresponding loader strategy also
support the concept of "raiseload", which is a loader strategy that will raise
:class:`.InvalidRequestError` if the attribute is accessed such that it would
need to emit a SQL query in order to be loaded. This behavior is the
-column-based equivalent of the :func:`.raiseload` feature for relationship
+column-based equivalent of the :func:`_orm.raiseload` feature for relationship
loading, discussed at :ref:`prevent_lazy_with_raiseload`. Using the
-:paramref:`.orm.defer.raiseload` parameter on the :func:`.defer` option,
+:paramref:`_orm.defer.raiseload` parameter on the :func:`_orm.defer` option,
an exception is raised if the attribute is accessed::
- book = session.scalars(select(Book).options(defer(Book.summary, raiseload=True)).limit(1)).first()
+ book = session.scalar(
+ select(Book).options(defer(Book.summary, raiseload=True)).limit(1)
+ )
# would raise an exception
book.summary
Deferred "raiseload" can be configured at the mapper level via
-:paramref:`.orm.deferred.raiseload` on :func:`.deferred`, so that an explicit
-:func:`.undefer` is required in order for the attribute to be usable::
+:paramref:`.orm.deferred.raiseload` on either :func:`_orm.mapped_column`
+or in :func:`.deferred`, so that an explicit
+:func:`.undefer` is required in order for the attribute to be usable.
+Below is a :ref:`Declarative table <orm_declarative_table>` configuration example::
+
+
+ from sqlalchemy import Text
+ from sqlalchemy.orm import DeclarativeBase
+ from sqlalchemy.orm import Mapped
+ from sqlalchemy.orm import mapped_column
+ class Base(DeclarativeBase):
+ pass
class Book(Base):
__tablename__ = 'book'
- book_id = Column(Integer, primary_key=True)
- title = Column(String(200), nullable=False)
- summary = deferred(Column(String(2000)), raiseload=True)
- excerpt = deferred(Column(Text), raiseload=True)
+ book_id: Mapped[int] = mapped_column(primary_key=True)
+ title: Mapped[str]
+ summary: Mapped[str] = mapped_column(raiseload=True)
+ excerpt: Mapped[str] = mapped_column(Text, raiseload=True)
+
+Alternatively, the example below illustrates the same mapping using a
+:ref:`Imperative table <orm_imperative_table_configuration>` configuration::
+
+ from sqlalchemy import Column, Integer, LargeBinary, String, Table, Text
+ from sqlalchemy.orm import DeclarativeBase
+ from sqlalchemy.orm import deferred
+
+
+ class Base(DeclarativeBase):
+ pass
+
+
+ book = Table(
+ "book",
+ Base.metadata,
+ Column("book_id", Integer, primary_key=True),
+ Column("title", String),
+ Column("summary", String),
+ Column("excerpt", Text),
+ )
+
+
+ class Book(Base):
+ __table__ = book
+
+ summary = deferred(book.c.summary, raiseload=True)
+ excerpt = deferred(book.c.excerpt, raiseload=True)
- book_w_excerpt = session.scalars(select(Book).options(undefer(Book.excerpt)).limit(1)).first()
+With both mappings, if we wish to have either or both of ``.excerpt``
+or ``.summary`` available on an object when loaded, we make use of the
+:func:`_orm.undefer` loader option::
+ book_w_excerpt = session.scalars(
+ select(Book).options(undefer(Book.excerpt)).where(Book.id == 12)
+ ).first()
+The :func:`_orm.undefer` option will populate the ``.excerpt`` attribute
+above, even if the ``Book`` object were already loaded, assuming the
+``.excerpt`` field was not populated by some other means previously.
Column Deferral API