Commits

Mike Bayer committed a953348

- fix some final pathing stuff, we weren't getting all the loads in the
inheritance examples, now its improved !
- final doc pass

  • Participants
  • Parent commits 138ad04

Comments (0)

Files changed (10)

     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 

File doc/build/mappers.rst

 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.

File doc/build/reference/orm/query.rst

 
 Options which are passed to ``query.options()``, to affect the behavior of loading.
 
+.. autofunction:: contains_alias
+
 .. autofunction:: contains_eager
 
 .. autofunction:: defer

File lib/sqlalchemy/orm/__init__.py

       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.
+      * '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.
+      * '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.
+      * '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)
         )
 

File lib/sqlalchemy/orm/interfaces.py

     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()

File lib/sqlalchemy/orm/query.py

                         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.

File lib/sqlalchemy/orm/strategies.py

                 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
 

File test/orm/inheritance/test_query.py

         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()

File test/orm/test_mapper.py

 
     @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):

File test/orm/test_subquery_relations.py

         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=[