From: Mike Bayer Date: Thu, 13 Dec 2012 23:45:15 +0000 (-0500) Subject: The :meth:`.Query.select_from` method can now be used with a X-Git-Tag: rel_0_8_0b2~3 X-Git-Url: http://git.ipfire.org/cgi-bin/gitweb.cgi?a=commitdiff_plain;h=ce3333a65c128d873a5c963a71d5c6961a42a3e8;p=thirdparty%2Fsqlalchemy%2Fsqlalchemy.git The :meth:`.Query.select_from` method can now be used with a :func:`.aliased` construct without it interfering with the entities being selected. [ticket:2635] --- diff --git a/doc/build/changelog/changelog_08.rst b/doc/build/changelog/changelog_08.rst index f8cf2c4d72..61b7a5cc46 100644 --- a/doc/build/changelog/changelog_08.rst +++ b/doc/build/changelog/changelog_08.rst @@ -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 diff --git a/lib/sqlalchemy/orm/query.py b/lib/sqlalchemy/orm/query.py index d6847177f0..533282671b 100644 --- a/lib/sqlalchemy/orm/query.py +++ b/lib/sqlalchemy/orm/query.py @@ -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): diff --git a/test/orm/test_froms.py b/test/orm/test_froms.py index 544f4bf7cb..4c566948af 100644 --- a/test/orm/test_froms.py +++ b/test/orm/test_froms.py @@ -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