]> git.ipfire.org Git - thirdparty/sqlalchemy/sqlalchemy.git/commitdiff
propagate _scalar_type() for SelectStatementGrouping
authorMike Bayer <mike_mp@zzzcomputing.com>
Tue, 18 Nov 2025 20:10:05 +0000 (15:10 -0500)
committerMike Bayer <mike_mp@zzzcomputing.com>
Tue, 18 Nov 2025 20:11:05 +0000 (15:11 -0500)
Fixed issue where using the :meth:`.ColumnOperators.in_` operator with a
nested :class:`.CompoundSelect` statement (e.g. an ``INTERSECT`` of
``UNION`` queries) would raise a :class:`NotImplementedError` when the
nested compound select was the first argument to the outer compound select.
The ``_scalar_type()`` internal method now properly handles nested compound
selects.

Fixes: #12987
Change-Id: I6aa1b38863588d371bbac74b3531b99ccd5fcaec

doc/build/changelog/unreleased_20/12987.rst [new file with mode: 0644]
lib/sqlalchemy/sql/selectable.py
test/sql/test_operators.py

diff --git a/doc/build/changelog/unreleased_20/12987.rst b/doc/build/changelog/unreleased_20/12987.rst
new file mode 100644 (file)
index 0000000..ef06101
--- /dev/null
@@ -0,0 +1,10 @@
+.. change::
+    :tags: bug, sql
+    :tickets: 12987
+
+    Fixed issue where using the :meth:`.ColumnOperators.in_` operator with a
+    nested :class:`.CompoundSelect` statement (e.g. an ``INTERSECT`` of
+    ``UNION`` queries) would raise a :class:`NotImplementedError` when the
+    nested compound select was the first argument to the outer compound select.
+    The ``_scalar_type()`` internal method now properly handles nested compound
+    selects.
index bf60995245599fd70286fa12986c40e79334d449..b8ca7a5659194a3b3b6d596897be3c5992e3fad7 100644 (file)
@@ -4007,6 +4007,9 @@ class SelectStatementGrouping(GroupedElement, SelectBase, Generic[_SB]):
     def _from_objects(self) -> List[FromClause]:
         return self.element._from_objects
 
+    def _scalar_type(self) -> TypeEngine[Any]:
+        return self.element._scalar_type()
+
     def add_cte(self, *ctes: CTE, nest_here: bool = False) -> Self:
         # SelectStatementGrouping not generative: has no attribute '_generate'
         raise NotImplementedError
index 007563f367f01da08786b79ed2601631e333a550..908c6018e4ef277522f8066d295ce6422e857a0a 100644 (file)
@@ -18,6 +18,7 @@ from sqlalchemy import Enum
 from sqlalchemy import exc
 from sqlalchemy import Float
 from sqlalchemy import Integer
+from sqlalchemy import intersect
 from sqlalchemy import join
 from sqlalchemy import LargeBinary
 from sqlalchemy import literal_column
@@ -2546,6 +2547,51 @@ class InTest(fixtures.TestBase, testing.AssertsCompiledSQL):
                 literal_binds=True,
             )
 
+    def test_in_scalar_grouping(self):
+        """test for :ticket:`12987`
+
+        Test that using in_() with a nested CompoundSelect works correctly.
+        This occurs when a CompoundSelect is the first argument to another
+        CompoundSelect.
+
+        """
+
+        t = self.table1
+
+        # Create nested compound selects
+        inner_compound_stmt = union(
+            select(t.c.myid).where(t.c.myid == 5),
+            select(t.c.myid).where(t.c.myid == 6),
+        )
+        simple_stmt = select(t.c.myid).where(t.c.myid == 7)
+
+        # When simple statement is first, should work
+        outer_compound_stmt = intersect(simple_stmt, inner_compound_stmt)
+        self.assert_compile(
+            select(t).where(t.c.myid.in_(outer_compound_stmt)),
+            "SELECT mytable.myid FROM mytable "
+            "WHERE mytable.myid IN ("
+            "SELECT mytable.myid FROM mytable WHERE mytable.myid = :myid_1 "
+            "INTERSECT (SELECT mytable.myid FROM mytable "
+            "WHERE mytable.myid = :myid_2 "
+            "UNION SELECT mytable.myid FROM mytable "
+            "WHERE mytable.myid = :myid_3))",
+        )
+
+        # When compound statement is first, previously raised
+        # NotImplementedError
+        outer_compound_stmt = intersect(inner_compound_stmt, simple_stmt)
+        self.assert_compile(
+            select(t).where(t.c.myid.in_(outer_compound_stmt)),
+            "SELECT mytable.myid FROM mytable "
+            "WHERE mytable.myid IN ("
+            "(SELECT mytable.myid FROM mytable WHERE mytable.myid = :myid_1 "
+            "UNION SELECT mytable.myid FROM mytable "
+            "WHERE mytable.myid = :myid_2) "
+            "INTERSECT SELECT mytable.myid FROM mytable "
+            "WHERE mytable.myid = :myid_3)",
+        )
+
     @testing.combinations(True, False, argnames="is_in")
     @testing.combinations(True, False, argnames="negate")
     def test_in_empty_tuple(self, is_in, negate):