On 12/09/2015 01:18 PM, Юрий Пайков wrote:
> Thank you for the detailed explanation of what happens. Now I
> understand. Shame on me I hadn't noticed this in the docs...
> Everything which deals with expiration and object state business is
> always a bit over my head...
> 
> You said it mostly the flush() business and is_modified() indeed looks
> like one of its private _function()s which behave bearing in mind what
> flush() wants and at what expense
> BUT exposed as public. Maybe it worths rewriting it so it behaves
> without assuming some calling "context", even less productive, but with
> more expected behavior? 

it would just be remarkably complicated to make it work in this way.  As
I said, a rule to at least accommodate simple many-to-ones is
potentially feasible but there are always more edge cases that still
might change the calculus of, "would an UPDATE really be emitted?".


It it would be pain maintaining diverging copies
> of logic... Anyway, thanks for the clarification, you're awesome as always
> 
> среда, 9 декабря 2015 г., 22:04:29 UTC+5 пользователь Michael Bayer написал:
> 
> 
> 
>     On 12/09/2015 02:07 AM, Юрий Пайков wrote:
>     >
>     > Ok, here is the test
>     > code https://gist.github.com/ojomio/941d03b728a88d93d010
>     <https://gist.github.com/ojomio/941d03b728a88d93d010>
>     > Apart from reproducing the (seeming) problem, it prints out
>     > "PASSIVE_NO_RESULT"(yes, you were right about the name) which is /in/
>     > committed_state
> 
> 
>     great, thanks. The PASSIVE_NO_RESULT symbol in there is a normal thing,
>     it means that for this object, we didn't actually know what the "old"
>     value of the A.second attribute was - it was expired when you called
>     session.commit().  So the fact that we set it to this B() object we
>     have
>     no choice but to record as "new history".
> 
>     Now, if we really wanted it to know that no, this is the *same* object,
>     it would have to go out to the identity map and/or database and *load*
>     the old one.  You can have that, like this:
> 
>     class A(Base):
>         __tablename__ = 'a'
>         first = Column(Integer, primary_key=True)
>         second_id = Column(Integer, ForeignKey('b.id <http://b.id>'))
>         second = relationship('B', active_history=True)
> 
> 
>     this isn't the default setting, because when a many-to-one object is
>     assigned to a parent, we don't need to know what the old one was in
>     order to do an UPDATE, so if we don't have it, we don't bother most
>     likely hitting the database again and getting it.   You can see that
>     the
>     test script emits an extra SELECT for it.   The majority of our users
>     were annoyed in the old days when every many-to-one assignment
>     triggered
>     an unnecessary SELECT, so that's why it works this way.
> 
>     Another level here is that, A.second points to a B which has an "id"
>     that is the same value we already know to have in A.second_id (we
>     emitted a SELECT for that also-expired value as well, but at least that
>     was just one SELECT), so in this (most common) case, we can actually
>     see
>     that hey, no there really won't be an UPDATE, because if we look inside
>     this B, and its primary key,  and the primaryjoin condition inside our
>     relationship() (which could be anything) and run the rules for the
>     A.second relationship for synchronizing a many-to-one value into the
>     attributes of our A, there's no net change; that's essentially a really
>     involved and highly tweaked process that occurs within flush() which
>     unfortunately is not simplistic enough to just pull out and run in this
>     local, non-flush context.   I can see some potential tweaks where maybe
>     we look at the fact that this is a "simple" many-to-one (e.g. the
>     primaryjoin has no weird conditions in it) and then only consider
>     the FK
>     columns, that could be doable.  I'd have it only on a flag.
> 
>     The simple answer is that session.merge() is very likely to cause an
>     object to look "modified", and if very accurate detection of changes is
>     a use case, I wouldn't use merge(), or I'd make sure I'm not running it
>     on expired objects.
> 
>     *or*, just look in the history yourself for the attributes you care
>     about:
> 
> 
>             def a_is_modified(some_a):
>                 attrs = inspect(some_a).attrs
>                 return bool(sum(
>                     attrs[key].history.has_changes()
>                     for key in ['first', 'second_id']
>                 ))
> 
> 
>             new_a = A(first=3, second=b)
>             returned_a = session.merge(new_a)
>             print a_is_modified(a)
> 
> 
>             
> 
>     The docs for session.is_modified() point this out as well:
> 
>     
> http://docs.sqlalchemy.org/en/rel_1_0/orm/session_api.html#sqlalchemy.orm.session.Session.is_modified
>     
> <http://docs.sqlalchemy.org/en/rel_1_0/orm/session_api.html#sqlalchemy.orm.session.Session.is_modified>
> 
> 
>     > Scalar attributes may not have recorded the previously set value when
>     a new value was applied, if the attribute was not loaded, or was
>     expired, at the time the new value was received - in these cases, the
>     attribute is assumed to have a change, even if there is ultimately no
>     net change against its database value. SQLAlchemy in most cases does
>     not
>     need the “old” value when a set event occurs, so it skips the
>     expense of
>     a SQL call if the old value isn’t present, based on the assumption that
>     an UPDATE of the scalar value is usually needed, and in those few cases
>     where it isn’t, is less expensive on average than issuing a defensive
>     SELECT.
> 
>     > The “old” value is fetched unconditionally upon set only if the
>     attribute container has the active_history flag set to True. This flag
>     is set typically for primary key attributes and scalar object
>     references
>     that are not a simple many-to-one. To set this flag for any arbitrary
>     mapped column, use the active_history argument with column_property().
> 
> 
> 
> 
> 
> 
> 
>     > вторник, 8 декабря 2015 г., 19:44:16 UTC+5 пользователь Michael Bayer
>     > написал:
>     >
>     >
>     >     We have a lot of "PASSIVE_*" symbols, but I can't find one, even
>     >     looking
>     >     way back, that is called "PASSIVE_NO_DELETE".  Also these
>     particular
>     >     symbols don't get populated into objects, they are arguments
>     we send to
>     >     various attribute fetch/history methods.    If I had to guess,
>     you
>     >     *might* be seeing PASSIVE_NO_RESULT, but that wouldn't be
>     present in
>     >     committed_state.
>     >
>     >     Overall this description of behavior is not clear.  If you could
>     >     reproduce your exact results in a console session or script
>     and please
>     >     report on those exactly, that would help understand your case.
>     >     Following the guidelines at http://stackoverflow.com/help/mcve
>     <http://stackoverflow.com/help/mcve>
>     >     <http://stackoverflow.com/help/mcve
>     <http://stackoverflow.com/help/mcve>> would be
>     >     most helpful.
>     >
>     >     The is_modified() method up until 0.8 included a default
>     setting for
>     >     its
>     >     own "passive" flag that would cause it to fetch relationships
>     as a side
>     >     effect.  This was fixed in 0.8.   Also it does not include
>     >     collection-bound relationships as part of its answer unless the
>     >     include_collections flag is set to True.
>     >
>     >     >
>     >     > Does anyone know why it happens like this? How can I correctly
>     >     check if
>     >     > the object's going to be updated on commit?
>     >     >
>     >     > --
>     >     > You received this message because you are subscribed to the
>     Google
>     >     > Groups "sqlalchemy" group.
>     >     > To unsubscribe from this group and stop receiving emails
>     from it,
>     >     send
>     >     > an email to sqlalchemy+...@googlegroups.com <javascript:>
>     >     > <mailto:sqlalchemy+unsubscr...@googlegroups.com
>     <javascript:> <javascript:>>.
>     >     > To post to this group, send email to sqlal...@googlegroups.com
>     >     <javascript:>
>     >     > <mailto:sqlal...@googlegroups.com <javascript:>>.
>     >     > Visit this group at
>     http://groups.google.com/group/sqlalchemy
>     <http://groups.google.com/group/sqlalchemy>
>     >     <http://groups.google.com/group/sqlalchemy
>     <http://groups.google.com/group/sqlalchemy>>.
>     >     > For more options, visit https://groups.google.com/d/optout
>     <https://groups.google.com/d/optout>
>     >     <https://groups.google.com/d/optout
>     <https://groups.google.com/d/optout>>.
>     >
>     > --
>     > You received this message because you are subscribed to the Google
>     > Groups "sqlalchemy" group.
>     > To unsubscribe from this group and stop receiving emails from it,
>     send
>     > an email to sqlalchemy+...@googlegroups.com <javascript:>
>     > <mailto:sqlalchemy+unsubscr...@googlegroups.com <javascript:>>.
>     > To post to this group, send email to sqlal...@googlegroups.com
>     <javascript:>
>     > <mailto:sqlal...@googlegroups.com <javascript:>>.
>     > Visit this group at http://groups.google.com/group/sqlalchemy
>     <http://groups.google.com/group/sqlalchemy>.
>     > For more options, visit https://groups.google.com/d/optout
>     <https://groups.google.com/d/optout>.
> 
> -- 
> You received this message because you are subscribed to the Google
> Groups "sqlalchemy" group.
> To unsubscribe from this group and stop receiving emails from it, send
> an email to sqlalchemy+unsubscr...@googlegroups.com
> <mailto:sqlalchemy+unsubscr...@googlegroups.com>.
> To post to this group, send email to sqlalchemy@googlegroups.com
> <mailto:sqlalchemy@googlegroups.com>.
> Visit this group at http://groups.google.com/group/sqlalchemy.
> For more options, visit https://groups.google.com/d/optout.

-- 
You received this message because you are subscribed to the Google Groups 
"sqlalchemy" group.
To unsubscribe from this group and stop receiving emails from it, send an email 
to sqlalchemy+unsubscr...@googlegroups.com.
To post to this group, send email to sqlalchemy@googlegroups.com.
Visit this group at http://groups.google.com/group/sqlalchemy.
For more options, visit https://groups.google.com/d/optout.

Reply via email to