diff options
| author | Mike Bayer <mike_mp@zzzcomputing.com> | 2013-05-30 20:09:44 -0400 |
|---|---|---|
| committer | Mike Bayer <mike_mp@zzzcomputing.com> | 2013-05-30 20:09:44 -0400 |
| commit | 04317bd5a887524956249565014c077afe41f905 (patch) | |
| tree | 7bfaa281474f7de2c79bf5fef5b0bdd49f50b280 /lib/sqlalchemy/orm | |
| parent | bef93660c1b4a613d8545d6d04f9cac45254cd89 (diff) | |
| download | sqlalchemy-04317bd5a887524956249565014c077afe41f905.tar.gz | |
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]
Diffstat (limited to 'lib/sqlalchemy/orm')
| -rw-r--r-- | lib/sqlalchemy/orm/query.py | 141 | ||||
| -rw-r--r-- | lib/sqlalchemy/orm/strategies.py | 2 |
2 files changed, 131 insertions, 12 deletions
diff --git a/lib/sqlalchemy/orm/query.py b/lib/sqlalchemy/orm/query.py index cb788e0a4..79fd61c63 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 4651c71b7..48ee6a5f8 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)) |
