]> git.ipfire.org Git - thirdparty/sqlalchemy/sqlalchemy.git/commitdiff
Re-send column value w/ onupdate default during post-update
authorMike Bayer <mike_mp@zzzcomputing.com>
Tue, 6 Jun 2017 17:54:33 +0000 (13:54 -0400)
committerMike Bayer <mike_mp@zzzcomputing.com>
Tue, 6 Jun 2017 17:54:33 +0000 (13:54 -0400)
Adjusted the behavior of post_update such that if a column with
an "onupdate" default has received an explicit value for
INSERT, re-send the same data during a post-update UPDATE so
that the value remains in effect, rather than an onupdate
overwriting it.

Change-Id: I26bccb6f957dcad07a2bcbda2dd9e14c60b92b06
Fixes: #3471
doc/build/changelog/changelog_12.rst
doc/build/changelog/migration_12.rst
lib/sqlalchemy/orm/persistence.py
test/orm/test_cycles.py

index edcb6f225d831af5c1a1e2715c917f290d92e555..5ea6b513ed0c31cabc5974ffd51869c71f922aca 100644 (file)
 
     .. change:: 3472
         :tags: bug, orm
-        :tickets: 3472
-
-        Fixed bug involving the :paramref:`.relationship.post_update` feature
-        where a column "onupdate" value would not result in expiration or
-        refresh of the corresponding object attribute, if the UPDATE for the
-        row were a result of the "post update" feature.  Additionally, the
-        :meth:`.SessionEvents.refresh_flush` event is now emitted for these
-        attributes when refreshed within the flush.
+        :tickets: 3471, 3472
+
+        Repaired several use cases involving the
+        :paramref:`.relationship.post_update` feature when used in conjunction
+        with a column that has an "onupdate" value.   When the UPDATE emits,
+        the corresponding object attribute is now expired or refreshed so that
+        the newly generated "onupdate" value can populate on the object;
+        previously the stale value would remain.  Additionally, if the target
+        attribute is set in Python for the INSERT of the object, the value is
+        now re-sent during the UPDATE so that the "onupdate" does not overwrite
+        it (note this works just as well for server-generated onupdates).
+        Finally, the :meth:`.SessionEvents.refresh_flush` event is now emitted
+        for these attributes when refreshed within the flush.
+
+        .. seealso::
+
+            :ref:`change_3471`
 
     .. change:: 3996
         :tags: bug, orm
index 4d4c9e247da6e2a4a260db6f4be668d1f5ee8189..b12970af0840d0ac82d4c1adcb50b7a057381a3a 100644 (file)
@@ -925,6 +925,61 @@ beta tesing, it can be restored with a deprecation.
 
 :ticket:`3796`
 
+.. _change_3471:
+
+Refinements to post_update in conjunction with onupdate
+-------------------------------------------------------
+
+A relationship that uses the :paramref:`.relationship.post_update` feature
+will now interact better with a column that has an :paramref:`.Column.onupdate`
+value set.   If an object is inserted with an explicit value for the column,
+it is re-stated during the UPDATE so that the "onupdate" rule does not
+overwrite it::
+
+    class A(Base):
+        __tablename__ = 'a'
+        id = Column(Integer, primary_key=True)
+        favorite_b_id = Column(ForeignKey('b.id', name="favorite_b_fk"))
+        bs = relationship("B", primaryjoin="A.id == B.a_id")
+        favorite_b = relationship(
+            "B", primaryjoin="A.favorite_b_id == B.id", post_update=True)
+        updated = Column(Integer, onupdate=my_onupdate_function)
+
+    class B(Base):
+        __tablename__ = 'b'
+        id = Column(Integer, primary_key=True)
+        a_id = Column(ForeignKey('a.id', name="a_fk"))
+
+    a1 = A()
+    b1 = B()
+
+    a1.bs.append(b1)
+    a1.favorite_b = b1
+    a1.updated = 5
+    s.add(a1)
+    s.flush()
+
+Above, the previous behavior would be that an UPDATE would emit after the
+INSERT, thus triggering the "onupdate" and overwriting the value
+"5".   The SQL now looks like::
+
+    INSERT INTO a (favorite_b_id, updated) VALUES (?, ?)
+    (None, 5)
+    INSERT INTO b (a_id) VALUES (?)
+    (1,)
+    UPDATE a SET favorite_b_id=?, updated=? WHERE a.id = ?
+    (1, 5, 1)
+
+Additionally, if the value of "updated" is *not* set, then we correctly
+get back the newly generated value on ``a1.updated``; previously, the logic
+that refreshes or expires the attribute to allow the generated value
+to be present would not fire off for a post-update.   The
+:meth:`.SessionEvents.refresh_flush` event is also emitted when a refresh
+within flush occurs in this case.
+
+:ticket:`3471`
+
+:ticket:`3472`
 
 Key Behavioral Changes - Core
 =============================
index 5fa9701badf9398c0d6ed556fa645f52ff9be321..0de64011a0f3540849ed9340395352d2e99bacb2 100644 (file)
@@ -591,7 +591,7 @@ def _collect_post_update_commands(base_mapper, uowtransaction, table,
                         state,
                         state_dict, col, passive=attributes.PASSIVE_OFF)
 
-            elif col in post_update_cols:
+            elif col in post_update_cols or col.onupdate is not None:
                 prop = mapper._columntoproperty[col]
                 history = state.manager[prop.key].impl.get_history(
                     state, state_dict,
index a1be28d3f77219c533b0472638dcb197255be80d..abd05067aa868e1ccc6e34555cdae191a66d76ad 100644 (file)
@@ -1358,3 +1358,19 @@ class PostUpdateOnUpdateTest(fixtures.DeclarativeMappedTest):
                 mock.call(a1, mock.ANY, ['updated'])
             ]
         )
+
+    def test_update_defaults_can_set_value(self):
+        A, B = self.classes("A", "B")
+
+        s = Session()
+        a1 = A()
+        b1 = B()
+
+        a1.bs.append(b1)
+        a1.favorite_b = b1
+        a1.updated = 5
+        s.add(a1)
+        s.flush()
+
+        eq_(a1.updated, 5)
+