From: Mike Bayer Date: Tue, 10 Jun 2025 14:07:53 +0000 (-0400) Subject: document column_expression applies only to outermost statement X-Git-Url: http://git.ipfire.org/?a=commitdiff_plain;h=0e33848fe5330a60037594370cd7868907348c18;p=thirdparty%2Fsqlalchemy%2Fsqlalchemy.git document column_expression applies only to outermost statement References: https://github.com/sqlalchemy/sqlalchemy/discussions/12660 Change-Id: Id7cf98bd4560804b2f778cde41642f02f7edaf95 --- diff --git a/doc/build/core/custom_types.rst b/doc/build/core/custom_types.rst index 4b27f2f18a..dc8b9e4733 100644 --- a/doc/build/core/custom_types.rst +++ b/doc/build/core/custom_types.rst @@ -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 diff --git a/lib/sqlalchemy/sql/type_api.py b/lib/sqlalchemy/sql/type_api.py index 890214e2e4..abfbcb6167 100644 --- a/lib/sqlalchemy/sql/type_api.py +++ b/lib/sqlalchemy/sql/type_api.py @@ -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