]> git.ipfire.org Git - thirdparty/sqlalchemy/sqlalchemy.git/commitdiff
ensure with_options not switched to a list
authorMike Bayer <mike_mp@zzzcomputing.com>
Mon, 10 Jan 2022 19:59:59 +0000 (14:59 -0500)
committerMike Bayer <mike_mp@zzzcomputing.com>
Tue, 11 Jan 2022 00:48:32 +0000 (19:48 -0500)
Fixed regression which appeared in 1.4.23 which could cause loader options
to be mis-handled in some cases, in particular when using joined table
inheritance in combination with the ``polymorphic_load="selectin"`` option
as well as relationship lazy loading, leading to a ``TypeError``.

Fixes: #7557
Change-Id: Id38619692f94308fd5f567a02337efef7a3a7544

doc/build/changelog/unreleased_14/7557.rst [new file with mode: 0644]
lib/sqlalchemy/orm/strategies.py
test/orm/inheritance/test_poly_loading.py

diff --git a/doc/build/changelog/unreleased_14/7557.rst b/doc/build/changelog/unreleased_14/7557.rst
new file mode 100644 (file)
index 0000000..b7ccc87
--- /dev/null
@@ -0,0 +1,9 @@
+.. change::
+    :tags: bug, orm, regression
+    :tickets: 7557
+
+    Fixed regression which appeared in 1.4.23 which could cause loader options
+    to be mis-handled in some cases, in particular when using joined table
+    inheritance in combination with the ``polymorphic_load="selectin"`` option
+    as well as relationship lazy loading, leading to a ``TypeError``.
+
index c29fc7749d6ea4103ef106c44c72035c9504fde8..a8a56395284fb2a816f30c645212386e2b41c38f 100644 (file)
@@ -964,7 +964,7 @@ class LazyLoader(AbstractRelationshipLoader, util.MemoizedSlots):
         if state.load_options or (loadopt and loadopt._extra_criteria):
             effective_path = state.load_path[self.parent_property]
 
-            opts = list(state.load_options)
+            opts = tuple(state.load_options)
 
             if loadopt and loadopt._extra_criteria:
                 use_get = False
index 4fe13887e1c905dc3b57e57301f8f1de69c10687..517431e7052608d03a49d36116a697a4ee9ffe3f 100644 (file)
@@ -1,18 +1,22 @@
 from sqlalchemy import exc
 from sqlalchemy import ForeignKey
+from sqlalchemy import inspect
 from sqlalchemy import Integer
 from sqlalchemy import select
 from sqlalchemy import String
 from sqlalchemy import testing
 from sqlalchemy.orm import backref
 from sqlalchemy.orm import defaultload
+from sqlalchemy.orm import immediateload
 from sqlalchemy.orm import joinedload
 from sqlalchemy.orm import lazyload
 from sqlalchemy.orm import relationship
 from sqlalchemy.orm import selectin_polymorphic
 from sqlalchemy.orm import selectinload
 from sqlalchemy.orm import Session
+from sqlalchemy.orm import subqueryload
 from sqlalchemy.orm import with_polymorphic
+from sqlalchemy.orm.interfaces import CompileStateOption
 from sqlalchemy.sql.selectable import LABEL_STYLE_TABLENAME_PLUS_COL
 from sqlalchemy.testing import assertsql
 from sqlalchemy.testing import eq_
@@ -589,17 +593,11 @@ class LoaderOptionsTest(
         session.add_all([parent, subclass1, other])
         session.commit()
 
-    def test_options_dont_pollute_baked(self):
-        self._test_options_dont_pollute(True)
-
-    def test_options_dont_pollute_unbaked(self):
-        self._test_options_dont_pollute(False)
-
-    def _test_options_dont_pollute(self, enable_baked):
+    def test_options_dont_pollute(self):
         Parent, ChildSubclass1, Other = self.classes(
             "Parent", "ChildSubclass1", "Other"
         )
-        session = fixture_session(enable_baked_queries=enable_baked)
+        session = fixture_session()
 
         def no_opt():
             q = session.query(Parent).options(
@@ -860,3 +858,74 @@ class IgnoreOptionsOnSubclassAttrLoad(fixtures.DeclarativeMappedTest):
             )
 
         asserter_.assert_(*expected)
+
+
+class LazyLoaderTransfersOptsTest(fixtures.DeclarativeMappedTest):
+    """test #7557"""
+
+    @classmethod
+    def setup_classes(cls):
+        Base = cls.DeclarativeBasic
+
+        class Address(Base):
+            __tablename__ = "address"
+
+            id = Column(Integer, primary_key=True)
+            user_id = Column(Integer, ForeignKey("user.id"))
+            address_type = Column(String(50))
+            __mapper_args__ = {
+                "polymorphic_identity": "base_address",
+                "polymorphic_on": address_type,
+            }
+
+        class EmailAddress(Address):
+            __tablename__ = "email_address"
+            email = Column(String(50))
+            address_id = Column(
+                Integer,
+                ForeignKey(Address.id),
+                primary_key=True,
+            )
+
+            __mapper_args__ = {
+                "polymorphic_identity": "email",
+                "polymorphic_load": "selectin",
+            }
+
+        class User(Base):
+            __tablename__ = "user"
+
+            id = Column(Integer, primary_key=True)
+            name = Column(String(50))
+            address = relationship(Address, uselist=False)
+
+    @classmethod
+    def insert_data(cls, connection):
+        User, EmailAddress = cls.classes("User", "EmailAddress")
+        with Session(connection) as sess:
+            sess.add_all(
+                [User(name="u1", address=EmailAddress(email="foo", user_id=1))]
+            )
+
+            sess.commit()
+
+    @testing.combinations(
+        None, selectinload, joinedload, lazyload, subqueryload, immediateload
+    )
+    def test_opt_propagates(self, strat):
+        User, EmailAddress = self.classes("User", "EmailAddress")
+        sess = fixture_session()
+
+        class AnyOpt(CompileStateOption):
+            _cache_key_traversal = ()
+            propagate_to_loaders = True
+
+        any_opt = AnyOpt()
+        if strat is None:
+            opts = (any_opt,)
+        else:
+            opts = (strat(User.address), any_opt)
+
+        u = sess.execute(select(User).options(*opts)).scalars().one()
+        address = u.address
+        eq_(inspect(address).load_options, set(opts))