summaryrefslogtreecommitdiff
path: root/lib/sqlalchemy/sql
diff options
context:
space:
mode:
Diffstat (limited to 'lib/sqlalchemy/sql')
-rw-r--r--lib/sqlalchemy/sql/coercions.py42
-rw-r--r--lib/sqlalchemy/sql/roles.py24
-rw-r--r--lib/sqlalchemy/sql/selectable.py42
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