summaryrefslogtreecommitdiff
path: root/lib/sqlalchemy/orm
diff options
context:
space:
mode:
authorMike Bayer <mike_mp@zzzcomputing.com>2015-12-04 11:52:16 -0500
committerMike Bayer <mike_mp@zzzcomputing.com>2015-12-04 11:52:16 -0500
commit3ec9b9f6b601b8ef69d4978c7182e8efedefd191 (patch)
treefded91038e426b974d141f2a4e4be714a18d0ae2 /lib/sqlalchemy/orm
parent935bc34dc50d5e4bdf181a8287d6e4cdbde073d0 (diff)
downloadsqlalchemy-3ec9b9f6b601b8ef69d4978c7182e8efedefd191.tar.gz
- The :meth:`.Session.merge` method now tracks pending objects by
primary key before emitting an INSERT, and merges distinct objects with duplicate primary keys together as they are encountered, which is essentially semi-deterministic at best. This behavior matches what happens already with persistent objects. fixes #3601
Diffstat (limited to 'lib/sqlalchemy/orm')
-rw-r--r--lib/sqlalchemy/orm/interfaces.py2
-rw-r--r--lib/sqlalchemy/orm/properties.py2
-rw-r--r--lib/sqlalchemy/orm/relationships.py14
-rw-r--r--lib/sqlalchemy/orm/session.py20
4 files changed, 28 insertions, 10 deletions
diff --git a/lib/sqlalchemy/orm/interfaces.py b/lib/sqlalchemy/orm/interfaces.py
index cd4a0116d..ed8f27332 100644
--- a/lib/sqlalchemy/orm/interfaces.py
+++ b/lib/sqlalchemy/orm/interfaces.py
@@ -234,7 +234,7 @@ class MapperProperty(_MappedAttribute, InspectionAttr, util.MemoizedSlots):
"""
def merge(self, session, source_state, source_dict, dest_state,
- dest_dict, load, _recursive):
+ dest_dict, load, _recursive, _resolve_conflict_map):
"""Merge the attribute represented by this ``MapperProperty``
from source to destination object.
diff --git a/lib/sqlalchemy/orm/properties.py b/lib/sqlalchemy/orm/properties.py
index b1f1c61c4..0d4e1b771 100644
--- a/lib/sqlalchemy/orm/properties.py
+++ b/lib/sqlalchemy/orm/properties.py
@@ -206,7 +206,7 @@ class ColumnProperty(StrategizedProperty):
get_committed_value(state, dict_, passive=passive)
def merge(self, session, source_state, source_dict, dest_state,
- dest_dict, load, _recursive):
+ dest_dict, load, _recursive, _resolve_conflict_map):
if not self.instrument:
return
elif self.key in source_dict:
diff --git a/lib/sqlalchemy/orm/relationships.py b/lib/sqlalchemy/orm/relationships.py
index 929c923a6..1d442eff8 100644
--- a/lib/sqlalchemy/orm/relationships.py
+++ b/lib/sqlalchemy/orm/relationships.py
@@ -1430,7 +1430,7 @@ class RelationshipProperty(StrategizedProperty):
source_dict,
dest_state,
dest_dict,
- load, _recursive):
+ load, _recursive, _resolve_conflict_map):
if load:
for r in self._reverse_property:
@@ -1463,8 +1463,10 @@ class RelationshipProperty(StrategizedProperty):
current_state = attributes.instance_state(current)
current_dict = attributes.instance_dict(current)
_recursive[(current_state, self)] = True
- obj = session._merge(current_state, current_dict,
- load=load, _recursive=_recursive)
+ obj = session._merge(
+ current_state, current_dict,
+ load=load, _recursive=_recursive,
+ _resolve_conflict_map=_resolve_conflict_map)
if obj is not None:
dest_list.append(obj)
@@ -1482,8 +1484,10 @@ class RelationshipProperty(StrategizedProperty):
current_state = attributes.instance_state(current)
current_dict = attributes.instance_dict(current)
_recursive[(current_state, self)] = True
- obj = session._merge(current_state, current_dict,
- load=load, _recursive=_recursive)
+ obj = session._merge(
+ current_state, current_dict,
+ load=load, _recursive=_recursive,
+ _resolve_conflict_map=_resolve_conflict_map)
else:
obj = None
diff --git a/lib/sqlalchemy/orm/session.py b/lib/sqlalchemy/orm/session.py
index 4272d7d78..f58e4de61 100644
--- a/lib/sqlalchemy/orm/session.py
+++ b/lib/sqlalchemy/orm/session.py
@@ -1689,6 +1689,10 @@ class Session(_SessionClassMethods):
See :ref:`unitofwork_merging` for a detailed discussion of merging.
+ .. versionchanged:: 1.1 - :meth:`.Session.merge` will now reconcile
+ pending objects with overlapping primary keys in the same way
+ as persistent. See :ref:`change_3601` for discussion.
+
:param instance: Instance to be merged.
:param load: Boolean, when False, :meth:`.merge` switches into
a "high performance" mode which causes it to forego emitting history
@@ -1713,12 +1717,14 @@ class Session(_SessionClassMethods):
should be "clean" as well, else this suggests a mis-use of the
method.
+
"""
if self._warn_on_events:
self._flush_warning("Session.merge()")
_recursive = {}
+ _resolve_conflict_map = {}
if load:
# flush current contents if we expect to load data
@@ -1731,11 +1737,13 @@ class Session(_SessionClassMethods):
return self._merge(
attributes.instance_state(instance),
attributes.instance_dict(instance),
- load=load, _recursive=_recursive)
+ load=load, _recursive=_recursive,
+ _resolve_conflict_map=_resolve_conflict_map)
finally:
self.autoflush = autoflush
- def _merge(self, state, state_dict, load=True, _recursive=None):
+ def _merge(self, state, state_dict, load=True, _recursive=None,
+ _resolve_conflict_map=None):
mapper = _state_mapper(state)
if state in _recursive:
return _recursive[state]
@@ -1751,9 +1759,14 @@ class Session(_SessionClassMethods):
"all changes on mapped instances before merging with "
"load=False.")
key = mapper._identity_key_from_state(state)
+ key_is_persistent = attributes.NEVER_SET not in key[1]
+ else:
+ 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:
@@ -1785,6 +1798,7 @@ class Session(_SessionClassMethods):
merged_dict = attributes.instance_dict(merged)
_recursive[state] = merged
+ _resolve_conflict_map[key] = merged
# check that we didn't just pull the exact same
# state out.
@@ -1823,7 +1837,7 @@ class Session(_SessionClassMethods):
for prop in mapper.iterate_properties:
prop.merge(self, state, state_dict,
merged_state, merged_dict,
- load, _recursive)
+ load, _recursive, _resolve_conflict_map)
if not load:
# remove any history