From: Mike Bayer Date: Wed, 14 Jul 2021 14:29:50 +0000 (-0400) Subject: Ensure alias traversal block works when adapt_from_selectables present X-Git-Tag: rel_1_4_21~1 X-Git-Url: http://git.ipfire.org/cgi-bin/gitweb.cgi?a=commitdiff_plain;h=79a3dafb1425488ba29d309cc32e0e24004be256;p=thirdparty%2Fsqlalchemy%2Fsqlalchemy.git Ensure alias traversal block works when adapt_from_selectables present Fixed regression which appeared in version 1.4.3 due to :ticket:`6060` where rules that limit ORM adaptation of derived selectables interfered with other ORM-adaptation based cases, in this case when applying adaptations for a :func:`_orm.with_polymorphic` against a mapping which uses a :func:`_orm.column_property` which in turn makes use of a scalar select that includes a :func:`_orm.aliased` object of the mapped table. Fixes: #6762 Change-Id: Ice3dc34b97d12b59f044bdc0c5faaefcc4015227 --- diff --git a/doc/build/changelog/unreleased_14/6762.rst b/doc/build/changelog/unreleased_14/6762.rst new file mode 100644 index 0000000000..1ef768b524 --- /dev/null +++ b/doc/build/changelog/unreleased_14/6762.rst @@ -0,0 +1,10 @@ +.. change:: + :tags: bug, orm, regression + :tickets: 6762 + + Fixed regression which appeared in version 1.4.3 due to :ticket:`6060` + where rules that limit ORM adaptation of derived selectables interfered + with other ORM-adaptation based cases, in this case when applying + adaptations for a :func:`_orm.with_polymorphic` against a mapping which + uses a :func:`_orm.column_property` which in turn makes use of a scalar + select that includes a :func:`_orm.aliased` object of the mapped table. diff --git a/lib/sqlalchemy/sql/compiler.py b/lib/sqlalchemy/sql/compiler.py index b9f55b746d..29ef8047ef 100644 --- a/lib/sqlalchemy/sql/compiler.py +++ b/lib/sqlalchemy/sql/compiler.py @@ -1414,7 +1414,6 @@ class SQLCompiler(Compiled): (label, labelname) + label._alt_names + result_map_targets, label.type, ) - return ( label.element._compiler_dispatch( self, diff --git a/lib/sqlalchemy/sql/util.py b/lib/sqlalchemy/sql/util.py index 4564699418..da5e31655f 100644 --- a/lib/sqlalchemy/sql/util.py +++ b/lib/sqlalchemy/sql/util.py @@ -853,14 +853,14 @@ class ClauseAdapter(visitors.ReplacingExternalTraversal): if isinstance(col, FromClause) and not isinstance( col, functions.FunctionElement ): - if self.adapt_from_selectables: - for adp in self.adapt_from_selectables: - if adp.is_derived_from(col): - break - else: - return None if self.selectable.is_derived_from(col): + if self.adapt_from_selectables: + for adp in self.adapt_from_selectables: + if adp.is_derived_from(col): + break + else: + return None return self.selectable elif isinstance(col, Alias) and isinstance( col.element, TableClause diff --git a/test/orm/inheritance/test_assorted_poly.py b/test/orm/inheritance/test_assorted_poly.py index 6606e9b0ce..cbf7b5042f 100644 --- a/test/orm/inheritance/test_assorted_poly.py +++ b/test/orm/inheritance/test_assorted_poly.py @@ -13,6 +13,7 @@ from sqlalchemy import String from sqlalchemy import testing from sqlalchemy import Unicode from sqlalchemy import util +from sqlalchemy.orm import aliased from sqlalchemy.orm import class_mapper from sqlalchemy.orm import column_property from sqlalchemy.orm import contains_eager @@ -25,6 +26,7 @@ from sqlalchemy.orm import Session from sqlalchemy.orm import sessionmaker from sqlalchemy.orm import with_polymorphic from sqlalchemy.orm.interfaces import MANYTOONE +from sqlalchemy.testing import AssertsCompiledSQL from sqlalchemy.testing import AssertsExecutionResults from sqlalchemy.testing import eq_ from sqlalchemy.testing import fixtures @@ -1107,6 +1109,69 @@ class RelationshipTest8(fixtures.MappedTest): ) +class ColPropWAliasJoinedToBaseTest( + AssertsCompiledSQL, fixtures.DeclarativeMappedTest +): + """test #6762""" + + __dialect__ = "default" + run_create_tables = None + + @classmethod + def setup_classes(cls): + Base = cls.DeclarativeBasic + + class Content(Base): + + __tablename__ = "content" + + id = Column(Integer, primary_key=True) + type = Column(String) + container_id = Column(Integer, ForeignKey("folder.id")) + + __mapper_args__ = {"polymorphic_on": type} + + class Folder(Content): + + __tablename__ = "folder" + + id = Column(ForeignKey("content.id"), primary_key=True) + + __mapper_args__ = { + "polymorphic_identity": "f", + "inherit_condition": id == Content.id, + } + + _alias = aliased(Content) + + Content.__mapper__.add_property( + "count_children", + column_property( + select(func.count("*")) + .where(_alias.container_id == Content.id) + .scalar_subquery() + ), + ) + + def test_alias_omitted(self): + Content = self.classes.Content + Folder = self.classes.Folder + + sess = fixture_session() + + entity = with_polymorphic(Content, [Folder], innerjoin=True) + + self.assert_compile( + sess.query(entity), + "SELECT content.id AS content_id, content.type AS content_type, " + "content.container_id AS content_container_id, " + "(SELECT count(:count_2) AS count_1 FROM content AS content_1 " + "WHERE content_1.container_id = content.id) AS anon_1, " + "folder.id AS folder_id FROM content " + "JOIN folder ON folder.id = content.id", + ) + + class SelfRefWPolyJoinedLoadTest(fixtures.DeclarativeMappedTest): """test #6495""" diff --git a/test/sql/test_external_traversal.py b/test/sql/test_external_traversal.py index c7e51c8070..b0241da590 100644 --- a/test/sql/test_external_traversal.py +++ b/test/sql/test_external_traversal.py @@ -2172,7 +2172,16 @@ class ClauseAdapterTest(fixtures.TestBase, AssertsCompiledSQL): {"param_1": 5, "param_2": 10}, ) - def test_aliasedselect_to_aliasedselect_join_nested_table(self): + @testing.combinations((True,), (False,), argnames="use_adapt_from") + def test_aliasedselect_to_aliasedselect_join_nested_table( + self, use_adapt_from + ): + """test the logic in clauseadapter regarding not traversing aliases. + + adapt_from_selectables case added to test #6762, which is a regression + from #6060 + + """ s1 = select(t1).alias("foo") s2 = select(s1).limit(5).offset(10).alias() talias = t1.alias("bar") @@ -2191,8 +2200,12 @@ class ClauseAdapterTest(fixtures.TestBase, AssertsCompiledSQL): j = s1.outerjoin(talias, s1.c.col1 == talias.c.col1) + if use_adapt_from: + vis = sql_util.ClauseAdapter(s2, adapt_from_selectables=[s1]) + else: + vis = sql_util.ClauseAdapter(s2) self.assert_compile( - sql_util.ClauseAdapter(s2).traverse(j).select(), + vis.traverse(j).select(), "SELECT anon_1.col1, anon_1.col2, " "anon_1.col3, bar.col1 AS col1_1, bar.col2 AS col2_1, " "bar.col3 AS col3_1 "