run _postfetch() for post_update to expire onupdates

Issue #3472 resolved
Mike Bayer repo owner created an issue

right now onupdates aren't caught

from sqlalchemy import *
from sqlalchemy.orm import *
from sqlalchemy.ext.declarative import declarative_base

Base = declarative_base()

from itertools import count

value = count()


class A(Base):
    __tablename__ = 'a'
    id = Column(Integer, primary_key=True)
    favorite_b_id = Column(ForeignKey('b.id'))
    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=lambda: next(value))


class B(Base):
    __tablename__ = 'b'
    id = Column(Integer, primary_key=True)
    a_id = Column(ForeignKey('a.id'))

e = create_engine("sqlite://", echo=True)
Base.metadata.create_all(e)

s = Session(e)
a1 = A()
b1 = B()

a1.bs.append(b1)
a1.favorite_b = b1
s.add(a1)
s.flush()
assert a1.updated == 0, a1.updated

this is definitely for 1.1 at earliest

Comments (5)

  1. Mike Bayer reporter

    patch:

    diff --git a/lib/sqlalchemy/orm/persistence.py b/lib/sqlalchemy/orm/persistence.py
    index 0bfee2e..71b5cf4 100644
    --- a/lib/sqlalchemy/orm/persistence.py
    +++ b/lib/sqlalchemy/orm/persistence.py
    @@ -562,7 +562,7 @@ def _collect_post_update_commands(base_mapper, uowtransaction, table,
                         params[col.key] = value
                         hasdata = True
             if hasdata:
    -            yield params, connection
    +            yield state, state_dict, mapper, connection, params
    
    
     def _collect_delete_commands(base_mapper, uowtransaction, table,
    @@ -822,15 +822,22 @@ def _emit_post_update_statements(base_mapper, uowtransaction,
         # to support executemany().
         for key, grouper in groupby(
             update, lambda rec: (
    -            rec[1],  # connection
    -            set(rec[0])  # parameter keys
    +            rec[3],  # connection
    +            set(rec[4]),  # parameter keys
             )
         ):
    +        grouper = list(grouper)
             connection = key[0]
    -        multiparams = [params for params, conn in grouper]
    -        cached_connections[connection].\
    +        multiparams = [
    +            params for state, state_dict, mapper_rec, conn, params in grouper]
    +        c = cached_connections[connection].\
                 execute(statement, multiparams)
    
    +        for state, state_dict, mapper_rec, connection, params in grouper:
    +            _postfetch_post_update(
    +                mapper, state, state_dict,
    +                c, c.context.compiled_parameters[0])
    +
    
     def _emit_delete_statements(base_mapper, uowtransaction, cached_connections,
                                 mapper, table, delete):
    @@ -956,6 +963,22 @@ def _finalize_insert_update_commands(base_mapper, uowtransaction, states):
                 mapper.dispatch.after_update(mapper, connection, state)
    
    
    +def _postfetch_post_update(mapper, state, dict_, result, params):
    +    prefetch_cols = result.context.compiled.prefetch
    +    postfetch_cols = result.context.compiled.postfetch
    +
    +    for c in prefetch_cols:
    +        if c.key in params and c in mapper._columntoproperty:
    +            dict_[mapper._columntoproperty[c].key] = params[c.key]
    +
    +    if postfetch_cols:
    +        state._expire_attributes(state.dict,
    +                                 [mapper._columntoproperty[c].key
    +                                  for c in postfetch_cols if c in
    +                                  mapper._columntoproperty]
    +                                 )
    +
    +
     def _postfetch(mapper, uowtransaction, table,
                    state, dict_, result, params, value_params, bulk=False):
         """Expire attributes in need of newly persisted database state,
    
  2. Mike Bayer reporter
    • changed milestone to 1.2

    starting to wrap up scope for 1.1, remaining behavioral changes being pushed out

  3. Mike Bayer reporter

    Implement _postfetch_post_update to expire/refresh onupdates in post_update

    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.

    Fixes: #3472 Change-Id: I5ee2d715e773a306ab1e8143e4382c228991ac78

    → <<cset 9dee44ae2f8b>>

  4. Log in to comment