]> git.ipfire.org Git - thirdparty/sqlalchemy/sqlalchemy.git/commitdiff
Narrow refresh populate_existing to just refresh_state
authorMike Bayer <mike_mp@zzzcomputing.com>
Tue, 20 Apr 2021 15:33:02 +0000 (11:33 -0400)
committerMike Bayer <mike_mp@zzzcomputing.com>
Tue, 20 Apr 2021 15:33:48 +0000 (11:33 -0400)
Fixed additional regression caused by the "eagerloaders on refresh" feature
added in :ticket:`1763` where the refresh operation historically would set
``populate_existing``, which given the new feature now overwrites pending
changes on eagerly loaded objects when autoflush is false. The
populate_existing flag has been turned off for this case and a more
specific method used to ensure the correct attributes refreshed.

Fixes: #6326
Change-Id: I40315e4164eae28972c5839c04580d292bc6cb24

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

diff --git a/doc/build/changelog/unreleased_14/6326.rst b/doc/build/changelog/unreleased_14/6326.rst
new file mode 100644 (file)
index 0000000..13ad425
--- /dev/null
@@ -0,0 +1,10 @@
+.. change::
+    :tags: orm, bug, regression
+    :tickets: 6326
+
+    Fixed additional regression caused by the "eagerloaders on refresh" feature
+    added in :ticket:`1763` where the refresh operation historically would set
+    ``populate_existing``, which given the new feature now overwrites pending
+    changes on eagerly loaded objects when autoflush is false. The
+    populate_existing flag has been turned off for this case and a more
+    specific method used to ensure the correct attributes refreshed.
index 12acfc5b786e891c1896ef95b71432c35e6cd30a..ea6d0f1fe673e81c42fae38a5b1dc18f088d4520 100644 (file)
@@ -481,7 +481,6 @@ def load_on_pk_identity(
         _, load_options = _set_get_options(
             compile_options,
             load_options,
-            populate_existing=bool(refresh_state),
             version_check=version_check,
             only_load_props=only_load_props,
             refresh_state=refresh_state,
@@ -513,7 +512,6 @@ def load_on_pk_identity(
         q._compile_options, load_options = _set_get_options(
             compile_options,
             load_options,
-            populate_existing=bool(refresh_state),
             version_check=version_check,
             only_load_props=only_load_props,
             refresh_state=refresh_state,
@@ -916,17 +914,23 @@ def _instance_processor(
                 state.session_id = session_id
                 session_identity_map._add_unpresent(state, identitykey)
 
+        effective_populate_existing = populate_existing
+        if refresh_state is state:
+            effective_populate_existing = True
+
         # populate.  this looks at whether this state is new
         # for this load or was existing, and whether or not this
         # row is the first row with this identity.
-        if currentload or populate_existing:
+        if currentload or effective_populate_existing:
             # full population routines.  Objects here are either
             # just created, or we are doing a populate_existing
 
             # be conservative about setting load_path when populate_existing
             # is in effect; want to maintain options from the original
             # load.  see test_expire->test_refresh_maintains_deferred_options
-            if isnew and (propagated_loader_options or not populate_existing):
+            if isnew and (
+                propagated_loader_options or not effective_populate_existing
+            ):
                 state.load_options = propagated_loader_options
                 state.load_path = load_path
 
@@ -938,7 +942,7 @@ def _instance_processor(
                 isnew,
                 load_path,
                 loaded_instance,
-                populate_existing,
+                effective_populate_existing,
                 populators,
             )
 
@@ -966,7 +970,7 @@ def _instance_processor(
                     if state.runid != runid:
                         _warn_for_runid_changed(state)
 
-                if populate_existing or state.modified:
+                if effective_populate_existing or state.modified:
                     if refresh_state and only_load_props:
                         state._commit(dict_, only_load_props)
                     else:
index 95efae850e7e5697c2842c2c7330e9aea446ba53..63ed7a3a71a608073c860bbf75e25bc75c44058c 100644 (file)
@@ -663,6 +663,38 @@ class ExpireTest(_fixtures.FixtureTest):
         # together when eager load used with Query
         self.assert_sql_count(testing.db, go, 1)
 
+    def test_unexpire_eager_dont_overwrite_related(self):
+        users, Address, addresses, User = (
+            self.tables.users,
+            self.classes.Address,
+            self.tables.addresses,
+            self.classes.User,
+        )
+
+        mapper(
+            User,
+            users,
+            properties={
+                "addresses": relationship(
+                    Address, backref="user", lazy="joined"
+                )
+            },
+        )
+        mapper(Address, addresses)
+
+        sess = fixture_session(autoflush=False)
+        u = sess.query(User).get(7)
+
+        a1 = u.addresses[0]
+        eq_(a1.email_address, "jack@bean.com")
+
+        sess.expire(u)
+        a1.email_address = "foo"
+
+        assert a1 in u.addresses
+        eq_(a1.email_address, "foo")
+        assert a1 in sess.dirty
+
     def test_relationship_changes_preserved(self):
         users, Address, addresses, User = (
             self.tables.users,