--- /dev/null
+.. change::
+ :tags: bug, orm
+ :tickets: 10800
+
+ Fixed issue where when making use of the
+ :paramref:`_orm.relationship.post_update` feature at the same time as using
+ a mapper version_id_col could lead to a situation where the second UPDATE
+ statement emitted by the post-update feature would fail to make use of the
+ correct version identifier, assuming an UPDATE was already emitted in that
+ flush which had already bumped the version counter.
for c in prefetch_cols:
if c.key in params and c in mapper._columntoproperty:
- dict_[mapper._columntoproperty[c].key] = params[c.key]
+ pkey = mapper._columntoproperty[c].key
+
+ # set prefetched value in dict and also pop from committed_state,
+ # since this is new database state that replaces whatever might
+ # have previously been fetched (see #10800). this is essentially a
+ # shorthand version of set_committed_value(), which could also be
+ # used here directly (with more overhead)
+ dict_[pkey] = params[c.key]
+ state.committed_state.pop(pkey, None)
+
if refresh_flush:
- load_evt_attrs.append(mapper._columntoproperty[c].key)
+ load_evt_attrs.append(pkey)
if refresh_flush and load_evt_attrs:
mapper.class_manager.dispatch.refresh_flush(
fixture_session.commit()
eq_(f1.version, 2)
+
+
+class PostUpdateVersioningTest(fixtures.DeclarativeMappedTest):
+ """test for #10800"""
+
+ @classmethod
+ def setup_classes(cls):
+ Base = cls.DeclarativeBasic
+
+ class User(Base):
+ __tablename__ = "user"
+
+ id = Column(Integer, primary_key=True)
+
+ class Parent(Base):
+ __tablename__ = "parent"
+
+ id = Column(Integer, primary_key=True)
+ version_id = Column(Integer)
+ updated_by_id = Column(
+ Integer,
+ ForeignKey("user.id"),
+ )
+
+ updated_by = relationship(
+ "User",
+ foreign_keys=[updated_by_id],
+ post_update=True,
+ )
+
+ __mapper_args__ = {
+ "version_id_col": version_id,
+ }
+
+ def test_bumped_version_id(self):
+ User, Parent = self.classes("User", "Parent")
+
+ session = fixture_session()
+ u1 = User(id=1)
+ u2 = User(id=2)
+ p1 = Parent(id=1, updated_by=u1)
+ session.add(u1)
+ session.add(u2)
+ session.add(p1)
+
+ u2id = u2.id
+ session.commit()
+ session.close()
+
+ p1 = session.get(Parent, 1)
+ p1.updated_by
+ p1.version_id = p1.version_id
+ p1.updated_by_id = u2id
+ assert "version_id" in inspect(p1).committed_state
+
+ with self.sql_execution_asserter(testing.db) as asserter:
+ session.commit()
+
+ asserter.assert_(
+ CompiledSQL(
+ "UPDATE parent SET version_id=:version_id, "
+ "updated_by_id=:updated_by_id WHERE parent.id = :parent_id "
+ "AND parent.version_id = :parent_version_id",
+ [
+ {
+ "version_id": 2,
+ "updated_by_id": 2,
+ "parent_id": 1,
+ "parent_version_id": 1,
+ }
+ ],
+ ),
+ CompiledSQL(
+ "UPDATE parent SET version_id=:version_id, "
+ "updated_by_id=:updated_by_id WHERE parent.id = :parent_id "
+ "AND parent.version_id = :parent_version_id",
+ [
+ {
+ "version_id": 3,
+ "updated_by_id": 2,
+ "parent_id": 1,
+ "parent_version_id": 2,
+ }
+ ],
+ ),
+ )