]> git.ipfire.org Git - thirdparty/sqlalchemy/sqlalchemy.git/commitdiff
The :meth:`.Query.select_from` method can now be used with a
authorMike Bayer <mike_mp@zzzcomputing.com>
Thu, 13 Dec 2012 23:45:15 +0000 (18:45 -0500)
committerMike Bayer <mike_mp@zzzcomputing.com>
Thu, 13 Dec 2012 23:45:15 +0000 (18:45 -0500)
:func:`.aliased` construct without it interfering with the entities
being selected.  [ticket:2635]

doc/build/changelog/changelog_08.rst
lib/sqlalchemy/orm/query.py
test/orm/test_froms.py

index f8cf2c4d72863f6cd0ba247def634cca12f97798..61b7a5cc463c3aff2c5e6747c1b24a79478693a6 100644 (file)
@@ -6,6 +6,43 @@
 .. changelog::
     :version: 0.8.0b2
 
+    .. change::
+        :tags: orm, bug
+        :tickets: 2635
+
+      The :meth:`.Query.select_from` method can now be used with a
+      :func:`.aliased` construct without it interfering with the entities
+      being selected.   Basically, a statement like this::
+
+        ua = aliased(User)
+        session.query(User.name).select_from(ua).join(User, User.name > ua.name)
+
+      Will maintain the columns clause of the SELECT as coming from the
+      unaliased "user", as specified; the select_from only takes place in the
+      FROM clause::
+
+        SELECT users.name AS users_name FROM users AS users_1
+        JOIN users ON users.name < users_1.name
+
+      Note that this behavior is in contrast
+      to the original, older use case for :meth:`.Query.select_from`, which is that
+      of restating the mapped entity in terms of a different selectable::
+
+        session.query(User.name).\
+          select_from(user_table.select().where(user_table.c.id > 5))
+
+      Which produces::
+
+        SELECT anon_1.name AS anon_1_name FROM (SELECT users.id AS id,
+        users.name AS name FROM users WHERE users.id > :id_1) AS anon_1
+
+      It was the "aliasing" behavior of the latter use case that was
+      getting in the way of the former use case.   The method now
+      specifically considers a SQL expression like
+      :func:`.expression.select` or :func:`.expression.alias`
+      separately from a mapped entity like a :func:`.aliased`
+      construct.
+
     .. change::
         :tags: sql, bug
         :tickets: 2633
index d6847177f0f0dd570b7912522d00b0c934723074..533282671bbd0346316bdb72c00e27e03ab29ee5 100644 (file)
@@ -163,17 +163,29 @@ class Query(object):
                 self._polymorphic_adapters[m.local_table] = adapter
 
     def _set_select_from(self, *obj):
-
         fa = []
+        select_from_alias = None
         for from_obj in obj:
-            if isinstance(from_obj, expression.SelectBase):
-                from_obj = from_obj.alias()
-            fa.append(from_obj)
+            info = inspect(from_obj)
+
+            if hasattr(info, 'mapper') and \
+                (info.is_mapper or info.is_aliased_class):
+                self._select_from_entity = from_obj
+                fa.append(info.selectable)
+            elif not info.is_selectable:
+                raise sa_exc.ArgumentError(
+                        "argument is not a mapped class, mapper, "
+                        "aliased(), or FromClause instance.")
+            else:
+                if isinstance(from_obj, expression.SelectBase):
+                    from_obj = from_obj.alias()
+                select_from_alias = from_obj
+                fa.append(from_obj)
 
         self._from_obj = tuple(fa)
 
         if len(self._from_obj) == 1 and \
-            isinstance(self._from_obj[0], expression.Alias):
+            isinstance(select_from_alias, expression.Alias):
             equivs = self.__all_equivs()
             self._from_obj_alias = sql_util.ColumnAdapter(
                                                 self._from_obj[0], equivs)
@@ -2011,20 +2023,8 @@ class Query(object):
         usage of :meth:`~.Query.select_from`.
 
         """
-        obj = []
-        for fo in from_obj:
-            info = inspect(fo)
-            if hasattr(info, 'mapper') and \
-                (info.is_mapper or info.is_aliased_class):
-                self._select_from_entity = fo
-                obj.append(info.selectable)
-            elif not info.is_selectable:
-                raise sa_exc.ArgumentError(
-                            "select_from() accepts FromClause objects only.")
-            else:
-                obj.append(fo)
 
-        self._set_select_from(*obj)
+        self._set_select_from(*from_obj)
 
     def __getitem__(self, item):
         if isinstance(item, slice):
index 544f4bf7cbbd0b9a28a40b81500424022239bd15..4c566948aff0001ceb38ace9ef81d51f49d5be1f 100644 (file)
@@ -1815,6 +1815,45 @@ class SelectFromTest(QueryTest, AssertsCompiledSQL):
         )
 
 
+    def test_aliased_class_vs_nonaliased(self):
+        User, users = self.classes.User, self.tables.users
+        mapper(User, users)
+
+        ua = aliased(User)
+
+        sess = create_session()
+        self.assert_compile(
+            sess.query(User).select_from(ua).join(User, ua.name > User.name),
+            "SELECT users.id AS users_id, users.name AS users_name "
+            "FROM users AS users_1 JOIN users ON users.name < users_1.name"
+        )
+
+        self.assert_compile(
+            sess.query(User.name).select_from(ua).join(User, ua.name > User.name),
+            "SELECT users.name AS users_name FROM users AS users_1 "
+            "JOIN users ON users.name < users_1.name"
+        )
+
+        self.assert_compile(
+            sess.query(ua.name).select_from(ua).join(User, ua.name > User.name),
+            "SELECT users_1.name AS users_1_name FROM users AS users_1 "
+            "JOIN users ON users.name < users_1.name"
+        )
+
+        self.assert_compile(
+            sess.query(ua).select_from(User).join(ua, ua.name > User.name),
+            "SELECT users_1.id AS users_1_id, users_1.name AS users_1_name "
+            "FROM users JOIN users AS users_1 ON users.name < users_1.name"
+        )
+
+        # this is tested in many other places here, just adding it
+        # here for comparison
+        self.assert_compile(
+            sess.query(User.name).\
+                    select_from(users.select().where(users.c.id > 5)),
+            "SELECT anon_1.name AS anon_1_name FROM (SELECT users.id AS id, "
+            "users.name AS name FROM users WHERE users.id > :id_1) AS anon_1"
+        )
 
     def test_join_no_order_by(self):
         User, users = self.classes.User, self.tables.users