[Sqlalchemy-commits] sqlalchemy: The :meth:`.Query.select_from` method can now be use...
Brought to you by:
zzzeek
From: <co...@sq...> - 2012-12-13 23:46:23
|
details: http://hg.sqlalchemy.org/sqlalchemy/sqlalchemy/rev/be91b6569059 changeset: 9012:be91b6569059 user: Mike Bayer <mi...@zz...> date: Thu Dec 13 18:45:15 2012 -0500 description: 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] Subject: sqlalchemy: merge default tip details: http://hg.sqlalchemy.org/sqlalchemy/sqlalchemy/rev/ca32d1aeef27 changeset: 9013:ca32d1aeef27 user: Mike Bayer <mi...@zz...> date: Thu Dec 13 18:45:47 2012 -0500 description: merge default tip diffstat: doc/build/changelog/changelog_08.rst | 37 ++++++++++++++++++++++++++++++++++ lib/sqlalchemy/orm/query.py | 38 +++++++++++++++++----------------- test/orm/test_froms.py | 39 ++++++++++++++++++++++++++++++++++++ 3 files changed, 95 insertions(+), 19 deletions(-) diffs (157 lines): diff -r 4e169fa25e3c -r ca32d1aeef27 doc/build/changelog/changelog_08.rst --- a/doc/build/changelog/changelog_08.rst Tue Dec 11 16:31:41 2012 -0500 +++ b/doc/build/changelog/changelog_08.rst Thu Dec 13 18:45:47 2012 -0500 @@ -7,6 +7,43 @@ :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 -r 4e169fa25e3c -r ca32d1aeef27 lib/sqlalchemy/orm/query.py --- a/lib/sqlalchemy/orm/query.py Tue Dec 11 16:31:41 2012 -0500 +++ b/lib/sqlalchemy/orm/query.py Thu Dec 13 18:45:47 2012 -0500 @@ -163,17 +163,29 @@ 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 @@ 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 -r 4e169fa25e3c -r ca32d1aeef27 test/orm/test_froms.py --- a/test/orm/test_froms.py Tue Dec 11 16:31:41 2012 -0500 +++ b/test/orm/test_froms.py Thu Dec 13 18:45:47 2012 -0500 @@ -1815,6 +1815,45 @@ ) + 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 |