From: Mike Bayer Date: Mon, 4 Sep 2017 16:11:19 +0000 (-0400) Subject: Guard against KeyError in session.merge after check for identity X-Git-Tag: rel_1_1_14~2^2 X-Git-Url: http://git.ipfire.org/cgi-bin/gitweb.cgi?a=commitdiff_plain;h=68953902dda9209ee72cde44d4802d730e4f75e3;p=thirdparty%2Fsqlalchemy%2Fsqlalchemy.git Guard against KeyError in session.merge after check for identity Fixed bug in :meth:`.Session.merge` following along similar lines as that of :ticket:`4030`, where an internal check for a target object in the identity map could lead to an error if it were to be garbage collected immediately before the merge routine actually retrieves the object. Change-Id: Ifecfb8b9d50c52d0ebd5a03e1bd69fe3abf1dc40 Fixes: #4069 (cherry picked from commit bfad032017fde2e519ad5eacc0011a71bf7a22a9) --- diff --git a/doc/build/changelog/unreleased_11/4069.rst b/doc/build/changelog/unreleased_11/4069.rst new file mode 100644 index 0000000000..dadae423c5 --- /dev/null +++ b/doc/build/changelog/unreleased_11/4069.rst @@ -0,0 +1,9 @@ +.. change:: + :tags: bug, orm + :tickets: 4069 + :versions: 1.2.0b3 + + Fixed bug in :meth:`.Session.merge` following along similar lines as that + of :ticket:`4030`, where an internal check for a target object in + the identity map could lead to an error if it were to be garbage collected + immediately before the merge routine actually retrieves the object. \ No newline at end of file diff --git a/lib/sqlalchemy/orm/session.py b/lib/sqlalchemy/orm/session.py index ebf1f61f1d..2c040561b0 100644 --- a/lib/sqlalchemy/orm/session.py +++ b/lib/sqlalchemy/orm/session.py @@ -1875,27 +1875,33 @@ class Session(_SessionClassMethods): key_is_persistent = True if key in self.identity_map: - merged = self.identity_map[key] - elif key_is_persistent and key in _resolve_conflict_map: - merged = _resolve_conflict_map[key] - - elif not load: - if state.modified: - raise sa_exc.InvalidRequestError( - "merge() with load=False option does not support " - "objects marked as 'dirty'. flush() all changes on " - "mapped instances before merging with load=False.") - merged = mapper.class_manager.new_instance() - merged_state = attributes.instance_state(merged) - merged_state.key = key - self._update_impl(merged_state) - new_instance = True - - elif key_is_persistent: - merged = self.query(mapper.class_).get(key[1]) + try: + merged = self.identity_map[key] + except KeyError: + # object was GC'ed right as we checked for it + merged = None else: merged = None + if merged is None: + if key_is_persistent and key in _resolve_conflict_map: + merged = _resolve_conflict_map[key] + + elif not load: + if state.modified: + raise sa_exc.InvalidRequestError( + "merge() with load=False option does not support " + "objects marked as 'dirty'. flush() all changes on " + "mapped instances before merging with load=False.") + merged = mapper.class_manager.new_instance() + merged_state = attributes.instance_state(merged) + merged_state.key = key + self._update_impl(merged_state) + new_instance = True + + elif key_is_persistent: + merged = self.query(mapper.class_).get(key[1]) + if merged is None: merged = mapper.class_manager.new_instance() merged_state = attributes.instance_state(merged)