diff options
author | Mike Bayer <mike_mp@zzzcomputing.com> | 2014-05-28 20:01:21 -0400 |
---|---|---|
committer | Mike Bayer <mike_mp@zzzcomputing.com> | 2014-05-28 20:01:21 -0400 |
commit | bc08ee9029258b23171bb67e191452e6c739c597 (patch) | |
tree | dac856792e87f4f68bd37d4f070d61ce5db0c596 /lib/sqlalchemy/orm/attributes.py | |
parent | d60b680e967ea70f0b6b572ff9febb12c48afd3e (diff) | |
download | sqlalchemy-bc08ee9029258b23171bb67e191452e6c739c597.tar.gz |
- Fixed a few edge cases which arise in the so-called "row switch"
scenario, where an INSERT/DELETE can be turned into an UPDATE.
In this situation, a many-to-one relationship set to None, or
in some cases a scalar attribute set to None, may not be detected
as a net change in value, and therefore the UPDATE would not reset
what was on the previous row. This is due to some as-yet
unresovled side effects of the way attribute history works in terms
of implicitly assuming None isn't really a "change" for a previously
un-set attribute. See also :ticket:`3061`. fixes #3060
Diffstat (limited to 'lib/sqlalchemy/orm/attributes.py')
-rw-r--r-- | lib/sqlalchemy/orm/attributes.py | 17 |
1 files changed, 12 insertions, 5 deletions
diff --git a/lib/sqlalchemy/orm/attributes.py b/lib/sqlalchemy/orm/attributes.py index bf7dab4e7..09d6e988d 100644 --- a/lib/sqlalchemy/orm/attributes.py +++ b/lib/sqlalchemy/orm/attributes.py @@ -568,7 +568,7 @@ class AttributeImpl(object): # if history present, don't load key = self.key if key not in state.committed_state or \ - state.committed_state[key] is NEVER_SET: + state.committed_state[key] is NEVER_SET: if not passive & CALLABLES_OK: return PASSIVE_NO_RESULT @@ -763,6 +763,13 @@ class ScalarObjectAttributeImpl(ScalarAttributeImpl): if self.dispatch._active_history: old = self.get(state, dict_, passive=PASSIVE_ONLY_PERSISTENT | NO_AUTOFLUSH) else: + # would like to call with PASSIVE_NO_FETCH ^ INIT_OK. However, + # we have a long-standing behavior that a "get()" on never set + # should implicitly set the value to None. Leaving INIT_OK + # set here means we are consistent whether or not we did a get + # first. + # see test_use_object_set_None vs. test_use_object_get_first_set_None + # in test_attributes.py old = self.get(state, dict_, passive=PASSIVE_NO_FETCH) if check_old is not None and \ @@ -777,6 +784,7 @@ class ScalarObjectAttributeImpl(ScalarAttributeImpl): state_str(state), self.key )) + value = self.fire_replace_event(state, dict_, value, old, initiator) dict_[self.key] = value @@ -793,8 +801,7 @@ class ScalarObjectAttributeImpl(ScalarAttributeImpl): def fire_replace_event(self, state, dict_, value, previous, initiator): if self.trackparent: if (previous is not value and - previous is not None and - previous is not PASSIVE_NO_RESULT): + previous not in (None, PASSIVE_NO_RESULT, NEVER_SET)): self.sethasparent(instance_state(previous), state, False) for fn in self.dispatch.set: @@ -1080,7 +1087,7 @@ def backref_listeners(attribute, key, uselist): def emit_backref_from_scalar_set_event(state, child, oldchild, initiator): if oldchild is child: return child - if oldchild is not None and oldchild is not PASSIVE_NO_RESULT: + if oldchild is not None and oldchild not in (PASSIVE_NO_RESULT, NEVER_SET): # With lazy=None, there's no guarantee that the full collection is # present when updating via a backref. old_state, old_dict = instance_state(oldchild),\ @@ -1208,7 +1215,7 @@ class History(History): return not bool( (self.added or self.deleted) - or self.unchanged and self.unchanged != [None] + or self.unchanged ) def sum(self): |