summaryrefslogtreecommitdiff
path: root/lib/sqlalchemy/orm/attributes.py
diff options
context:
space:
mode:
authorMike Bayer <mike_mp@zzzcomputing.com>2014-05-28 20:01:21 -0400
committerMike Bayer <mike_mp@zzzcomputing.com>2014-05-28 20:01:21 -0400
commitbc08ee9029258b23171bb67e191452e6c739c597 (patch)
treedac856792e87f4f68bd37d4f070d61ce5db0c596 /lib/sqlalchemy/orm/attributes.py
parentd60b680e967ea70f0b6b572ff9febb12c48afd3e (diff)
downloadsqlalchemy-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.py17
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):