summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorMike Bayer <mike_mp@zzzcomputing.com>2017-06-06 13:54:33 -0400
committerMike Bayer <mike_mp@zzzcomputing.com>2017-06-06 13:54:33 -0400
commit6b68a70b5f903079f3c42a827daa3ea08a1a4b53 (patch)
treebd602ca0b8f63a845b555c9d3039e4f9c10b25d9
parenta2099ed44f390f1d480341907a4dc601f9195ec9 (diff)
downloadsqlalchemy-6b68a70b5f903079f3c42a827daa3ea08a1a4b53.tar.gz
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
-rw-r--r--doc/build/changelog/changelog_12.rst25
-rw-r--r--doc/build/changelog/migration_12.rst55
-rw-r--r--lib/sqlalchemy/orm/persistence.py2
-rw-r--r--test/orm/test_cycles.py16
4 files changed, 89 insertions, 9 deletions
diff --git a/doc/build/changelog/changelog_12.rst b/doc/build/changelog/changelog_12.rst
index edcb6f225..5ea6b513e 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 4d4c9e247..b12970af0 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 5fa9701ba..0de64011a 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 a1be28d3f..abd05067a 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)
+