]> git.ipfire.org Git - thirdparty/sqlalchemy/sqlalchemy.git/commitdiff
disable raiseerr for refresh state loader options
authorMike Bayer <mike_mp@zzzcomputing.com>
Thu, 18 Nov 2021 17:46:25 +0000 (12:46 -0500)
committerMike Bayer <mike_mp@zzzcomputing.com>
Thu, 18 Nov 2021 17:46:25 +0000 (12:46 -0500)
Fixed ORM regression where the new behavior of "eager loaders run on
unexpire" added in :ticket:`1763` would lead to loader option errors being
raised inappropriately for the case where a single :class:`_orm.Query` or
:class:`_sql.Select` were used to load multiple kinds of entities, along
with loader options that apply to just one of those kinds of entity like a
:func:`_orm.joinedload`, and later the objects would be refreshed from
expiration, where the loader options would attempt to be applied to the
mismatched object type and then raise an exception. The check for this
mismatch now bypasses raising an error for this case.

Fixes: #7318
Change-Id: I111e0f3e0fb0447355574cbdcde002f734833490

doc/build/changelog/unreleased_14/7318.rst [new file with mode: 0644]
lib/sqlalchemy/orm/strategy_options.py
test/orm/test_expire.py

diff --git a/doc/build/changelog/unreleased_14/7318.rst b/doc/build/changelog/unreleased_14/7318.rst
new file mode 100644 (file)
index 0000000..d60e303
--- /dev/null
@@ -0,0 +1,13 @@
+.. change::
+    :tags: bug, orm, regression
+    :tickets: 7318
+
+    Fixed ORM regression where the new behavior of "eager loaders run on
+    unexpire" added in :ticket:`1763` would lead to loader option errors being
+    raised inappropriately for the case where a single :class:`_orm.Query` or
+    :class:`_sql.Select` were used to load multiple kinds of entities, along
+    with loader options that apply to just one of those kinds of entity like a
+    :func:`_orm.joinedload`, and later the objects would be refreshed from
+    expiration, where the loader options would attempt to be applied to the
+    mismatched object type and then raise an exception. The check for this
+    mismatch now bypasses raising an error for this case.
index 675c7218bd64cdc21380cc5876b78a63fa30b9af..23a8c4533235fb035855330c64313e5a4f09869c 100644 (file)
@@ -260,7 +260,8 @@ class Load(Generative, LoaderOption):
         self._process(
             compile_state,
             compile_state._lead_mapper_entities,
-            not bool(compile_state.current_path),
+            not bool(compile_state.current_path)
+            and not compile_state.compile_options._for_refresh_state,
         )
 
     def _process(self, compile_state, mapper_entities, raiseerr):
index ae7ac010b537c81cd629368e68934000bdc4623c..a5abcb355435ba8c511564755274aaf2ca3b90ae 100644 (file)
@@ -25,6 +25,7 @@ from sqlalchemy.testing import assert_raises
 from sqlalchemy.testing import assert_raises_message
 from sqlalchemy.testing import eq_
 from sqlalchemy.testing import fixtures
+from sqlalchemy.testing.assertions import expect_raises_message
 from sqlalchemy.testing.assertsql import CountStatements
 from sqlalchemy.testing.fixtures import fixture_session
 from sqlalchemy.testing.schema import Column
@@ -850,6 +851,58 @@ class ExpireTest(_fixtures.FixtureTest):
         assert "name" in u.__dict__
         assert len(u.addresses) == 2
 
+    @testing.combinations(
+        (True, False),
+        (False, False),
+        (False, True),
+    )
+    def test_skip_options_that_dont_match(self, test_control_case, do_expire):
+        """test #7318"""
+
+        User, Address, Order = self.classes("User", "Address", "Order")
+        users, addresses, orders = self.tables("users", "addresses", "orders")
+
+        self.mapper_registry.map_imperatively(Order, orders)
+
+        self.mapper_registry.map_imperatively(
+            User,
+            users,
+            properties={
+                "addresses": relationship(
+                    Address, backref="user", lazy="joined"
+                ),
+                "orders": relationship(Order),
+            },
+        )
+        self.mapper_registry.map_imperatively(Address, addresses)
+        sess = fixture_session()
+
+        if test_control_case:
+            # this would be the error we are skipping, make sure it happens
+            # for up front
+            with expect_raises_message(
+                sa.exc.ArgumentError,
+                'Mapped attribute "User.addresses" does not apply to '
+                "any of the root entities in this query",
+            ):
+                row = sess.execute(
+                    select(Order).options(joinedload(User.addresses))
+                ).first()
+        else:
+            stmt = (
+                select(User, Order)
+                .join_from(User, Order)
+                .options(joinedload(User.addresses))
+                .order_by(User.id, Order.id)
+            )
+
+            row = sess.execute(stmt).first()
+
+            u1, o1 = row
+            if do_expire:
+                sess.expire(o1)
+            eq_(o1.description, "order 1")
+
     def test_mapper_joinedload_props_load(self):
         users, Address, addresses, User = (
             self.tables.users,