]> git.ipfire.org Git - thirdparty/sqlalchemy/sqlalchemy.git/commitdiff
- fix some final pathing stuff, we weren't getting all the loads in the
authorMike Bayer <mike_mp@zzzcomputing.com>
Thu, 25 Mar 2010 03:51:49 +0000 (23:51 -0400)
committerMike Bayer <mike_mp@zzzcomputing.com>
Thu, 25 Mar 2010 03:51:49 +0000 (23:51 -0400)
inheritance examples, now its improved !
- final doc pass

CHANGES
doc/build/mappers.rst
doc/build/reference/orm/query.rst
lib/sqlalchemy/orm/__init__.py
lib/sqlalchemy/orm/interfaces.py
lib/sqlalchemy/orm/query.py
lib/sqlalchemy/orm/strategies.py
test/orm/inheritance/test_query.py
test/orm/test_mapper.py
test/orm/test_subquery_relations.py

diff --git a/CHANGES b/CHANGES
index 93feede32302bcf6fa4b7e75276d3fcee4ec5761..6f79fc96fd8b022f84f091fc4fbd4e7c6def3627 100644 (file)
--- a/CHANGES
+++ b/CHANGES
@@ -15,11 +15,11 @@ CHANGES
     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 
index 7eb809cbed425417f1324f4cd95a3a6a52d9cb1c..ebf74400c23c11acb9ba5c7599dc4ffd18bf2139 100644 (file)
@@ -1823,12 +1823,12 @@ Note that eager/lazy loading options cannot be used in conjunction dynamic relat
 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.
index 6ff9048bc8ab13b501ce465388ad76e3ddc19839..98ebdee59f969ba140c6043b20464145605636ae 100644 (file)
@@ -34,6 +34,8 @@ Query Options
 
 Options which are passed to ``query.options()``, to affect the behavior of loading.
 
+.. autofunction:: contains_alias
+
 .. autofunction:: contains_eager
 
 .. autofunction:: defer
index fb05f4181c1f305257465049bc35e07b7d58a69b..5d4bc2ee4fc9cf7bad44eaba521cf0bda1ff74cb 100644 (file)
@@ -267,7 +267,6 @@ def relationship(argument, secondary=None, **kwargs):
       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
@@ -280,7 +279,7 @@ def relationship(argument, secondary=None, **kwargs):
       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
@@ -291,43 +290,46 @@ def relationship(argument, secondary=None, **kwargs):
       
     :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
@@ -959,11 +961,11 @@ def joinedload(*keys, **kw):
     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):
@@ -997,11 +999,11 @@ 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()`."""
@@ -1054,7 +1056,7 @@ def subqueryload_all(*keys):
 
     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`
 
@@ -1132,7 +1134,7 @@ def contains_eager(*keys, **kwargs):
         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)
         )
 
index 255b6b6fef8fd5697a2927fd5b8b2eb5d9822649..7fbb0862d0e5eda80c4a51784bcbcf09992b46c5 100644 (file)
@@ -932,8 +932,7 @@ class StrategizedOption(PropertyOption):
     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
@@ -941,7 +940,7 @@ class StrategizedOption(PropertyOption):
         # 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()
index 859333a39b20cdbe3ebc895a273973d89f03e998..5b9169c2e391685bcc0e524bd2f8b1b01d87441f 100644 (file)
@@ -381,7 +381,8 @@ class Query(object):
                         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.
 
@@ -397,11 +398,14 @@ class Query(object):
 
     @_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
@@ -508,13 +512,16 @@ class Query(object):
         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.
         
@@ -1347,7 +1354,7 @@ class Query(object):
            
         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.
index a766aab09d491ccba5a873a115f661a660bac9da..25c2f83a5c0fe0e063e38c1a572b8a8644e1232c 100644 (file)
@@ -664,7 +664,7 @@ class SubqueryLoader(AbstractRelationshipLoader):
                 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(
@@ -1132,10 +1132,15 @@ class EagerLazyOption(StrategizedOption):
         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
 
index 239f2e45ab467be6bd5c1ce23465262524e440fa..a83c3c8a146b9344f703c6199bdb9c8a86eab32c 100644 (file)
@@ -487,7 +487,9 @@ def _produce_test(select_type):
         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"),
@@ -506,27 +508,38 @@ def _produce_test(select_type):
         
             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()
index de8124dbc5745169fe5ffa76d847fc994a6b4c12..02be04edc6e0f629b96b8beb1dd4798545380d24 100644 (file)
@@ -1292,16 +1292,24 @@ class DeepOptionsTest(_fixtures.FixtureTest):
 
     @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):
index bcbd2c24597617c8ceb4b888af61da07f3d82a28..5b9a46d075b7a5d0f40eb0b7575962d78f2f4751 100644 (file)
@@ -726,7 +726,7 @@ class SelfReferentialTest(_base.MappedTest):
         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=[