diff --git a/doc/build/changelog/unreleased_14/7514.rst b/doc/build/changelog/unreleased_14/7514.rst new file mode 100644 index 0000000000..bf6fd471ee --- /dev/null +++ b/doc/build/changelog/unreleased_14/7514.rst @@ -0,0 +1,9 @@ +.. change:: + :tags: bug, orm + :tickets: 7514 + + Fixed issue where :meth:`_sql.Select.correlate_except` method, when passed + either the ``None`` value or no arguments, would not correlate any elements + when used in an ORM context (that is, passing ORM entities as FROM + clauses), rather than causing all FROM elements to be considered as + "correlated" in the same way which occurs when using Core-only constructs. diff --git a/lib/sqlalchemy/orm/context.py b/lib/sqlalchemy/orm/context.py index 7c2d729548..0c1d16d0e2 100644 --- a/lib/sqlalchemy/orm/context.py +++ b/lib/sqlalchemy/orm/context.py @@ -774,7 +774,7 @@ class ORMSelectCompileState(ORMCompileState, SelectState): for s in query._correlate ) ) - elif query._correlate_except: + elif query._correlate_except is not None: self.correlate_except = tuple( util.flatten_iterator( sql_util.surface_selectables(s) if s is not None else None @@ -1192,7 +1192,7 @@ class ORMSelectCompileState(ORMCompileState, SelectState): if correlate: statement.correlate.non_generative(statement, *correlate) - if correlate_except: + if correlate_except is not None: statement.correlate_except.non_generative( statement, *correlate_except ) diff --git a/test/orm/test_core_compilation.py b/test/orm/test_core_compilation.py index 000a96a422..28f42797e4 100644 --- a/test/orm/test_core_compilation.py +++ b/test/orm/test_core_compilation.py @@ -1,8 +1,10 @@ from sqlalchemy import bindparam +from sqlalchemy import Column from sqlalchemy import exc from sqlalchemy import func from sqlalchemy import insert from sqlalchemy import inspect +from sqlalchemy import Integer from sqlalchemy import literal_column from sqlalchemy import null from sqlalchemy import or_ @@ -31,12 +33,15 @@ from sqlalchemy.sql.selectable import LABEL_STYLE_TABLENAME_PLUS_COL from sqlalchemy.testing import assert_raises_message from sqlalchemy.testing import AssertsCompiledSQL from sqlalchemy.testing import eq_ +from sqlalchemy.testing import fixtures from sqlalchemy.testing import is_ from sqlalchemy.testing.assertions import expect_raises_message from sqlalchemy.testing.fixtures import fixture_session from sqlalchemy.testing.util import resolve_lambda +from sqlalchemy.util.langhelpers import hybridproperty from .inheritance import _poly_fixtures from .test_query import QueryTest +from ..sql.test_compiler import CorrelateTest as _CoreCorrelateTest # TODO: # composites / unions, etc. @@ -2320,3 +2325,29 @@ class RawSelectTest(QueryTest, AssertsCompiledSQL): ) self.assert_compile(stmt1, expected) self.assert_compile(stmt2, expected) + + +class CorrelateTest(fixtures.DeclarativeMappedTest, _CoreCorrelateTest): + @classmethod + def setup_classes(cls): + Base = cls.DeclarativeBasic + + class T1(Base): + __tablename__ = "t1" + a = Column(Integer, primary_key=True) + + @hybridproperty + def c(self): + return self + + class T2(Base): + __tablename__ = "t2" + a = Column(Integer, primary_key=True) + + @hybridproperty + def c(self): + return self + + def _fixture(self): + t1, t2 = self.classes("T1", "T2") + return t1, t2, select(t1).where(t1.c.a == t2.c.a) diff --git a/test/sql/test_compiler.py b/test/sql/test_compiler.py index c0fa574843..5ea1110c6f 100644 --- a/test/sql/test_compiler.py +++ b/test/sql/test_compiler.py @@ -5920,6 +5920,14 @@ class CorrelateTest(fixtures.TestBase, AssertsCompiledSQL): ) ) + def test_correlate_except_empty(self): + t1, t2, s1 = self._fixture() + self._assert_where_all_correlated( + select(t1, t2).where( + t2.c.a == s1.correlate_except().scalar_subquery() + ) + ) + def test_correlate_except_having(self): t1, t2, s1 = self._fixture() self._assert_having_correlated(