From: Mike Bayer Date: Fri, 31 May 2013 00:09:44 +0000 (-0400) Subject: The "auto-aliasing" behavior of the :class:`.Query.select_from` X-Git-Tag: rel_0_9_0b1~304^2~5 X-Git-Url: http://git.ipfire.org/gitweb.cgi?a=commitdiff_plain;h=04317bd5a887524956249565014c077afe41f905;p=thirdparty%2Fsqlalchemy%2Fsqlalchemy.git The "auto-aliasing" behavior of the :class:`.Query.select_from` method has been turned off. The specific behavior is now availble via a new method :class:`.Query.select_entity_from`. [ticket:2736] --- diff --git a/doc/build/changelog/changelog_09.rst b/doc/build/changelog/changelog_09.rst index ccb2960ab1..baddf7e2a9 100644 --- a/doc/build/changelog/changelog_09.rst +++ b/doc/build/changelog/changelog_09.rst @@ -18,4 +18,22 @@ :tags: feature, oracle, py3k The Oracle unit tests with cx_oracle now pass - fully under Python 3. \ No newline at end of file + fully under Python 3. + + .. change:: + :tags: bug, orm + :tickets: 2736 + + The "auto-aliasing" behavior of the :class:`.Query.select_from` + method has been turned off. The specific behavior is now + availble via a new method :class:`.Query.select_entity_from`. + The auto-aliasing behavior here was never well documented and + is generally not what's desired, as :class:`.Query.select_from` + has become more oriented towards controlling how a JOIN is + rendered. :class:`.Query.select_entity_from` will also be made + available in 0.8 so that applications which rely on the auto-aliasing + can shift their applications to use this method. + + .. seealso:: + + :ref:`migration_2736` \ No newline at end of file diff --git a/doc/build/changelog/migration_09.rst b/doc/build/changelog/migration_09.rst index 82a0e788bd..969bfb6245 100644 --- a/doc/build/changelog/migration_09.rst +++ b/doc/build/changelog/migration_09.rst @@ -20,8 +20,8 @@ their applications from the 0.8 series of SQLAlchemy to 0.9. Version 0.9 is a faster-than-usual push from version 0.8, featuring a more versatile codebase with regards to modern -Python versions. The upgrade path at the moment requires no changes -to user code, however this is subject to change. +Python versions. See :ref:`behavioral_changes_09` for +potentially backwards-incompatible changes. Platform Support ================ @@ -39,3 +39,89 @@ the 3.1 and 3.2 interpreters. At the moment, the C extensions are still not fully ported to Python 3. + + +.. _behavioral_changes_09: + +Behavioral Changes +================== + +.. _migration_2736: + +:meth:`.Query.select_from` no longer applies the clause to corresponding entities +--------------------------------------------------------------------------------- + +The :meth:`.Query.select_from` method has been popularized in recent versions +as a means of controlling the first thing that a :class:`.Query` object +"selects from", typically for the purposes of controlling how a JOIN will +render. + +Consider the following example against the usual ``User`` mapping:: + + select_stmt = select([User]).where(User.id == 7).alias() + + q = session.query(User).\ + join(select_stmt, User.id == select_stmt.c.id).\ + filter(User.name == 'ed') + +The above statement predictably renders SQL like the following:: + + SELECT "user".id AS user_id, "user".name AS user_name + FROM "user" JOIN (SELECT "user".id AS id, "user".name AS name + FROM "user" + WHERE "user".id = :id_1) AS anon_1 ON "user".id = anon_1.id + WHERE "user".name = :name_1 + +If we wanted to reverse the order of the left and right elements of the +JOIN, the documentation would lead us to believe we could use +:meth:`.Query.select_from` to do so:: + + q = session.query(User).\ + select_from(select_stmt).\ + join(User, User.id == select_stmt.c.id).\ + filter(User.name == 'ed') + +However, in version 0.8 and earlier, the above use of :meth:`.Query.select_from` +would apply the ``select_stmt`` to **replace** the ``User`` entity, as it +selects from the ``user`` table which is compatible with ``User``:: + + -- SQLAlchemy 0.8 and earlier... + SELECT anon_1.id AS anon_1_id, anon_1.name AS anon_1_name + FROM (SELECT "user".id AS id, "user".name AS name + FROM "user" + WHERE "user".id = :id_1) AS anon_1 JOIN "user" ON anon_1.id = anon_1.id + WHERE anon_1.name = :name_1 + +The above statement is a mess, the ON clause refers ``anon_1.id = anon_1.id``, +our WHERE clause has been replaced with ``anon_1`` as well. + +This behavior is quite intentional, but has a different use case from that +which has become popular for :meth:`.Query.select_from`. The above behavior +is now available by a new method known as :meth:`.Query.select_entity_from`. +This is a lesser used behavior that in modern SQLAlchemy is roughly equivalent +to selecting from a customized :func:`.aliased` construct:: + + select_stmt = select([User]).where(User.id == 7) + user_from_stmt = aliased(User, select_stmt.alias()) + + q = session.query(user_from_stmt).filter(user_from_stmt.name == 'ed') + +So with SQLAlchemy 0.9, our query that selects from ``select_stmt`` produces +the SQL we expect:: + + -- SQLAlchemy 0.9 + SELECT "user".id AS user_id, "user".name AS user_name + FROM (SELECT "user".id AS id, "user".name AS name + FROM "user" + WHERE "user".id = :id_1) AS anon_1 JOIN "user" ON "user".id = id + WHERE "user".name = :name_1 + +The :meth:`.Query.select_entity_from` method will be available in SQLAlchemy +**0.8.2**, so applications which rely on the old behavior can transition +to this method first, ensure all tests continue to function, then upgrade +to 0.9 without issue. + +:ticket:`2736` + + + diff --git a/lib/sqlalchemy/orm/query.py b/lib/sqlalchemy/orm/query.py index cb788e0a43..79fd61c639 100644 --- a/lib/sqlalchemy/orm/query.py +++ b/lib/sqlalchemy/orm/query.py @@ -162,15 +162,19 @@ class Query(object): for m in m2.iterate_to_root(): self._polymorphic_adapters[m.local_table] = adapter - def _set_select_from(self, *obj): + def _set_select_from(self, obj, set_base_alias): fa = [] select_from_alias = None + for from_obj in obj: info = inspect(from_obj) if hasattr(info, 'mapper') and \ (info.is_mapper or info.is_aliased_class): - self._select_from_entity = from_obj + if set_base_alias: + raise sa_exc.ArgumentError( + "A selectable (FromClause) instance is " + "expected when the base alias is being set.") fa.append(info.selectable) elif not info.is_selectable: raise sa_exc.ArgumentError( @@ -179,12 +183,14 @@ class Query(object): else: if isinstance(from_obj, expression.SelectBase): from_obj = from_obj.alias() - select_from_alias = from_obj + if set_base_alias: + select_from_alias = from_obj fa.append(from_obj) self._from_obj = tuple(fa) - if len(self._from_obj) == 1 and \ + if set_base_alias and \ + len(self._from_obj) == 1 and \ isinstance(select_from_alias, expression.Alias): equivs = self.__all_equivs() self._from_obj_alias = sql_util.ColumnAdapter( @@ -953,7 +959,7 @@ class Query(object): '_prefixes', ): self.__dict__.pop(attr, None) - self._set_select_from(fromclause) + self._set_select_from([fromclause], True) # this enables clause adaptation for non-ORM # expressions. @@ -1970,21 +1976,134 @@ class Query(object): def select_from(self, *from_obj): """Set the FROM clause of this :class:`.Query` explicitly. - Sending a mapped class or entity here effectively replaces the + :meth:`.Query.select_from` is often used in conjunction with + :meth:`.Query.join` in order to control which entity is selected + from on the "left" side of the join. + + The entity or selectable object 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. + A typical example:: + + q = session.query(Address).select_from(User).\\ + join(User.addresses).\\ + filter(User.name == 'ed') + + Which produces SQL equivalent to:: + + SELECT address.* FROM user + JOIN address ON user.id=address.user_id + WHERE user.name = :name_1 + + :param \*from_obj: collection of one or more entities to apply + to the FROM clause. Entities can be mapped classes, + :class:`.AliasedClass` objects, :class:`.Mapper` objects + as well as core :class:`.FromClause` elements like subqueries. + + .. versionchanged:: 0.9 + This method no longer applies the given FROM object + to be the selectable from which matching entities + select from; the :meth:`.select_entity_from` method + now accomplishes this. See that method for a description + of this behavior. + + .. seealso:: + + :meth:`~.Query.join` + + :meth:`.Query.select_entity_from` + + """ + + self._set_select_from(from_obj, False) + + @_generative(_no_clauseelement_condition) + def select_entity_from(self, from_obj): + """Set the FROM clause of this :class:`.Query` to a + core selectable, applying it as a replacement FROM clause + for corresponding mapped entities. + + This method is similar to the :meth:`.Query.select_from` + method, in that it sets the FROM clause of the query. However, + where :meth:`.Query.select_from` only affects what is placed + in the FROM, this method also applies the given selectable + to replace the FROM which the selected entities would normally + select from. + + The given ``from_obj`` must be an instance of a :class:`.FromClause`, + e.g. a :func:`.select` or :class:`.Alias` construct. + + An example would be a :class:`.Query` that selects ``User`` entities, + but uses :meth:`.Query.select_entity_from` to have the entities + selected from a :func:`.select` construct instead of the + base ``user`` table:: + + select_stmt = select([User]).where(User.id == 7) + + q = session.query(User).\\ + select_entity_from(select_stmt).\\ + filter(User.name == 'ed') + + The query generated will select ``User`` entities directly + from the given :func:`.select` construct, and will be:: + + SELECT anon_1.id AS anon_1_id, anon_1.name AS anon_1_name + FROM (SELECT "user".id AS id, "user".name AS name + FROM "user" + WHERE "user".id = :id_1) AS anon_1 + WHERE anon_1.name = :name_1 + + Notice above that even the WHERE criterion was "adapted" such that + the ``anon_1`` subquery effectively replaces all references to the + ``user`` table, except for the one that it refers to internally. + + Compare this to :meth:`.Query.select_from`, which as of + version 0.9, does not affect existing entities. The + statement below:: + + q = session.query(User).\\ + select_from(select_stmt).\\ + filter(User.name == 'ed') + + Produces SQL where both the ``user`` table as well as the + ``select_stmt`` construct are present as separate elements + in the FROM clause. No "adaptation" of the ``user`` table + is applied:: + + SELECT "user".id AS user_id, "user".name AS user_name + FROM "user", (SELECT "user".id AS id, "user".name AS name + FROM "user" + WHERE "user".id = :id_1) AS anon_1 + WHERE "user".name = :name_1 + + :meth:`.Query.select_entity_from` maintains an older + behavior of :meth:`.Query.select_from`. In modern usage, + similar results can also be achieved using :func:`.aliased`:: + + select_stmt = select([User]).where(User.id == 7) + user_from_select = aliased(User, select_stmt.alias()) + + q = session.query(user_from_select) + + :param from_obj: a :class:`.FromClause` object that will replace + the FROM clause of this :class:`.Query`. + + .. seealso:: + + :meth:`.Query.select_from` - See the example in :meth:`~.Query.join` for a typical - usage of :meth:`~.Query.select_from`. + .. versionadded:: 0.8 + :meth:`.Query.select_entity_from` was added to specify + the specific behavior of entity replacement, however + the :meth:`.Query.select_from` maintains this behavior + as well until 0.9. """ - self._set_select_from(*from_obj) + self._set_select_from([from_obj], True) def __getitem__(self, item): if isinstance(item, slice): diff --git a/lib/sqlalchemy/orm/strategies.py b/lib/sqlalchemy/orm/strategies.py index 4651c71b79..48ee6a5f8f 100644 --- a/lib/sqlalchemy/orm/strategies.py +++ b/lib/sqlalchemy/orm/strategies.py @@ -781,7 +781,7 @@ class SubqueryLoader(AbstractRelationshipLoader): # set a real "from" if not present, as this is more # accurate than just going off of the column expression if not q._from_obj and entity_mapper.isa(leftmost_mapper): - q._set_select_from(entity_mapper) + q._set_select_from([entity_mapper], False) # select from the identity columns of the outer q._set_entities(q._adapt_col_list(leftmost_attr)) diff --git a/test/orm/test_froms.py b/test/orm/test_froms.py index 5040c46852..2bc9383682 100644 --- a/test/orm/test_froms.py +++ b/test/orm/test_froms.py @@ -161,7 +161,7 @@ class RawSelectTest(QueryTest, AssertsCompiledSQL): sess = create_session() - self.assert_compile(sess.query(users).select_from( + self.assert_compile(sess.query(users).select_entity_from( users.select()).with_labels().statement, "SELECT users.id AS users_id, users.name AS users_name FROM users, " "(SELECT users.id AS id, users.name AS name FROM users) AS anon_1", @@ -180,7 +180,7 @@ class RawSelectTest(QueryTest, AssertsCompiledSQL): filter(addresses.c.user_id == users.c.id).correlate(users).\ statement.alias() - self.assert_compile(sess.query(users, s.c.email).select_from( + self.assert_compile(sess.query(users, s.c.email).select_entity_from( users.join(s, s.c.id == users.c.id) ).with_labels().statement, "SELECT users.id AS users_id, users.name AS users_name, " @@ -425,12 +425,12 @@ class ColumnAccessTest(QueryTest, AssertsCompiledSQL): "WHERE anon_1.anon_2_users_name = :name_1" ) - def test_select_from(self): + def test_select_entity_from(self): User = self.classes.User sess = create_session() q = sess.query(User) - q = sess.query(User).select_from(q.statement) + q = sess.query(User).select_entity_from(q.statement) self.assert_compile( q.filter(User.name=='ed'), "SELECT anon_1.id AS anon_1_id, anon_1.name AS anon_1_name " @@ -438,6 +438,19 @@ class ColumnAccessTest(QueryTest, AssertsCompiledSQL): "users) AS anon_1 WHERE anon_1.name = :name_1" ) + def test_select_from_no_aliasing(self): + User = self.classes.User + sess = create_session() + + q = sess.query(User) + q = sess.query(User).select_from(q.statement) + self.assert_compile( + q.filter(User.name=='ed'), + "SELECT users.id AS users_id, users.name AS users_name " + "FROM users, (SELECT users.id AS id, users.name AS name FROM " + "users) AS anon_1 WHERE users.name = :name_1" + ) + def test_anonymous_expression(self): from sqlalchemy.sql import column @@ -681,15 +694,15 @@ class InstancesTest(QueryTest, AssertsCompiledSQL): assert self.static.user_address_result == l self.assert_sql_count(testing.db, go, 1) - # better way. use select_from() + # better way. use select_entity_from() def go(): - l = sess.query(User).select_from(query).\ + l = sess.query(User).select_entity_from(query).\ options(contains_eager('addresses')).all() assert self.static.user_address_result == l self.assert_sql_count(testing.db, go, 1) # same thing, but alias addresses, so that the adapter - # generated by select_from() is wrapped within + # generated by select_entity_from() is wrapped within # the adapter created by contains_eager() adalias = addresses.alias() query = users.select(users.c.id==7).\ @@ -699,7 +712,7 @@ class InstancesTest(QueryTest, AssertsCompiledSQL): select(use_labels=True, order_by=['ulist.id', adalias.c.id]) def go(): - l = sess.query(User).select_from(query).\ + l = sess.query(User).select_entity_from(query).\ options(contains_eager('addresses', alias=adalias)).all() assert self.static.user_address_result == l self.assert_sql_count(testing.db, go, 1) @@ -733,7 +746,7 @@ class InstancesTest(QueryTest, AssertsCompiledSQL): adalias = addresses.alias() q = sess.query(User).\ - select_from(users.outerjoin(adalias)).\ + select_entity_from(users.outerjoin(adalias)).\ options(contains_eager(User.addresses, alias=adalias)).\ order_by(User.id, adalias.c.id) def go(): @@ -1044,7 +1057,7 @@ class MixedEntitiesTest(QueryTest, AssertsCompiledSQL): sel = users.select(User.id.in_([7, 8])).alias() q = sess.query(User) - q2 = q.select_from(sel).values(User.name) + q2 = q.select_entity_from(sel).values(User.name) eq_(list(q2), [('jack',), ('ed',)]) q = sess.query(User) @@ -1080,17 +1093,17 @@ class MixedEntitiesTest(QueryTest, AssertsCompiledSQL): q2 = q.values(func.count(User.name)) assert next(q2) == (4,) - q2 = q.select_from(sel).filter(User.id==8).values(User.name, sel.c.name, User.name) + q2 = q.select_entity_from(sel).filter(User.id==8).values(User.name, sel.c.name, User.name) eq_(list(q2), [('ed', 'ed', 'ed')]) # using User.xxx is alised against "sel", so this query returns nothing - q2 = q.select_from(sel).\ + q2 = q.select_entity_from(sel).\ filter(User.id==8).\ filter(User.id>sel.c.id).values(User.name, sel.c.name, User.name) eq_(list(q2), []) # whereas this uses users.c.xxx, is not aliased and creates a new join - q2 = q.select_from(sel).\ + q2 = q.select_entity_from(sel).\ filter(users.c.id==8).\ filter(users.c.id>sel.c.id).values(users.c.name, sel.c.name, User.name) eq_(list(q2), [('ed', 'jack', 'jack')]) @@ -1119,7 +1132,7 @@ class MixedEntitiesTest(QueryTest, AssertsCompiledSQL): sel = users.select(User.id.in_([7, 8])).alias() q = sess.query(User) u2 = aliased(User) - q2 = q.select_from(sel).\ + q2 = q.select_entity_from(sel).\ filter(u2.id>1).\ order_by(User.id, sel.c.id, u2.id).\ values(User.name, sel.c.name, u2.name) @@ -1206,7 +1219,7 @@ class MixedEntitiesTest(QueryTest, AssertsCompiledSQL): sel = users.select(User.id.in_([7, 8])).alias() q = sess.query(User.name) - q2 = q.select_from(sel).all() + q2 = q.select_entity_from(sel).all() eq_(list(q2), [('jack',), ('ed',)]) eq_(sess.query(User.name, Address.email_address).filter(User.id==Address.user_id).all(), [ @@ -1277,7 +1290,7 @@ class MixedEntitiesTest(QueryTest, AssertsCompiledSQL): ] ) - # test eager aliasing, with/without select_from aliasing + # test eager aliasing, with/without select_entity_from aliasing for q in [ sess.query(User, adalias.email_address).\ outerjoin(adalias, User.addresses).\ @@ -1466,14 +1479,14 @@ class MixedEntitiesTest(QueryTest, AssertsCompiledSQL): q = sess.query(User) adalias = addresses.alias('adalias') - q = q.add_entity(Address, alias=adalias).select_from(users.outerjoin(adalias)) + q = q.add_entity(Address, alias=adalias).select_entity_from(users.outerjoin(adalias)) l = q.order_by(User.id, adalias.c.id).all() assert l == expected sess.expunge_all() q = sess.query(User).add_entity(Address, alias=adalias) - l = q.select_from(users.outerjoin(adalias)).filter(adalias.c.email_address=='ed@bettyboop.com').all() + l = q.select_entity_from(users.outerjoin(adalias)).filter(adalias.c.email_address=='ed@bettyboop.com').all() assert l == [(user8, address3)] def test_with_entities(self): @@ -1591,9 +1604,9 @@ class MixedEntitiesTest(QueryTest, AssertsCompiledSQL): sess.expunge_all() - # test with select_from() + # test with select_entity_from() q = create_session().query(User).add_column(func.count(addresses.c.id))\ - .add_column(("Name:" + users.c.name)).select_from(users.outerjoin(addresses))\ + .add_column(("Name:" + users.c.name)).select_entity_from(users.outerjoin(addresses))\ .group_by(users).order_by(users.c.id) assert q.all() == expected @@ -1718,19 +1731,19 @@ class SelectFromTest(QueryTest, AssertsCompiledSQL): sel = users.select(users.c.id.in_([7, 8])).alias() sess = create_session() - eq_(sess.query(User).select_from(sel).all(), [User(id=7), User(id=8)]) + eq_(sess.query(User).select_entity_from(sel).all(), [User(id=7), User(id=8)]) - eq_(sess.query(User).select_from(sel).filter(User.id==8).all(), [User(id=8)]) + eq_(sess.query(User).select_entity_from(sel).filter(User.id==8).all(), [User(id=8)]) - eq_(sess.query(User).select_from(sel).order_by(desc(User.name)).all(), [ + eq_(sess.query(User).select_entity_from(sel).order_by(desc(User.name)).all(), [ User(name='jack',id=7), User(name='ed',id=8) ]) - eq_(sess.query(User).select_from(sel).order_by(asc(User.name)).all(), [ + eq_(sess.query(User).select_entity_from(sel).order_by(asc(User.name)).all(), [ User(name='ed',id=8), User(name='jack',id=7) ]) - eq_(sess.query(User).select_from(sel).options(joinedload('addresses')).first(), + eq_(sess.query(User).select_entity_from(sel).options(joinedload('addresses')).first(), User(name='jack', addresses=[Address(id=1)]) ) @@ -1745,7 +1758,7 @@ class SelectFromTest(QueryTest, AssertsCompiledSQL): sel = users.select(users.c.id.in_([7, 8])) sess = create_session() - eq_(sess.query(User).select_from(sel).all(), + eq_(sess.query(User).select_entity_from(sel).all(), [ User(name='jack',id=7), User(name='ed',id=8) ] @@ -1772,14 +1785,14 @@ class SelectFromTest(QueryTest, AssertsCompiledSQL): ) self.assert_compile( - sess.query(ualias).select_from(sel).filter(ualias.id>sel.c.id), + sess.query(ualias).select_entity_from(sel).filter(ualias.id>sel.c.id), "SELECT users_1.id AS users_1_id, users_1.name AS users_1_name FROM " "users AS users_1, (SELECT users.id AS id, users.name AS name FROM " "users WHERE users.id IN (:id_1, :id_2)) AS anon_1 WHERE users_1.id > anon_1.id", ) self.assert_compile( - sess.query(ualias).select_from(sel).join(ualias, ualias.id>sel.c.id), + sess.query(ualias).select_entity_from(sel).join(ualias, ualias.id>sel.c.id), "SELECT users_1.id AS users_1_id, users_1.name AS users_1_name " "FROM (SELECT users.id AS id, users.name AS name " "FROM users WHERE users.id IN (:id_1, :id_2)) AS anon_1 " @@ -1787,7 +1800,7 @@ class SelectFromTest(QueryTest, AssertsCompiledSQL): ) self.assert_compile( - sess.query(ualias).select_from(sel).join(ualias, ualias.id>User.id), + sess.query(ualias).select_entity_from(sel).join(ualias, ualias.id>User.id), "SELECT users_1.id AS users_1_id, users_1.name AS users_1_name " "FROM (SELECT users.id AS id, users.name AS name FROM " "users WHERE users.id IN (:id_1, :id_2)) AS anon_1 " @@ -1805,7 +1818,7 @@ class SelectFromTest(QueryTest, AssertsCompiledSQL): # this one uses an explicit join(left, right, onclause) so works self.assert_compile( - sess.query(ualias).select_from(join(sel, ualias, ualias.id>sel.c.id)), + sess.query(ualias).select_entity_from(join(sel, ualias, ualias.id>sel.c.id)), "SELECT users_1.id AS users_1_id, users_1.name AS users_1_name FROM " "(SELECT users.id AS id, users.name AS name FROM users WHERE users.id " "IN (:id_1, :id_2)) AS anon_1 JOIN users AS users_1 ON users_1.id > anon_1.id", @@ -1848,7 +1861,7 @@ class SelectFromTest(QueryTest, AssertsCompiledSQL): # here for comparison self.assert_compile( sess.query(User.name).\ - select_from(users.select().where(users.c.id > 5)), + select_entity_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" ) @@ -1861,7 +1874,7 @@ class SelectFromTest(QueryTest, AssertsCompiledSQL): sel = users.select(users.c.id.in_([7, 8])) sess = create_session() - eq_(sess.query(User).select_from(sel).all(), + eq_(sess.query(User).select_entity_from(sel).all(), [ User(name='jack',id=7), User(name='ed',id=8) ] @@ -1881,7 +1894,7 @@ class SelectFromTest(QueryTest, AssertsCompiledSQL): sel = users.select(users.c.id.in_([7, 8])) sess = create_session() - eq_(sess.query(User).select_from(sel).join('addresses'). + eq_(sess.query(User).select_entity_from(sel).join('addresses'). add_entity(Address).order_by(User.id).order_by(Address.id).all(), [ (User(name='jack',id=7), Address(user_id=7,email_address='jack@bean.com',id=1)), @@ -1892,7 +1905,7 @@ class SelectFromTest(QueryTest, AssertsCompiledSQL): ) adalias = aliased(Address) - eq_(sess.query(User).select_from(sel).join(adalias, 'addresses'). + eq_(sess.query(User).select_entity_from(sel).join(adalias, 'addresses'). add_entity(adalias).order_by(User.id).order_by(adalias.id).all(), [ (User(name='jack',id=7), Address(user_id=7,email_address='jack@bean.com',id=1)), @@ -1931,7 +1944,7 @@ class SelectFromTest(QueryTest, AssertsCompiledSQL): sess = create_session() sel = users.select(users.c.id.in_([7, 8])) - eq_(sess.query(User).select_from(sel).\ + eq_(sess.query(User).select_entity_from(sel).\ join('orders', 'items', 'keywords').\ filter(Keyword.name.in_(['red', 'big', 'round'])).\ all(), @@ -1939,7 +1952,7 @@ class SelectFromTest(QueryTest, AssertsCompiledSQL): User(name='jack',id=7) ]) - eq_(sess.query(User).select_from(sel).\ + eq_(sess.query(User).select_entity_from(sel).\ join('orders', 'items', 'keywords', aliased=True).\ filter(Keyword.name.in_(['red', 'big', 'round'])).\ all(), @@ -1949,7 +1962,7 @@ class SelectFromTest(QueryTest, AssertsCompiledSQL): def go(): eq_( - sess.query(User).select_from(sel). + sess.query(User).select_entity_from(sel). options(joinedload_all('orders.items.keywords')). join('orders', 'items', 'keywords', aliased=True). filter(Keyword.name.in_(['red', 'big', 'round'])).\ @@ -1994,14 +2007,14 @@ class SelectFromTest(QueryTest, AssertsCompiledSQL): sess.expunge_all() sel2 = orders.select(orders.c.id.in_([1,2,3])) - eq_(sess.query(Order).select_from(sel2).\ + eq_(sess.query(Order).select_entity_from(sel2).\ join('items', 'keywords').\ filter(Keyword.name == 'red').\ order_by(Order.id).all(), [ Order(description='order 1',id=1), Order(description='order 2',id=2), ]) - eq_(sess.query(Order).select_from(sel2).\ + eq_(sess.query(Order).select_entity_from(sel2).\ join('items', 'keywords', aliased=True).\ filter(Keyword.name == 'red').\ order_by(Order.id).all(), [ @@ -2025,7 +2038,9 @@ class SelectFromTest(QueryTest, AssertsCompiledSQL): sess = create_session() def go(): - eq_(sess.query(User).options(joinedload('addresses')).select_from(sel).order_by(User.id).all(), + eq_(sess.query(User).options( + joinedload('addresses') + ).select_entity_from(sel).order_by(User.id).all(), [ User(id=7, addresses=[Address(id=1)]), User(id=8, addresses=[Address(id=2), Address(id=3), Address(id=4)]) @@ -2035,14 +2050,19 @@ class SelectFromTest(QueryTest, AssertsCompiledSQL): sess.expunge_all() def go(): - eq_(sess.query(User).options(joinedload('addresses')).select_from(sel).filter(User.id==8).order_by(User.id).all(), + eq_(sess.query(User).options( + joinedload('addresses') + ).select_entity_from(sel).filter(User.id==8).order_by(User.id).all(), [User(id=8, addresses=[Address(id=2), Address(id=3), Address(id=4)])] ) self.assert_sql_count(testing.db, go, 1) sess.expunge_all() def go(): - eq_(sess.query(User).options(joinedload('addresses')).select_from(sel).order_by(User.id)[1], User(id=8, addresses=[Address(id=2), Address(id=3), Address(id=4)])) + eq_(sess.query(User).options( + joinedload('addresses') + ).select_entity_from(sel).order_by(User.id)[1], + User(id=8, addresses=[Address(id=2), Address(id=3), Address(id=4)])) self.assert_sql_count(testing.db, go, 1) class CustomJoinTest(QueryTest): @@ -2180,7 +2200,7 @@ class ExternalColumnsTest(QueryTest): ua = aliased(User) eq_(sess.query(Address, ua.concat, ua.count). - select_from(join(Address, ua, 'user')). + select_entity_from(join(Address, ua, 'user')). options(joinedload(Address.user)).order_by(Address.id).all(), [ (Address(id=1, user=User(id=7, concat=14, count=1)), 14, 1), @@ -2195,7 +2215,7 @@ class ExternalColumnsTest(QueryTest): [(1, 7, 14, 1), (2, 8, 16, 3), (3, 8, 16, 3), (4, 8, 16, 3), (5, 9, 18, 1)] ) - eq_(list(sess.query(Address, ua).select_from(join(Address,ua, 'user')).values(Address.id, ua.id, ua.concat, ua.count)), + eq_(list(sess.query(Address, ua).select_entity_from(join(Address,ua, 'user')).values(Address.id, ua.id, ua.concat, ua.count)), [(1, 7, 14, 1), (2, 8, 16, 3), (3, 8, 16, 3), (4, 8, 16, 3), (5, 9, 18, 1)] ) diff --git a/test/orm/test_joins.py b/test/orm/test_joins.py index 320104acb4..2dac591501 100644 --- a/test/orm/test_joins.py +++ b/test/orm/test_joins.py @@ -1066,7 +1066,7 @@ class JoinTest(QueryTest, AssertsCompiledSQL): # same with an explicit select_from() eq_( - sess.query(User).select_from(select([users]). + sess.query(User).select_entity_from(select([users]). order_by(User.id).offset(2).alias()). join(Order, User.id==Order.user_id). all(),