On 12/09/2015 02:07 AM, Юрий Пайков wrote:
> 
> Ok, here is the test
> code 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'))
    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

> 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> 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:>>.
>     > 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