diff options
-rw-r--r-- | doc/build/changelog/unreleased_14/8084.rst | 10 | ||||
-rw-r--r-- | lib/sqlalchemy/sql/elements.py | 8 | ||||
-rw-r--r-- | test/sql/test_compiler.py | 3 | ||||
-rw-r--r-- | test/sql/test_labels.py | 28 |
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 |