]> git.ipfire.org Git - thirdparty/sqlalchemy/sqlalchemy.git/commitdiff
Ensure alias traversal block works when adapt_from_selectables present
authorMike Bayer <mike_mp@zzzcomputing.com>
Wed, 14 Jul 2021 14:29:50 +0000 (10:29 -0400)
committerMike Bayer <mike_mp@zzzcomputing.com>
Wed, 14 Jul 2021 17:28:25 +0000 (13:28 -0400)
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

doc/build/changelog/unreleased_14/6762.rst [new file with mode: 0644]
lib/sqlalchemy/sql/compiler.py
lib/sqlalchemy/sql/util.py
test/orm/inheritance/test_assorted_poly.py
test/sql/test_external_traversal.py

diff --git a/doc/build/changelog/unreleased_14/6762.rst b/doc/build/changelog/unreleased_14/6762.rst
new file mode 100644 (file)
index 0000000..1ef768b
--- /dev/null
@@ -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.
index b9f55b746d85fa481a44928d91fc9534432d3233..29ef8047ef72afd785816cafd88ce03083b7768b 100644 (file)
@@ -1414,7 +1414,6 @@ class SQLCompiler(Compiled):
                     (label, labelname) + label._alt_names + result_map_targets,
                     label.type,
                 )
-
             return (
                 label.element._compiler_dispatch(
                     self,
index 4564699418713848bfa1ee3757d0e45b892b0344..da5e31655fca0a6779ee692e221d9773fa3cb00b 100644 (file)
@@ -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
index 6606e9b0ce8245832d9f89df49c0d573c5e4e454..cbf7b5042f753c6ea3bcbcd6fe64bdd264690876 100644 (file)
@@ -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"""
 
index c7e51c80703603b767298c92fb723f9c05ccc2f4..b0241da5901f6387fbb05d913026b2e56616a215 100644 (file)
@@ -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 "