]> git.ipfire.org Git - thirdparty/sqlalchemy/sqlalchemy.git/commitdiff
- Query.select_from() has been beefed up to help
authorMike Bayer <mike_mp@zzzcomputing.com>
Tue, 21 Sep 2010 22:09:29 +0000 (18:09 -0400)
committerMike Bayer <mike_mp@zzzcomputing.com>
Tue, 21 Sep 2010 22:09:29 +0000 (18:09 -0400)
ensure that a subsequent call to query.join()
will use the select_from() entity, assuming it's
a mapped entity and not a plain selectable,
as the default "left" side, not the first entity
in the Query object's list of entities.

CHANGES
lib/sqlalchemy/orm/query.py
test/orm/test_query.py

diff --git a/CHANGES b/CHANGES
index 3362efb22ee84a382fb9b56b7a6b78cc6b264140..a59b2957075f474940fa27989124938e19aba398 100644 (file)
--- a/CHANGES
+++ b/CHANGES
@@ -19,6 +19,13 @@ CHANGES
   - Patched a case where query.join() would adapt the
     right side to the right side of the left's join
     inappropriately [ticket:1925]
+
+  - Query.select_from() has been beefed up to help
+    ensure that a subsequent call to query.join()
+    will use the select_from() entity, assuming it's
+    a mapped entity and not a plain selectable, 
+    as the default "left" side, not the first entity 
+    in the Query object's list of entities.
     
   - The exception raised by Session when it is used
     subsequent to a subtransaction rollback (which is what
index 0ce84435ffc672e4432c0b8ecc410a80489f8cff..cba3e7619ce7ec91d9a0c674b18d86749dd0f9fc 100644 (file)
@@ -90,6 +90,7 @@ class Query(object):
     _only_load_props = None
     _refresh_state = None
     _from_obj = ()
+    _select_from_entity = None
     _filter_aliases = None
     _from_obj_alias = None
     _joinpath = _joinpoint = util.frozendict()
@@ -266,7 +267,8 @@ class Query(object):
         return self._entities[0]
 
     def _mapper_zero(self):
-        return self._entity_zero().entity_zero
+        return self._select_from_entity or \
+            self._entity_zero().entity_zero
 
     def _extension_zero(self):
         ent = self._entity_zero()
@@ -283,8 +285,9 @@ class Query(object):
 
     def _joinpoint_zero(self):
         return self._joinpoint.get(
-                            '_joinpoint_entity',
-                            self._entity_zero().entity_zero)
+            '_joinpoint_entity',
+            self._mapper_zero()
+        )
 
     def _mapper_zero_or_none(self):
         if not getattr(self._entities[0], 'primary_entity', False):
@@ -1169,7 +1172,7 @@ class Query(object):
                 arg1, arg2 = arg1
             else:
                 arg2 = None
-
+            
             # determine onclause/right_entity.  there
             # is a little bit of legacy behavior still at work here
             # which means they might be in either order.  may possibly
@@ -1417,20 +1420,23 @@ class Query(object):
 
     @_generative(_no_clauseelement_condition)
     def select_from(self, *from_obj):
-        """Set the `from_obj` parameter of the query and return the newly
-        resulting ``Query``.  This replaces the table which this Query selects
-        from with the given table.
+        """Set the FROM clause of this :class:`.Query` explicitly.
         
-        ``select_from()`` also accepts class arguments. Though usually not
-        necessary, can ensure that the full selectable of the given mapper is
-        applied, e.g. for joined-table mappers.
-
-        """
+        Sending a mapped class or entity here effectively replaces the
+        "left edge" of any calls to :meth:`.Query.join`, when no 
+        joinpoint is otherwise established - usually, the default "join
+        point" is the leftmost entity in the :class:`.Query` object's
+        list of entities to be selected.
         
+        Mapped entities or plain :class:`.Table` or other selectables
+        can be sent here which will form the default FROM clause.
+        
+        """
         obj = []
         for fo in from_obj:
             if _is_mapped_class(fo):
                 mapper, selectable, is_aliased_class = _entity_info(fo)
+                self._select_from_entity = fo
                 obj.append(selectable)
             elif not isinstance(fo, expression.FromClause):
                 raise sa_exc.ArgumentError(
@@ -1439,7 +1445,7 @@ class Query(object):
                 obj.append(fo)  
                 
         self._set_select_from(*obj)
-
+        
     def __getitem__(self, item):
         if isinstance(item, slice):
             start, stop, step = util.decode_slice(item)
index b52716198cebada1ab8135781152679b11b09dbe..3ba1f0fa09641ec0c0af90e480a5daff9ef6ca58 100644 (file)
@@ -2543,6 +2543,30 @@ class JoinTest(QueryTest, AssertsCompiledSQL):
             "Could not find a FROM",
             sess.query(users.c.id).select_from(users).join, User
         )
+    
+    def test_select_from(self):
+        """Test that the left edge of the join can be set reliably with select_from()."""
+        
+        sess = create_session()
+        self.assert_compile(
+            sess.query(Item.id).select_from(User).join(User.orders).join(Order.items),
+            "SELECT items.id AS items_id FROM users JOIN orders ON "
+            "users.id = orders.user_id JOIN order_items AS order_items_1 "
+            "ON orders.id = order_items_1.order_id JOIN items ON items.id = "
+            "order_items_1.item_id",
+            use_default_dialect=True
+        )
+
+        # here, the join really wants to add a second FROM clause
+        # for "Item".  but select_from disallows that
+        self.assert_compile(
+            sess.query(Item.id).select_from(User).join((Item, User.id==Item.id)),
+            "SELECT items.id AS items_id FROM users JOIN items ON users.id = items.id",
+            use_default_dialect=True
+        )
+        
+        
+        
         
     def test_from_self_resets_joinpaths(self):
         """test a join from from_self() doesn't confuse joins inside the subquery