From a16687b3fc4179e8f42d8099aa6d1e6d18fedf70 Mon Sep 17 00:00:00 2001 From: Mike Bayer Date: Fri, 2 Apr 2021 11:06:28 -0400 Subject: [PATCH] Ensure Grouping._with_binary_element_type() maintains class Fixed regression where use of the :meth:`.Operators.in_` method with a :class:`_sql.Select` object against a non-table-bound column would produce an ``AttributeError``, or more generally using a :class:`_sql.ScalarSelect` that has no datatype in a binary expression would produce invalid state. Fixes: #6181 Change-Id: I1ddea433b3603fdab8f489bff571b512a6ffc66b --- doc/build/changelog/unreleased_14/6181.rst | 9 +++++++++ lib/sqlalchemy/sql/elements.py | 2 +- test/sql/test_operators.py | 16 ++++++++++++++++ test/sql/test_roles.py | 14 ++++++++++++++ 4 files changed, 40 insertions(+), 1 deletion(-) create mode 100644 doc/build/changelog/unreleased_14/6181.rst diff --git a/doc/build/changelog/unreleased_14/6181.rst b/doc/build/changelog/unreleased_14/6181.rst new file mode 100644 index 0000000000..5881321b32 --- /dev/null +++ b/doc/build/changelog/unreleased_14/6181.rst @@ -0,0 +1,9 @@ +.. change:: + :tags: bug, regression, sql + :tickets: 6181 + + Fixed regression where use of the :meth:`.Operators.in_` method with a + :class:`_sql.Select` object against a non-table-bound column would produce + an ``AttributeError``, or more generally using a :class:`_sql.ScalarSelect` + that has no datatype in a binary expression would produce invalid state. + diff --git a/lib/sqlalchemy/sql/elements.py b/lib/sqlalchemy/sql/elements.py index 7a27690b89..c483383033 100644 --- a/lib/sqlalchemy/sql/elements.py +++ b/lib/sqlalchemy/sql/elements.py @@ -3764,7 +3764,7 @@ class Grouping(GroupedElement, ColumnElement): self.type = getattr(element, "type", type_api.NULLTYPE) def _with_binary_element_type(self, type_): - return Grouping(self.element._with_binary_element_type(type_)) + return self.__class__(self.element._with_binary_element_type(type_)) @util.memoized_property def _is_implicitly_boolean(self): diff --git a/test/sql/test_operators.py b/test/sql/test_operators.py index f6a13f8ca0..0d7f331e0e 100644 --- a/test/sql/test_operators.py +++ b/test/sql/test_operators.py @@ -1973,6 +1973,22 @@ class InTest(fixtures.TestBase, testing.AssertsCompiledSQL): checkparams={"myid_1": [1, 2, 3]}, ) + def test_scalar_subquery_wo_type(self): + """ test for :ticket:`6181` """ + + m = MetaData() + t = Table("t", m, Column("a", Integer)) + + # the scalar subquery of this will have no type; coercions will + # want to call _with_binary_element_type(); that has to return + # a scalar select + req = select(column("scan")) + + self.assert_compile( + select(t.c.a).where(t.c.a.in_(req)), + "SELECT t.a FROM t WHERE t.a IN (SELECT scan)", + ) + class MathOperatorTest(fixtures.TestBase, testing.AssertsCompiledSQL): __dialect__ = "default" diff --git a/test/sql/test_roles.py b/test/sql/test_roles.py index d5e8f476c4..e47a7e8897 100644 --- a/test/sql/test_roles.py +++ b/test/sql/test_roles.py @@ -26,6 +26,7 @@ from sqlalchemy.sql.coercions import expect from sqlalchemy.sql.elements import _truncated_label from sqlalchemy.sql.elements import Null from sqlalchemy.sql.selectable import FromGrouping +from sqlalchemy.sql.selectable import ScalarSelect from sqlalchemy.sql.selectable import SelectStatementGrouping from sqlalchemy.testing import assert_raises from sqlalchemy.testing import assert_raises_message @@ -145,6 +146,19 @@ class RoleTest(fixtures.TestBase): ) ) + def test_untyped_scalar_subquery(self): + """test for :ticket:`6181` """ + + c = column("q") + subq = select(c).scalar_subquery() + + assert isinstance( + subq._with_binary_element_type(Integer()), ScalarSelect + ) + + expr = column("a", Integer) == subq + assert isinstance(expr.right, ScalarSelect) + def test_no_clauseelement_in_bind(self): with testing.expect_raises_message( exc.ArgumentError, -- 2.47.2