re-issues the original end-user query wrapped in a subquery,
applies joins out to the target collection, and loads
all those collections fully in one result, similar to
- eager loading but using all inner joins and not re-fetching
- full parent rows repeatedly (as most DBAPIs seem to do,
- even if columns are skipped). Subquery loading is available
- at mapper config level using "lazy='subquery'" and at the query
- options level using "subqueryload(props..)",
+ "joined" eager loading but using all inner joins and not
+ re-fetching full parent rows repeatedly (as most DBAPIs seem
+ to do, even if columns are skipped). Subquery loading is
+ available at mapper config level using "lazy='subquery'" and
+ at the query options level using "subqueryload(props..)",
"subqueryload_all(props...)". [ticket:1675]
- To accomodate the fact that there are now two kinds of eager
Setting Noload
~~~~~~~~~~~~~~~
-The opposite of the dynamic relationship is simply "noload", specified using ``lazy=None``:
+The opposite of the dynamic relationship is simply "noload", specified using ``lazy='noload'``:
.. sourcecode:: python+sql
mapper(MyClass, table, properties={
- 'children': relationship(MyOtherClass, lazy=None)
+ 'children': relationship(MyOtherClass, lazy='noload')
})
Above, the ``children`` collection is fully writeable, and changes to it will be persisted to the database as well as locally available for reading at the time they are added. However when instances of ``MyClass`` are freshly loaded from the database, the ``children`` collection stays empty.
Options which are passed to ``query.options()``, to affect the behavior of loading.
+.. autofunction:: contains_alias
+
.. autofunction:: contains_eager
.. autofunction:: defer
change the value used in the operation.
:param foreign_keys:
-
a list of columns which are to be used as "foreign key" columns.
this parameter should be used in conjunction with explicit
``primaryjoin`` and ``secondaryjoin`` (if needed) arguments, and
the table-defined foreign keys.
:param innerjoin=False:
- when ``True``, eager loads will use an inner join to join
+ when ``True``, joined eager loads will use an inner join to join
against related tables instead of an outer join. The purpose
of this option is strictly one of performance, as inner joins
generally perform better than outer joins. This flag can
:param join_depth:
when non-``None``, an integer value indicating how many levels
- deep eagerload joins should be constructed on a self-referring
- or cyclical relationship. The number counts how many times the
- same Mapper shall be present in the loading condition along a
- particular join branch. When left at its default of ``None``,
- eager loads will automatically stop chaining joins when they
- encounter a mapper which is already higher up in the chain.
+ deep "eager" loaders should join on a self-referring or cyclical
+ relationship. The number counts how many times the same Mapper
+ shall be present in the loading condition along a particular join
+ branch. When left at its default of ``None``, eager loaders
+ will stop chaining when they encounter a the same target mapper
+ which is already higher up in the chain. This option applies
+ both to joined- and subquery- eager loaders.
:param lazy=('select'|'joined'|'subquery'|'noload'|'dynamic'):
specifies how the related items should be loaded. Values include:
- 'select' - items should be loaded lazily when the property is first
- accessed.
+ * 'select' - items should be loaded lazily when the property is first
+ accessed.
- 'joined' - items should be loaded "eagerly" in the same query as
- that of the parent, using a JOIN or LEFT OUTER JOIN.
+ * 'joined' - items should be loaded "eagerly" in the same query as
+ that of the parent, using a JOIN or LEFT OUTER JOIN.
- 'subquery' - items should be loaded "eagerly" within the same
- query as that of the parent, using a second SQL statement
- which issues a JOIN to a subquery of the original
- statement.
-
- 'noload' - no loading should occur at any time. This is to support
- "write-only" attributes, or attributes which are
- populated in some manner specific to the application.
-
- 'dynamic' - a ``DynaLoader`` will be attached, which returns a
- ``Query`` object for all read operations. The
- dynamic- collection supports only ``append()`` and
- ``remove()`` for write operations; changes to the
- dynamic property will not be visible until the data
- is flushed to the database.
+ * 'subquery' - items should be loaded "eagerly" within the same
+ query as that of the parent, using a second SQL statement
+ which issues a JOIN to a subquery of the original
+ statement.
+
+ * 'noload' - no loading should occur at any time. This is to support
+ "write-only" attributes, or attributes which are
+ populated in some manner specific to the application.
+
+ * 'dynamic' - the attribute will return a pre-configured
+ :class:`~sqlalchemy.orm.query.Query` object for all read
+ operations, onto which further filtering operations can be
+ applied before iterating the results. The dynamic
+ collection supports a limited set of mutation operations,
+ allowing ``append()`` and ``remove()``. Changes to the
+ collection will not be visible until flushed
+ to the database, where it is then refetched upon iteration.
- True - a synonym for 'select'
+ * True - a synonym for 'select'
- False - a synonyn for 'joined'
+ * False - a synonyn for 'joined'
- None - a synonym for 'noload'
+ * None - a synonym for 'noload'
:param order_by:
indicates the ordering that should be applied when loading these
innerjoin = kw.pop('innerjoin', None)
if innerjoin is not None:
return (
- strategies.EagerLazyOption(keys, lazy=False),
+ strategies.EagerLazyOption(keys, lazy='joined'),
strategies.EagerJoinOption(keys, innerjoin)
)
else:
- return strategies.EagerLazyOption(keys, lazy=False)
+ return strategies.EagerLazyOption(keys, lazy='joined')
@sa_util.accepts_a_list_as_starargs(list_deprecation='deprecated')
def joinedload_all(*keys, **kw):
innerjoin = kw.pop('innerjoin', None)
if innerjoin is not None:
return (
- strategies.EagerLazyOption(keys, lazy=False, chained=True),
+ strategies.EagerLazyOption(keys, lazy='joined', chained=True),
strategies.EagerJoinOption(keys, innerjoin, chained=True)
)
else:
- return strategies.EagerLazyOption(keys, lazy=False, chained=True)
+ return strategies.EagerLazyOption(keys, lazy='joined', chained=True)
def eagerload(*args, **kwargs):
"""A synonym for :func:`joinedload()`."""
Individual descriptors are accepted as arguments as well::
- query.options(subquryload_all(User.orders, Order.items, Item.keywords))
+ query.options(subqueryload_all(User.orders, Order.items, Item.keywords))
See also: :func:`joinedload_all`, :func:`lazyload`
raise exceptions.ArgumentError("Invalid kwargs for contains_eager: %r" % kwargs.keys())
return (
- strategies.EagerLazyOption(keys, lazy=False, propagate_to_loaders=False),
+ strategies.EagerLazyOption(keys, lazy='joined', propagate_to_loaders=False),
strategies.LoadEagerFromAliasOption(keys, alias=alias)
)
for an operation by a StrategizedProperty.
"""
- def is_chained(self):
- return False
+ is_chained = False
def process_query_property(self, query, paths, mappers):
# _get_context_strategy may receive the path in terms of
# in the polymorphic tests leads to "(Person, 'machines')" in
# the path due to the mechanics of how the eager strategy builds
# up the path
- if self.is_chained():
+ if self.is_chained:
for path in paths:
query._attributes[("loaderstrategy", _reduce_path(path))] = \
self.get_strategy_class()
statement._annotate({'_halt_adapt': True})
def subquery(self):
- """return the full SELECT statement represented by this Query, embedded within an Alias.
+ """return the full SELECT statement represented by this Query,
+ embedded within an Alias.
Eager JOIN generation within the query is disabled.
@_generative()
def enable_eagerloads(self, value):
- """Control whether or not eager joins are rendered.
+ """Control whether or not eager joins and subqueries are
+ rendered.
When set to False, the returned Query will not render
- eager joins regardless of eagerload() options
- or mapper-level lazy=False configurations.
+ eager joins regardless of :func:`~sqlalchemy.orm.joinedload`,
+ :func:`~sqlalchemy.orm.subqueryload` options
+ or mapper-level ``lazy='joined'``/``lazy='subquery'``
+ configurations.
This is used primarily when nesting the Query's
statement into a subquery or other
overwritten.
In particular, it's usually impossible to use this setting with
- eagerly loaded collections (i.e. any lazy=False) since those
- collections will be cleared for a new load when encountered in a
- subsequent result batch.
+ eagerly loaded collections (i.e. any lazy='joined' or 'subquery')
+ since those collections will be cleared for a new load when
+ encountered in a subsequent result batch. In the case of 'subquery'
+ loading, the full result for all rows is fetched which generally
+ defeats the purpose of :meth:`~sqlalchemy.orm.query.Query.yield_per`.
Also note that many DBAPIs do not "stream" results, pre-buffering
all rows before making them available, including mysql-python and
- psycopg2. yield_per() will also set the ``stream_results`` execution
+ psycopg2. :meth:`~sqlalchemy.orm.query.Query.yield_per` will also
+ set the ``stream_results`` execution
option to ``True``, which currently is only understood by psycopg2
and causes server side cursors to be used.
first() applies a limit of one within the generated SQL, so that
only one primary entity row is generated on the server side
- (note this may consist of multiple result rows if eagerly loaded
+ (note this may consist of multiple result rows if join-loaded
collections are present).
Calling ``first()`` results in an execution of the underlying query.
if len(path) / 2 > self.join_depth:
return
else:
- if self.mapper.base_mapper in reduced_path:
+ if self.mapper.base_mapper in interfaces._reduce_path(subq_path):
return
orig_query = context.attributes.get(
self.chained = chained
self.propagate_to_loaders = propagate_to_loaders
self.strategy_cls = factory(lazy)
-
+
+ @property
+ def is_eager(self):
+ return self.lazy in (False, 'joined', 'subquery')
+
+ @property
def is_chained(self):
- return not self.lazy and self.chained
-
+ return self.is_eager and self.chained
+
def get_strategy_class(self):
return self.strategy_cls
def test_relationship_to_polymorphic(self):
assert_result = [
Company(name="MegaCorp, Inc.", employees=[
- Engineer(name="dilbert", engineer_name="dilbert", primary_language="java", status="regular engineer", machines=[Machine(name="IBM ThinkPad"), Machine(name="IPhone")]),
+ Engineer(name="dilbert", engineer_name="dilbert",
+ primary_language="java", status="regular engineer",
+ machines=[Machine(name="IBM ThinkPad"), Machine(name="IPhone")]),
Engineer(name="wally", engineer_name="wally", primary_language="c++", status="regular engineer"),
Boss(name="pointy haired boss", golf_swing="fore", manager_name="pointy", status="da boss"),
Manager(name="dogbert", manager_name="dogbert", status="regular manager"),
sess = create_session()
def go():
- # currently, it doesn't matter if we say Company.employees, or Company.employees.of_type(Engineer). joinedloader doesn't
+ # currently, it doesn't matter if we say Company.employees,
+ # or Company.employees.of_type(Engineer). joinedloader doesn't
# pick up on the "of_type()" as of yet.
eq_(
- sess.query(Company).options(
- joinedload_all(Company.employees.of_type(Engineer), Engineer.machines
- )).all(),
- assert_result)
+ sess.query(Company).options(
+ joinedload_all(Company.employees.of_type(Engineer), Engineer.machines
+ )).all(),
+ assert_result)
- # in the case of select_type='', the joinedload doesn't take in this case;
- # it joinedloads company->people, then a load for each of 5 rows, then lazyload of "machines"
- self.assert_sql_count(testing.db, go, {'':7, 'Polymorphic':1}.get(select_type, 2))
+ # in the case of select_type='', the joinedload
+ # doesn't take in this case; it joinedloads company->people,
+ # then a load for each of 5 rows, then lazyload of "machines"
+ self.assert_sql_count(testing.db, go,
+ {'':7, 'Polymorphic':1}.get(select_type, 2)
+ )
sess = create_session()
def go():
eq_(
- sess.query(Company).options(
- subqueryload_all(Company.employees.of_type(Engineer), Engineer.machines
- )).all(),
- assert_result)
+ sess.query(Company).options(
+ subqueryload_all(Company.employees.of_type(Engineer), Engineer.machines
+ )).all(),
+ assert_result)
- self.assert_sql_count(testing.db, go, {'':9, 'Joins':6,'Unions':6,'Polymorphic':5,'AliasedJoins':6}[select_type])
+ self.assert_sql_count(
+ testing.db, go,
+ {'':8,
+ 'Joins':4,
+ 'Unions':4,
+ 'Polymorphic':3,
+ 'AliasedJoins':4}[select_type]
+ )
def test_joinedload_on_subclass(self):
sess = create_session()
@testing.resolve_artifact_names
def test_deep_options_2(self):
+ """test (joined|subquery)load_all() options"""
+
sess = create_session()
- # joinedload orders.items.keywords; joinedload_all() implies eager load
- # of orders, orders.items
l = (sess.query(User).
options(sa.orm.joinedload_all('orders.items.keywords'))).all()
def go():
x = l[0].orders[1].items[0].keywords[1]
self.sql_count_(0, go)
+ sess = create_session()
+
+ l = (sess.query(User).
+ options(sa.orm.subqueryload_all('orders.items.keywords'))).all()
+ def go():
+ x = l[0].orders[1].items[0].keywords[1]
+ self.sql_count_(0, go)
+
@testing.resolve_artifact_names
def test_deep_options_3(self):
sess.expunge_all()
def go():
d = sess.query(Node).filter_by(data='n1').\
- options(subqueryload('children.children')).first()
+ options(subqueryload_all('children.children')).first()
eq_(Node(data='n1', children=[
Node(data='n11'),
Node(data='n12', children=[