]> git.ipfire.org Git - thirdparty/sqlalchemy/sqlalchemy.git/commitdiff
document column_expression applies only to outermost statement
authorMike Bayer <mike_mp@zzzcomputing.com>
Tue, 10 Jun 2025 14:07:53 +0000 (10:07 -0400)
committerMike Bayer <mike_mp@zzzcomputing.com>
Tue, 10 Jun 2025 14:12:38 +0000 (10:12 -0400)
References: https://github.com/sqlalchemy/sqlalchemy/discussions/12660
Change-Id: Id7cf98bd4560804b2f778cde41642f02f7edaf95

doc/build/core/custom_types.rst
lib/sqlalchemy/sql/type_api.py

index 4b27f2f18a277c1aa3f2e8c46a6780ea8fd75f4f..dc8b9e473328d9932b728de6ca711bf1586adcd0 100644 (file)
@@ -176,7 +176,7 @@ Backend-agnostic GUID Type
     just as an example of a type decorator that receives and returns
     python objects.
 
-Receives and returns Python uuid() objects.  
+Receives and returns Python uuid() objects.
 Uses the PG UUID type when using PostgreSQL, UNIQUEIDENTIFIER when using MSSQL,
 CHAR(32) on other backends, storing them in stringified format.
 The ``GUIDHyphens`` version stores the value with hyphens instead of just the hex
@@ -405,16 +405,32 @@ to coerce incoming and outgoing data between an application and persistence form
 Examples include using database-defined encryption/decryption functions, as well
 as stored procedures that handle geographic data.
 
-Any :class:`.TypeEngine`, :class:`.UserDefinedType` or :class:`.TypeDecorator` subclass
-can include implementations of
-:meth:`.TypeEngine.bind_expression` and/or :meth:`.TypeEngine.column_expression`, which
-when defined to return a non-``None`` value should return a :class:`_expression.ColumnElement`
-expression to be injected into the SQL statement, either surrounding
-bound parameters or a column expression.  For example, to build a ``Geometry``
-type which will apply the PostGIS function ``ST_GeomFromText`` to all outgoing
-values and the function ``ST_AsText`` to all incoming data, we can create
-our own subclass of :class:`.UserDefinedType` which provides these methods
-in conjunction with :data:`~.sqlalchemy.sql.expression.func`::
+Any :class:`.TypeEngine`, :class:`.UserDefinedType` or :class:`.TypeDecorator`
+subclass can include implementations of :meth:`.TypeEngine.bind_expression`
+and/or :meth:`.TypeEngine.column_expression`, which when defined to return a
+non-``None`` value should return a :class:`_expression.ColumnElement`
+expression to be injected into the SQL statement, either surrounding bound
+parameters or a column expression.
+
+.. tip:: As SQL-level result processing features are intended to assist with
+   coercing data from a SELECT statement into result rows in Python, the
+   :meth:`.TypeEngine.column_expression` conversion method is applied only to
+   the **outermost** columns clause in a SELECT; it does **not** apply to
+   columns rendered inside of subqueries, as these column expressions are not
+   directly delivered to a result.  The expression could not be applied to
+   both, as this would lead to double-conversion of columns, and the
+   "outermost" level rather than the "innermost" level is used so that
+   conversion routines don't interfere with the internal expressions used by
+   the statement, and so that only data that's outgoing to a result row is
+   actually subject to conversion, which is consistent with the result
+   row processing functionality provided by
+   :meth:`.TypeDecorator.process_result_value`.
+
+For example, to build a ``Geometry`` type which will apply the PostGIS function
+``ST_GeomFromText`` to all outgoing values and the function ``ST_AsText`` to
+all incoming data, we can create our own subclass of :class:`.UserDefinedType`
+which provides these methods in conjunction with
+:data:`~.sqlalchemy.sql.expression.func`::
 
     from sqlalchemy import func
     from sqlalchemy.types import UserDefinedType
index 890214e2e4d84be96974dfd628cc5e111b210d04..abfbcb61673da0429ec8b3439e9e55221155f578 100644 (file)
@@ -387,7 +387,7 @@ class TypeEngine(Visitable, Generic[_T]):
         as the sole positional argument and will return a string representation
         to be rendered in a SQL statement.
 
-        .. note::
+        .. tip::
 
             This method is only called relative to a **dialect specific type
             object**, which is often **private to a dialect in use** and is not
@@ -421,7 +421,7 @@ class TypeEngine(Visitable, Generic[_T]):
 
         If processing is not necessary, the method should return ``None``.
 
-        .. note::
+        .. tip::
 
             This method is only called relative to a **dialect specific type
             object**, which is often **private to a dialect in use** and is not
@@ -457,7 +457,7 @@ class TypeEngine(Visitable, Generic[_T]):
 
         If processing is not necessary, the method should return ``None``.
 
-        .. note::
+        .. tip::
 
             This method is only called relative to a **dialect specific type
             object**, which is often **private to a dialect in use** and is not
@@ -496,11 +496,19 @@ class TypeEngine(Visitable, Generic[_T]):
         It is the SQL analogue of the :meth:`.TypeEngine.result_processor`
         method.
 
+        .. note:: The :func:`.TypeEngine.column_expression` method is applied
+           only to the **outermost columns clause** of a SELECT statement, that
+           is, the columns that are to be delivered directly into the returned
+           result rows.  It does **not** apply to the columns clause inside
+           of subqueries.  This necessarily avoids double conversions against
+           the column and only runs the conversion when ready to be returned
+           to the client.
+
         This method is called during the **SQL compilation** phase of a
         statement, when rendering a SQL string. It is **not** called
         against specific values.
 
-        .. note::
+        .. tip::
 
             This method is only called relative to a **dialect specific type
             object**, which is often **private to a dialect in use** and is not