From: Mike Bayer Date: Thu, 18 Nov 2021 17:46:25 +0000 (-0500) Subject: disable raiseerr for refresh state loader options X-Git-Tag: rel_2_0_0b1~644^2 X-Git-Url: http://git.ipfire.org/cgi-bin/gitweb.cgi?a=commitdiff_plain;h=d4c170238dc95eeb2dc7e6c5e03270fbc8b36f8f;p=thirdparty%2Fsqlalchemy%2Fsqlalchemy.git disable raiseerr for refresh state loader options 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 --- diff --git a/doc/build/changelog/unreleased_14/7318.rst b/doc/build/changelog/unreleased_14/7318.rst new file mode 100644 index 0000000000..d60e303c9c --- /dev/null +++ b/doc/build/changelog/unreleased_14/7318.rst @@ -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. diff --git a/lib/sqlalchemy/orm/strategy_options.py b/lib/sqlalchemy/orm/strategy_options.py index 675c7218bd..23a8c45332 100644 --- a/lib/sqlalchemy/orm/strategy_options.py +++ b/lib/sqlalchemy/orm/strategy_options.py @@ -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): diff --git a/test/orm/test_expire.py b/test/orm/test_expire.py index ae7ac010b5..a5abcb3554 100644 --- a/test/orm/test_expire.py +++ b/test/orm/test_expire.py @@ -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,