summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--doc/build/changelog/unreleased_14/8084.rst10
-rw-r--r--lib/sqlalchemy/sql/elements.py8
-rw-r--r--test/sql/test_compiler.py3
-rw-r--r--test/sql/test_labels.py28
4 files changed, 48 insertions, 1 deletions
diff --git a/doc/build/changelog/unreleased_14/8084.rst b/doc/build/changelog/unreleased_14/8084.rst
new file mode 100644
index 000000000..43095e8c9
--- /dev/null
+++ b/doc/build/changelog/unreleased_14/8084.rst
@@ -0,0 +1,10 @@
+.. change::
+ :tags: bug, sql
+ :tickets: 8084
+
+ Enhanced the mechanism of :class:`.Cast` and other "wrapping"
+ column constructs to more fully preserve a wrapped :class:`.Label`
+ construct, including that the label name will be preserved in the
+ ``.c`` collection of a :class:`.Subquery`. The label was already
+ able to render in the SQL correctly on the outside of the construct
+ which it was wrapped inside.
diff --git a/lib/sqlalchemy/sql/elements.py b/lib/sqlalchemy/sql/elements.py
index ce08a0a10..6032253c2 100644
--- a/lib/sqlalchemy/sql/elements.py
+++ b/lib/sqlalchemy/sql/elements.py
@@ -1781,6 +1781,14 @@ class WrapsColumnExpression(ColumnElement[_T]):
else:
return self._dedupe_anon_tq_label_idx(idx)
+ @property
+ def _proxy_key(self):
+ wce = self.wrapped_column_expression
+
+ if not wce._is_text_clause:
+ return wce._proxy_key
+ return super()._proxy_key
+
SelfBindParameter = TypeVar("SelfBindParameter", bound="BindParameter[Any]")
diff --git a/test/sql/test_compiler.py b/test/sql/test_compiler.py
index 6ad2aa2c1..94c38548f 100644
--- a/test/sql/test_compiler.py
+++ b/test/sql/test_compiler.py
@@ -3298,7 +3298,7 @@ class SelectTest(fixtures.TestBase, AssertsCompiledSQL):
(exprs[1], "hoho", "hoho(mytable.myid)", "hoho_1"),
(
exprs[2],
- "_no_label",
+ "name",
"CAST(mytable.name AS NUMERIC)",
"name", # due to [ticket:4449]
),
@@ -3322,6 +3322,7 @@ class SelectTest(fixtures.TestBase, AssertsCompiledSQL):
t = table1
s1 = select(col).select_from(t)
+ eq_(col._proxy_key, key if key != "_no_label" else None)
eq_(list(s1.subquery().c.keys()), [key])
if lbl:
diff --git a/test/sql/test_labels.py b/test/sql/test_labels.py
index 8c8e9dbed..d385b9e8d 100644
--- a/test/sql/test_labels.py
+++ b/test/sql/test_labels.py
@@ -26,6 +26,7 @@ from sqlalchemy.testing import AssertsCompiledSQL
from sqlalchemy.testing import engines
from sqlalchemy.testing import eq_
from sqlalchemy.testing import fixtures
+from sqlalchemy.testing import is_
from sqlalchemy.testing import mock
from sqlalchemy.testing.schema import Column
from sqlalchemy.testing.schema import Table
@@ -938,6 +939,33 @@ class ColExprLabelTest(fixtures.TestBase, AssertsCompiledSQL):
"some_table.name FROM some_table",
)
+ @testing.combinations("inside", "outside")
+ def test_wraps_col_expr_label_propagate(self, cast_location):
+ """test #8084"""
+
+ table1 = self.table1
+
+ if cast_location == "inside":
+ expr = cast(table1.c.name, Integer).label("foo")
+ elif cast_location == "outside":
+ expr = cast(table1.c.name.label("foo"), Integer)
+ else:
+ assert False
+
+ self.assert_compile(
+ select(expr),
+ "SELECT CAST(some_table.name AS INTEGER) AS foo FROM some_table",
+ )
+ is_(select(expr).selected_columns.foo, expr)
+
+ subq = select(expr).subquery()
+ self.assert_compile(
+ select(subq).where(subq.c.foo == 10),
+ "SELECT anon_1.foo FROM (SELECT CAST(some_table.name AS INTEGER) "
+ "AS foo FROM some_table) AS anon_1 WHERE anon_1.foo = :foo_1",
+ checkparams={"foo_1": 10},
+ )
+
def test_type_coerce_auto_label_label_style_disambiguate(self):
table1 = self.table1