]> git.ipfire.org Git - thirdparty/sqlalchemy/sqlalchemy.git/commitdiff
- DeferredColumnLoader checks row for column, if present sends it to
authorMike Bayer <mike_mp@zzzcomputing.com>
Wed, 14 Nov 2007 16:43:21 +0000 (16:43 +0000)
committerMike Bayer <mike_mp@zzzcomputing.com>
Wed, 14 Nov 2007 16:43:21 +0000 (16:43 +0000)
ColumnLoader to create the row processor
- eager loaders ensure deferred foreign key cols are present in the primary list of columns (and secondary...).  because eager loading with LIMIT/OFFSET doesn't re-join to the parent table anymore this is now necessary. [ticket:864]

lib/sqlalchemy/engine/base.py
lib/sqlalchemy/orm/query.py
lib/sqlalchemy/orm/strategies.py
lib/sqlalchemy/orm/util.py
lib/sqlalchemy/sql/util.py
test/orm/eager_relations.py
test/orm/mapper.py

index 859fb796e82e72d44967cbea4d676beb304bc48c..c3f99bb246446499c3f7f1bcc03799908f5a6074 100644 (file)
@@ -1406,6 +1406,8 @@ class ResultProxy(object):
 
     def _has_key(self, row, key):
         try:
+            # _key_cache uses __missing__ in 2.5, so not much alternative
+            # to catching KeyError
             self._key_cache[key]
             return True
         except KeyError:
index 4052e94051efc56bd01af3056d44cba612deca58..a4460ea2542f94274b1c060d6b88070364f43b6d 100644 (file)
@@ -884,14 +884,14 @@ class Query(object):
             # locate all embedded Column clauses so they can be added to the
             # "inner" select statement where they'll be available to the enclosing
             # statement's "order by"
+
+            cf = sql_util.ColumnFinder()
+
             if order_by:
                 order_by = [expression._literal_as_text(o) for o in util.to_list(order_by) or []]
-                cf = sql_util.ColumnFinder()
                 for o in order_by:
                     cf.traverse(o)
-            else:
-                cf = []
-
+            
             s2 = sql.select(context.primary_columns + list(cf), whereclause, from_obj=context.from_clauses, use_labels=True, correlate=False, **self._select_args())
 
             if order_by:
index d517be007ee940e00e363d302318bf1a22e0c89d..0d2d751f05b288d3936d9125e92dba76e48b97ec 100644 (file)
@@ -134,7 +134,7 @@ class DeferredColumnLoader(LoaderStrategy):
     """Deferred column loader, a per-column or per-column-group lazy loader."""
     
     def create_row_processor(self, selectcontext, mapper, row):
-        if self.group is not None and selectcontext.attributes.get(('undefer', self.group), False):
+        if (self.group is not None and selectcontext.attributes.get(('undefer', self.group), False)) or self.columns[0] in row:
             return self.parent_property._get_strategy(ColumnLoader).create_row_processor(selectcontext, mapper, row)
         elif not self.is_class_level or len(selectcontext.options):
             def new_execute(instance, row, **flags):
@@ -532,10 +532,24 @@ class EagerLoader(AbstractRelationLoader):
         
         if self.secondaryjoin is not None:
             context.eager_joins = sql.outerjoin(towrap, clauses.secondary, clauses.primaryjoin).outerjoin(clauses.alias, clauses.secondaryjoin)
+            
+            # TODO: check for "deferred" cols on parent/child tables here ?  this would only be
+            # useful if the primary/secondaryjoin are against non-PK columns on the tables (and therefore might be deferred)
+            
             if self.order_by is False and self.secondary.default_order_by() is not None:
                 context.eager_order_by += clauses.secondary.default_order_by()
         else:
             context.eager_joins = towrap.outerjoin(clauses.alias, clauses.primaryjoin)
+
+            # ensure all the cols on the parent side are actually in the
+            # columns clause (i.e. are not deferred), so that aliasing applied by the Query propagates 
+            # those columns outward.  This has the effect of "undefering" those columns.
+            for col in sql_util.find_columns(clauses.primaryjoin):
+                if localparent.mapped_table.c.contains_column(col):
+                    context.primary_columns.append(col)
+                else:
+                    context.secondary_columns.append(col)
+                
             if self.order_by is False and clauses.alias.default_order_by() is not None:
                 context.eager_order_by += clauses.alias.default_order_by()
 
