Expand sibling tests for overlaps warning

Scaled back the warning message added in 🎫`5171` to not warn for
overlapping columns in an inheritance scenario where a particular
relationship is local to a subclass and therefore does not represent an
overlap.

Add errors documentation for the warning and also expand
``util.warn()`` to include a code parameter.

Fixes: #6171
Change-Id: Icb1f12d8d645d439ffd2bbb7371c6b00042b6ae3
This commit is contained in:
Mike Bayer
2021-03-31 13:16:04 -04:00
parent 42185a1f38
commit b99f8cad8d
5 changed files with 124 additions and 2 deletions
+8
View File
@@ -0,0 +1,8 @@
.. change::
:tags: bug, orm, regression
:tickets: 6171
Scaled back the warning message added in :ticket:`5171` to not warn for
overlapping columns in an inheritance scenario where a particular
relationship is local to a subclass and therefore does not represent an
overlap.
+77
View File
@@ -1116,6 +1116,83 @@ message for details.
:ref:`error_bbf0`
.. _error_qzyx:
relationship X will copy column Q to column P, which conflicts with relationship(s): 'Y'
----------------------------------------------------------------------------------------
This warning refers to the case when two or more relationships will write data to the
same columns on flush, but the ORM does not have any kind of back population configuration
between the two relationships. The fix is usually to install the correct
:paramref:`_orm.back_populates` configuration. Given the following mapping::
class Parent(Base):
__tablename__ = "parent"
id = Column(Integer, primary_key=True)
children = relationship("Child")
class Child(Base):
__tablename__ = "child"
id = Column(Integer, primary_key=True)
parent_id = Column(ForeignKey("parent.id"))
parent = relationship("Parent")
The above mapping will generate warnings::
SAWarning: relationship 'Child.parent' will copy column parent.id to column child.parent_id,
which conflicts with relationship(s): 'Parent.children' (copies parent.id to child.parent_id).
The relationships ``Child.parent`` and ``Parent.children`` appear to be in conflict.
The solution is to apply :paramref:`_orm.relationship.back_populates`::
class Parent(Base):
__tablename__ = "parent"
id = Column(Integer, primary_key=True)
children = relationship("Child", back_populates="parent")
class Child(Base):
__tablename__ = "child"
id = Column(Integer, primary_key=True)
parent_id = Column(ForeignKey("parent.id"))
parent = relationship("Parent", back_populates="children")
For more customized relationships where an "overlap" situation may be
intentional and cannot be resolved, the :paramref:`_orm.relationship.overlaps`
parameter may specify the names of relationships for which the warning should
not take effect. This typically occurs for two or more relationships to the
same underlying table that include custom
:paramref:`_orm.relationship.primaryjoin` conditions that limit the related
items in each case::
class Parent(Base):
__tablename__ = "parent"
id = Column(Integer, primary_key=True)
c1 = relationship(
"Child",
primaryjoin="and_(Parent.id == Child.parent_id, Child.flag == 0)",
backref="parent",
overlaps="c2, parent"
)
c2 = relationship(
"Child",
primaryjoin="and_(Parent.id == Child.parent_id, Child.flag == 1)",
overlaps="c1, parent"
)
class Child(Base):
__tablename__ = "child"
id = Column(Integer, primary_key=True)
parent_id = Column(ForeignKey("parent.id"))
flag = Column(Integer)
Above, the ORM will know that the overlap between ``Parent.c1``,
``Parent.c2`` and ``Child.parent`` is intentional.
AsyncIO Exceptions
==================
+8 -1
View File
@@ -364,6 +364,10 @@ class RelationshipProperty(StrategizedProperty):
.. versionadded:: 1.4
.. seealso::
:ref:`error_qzyx` - usage example
:param bake_queries=True:
Use the :class:`.BakedQuery` cache to cache the construction of SQL
used in lazy loads. True by default. Set to False if the
@@ -3423,6 +3427,8 @@ class JoinCondition(object):
and self.prop.key not in pr._overlaps
and not self.prop.parent.is_sibling(pr.parent)
and not self.prop.mapper.is_sibling(pr.mapper)
and not self.prop.parent.is_sibling(pr.mapper)
and not self.prop.mapper.is_sibling(pr.parent)
and (
self.prop.key != pr.key
or not self.prop.parent.common_parent(pr.parent)
@@ -3453,7 +3459,8 @@ class JoinCondition(object):
"'%s' (copies %s to %s)" % (pr, fr_, to_)
for (pr, fr_) in other_props
),
)
),
code="qzyx",
)
self._track_overlapping_sync_targets[to_][self.prop] = from_
+4 -1
View File
@@ -1604,13 +1604,16 @@ class _hash_limit_string(compat.text_type):
return hash(self) == hash(other)
def warn(msg):
def warn(msg, code=None):
"""Issue a warning.
If msg is a string, :class:`.exc.SAWarning` is used as
the category.
"""
if code:
msg = "%s %s" % (msg, exc.SQLAlchemyError(msg, code=code)._code_str())
warnings.warn(msg, exc.SAWarning, stacklevel=2)
+27
View File
@@ -802,6 +802,29 @@ class OverlappingFksSiblingTest(fixtures.TestBase):
configure_mappers()
def _fixture_four(self):
Base = declarative_base(metadata=self.metadata)
class A(Base):
__tablename__ = "a"
id = Column(Integer, primary_key=True)
c_id = Column(ForeignKey("c.id"))
class B1(A):
pass
class B2(A):
pass
class C(Base):
__tablename__ = "c"
id = Column(Integer, primary_key=True)
b1 = relationship(B1, backref="c")
b2 = relationship(B2, backref="c")
@testing.provide_metadata
def _test_fixture_one_run(self, **kw):
A, AMember, B, BSub1, BSub2 = self._fixture_one(**kw)
@@ -853,6 +876,10 @@ class OverlappingFksSiblingTest(fixtures.TestBase):
setup_backrefs=False,
)
@testing.provide_metadata
def test_fixture_four(self):
self._fixture_four()
@testing.provide_metadata
def test_simple_backrefs_works(self):
self._fixture_two(setup_backrefs=True)