diff options
Diffstat (limited to 'lib/sqlalchemy/sql')
| -rw-r--r-- | lib/sqlalchemy/sql/coercions.py | 42 | ||||
| -rw-r--r-- | lib/sqlalchemy/sql/roles.py | 24 | ||||
| -rw-r--r-- | lib/sqlalchemy/sql/selectable.py | 42 |
3 files changed, 99 insertions, 9 deletions
diff --git a/lib/sqlalchemy/sql/coercions.py b/lib/sqlalchemy/sql/coercions.py index 7c7222f9f..d4551eb60 100644 --- a/lib/sqlalchemy/sql/coercions.py +++ b/lib/sqlalchemy/sql/coercions.py @@ -199,7 +199,7 @@ def _no_text_coercion( class _NoTextCoercion(object): - def _literal_coercion(self, element, argname=None): + def _literal_coercion(self, element, argname=None, **kw): if isinstance(element, util.string_types) and issubclass( elements.TextClause, self._role_class ): @@ -216,7 +216,7 @@ class _CoerceLiterals(object): def _text_coercion(self, element, argname=None): return _no_text_coercion(element, argname) - def _literal_coercion(self, element, argname=None): + def _literal_coercion(self, element, argname=None, **kw): if isinstance(element, util.string_types): if self._coerce_star and element == "*": return elements.ColumnClause("*", is_literal=True) @@ -240,7 +240,9 @@ class _CoerceLiterals(object): class ExpressionElementImpl( _ColumnCoercions, RoleImpl, roles.ExpressionElementRole ): - def _literal_coercion(self, element, name=None, type_=None, argname=None): + def _literal_coercion( + self, element, name=None, type_=None, argname=None, **kw + ): if element is None: return elements.Null() else: @@ -256,7 +258,7 @@ class BinaryElementImpl( ExpressionElementImpl, RoleImpl, roles.BinaryElementRole ): def _literal_coercion( - self, element, expr, operator, bindparam_type=None, argname=None + self, element, expr, operator, bindparam_type=None, argname=None, **kw ): try: return expr._bind_param(operator, element, type_=bindparam_type) @@ -393,7 +395,7 @@ class DMLColumnImpl(_ReturnsStringKey, RoleImpl, roles.DMLColumnRole): class ConstExprImpl(RoleImpl, roles.ConstExprRole): - def _literal_coercion(self, element, argname=None): + def _literal_coercion(self, element, argname=None, **kw): if element is None: return elements.Null() elif element is False: @@ -413,7 +415,7 @@ class TruncatedLabelImpl(_StringOnly, RoleImpl, roles.TruncatedLabelRole): else: self._raise_for_expected(original_element, argname) - def _literal_coercion(self, element, argname=None): + def _literal_coercion(self, element, argname=None, **kw): """coerce the given value to :class:`._truncated_label`. Existing :class:`._truncated_label` and @@ -542,6 +544,34 @@ class FromClauseImpl(_NoTextCoercion, RoleImpl, roles.FromClauseRole): self._raise_for_expected(original_element, argname) +class StrictFromClauseImpl(FromClauseImpl, roles.StrictFromClauseRole): + def _implicit_coercions( + self, + original_element, + resolved, + argname=None, + allow_select=False, + **kw + ): + if resolved._is_select_statement and allow_select: + util.warn_deprecated( + "Implicit coercion of SELECT and textual SELECT constructs " + "into FROM clauses is deprecated; please call .subquery() " + "on any Core select or ORM Query object in order to produce a " + "subquery object." + ) + return resolved.subquery() + else: + self._raise_for_expected(original_element, argname) + + +class AnonymizedFromClauseImpl( + StrictFromClauseImpl, roles.AnonymizedFromClauseRole +): + def _post_coercion(self, element, flat=False, **kw): + return element.alias(flat=flat) + + class DMLSelectImpl(_NoTextCoercion, RoleImpl, roles.DMLSelectRole): def _implicit_coercions( self, original_element, resolved, argname=None, **kw diff --git a/lib/sqlalchemy/sql/roles.py b/lib/sqlalchemy/sql/roles.py index 2d3aaf903..053bd7146 100644 --- a/lib/sqlalchemy/sql/roles.py +++ b/lib/sqlalchemy/sql/roles.py @@ -100,6 +100,30 @@ class FromClauseRole(ColumnsClauseRole): raise NotImplementedError() +class StrictFromClauseRole(FromClauseRole): + # does not allow text() or select() objects + pass + + +class AnonymizedFromClauseRole(StrictFromClauseRole): + # calls .alias() as a post processor + + def _anonymous_fromclause(self, name=None, flat=False): + """A synonym for ``.alias()`` that is only present on objects of this + role. + + This is an implicit assurance of the target object being part of the + role where anonymous aliasing without any warnings is allowed, + as opposed to other kinds of SELECT objects that may or may not have + an ``.alias()`` method. + + The method is used by the ORM but is currently semi-private to + preserve forwards-compatibility. + + """ + return self.alias(name=name, flat=flat) + + class CoerceTextStatementRole(SQLRole): _role_name = "Executable SQL, text() construct, or string statement" diff --git a/lib/sqlalchemy/sql/selectable.py b/lib/sqlalchemy/sql/selectable.py index 41be9fc5a..b0d6002b7 100644 --- a/lib/sqlalchemy/sql/selectable.py +++ b/lib/sqlalchemy/sql/selectable.py @@ -662,7 +662,7 @@ class FromClause(roles.FromClauseRole, Selectable): return None -class Join(FromClause): +class Join(roles.AnonymizedFromClauseRole, FromClause): """represent a ``JOIN`` construct between two :class:`.FromClause` elements. @@ -1178,7 +1178,7 @@ class Join(FromClause): ) -class Alias(FromClause): +class Alias(roles.AnonymizedFromClauseRole, FromClause): """Represents an table or selectable alias (AS). Represents an alias, as typically applied to any table or @@ -1772,7 +1772,7 @@ class FromGrouping(FromClause): self.element = state["element"] -class TableClause(Immutable, FromClause): +class TableClause(roles.AnonymizedFromClauseRole, Immutable, FromClause): """Represents a minimal "table" construct. This is a lightweight table object that has only a name and a @@ -2116,6 +2116,42 @@ class SelectBase( def _from_objects(self): return [self] + def subquery(self, name=None): + """Return a subquery of this :class:`.SelectBase`. + + A subquery is from a SQL perspective a parentheized, named construct + that can be placed in the FROM clause of another SELECT statement. + + Given a SELECT statement such as:: + + stmt = select([table.c.id, table.c.name]) + + The above statement might look like:: + + SELECT table.id, table.name FROM table + + The subquery form by itself renders the same way, however when + embedded into the FROM clause of another SELECT statement, it becomes + a named sub-element:: + + subq = stmt.subquery() + new_stmt = select([subq]) + + The above renders as:: + + SELECT anon_1.id, anon_1.name + FROM (SELECT table.id, table.name FROM table) AS anon_1 + + Historically, :meth:`.SelectBase.subquery` is equivalent to calling + the :meth:`.FromClause.alias` method on a FROM object; however, + as a :class:`.SelectBase` object is not directly FROM object, + the :meth:`.SelectBase.subquery` method provides clearer semantics. + + .. versionadded:: 1.4 + + """ + return self.alias() + class GenerativeSelect(SelectBase): """Base class for SELECT statements where additional elements can be |
