--- /dev/null
+.. change::
+ :tags: bug, orm, regression
+ :tickets: 6232
+
+ Fixed critical regression caused by the new feature added as part of
+ :ticket:`1763`, eager loaders are invoked on unexpire operations. The new
+ feature makes use of the "immediateload" eager loader strategy as a
+ substitute for a collection loading strategy, which unlike the other
+ "post-load" strategies was not accommodating for recursive invocations
+ between mutually-dependent relationships, leading to recursion overflow
+ errors.
+
class PostLoader(AbstractRelationshipLoader):
"""A relationship loader that emits a second SELECT statement."""
+ def _check_recursive_postload(self, context, path, join_depth=None):
+ effective_path = (
+ context.compile_state.current_path or orm_util.PathRegistry.root
+ ) + path
+
+ if loading.PostLoad.path_exists(
+ context, effective_path, self.parent_property
+ ):
+ return True
+
+ path_w_prop = path[self.parent_property]
+ effective_path_w_prop = effective_path[self.parent_property]
+
+ if not path_w_prop.contains(context.attributes, "loader"):
+ if join_depth:
+ if effective_path_w_prop.length / 2 > join_depth:
+ return True
+ elif effective_path_w_prop.contains_mapper(self.mapper):
+ return True
+
+ return False
+
def _immediateload_create_row_processor(
self,
context,
def load_immediate(state, dict_, row):
state.get_impl(self.key).get(state, dict_)
+ if self._check_recursive_postload(context, path):
+ return
+
populators["delayed"].append((self.key, load_immediate))
adapter,
populators,
):
+
if context.refresh_state:
return self._immediateload_create_row_processor(
context,
adapter,
populators,
)
+ # the subqueryloader does a similar check in setup_query() unlike
+ # the other post loaders, however we have this here for consistency
+ elif self._check_recursive_postload(context, path, self.join_depth):
+ return
if not self.parent.class_manager[self.key].impl.supports_population:
raise sa_exc.InvalidRequestError(
adapter,
populators,
):
+
if context.refresh_state:
return self._immediateload_create_row_processor(
context,
adapter,
populators,
)
+ elif self._check_recursive_postload(context, path, self.join_depth):
+ return
if not self.parent.class_manager[self.key].impl.supports_population:
raise sa_exc.InvalidRequestError(
context.compile_state.current_path or orm_util.PathRegistry.root
) + path
- if loading.PostLoad.path_exists(
- context, selectin_path, self.parent_property
- ):
- return
-
path_w_prop = path[self.parent_property]
- selectin_path_w_prop = selectin_path[self.parent_property]
# build up a path indicating the path from the leftmost
# entity to the thing we're subquery loading.
else:
effective_entity = self.entity
- if not path_w_prop.contains(context.attributes, "loader"):
- if self.join_depth:
- if selectin_path_w_prop.length / 2 > self.join_depth:
- return
- elif selectin_path_w_prop.contains_mapper(self.mapper):
- return
loading.PostLoad.callable_for_path(
context,
selectin_path,
assert "addresses" in u1.__dict__
+ @testing.combinations(
+ ("selectin",),
+ ("subquery",),
+ ("immediate",),
+ )
+ def test_refresh_no_recursion(self, strat):
+ User, Address = self.classes.User, self.classes.Address
+ mapper(
+ User,
+ self.tables.users,
+ properties={
+ "addresses": relationship(
+ Address, lazy="joined", back_populates="user"
+ )
+ },
+ )
+ mapper(
+ Address,
+ self.tables.addresses,
+ properties={
+ "user": relationship(
+ User, lazy=strat, back_populates="addresses"
+ )
+ },
+ )
+ sess = fixture_session(autoflush=False)
+
+ u1 = sess.query(User).get(8)
+ assert "addresses" in u1.__dict__
+ sess.expire(u1)
+
+ def go():
+ eq_(u1.id, 8)
+
+ self.assert_sql_count(testing.db, go, 1)
+
+ assert "addresses" in u1.__dict__
+
def test_populate_existing_propagate(self):
# both SelectInLoader and SubqueryLoader receive the loaded collection
# at once and use attributes.set_committed_value(). However