diff options
author | Mike Bayer <mike_mp@zzzcomputing.com> | 2014-10-18 17:56:13 -0400 |
---|---|---|
committer | Mike Bayer <mike_mp@zzzcomputing.com> | 2014-10-19 00:01:50 -0400 |
commit | 55cad302cee51aff6d2bcda2f2f963004d54e6de (patch) | |
tree | 8ed5eba6416b26982dd3d42f81278a91d47410f0 /lib/sqlalchemy/orm/relationships.py | |
parent | a7c1258d0340e94fd12e1b8aaa82ca3e282fb61d (diff) | |
download | sqlalchemy-ticket_3230.tar.gz |
- A warning is emitted in the case of multiple relationships thatticket_3230
ultimately will populate a foreign key column in conflict with
another, where the relationships are attempting to copy values
from different source columns. This occurs in the case where
composite foreign keys with overlapping columns are mapped to
relationships that each refer to a different referenced column.
A new documentation section illustrates the example as well as how
to overcome the issue by specifying "foreign" columns specifically
on a per-relationship basis.
fixes #3230
Diffstat (limited to 'lib/sqlalchemy/orm/relationships.py')
-rw-r--r-- | lib/sqlalchemy/orm/relationships.py | 51 |
1 files changed, 51 insertions, 0 deletions
diff --git a/lib/sqlalchemy/orm/relationships.py b/lib/sqlalchemy/orm/relationships.py index 56a33742d..4a6159144 100644 --- a/lib/sqlalchemy/orm/relationships.py +++ b/lib/sqlalchemy/orm/relationships.py @@ -16,6 +16,7 @@ and `secondaryjoin` aspects of :func:`.relationship`. from __future__ import absolute_import from .. import sql, util, exc as sa_exc, schema, log +import weakref from .util import CascadeOptions, _orm_annotate, _orm_deannotate from . import dependency from . import attributes @@ -1532,6 +1533,7 @@ class RelationshipProperty(StrategizedProperty): self._check_cascade_settings(self._cascade) self._post_init() self._generate_backref() + self._join_condition._warn_for_conflicting_sync_targets() super(RelationshipProperty, self).do_init() self._lazy_strategy = self._get_strategy((("lazy", "select"),)) @@ -2519,6 +2521,55 @@ class JoinCondition(object): self.secondary_synchronize_pairs = \ self._deannotate_pairs(secondary_sync_pairs) + _track_sync_targets = weakref.WeakKeyDictionary() + + def _warn_for_conflicting_sync_targets(self): + if not self.support_sync: + return + + # totally complex code that takes place for virtually all + # relationships, detecting an incredibly rare edge case, + # and even then, all just to emit a warning. + # we would like to detect if we are synchronizing any column + # pairs in conflict with another relationship that wishes to sync + # an entirely different column to the same target. This is typically + # when using complex overlapping composite foreign keys. + for from_, to_ in [ + (from_, to_) + for (from_, to_) in self.synchronize_pairs + ] + [ + (from_, to_) for + (from_, to_) in self.secondary_synchronize_pairs + ]: + if to_ not in self._track_sync_targets: + self._track_sync_targets[to_] = weakref.WeakKeyDictionary( + {self.prop: from_}) + else: + other_props = [] + prop_to_from = self._track_sync_targets[to_] + for pr, fr_ in prop_to_from.items(): + if pr.mapper in mapperlib._mapper_registry and \ + fr_ is not from_ and \ + pr not in self.prop._reverse_property: + other_props.append((pr, fr_)) + + if other_props: + util.warn( + "relationship '%s' will copy column %s to column %s, " + "which conflicts with relationship(s): %s. " + "Consider applying " + "viewonly=True to read-only relationships, or provide " + "a primaryjoin condition marking writable columns " + "with the foreign() annotation." % ( + self.prop, + from_, to_, + ", ".join( + "'%s' (copies %s to %s)" % (pr, fr_, to_) + for (pr, fr_) in other_props) + ) + ) + self._track_sync_targets[to_][self.prop] = from_ + @util.memoized_property def remote_columns(self): return self._gather_join_annotations("remote") |