diff options
| author | Mike Bayer <mike_mp@zzzcomputing.com> | 2019-05-18 20:35:01 -0400 |
|---|---|---|
| committer | Mike Bayer <mike_mp@zzzcomputing.com> | 2019-05-27 21:19:06 -0400 |
| commit | 47552aa93bedcce3756dc89774f02db9f1868e68 (patch) | |
| tree | 4a00be1f85b5964e8cf6d035ed94345eba0459e4 /lib/sqlalchemy/sql | |
| parent | 8b2eb2a2d0f4c7e283d96cf3e5263b5dd48e3b14 (diff) | |
| download | sqlalchemy-47552aa93bedcce3756dc89774f02db9f1868e68.tar.gz | |
Use roles for ORM alias() conversion
as SELECT statements will have subquery() and not alias(),
start getting ready for the places where the ORM coerces SELECTs
into subqueries and be ready to warn about it
Change-Id: I90d4b6cae2c72816c6b192016ce074589caf4731
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 |
