summaryrefslogtreecommitdiff
path: root/lib/sqlalchemy
diff options
context:
space:
mode:
authorMike Bayer <mike_mp@zzzcomputing.com>2019-02-03 13:29:06 -0500
committerMike Bayer <mike_mp@zzzcomputing.com>2019-02-04 10:34:25 -0500
commit11845453d76e1576f637161e660160f0a6117af6 (patch)
tree46365ae7f40789150ce358689e972fd1d54191c7 /lib/sqlalchemy
parent025cf864419051de63f8c86c39a87c05ddbd8a65 (diff)
downloadsqlalchemy-11845453d76e1576f637161e660160f0a6117af6.tar.gz
Add bulk_replace to AssociationSet, AssociationDict
Implemented a more comprehensive assignment operation (e.g. "bulk replace") when using association proxy with sets or dictionaries. Fixes the problem of redundant proxy objects being created to replace the old ones, which leads to excessive events and SQL and in the case of unique constraints will cause the flush to fail. Fixes: #2642 Change-Id: I57ab27dd9feba057e539267722cce92254fca777
Diffstat (limited to 'lib/sqlalchemy')
-rw-r--r--lib/sqlalchemy/ext/associationproxy.py40
1 files changed, 38 insertions, 2 deletions
diff --git a/lib/sqlalchemy/ext/associationproxy.py b/lib/sqlalchemy/ext/associationproxy.py
index 12e6c1f52..873145b4e 100644
--- a/lib/sqlalchemy/ext/associationproxy.py
+++ b/lib/sqlalchemy/ext/associationproxy.py
@@ -560,8 +560,7 @@ class AssociationProxyInstance(object):
proxy = self.get(obj)
assert self.collection_class is not None
if proxy is not values:
- proxy.clear()
- self._set(proxy, values)
+ proxy._bulk_replace(self, values)
def delete(self, obj):
if self.owning_class is None:
@@ -959,6 +958,10 @@ class _AssociationCollection(object):
self.lazy_collection = state["lazy_collection"]
self.parent._inflate(self)
+ def _bulk_replace(self, assoc_proxy, values):
+ self.clear()
+ assoc_proxy._set(self, values)
+
class _AssociationList(_AssociationCollection):
"""Generic, converting, list-to-list proxy."""
@@ -1310,6 +1313,21 @@ class _AssociationDict(_AssociationCollection):
for key, value in kw:
self[key] = value
+ def _bulk_replace(self, assoc_proxy, values):
+ existing = set(self)
+ constants = existing.intersection(values or ())
+ additions = set(values or ()).difference(constants)
+ removals = existing.difference(constants)
+
+ for key, member in values.items() or ():
+ if key in additions:
+ self[key] = member
+ elif key in constants:
+ self[key] = member
+
+ for key in removals:
+ del self[key]
+
def copy(self):
return dict(self.items())
@@ -1394,6 +1412,24 @@ class _AssociationSet(_AssociationCollection):
for value in other:
self.add(value)
+ def _bulk_replace(self, assoc_proxy, values):
+ existing = set(self)
+ constants = existing.intersection(values or ())
+ additions = set(values or ()).difference(constants)
+ removals = existing.difference(constants)
+
+ appender = self.add
+ remover = self.remove
+
+ for member in values or ():
+ if member in additions:
+ appender(member)
+ elif member in constants:
+ appender(member)
+
+ for member in removals:
+ remover(member)
+
def __ior__(self, other):
if not collections._set_binops_check_strict(self, other):
return NotImplemented