index de9694bb2feed72e9fd3b7dbea3a61e0d280382d..2418d13246522d94ea6156a7aee9f2fd00de335b 100644 (file)
@@ -252,7 +252,7 @@ def create_row_adapter(from_, to, equivalent_columns=None):
         def __init__(self, row):
             self.row = row
         def __contains__(self, key):
-            return key in map or key in self.row
+            return key in self.row or (key in map and map[key] in self.row)
         def has_key(self, key):
             return key in self
         def __getitem__(self, key):
index 81d28ac7ed6d62ff0d291fc80fc80b0f24c0b365..70d1940e62e7b842e0fb0d5b4b5b161c25eb4b11 100644 (file)
@@ -96,6 +96,11 @@ class ColumnFinder(visitors.ClauseVisitor):
     def __iter__(self):
         return iter(self.columns)
 
+def find_columns(selectable):
+    cf = ColumnFinder()
+    cf.traverse(selectable)
+    return iter(cf)
+    
 class ColumnsInClause(visitors.ClauseVisitor):
     """Given a selectable, visit clauses and determine if any columns
     from the clause are in the selectable.
index a091a42ea3dec8981b0ca709fbfa20bbcfc15d46..858f139ba8efd1a758bf92da855228600aec2663 100644 (file)
@@ -124,6 +124,39 @@ class EagerTest(QueryTest):
             User(id=10, addresses=[])
         ] == sess.query(User).all()
 
+    def test_deferred_fk_col(self):
+        mapper(Address, addresses, properties={
+            'user_id':deferred(addresses.c.user_id),
+            'user':relation(User, lazy=False)
+        })
+        mapper(User, users)
+
+        assert [Address(id=1, user=User(id=7)), Address(id=4, user=User(id=8)), Address(id=5, user=User(id=9))] == create_session().query(Address).filter(Address.id.in_([1, 4, 5])).all()
+
+        assert [Address(id=1, user=User(id=7)), Address(id=4, user=User(id=8)), Address(id=5, user=User(id=9))] == create_session().query(Address).filter(Address.id.in_([1, 4, 5])).limit(3).all()
+
+        sess = create_session()
+        a = sess.query(Address).get(1)
+        def go():
+            assert a.user_id==7
+        # assert that the eager loader added 'user_id' to the row
+        # and deferred loading of that col was disabled
+        self.assert_sql_count(testbase.db, go, 0)
+        
+        # do the mapping in reverse
+        # (we would have just used an "addresses" backref but the test fixtures then require the whole
+        # backref to be set up, lazy loaders trigger, etc.)
+        clear_mappers()
+
+        mapper(Address, addresses, properties={
+            'user_id':deferred(addresses.c.user_id),
+        })
+        mapper(User, users, properties={'addresses':relation(Address, lazy=False)})
+
+        assert [User(id=7, addresses=[Address(id=1)])] == create_session().query(User).filter(User.id==7).options(eagerload('addresses')).all()
+
+        assert [User(id=7, addresses=[Address(id=1)])] == create_session().query(User).limit(1).filter(User.id==7).options(eagerload('addresses')).all()
+        
     def test_many_to_many(self):
 
         mapper(Keyword, keywords)
index 0a993dd107d89e7e9ca2725007088071c6d96153..fe763c0bed8f49106004b0d719fb2c7ffcb6acd8 100644 (file)
@@ -905,8 +905,25 @@ class DeferredTest(MapperSuperTest):
         self.assert_sql(testbase.db, go, [
             ("SELECT orders.user_id AS orders_user_id, orders.description AS orders_description, orders.isopen AS orders_isopen, orders.order_id AS orders_order_id FROM orders ORDER BY %s" % orderby, {}),
         ])
+        
+    def test_locates_col(self):
+        """test that manually adding a col to the result undefers the column"""
+        mapper(Order, orders, properties={
+            'description':deferred(orders.c.description)
+        })
 
-
+        sess = create_session()
+        o1 = sess.query(Order).first()
+        def go():
+            assert o1.description == 'order 1'
+        self.assert_sql_count(testbase.db, go, 1)
+        
+        sess = create_session()
+        o1 = sess.query(Order).add_column(orders.c.description).first()[0]
+        def go():
+            assert o1.description == 'order 1'
+        self.assert_sql_count(testbase.db, go, 0)
+        
     def test_deepoptions(self):
         m = mapper(User, users, properties={
             'orders':relation(mapper(Order, orders, properties={