From: Mike Bayer Date: Mon, 16 Jun 2025 23:53:30 +0000 (-0400) Subject: rework wraps_column_expression logic to be purely compile time checking X-Git-Url: http://git.ipfire.org/cgi-bin/gitweb.cgi?a=commitdiff_plain;h=c96805a43aa76bc3ec5134832a5050d527e432fe;p=thirdparty%2Fsqlalchemy%2Fsqlalchemy.git rework wraps_column_expression logic to be purely compile time checking Fixed issue where :func:`.select` of a free-standing, unnamed scalar expression that has a unary operator applied, such as negation, would not apply result processors to the selected column even though the correct type remains in place for the unary expression. This change opened up a typing rabbithole where we were led to also improve and harden the typing for the Exists element, in particular in that the Exists now always refers to a ScalarSelect object, and no longer a SelectStatementGrouping within the _regroup() cases; there did not seem to be any reason for this inconsistency. Fixes: #12681 Change-Id: If9131807941030c627ab31ede4ccbd86e44e707f --- diff --git a/doc/build/changelog/unreleased_20/12681.rst b/doc/build/changelog/unreleased_20/12681.rst new file mode 100644 index 0000000000..72e7e1e58e --- /dev/null +++ b/doc/build/changelog/unreleased_20/12681.rst @@ -0,0 +1,9 @@ +.. change:: + :tags: bug, sql + :tickets: 12681 + + Fixed issue where :func:`.select` of a free-standing scalar expression that + has a unary operator applied, such as negation, would not apply result + processors to the selected column even though the correct type remains in + place for the unary expression. + diff --git a/lib/sqlalchemy/sql/compiler.py b/lib/sqlalchemy/sql/compiler.py index 5e874b3799..5b992269a5 100644 --- a/lib/sqlalchemy/sql/compiler.py +++ b/lib/sqlalchemy/sql/compiler.py @@ -4562,7 +4562,52 @@ class SQLCompiler(Compiled): elif isinstance(column, elements.TextClause): render_with_label = False elif isinstance(column, elements.UnaryExpression): - render_with_label = column.wraps_column_expression or asfrom + # unary expression. notes added as of #12681 + # + # By convention, the visit_unary() method + # itself does not add an entry to the result map, and relies + # upon either the inner expression creating a result map + # entry, or if not, by creating a label here that produces + # the result map entry. Where that happens is based on whether + # or not the element immediately inside the unary is a + # NamedColumn subclass or not. + # + # Now, this also impacts how the SELECT is written; if + # we decide to generate a label here, we get the usual + # "~(x+y) AS anon_1" thing in the columns clause. If we + # don't, we don't get an AS at all, we get like + # "~table.column". + # + # But here is the important thing as of modernish (like 1.4) + # versions of SQLAlchemy - **whether or not the AS