From: Mike Bayer Date: Sun, 30 Jan 2011 18:39:08 +0000 (-0500) Subject: - Added a `name` argument to `Query.subquery()`, to allow X-Git-Tag: rel_0_7b1~47 X-Git-Url: http://git.ipfire.org/cgi-bin/gitweb.cgi?a=commitdiff_plain;h=7ac30f96b9f506ef4ab34e1c725bf6911a6c22fb;p=thirdparty%2Fsqlalchemy%2Fsqlalchemy.git - Added a `name` argument to `Query.subquery()`, to allow a fixed name to be assigned to the alias object. [ticket:2030] - changed the kw name 'alias' to 'name' on the alias() standalone function. - fixed up some alias/join docstrings --- diff --git a/CHANGES b/CHANGES index 210bfbc519..bd8294b5f5 100644 --- a/CHANGES +++ b/CHANGES @@ -176,6 +176,11 @@ CHANGES 0.6.7 ===== +- orm + - Added a `name` argument to `Query.subquery()`, to allow + a fixed name to be assigned to the alias object. + [ticket:2030] + - sql - Column.copy(), as used in table.tometadata(), copies the 'doc' attribute. [ticket:2028] diff --git a/lib/sqlalchemy/orm/query.py b/lib/sqlalchemy/orm/query.py index 42dd60a2fc..79b0809277 100644 --- a/lib/sqlalchemy/orm/query.py +++ b/lib/sqlalchemy/orm/query.py @@ -420,18 +420,25 @@ class Query(object): stmt = stmt.params(self._params) return stmt._annotate({'_halt_adapt': True}) - def subquery(self): + def subquery(self, name=None): """return the full SELECT statement represented by this :class:`.Query`, embedded within an :class:`.Alias`. Eager JOIN generation within the query is disabled. - The statement by default will not have disambiguating labels - applied to the construct unless with_labels(True) is called - first. + The statement will not have disambiguating labels + applied to the list of selected columns unless the + :meth:`.Query.with_labels` method is used to generate a new + :class:`.Query` with the option enabled. + + :param name: string name to be assigned as the alias; + this is passed through to :meth:`.FromClause.alias`. + If ``None``, a name will be deterministically generated + at compile time. + """ - return self.enable_eagerloads(False).statement.alias() + return self.enable_eagerloads(False).statement.alias(name=name) def label(self, name): """Return the full SELECT statement represented by this :class:`.Query`, converted diff --git a/lib/sqlalchemy/sql/expression.py b/lib/sqlalchemy/sql/expression.py index 64274359b1..10041940a1 100644 --- a/lib/sqlalchemy/sql/expression.py +++ b/lib/sqlalchemy/sql/expression.py @@ -636,23 +636,38 @@ def intersect_all(*selects, **kwargs): """ return CompoundSelect(CompoundSelect.INTERSECT_ALL, *selects, **kwargs) -def alias(selectable, alias=None): - """Return an :class:`Alias` object. +def alias(selectable, name=None): + """Return an :class:`.Alias` object. - An :class:`Alias` represents any :class:`FromClause` + An :class:`.Alias` represents any :class:`.FromClause` with an alternate name assigned within SQL, typically using the ``AS`` clause when generated, e.g. ``SELECT * FROM table AS aliasname``. - Similar functionality is available via the :func:`alias()` method - available on all :class:`FromClause` subclasses. + Similar functionality is available via the + :meth:`~.FromClause.alias` method + available on all :class:`.FromClause` subclasses. - selectable - any :class:`FromClause` subclass, such as a table, select - statement, etc.. + When an :class:`.Alias` is created from a :class:`.Table` object, + this has the effect of the table being rendered + as ``tablename AS aliasname`` in a SELECT statement. + + For :func:`.select` objects, the effect is that of creating a named + subquery, i.e. ``(select ...) AS aliasname``. - alias - string name to be assigned as the alias. If ``None``, a - random name will be generated. + The ``name`` parameter is optional, and provides the name + to use in the rendered SQL. If blank, an "anonymous" name + will be deterministically generated at compile time. + Deterministic means the name is guaranteed to be unique against + other constructs used in the same statement, and will also be the + same name for each successive compilation of the same statement + object. + + :param selectable: any :class:`.FromClause` subclass, + such as a table, select statement, etc. + + :param name: string name to be assigned as the alias. + If ``None``, a name will be deterministically generated + at compile time. """ return Alias(selectable, alias=alias) @@ -2232,19 +2247,14 @@ class FromClause(Selectable): return Join(self, right, onclause, True) def alias(self, name=None): - """return an alias of this :class:`FromClause`. - - For table objects, this has the effect of the table being rendered - as ``tablename AS aliasname`` in a SELECT statement. - For select objects, the effect is that of creating a named - subquery, i.e. ``(select ...) AS aliasname``. - The :func:`alias()` method is the general way to create - a "subquery" out of an existing SELECT. - - The ``name`` parameter is optional, and if left blank an - "anonymous" name will be generated at compile time, guaranteed - to be unique against other anonymous constructs used in the - same statement. + """return an alias of this :class:`.FromClause`. + + This is shorthand for calling:: + + from sqlalchemy import alias + a = alias(self, name=name) + + See :func:`~.expression.alias` for details. """ @@ -3190,6 +3200,13 @@ class Join(FromClause): __visit_name__ = 'join' def __init__(self, left, right, onclause=None, isouter=False): + """Construct a new :class:`.Join`. + + The usual entrypoint here is the :func:`~.expression.join` + function or the :meth:`.FromClause.join` method of any + :class:`.FromClause` object. + + """ self.left = _literal_as_text(left) self.right = _literal_as_text(right).self_group() @@ -3246,12 +3263,20 @@ class Join(FromClause): def select(self, whereclause=None, fold_equivalents=False, **kwargs): """Create a :class:`Select` from this :class:`Join`. - + + The equivalent long-hand form, given a :class:`.Join` object + ``j``, is:: + + from sqlalchemy import select + j = select([j.left, j.right], **kw).\\ + where(whereclause).\\ + select_from(j) + :param whereclause: the WHERE criterion that will be sent to the :func:`select()` function :param fold_equivalents: based on the join criterion of this - :class:`Join`, do not include + :class:`.Join`, do not include repeat column names in the column list of the resulting select, for columns that are calculated to be "equivalent" based on the join criterion of this :class:`Join`. This will @@ -3274,11 +3299,32 @@ class Join(FromClause): return self.left.bind or self.right.bind def alias(self, name=None): - """Create a :class:`Select` out of this :class:`Join` clause and - return an :class:`Alias` of it. - - The :class:`Select` is not correlating. + """return an alias of this :class:`.Join`. + + Used against a :class:`.Join` object, + :meth:`~.Join.alias` calls the :meth:`~.Join.select` + method first so that a subquery against a + :func:`.select` construct is generated. + the :func:`~expression.select` construct also has the + ``correlate`` flag set to ``False`` and will not + auto-correlate inside an enclosing :func:`~expression.select` + construct. + + The equivalent long-hand form, given a :class:`.Join` object + ``j``, is:: + + from sqlalchemy import select, alias + j = alias( + select([j.left, j.right]).\\ + select_from(j).\\ + with_labels(True).\\ + correlate(False), + name=name + ) + See :func:`~.expression.alias` for further details on + aliases. + """ return self.select(use_labels=True, correlate=False).alias(name) @@ -3301,9 +3347,9 @@ class Alias(FromClause): sub-select within a SQL statement using the ``AS`` keyword (or without the keyword on certain databases such as Oracle). - This object is constructed from the :func:`alias()` module level - function as well as the :func:`alias()` method available on all - :class:`FromClause` subclasses. + This object is constructed from the :func:`~.expression.alias` module level + function as well as the :meth:`.FromClause.alias` method available on all + :class:`.FromClause` subclasses. """ diff --git a/test/orm/test_query.py b/test/orm/test_query.py index 948d04620e..8e94f56c93 100644 --- a/test/orm/test_query.py +++ b/test/orm/test_query.py @@ -656,6 +656,17 @@ class ExpressionTest(QueryTest, AssertsCompiledSQL): eq_(User(id=7), q.one()) + def test_named_subquery(self): + session = create_session() + a1 = session.query(User.id).filter(User.id==7).subquery('foo1') + a2 = session.query(User.id).filter(User.id==7).subquery(name='foo2') + a3 = session.query(User.id).filter(User.id==7).subquery() + + eq_(a1.name, 'foo1') + eq_(a2.name, 'foo2') + eq_(a3.name, '%%(%d anon)s' % id(a3)) + + def test_label(self): session = create_session()