From: Mike Bayer Date: Tue, 6 Jun 2017 17:54:33 +0000 (-0400) Subject: Re-send column value w/ onupdate default during post-update X-Git-Tag: rel_1_2_0b1~33 X-Git-Url: http://git.ipfire.org/cgi-bin/gitweb.cgi?a=commitdiff_plain;h=6b68a70b5f903079f3c42a827daa3ea08a1a4b53;p=thirdparty%2Fsqlalchemy%2Fsqlalchemy.git Re-send column value w/ onupdate default during post-update 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 --- diff --git a/doc/build/changelog/changelog_12.rst b/doc/build/changelog/changelog_12.rst index edcb6f225d..5ea6b513ed 100644 --- a/doc/build/changelog/changelog_12.rst +++ b/doc/build/changelog/changelog_12.rst @@ -59,14 +59,23 @@ .. 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 diff --git a/doc/build/changelog/migration_12.rst b/doc/build/changelog/migration_12.rst index 4d4c9e247d..b12970af08 100644 --- a/doc/build/changelog/migration_12.rst +++ b/doc/build/changelog/migration_12.rst @@ -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 ============================= diff --git a/lib/sqlalchemy/orm/persistence.py b/lib/sqlalchemy/orm/persistence.py index 5fa9701bad..0de64011a0 100644 --- a/lib/sqlalchemy/orm/persistence.py +++ b/lib/sqlalchemy/orm/persistence.py @@ -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, diff --git a/test/orm/test_cycles.py b/test/orm/test_cycles.py index a1be28d3f7..abd05067aa 100644 --- a/test/orm/test_cycles.py +++ b/test/orm/test_cycles.py @@ -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) +