From: Mike Bayer Date: Sun, 25 Sep 2022 18:56:22 +0000 (-0400) Subject: warn for local-only column in remote side X-Git-Tag: rel_1_4_42~19 X-Git-Url: http://git.ipfire.org/cgi-bin/gitweb.cgi?a=commitdiff_plain;h=e05a8464c120fc7d2e3776b5a70fefbbe48be81c;p=thirdparty%2Fsqlalchemy%2Fsqlalchemy.git warn for local-only column in remote side A warning is emitted in ORM configurations when an explicit :func:`_orm.remote` annotation is applied to columns that are local to the immediate mapped class, when the referenced class does not include any of the same table columns. Ideally this would raise an error at some point as it's not correct from a mapping point of view. Fixes: #7094 Fixes: #8575 Change-Id: Ia31be24aebe143161e19dc311b52c08fd5014d33 (cherry picked from commit 29838ef584d49e5ecca08f76e4966454dc7f060f) --- diff --git a/doc/build/changelog/unreleased_14/7094.rst b/doc/build/changelog/unreleased_14/7094.rst new file mode 100644 index 0000000000..b6fb30d998 --- /dev/null +++ b/doc/build/changelog/unreleased_14/7094.rst @@ -0,0 +1,9 @@ +.. change:: + :tags: bug, orm + :tickets: 7094 + + A warning is emitted in ORM configurations when an explicit + :func:`_orm.remote` annotation is applied to columns that are local to the + immediate mapped class, when the referenced class does not include any of + the same table columns. Ideally this would raise an error at some point as + it's not correct from a mapping point of view. diff --git a/lib/sqlalchemy/orm/relationships.py b/lib/sqlalchemy/orm/relationships.py index b51ea0e009..9a6cfb68cc 100644 --- a/lib/sqlalchemy/orm/relationships.py +++ b/lib/sqlalchemy/orm/relationships.py @@ -3195,6 +3195,22 @@ class JoinCondition(object): "condition that are on the remote side of " "the relationship." % (self.prop,) ) + else: + + not_target = util.column_set( + self.parent_persist_selectable.c + ).difference(self.child_persist_selectable.c) + + for _, rmt in self.local_remote_pairs: + if rmt in not_target: + util.warn( + "Expression %s is marked as 'remote', but these " + "column(s) are local to the local side. The " + "remote() annotation is needed only for a " + "self-referential relationship where both sides " + "of the relationship refer to the same tables." + % (rmt,) + ) def _check_foreign_cols(self, join_condition, primary): """Check the foreign key columns collected and emit error diff --git a/test/orm/test_cycles.py b/test/orm/test_cycles.py index 9d0369191e..1d749cac97 100644 --- a/test/orm/test_cycles.py +++ b/test/orm/test_cycles.py @@ -918,7 +918,6 @@ class OneToManyManyToOneTest(fixtures.MappedTest): favorite=relationship( Ball, primaryjoin=person.c.favorite_ball_id == ball.c.id, - remote_side=person.c.favorite_ball_id, post_update=True, _legacy_inactive_history_style=( self._legacy_inactive_history_style @@ -1036,7 +1035,6 @@ class OneToManyManyToOneTest(fixtures.MappedTest): favorite=relationship( Ball, primaryjoin=person.c.favorite_ball_id == ball.c.id, - remote_side=person.c.favorite_ball_id, _legacy_inactive_history_style=( self._legacy_inactive_history_style ), @@ -1096,7 +1094,6 @@ class OneToManyManyToOneTest(fixtures.MappedTest): favorite=relationship( Ball, primaryjoin=person.c.favorite_ball_id == ball.c.id, - remote_side=person.c.favorite_ball_id, _legacy_inactive_history_style=( self._legacy_inactive_history_style ), diff --git a/test/orm/test_relationships.py b/test/orm/test_relationships.py index 1dc5b37fd2..e98068eddb 100644 --- a/test/orm/test_relationships.py +++ b/test/orm/test_relationships.py @@ -2588,6 +2588,55 @@ class JoinConditionErrorTest(fixtures.TestBase): registry.map_imperatively(C2, t3) assert C1.c2.property.primaryjoin.compare(t1.c.id == t3.c.t1id) + @testing.combinations( + "annotation", "local_remote", argnames="remote_anno_type" + ) + @testing.combinations("orm_col", "core_col", argnames="use_col_from") + def test_no_remote_on_local_only_cols( + self, decl_base, remote_anno_type, use_col_from + ): + """test #7094. + + a warning should be emitted for an inappropriate remote_side argument + + """ + + class A(decl_base): + __tablename__ = "a" + + id = Column(Integer, primary_key=True) + data = Column(String) + + if remote_anno_type == "annotation": + if use_col_from == "core_col": + bs = relationship( + "B", + primaryjoin=lambda: remote(A.__table__.c.id) + == B.__table__.c.a_id, + ) + elif use_col_from == "orm_col": + bs = relationship( + "B", primaryjoin="remote(A.id) == B.a_id" + ) + elif remote_anno_type == "local_remote": + if use_col_from == "core_col": + bs = relationship( + "B", remote_side=lambda: A.__table__.c.id + ) + elif use_col_from == "orm_col": + bs = relationship("B", remote_side="A.id") + + class B(decl_base): + __tablename__ = "b" + id = Column(Integer, primary_key=True) + a_id = Column(ForeignKey("a.id")) + + with expect_warnings( + r"Expression a.id is marked as 'remote', but these column\(s\) " + r"are local to the local side. " + ): + decl_base.registry.configure() + def test_join_error_raised(self, registry): m = MetaData() t1 = Table("t1", m, Column("id", Integer, primary_key=True))