summaryrefslogtreecommitdiff
path: root/lib/sqlalchemy/sql/elements.py
diff options
context:
space:
mode:
authorMike Bayer <mike_mp@zzzcomputing.com>2019-08-27 17:08:11 -0400
committerMike Bayer <mike_mp@zzzcomputing.com>2019-08-28 17:11:15 -0400
commit3bb402ff8ed980ae393def7462b1da49c0e0a8a7 (patch)
treeac4a0a3cfef40d785fecd79015f75caf51054070 /lib/sqlalchemy/sql/elements.py
parent5bf264ca08b8bb38d50baeb48fe1729da4164711 (diff)
downloadsqlalchemy-3bb402ff8ed980ae393def7462b1da49c0e0a8a7.tar.gz
Label simple column transformations as the column name
Additional logic has been added such that certain SQL expressions which typically wrap a single database column will use the name of that column as their "anonymous label" name within a SELECT statement, potentially making key-based lookups in result tuples more intutive. The primary example of this is that of a CAST expression, e.g. ``CAST(table.colname AS INTEGER)``, which will export its default name as "colname", rather than the usual "anon_1" label, that is, ``CAST(table.colname AS INTEGER) AS colname``. If the inner expression doesn't have a name, then the previous "anonymous label" logic is used. When using SELECT statements that make use of :meth:`.Select.apply_labels`, such as those emitted by the ORM, the labeling logic will produce ``<tablename>_<inner column name>`` in the same was as if the column were named alone. The logic applies right now to the :func:`.cast` and :func:`.type_coerce` constructs as well as some single-element boolean expressions. Fixes: #4449 Change-Id: Ie3b73470e3bea53f2386cd86514cdc556491564e
Diffstat (limited to 'lib/sqlalchemy/sql/elements.py')
-rw-r--r--lib/sqlalchemy/sql/elements.py54
1 files changed, 51 insertions, 3 deletions
diff --git a/lib/sqlalchemy/sql/elements.py b/lib/sqlalchemy/sql/elements.py
index e2df1adc2..669519d1a 100644
--- a/lib/sqlalchemy/sql/elements.py
+++ b/lib/sqlalchemy/sql/elements.py
@@ -914,6 +914,42 @@ class ColumnElement(
return self._anon_label(getattr(self, "_label", None))
+class WrapsColumnExpression(object):
+ """Mixin that defines a :class:`.ColumnElement` as a wrapper with special
+ labeling behavior for an expression that already has a name.
+
+ .. versionadded:: 1.4
+
+ .. seealso::
+
+ :ref:`change_4449`
+
+
+ """
+
+ @property
+ def wrapped_column_expression(self):
+ raise NotImplementedError()
+
+ @property
+ def _label(self):
+ wce = self.wrapped_column_expression
+ if hasattr(wce, "_label"):
+ return wce._label
+ else:
+ return None
+
+ @property
+ def anon_label(self):
+ wce = self.wrapped_column_expression
+ if hasattr(wce, "name"):
+ return wce.name
+ elif hasattr(wce, "anon_label"):
+ return wce.anon_label
+ else:
+ return super(WrapsColumnExpression, self).anon_label
+
+
class BindParameter(roles.InElementRole, ColumnElement):
r"""Represent a "bound expression".
@@ -2477,7 +2513,7 @@ def literal_column(text, type_=None):
return ColumnClause(text, type_=type_, is_literal=True)
-class Cast(ColumnElement):
+class Cast(WrapsColumnExpression, ColumnElement):
"""Represent a ``CAST`` expression.
:class:`.Cast` is produced using the :func:`.cast` factory function,
@@ -2582,8 +2618,12 @@ class Cast(ColumnElement):
def _from_objects(self):
return self.clause._from_objects
+ @property
+ def wrapped_column_expression(self):
+ return self.clause
+
-class TypeCoerce(ColumnElement):
+class TypeCoerce(WrapsColumnExpression, ColumnElement):
"""Represent a Python-side type-coercion wrapper.
:class:`.TypeCoerce` supplies the :func:`.expression.type_coerce`
@@ -2694,6 +2734,10 @@ class TypeCoerce(ColumnElement):
else:
return self.clause
+ @property
+ def wrapped_column_expression(self):
+ return self.clause
+
class Extract(ColumnElement):
"""Represent a SQL EXTRACT clause, ``extract(field FROM expr)``."""
@@ -3162,7 +3206,7 @@ class CollectionAggregate(UnaryExpression):
)
-class AsBoolean(UnaryExpression):
+class AsBoolean(WrapsColumnExpression, UnaryExpression):
def __init__(self, element, operator, negate):
self.element = element
self.type = type_api.BOOLEANTYPE
@@ -3172,6 +3216,10 @@ class AsBoolean(UnaryExpression):
self.wraps_column_expression = True
self._is_implicitly_boolean = element._is_implicitly_boolean
+ @property
+ def wrapped_column_expression(self):
+ return self.element
+
def self_group(self, against=None):
# type: (Optional[Any]) -> ClauseElement
return self