make a reasonable assumption about primary/secondary with m2m

Issue #1877 resolved
Mike Bayer repo owner created an issue

if foreign_keys is specified with "secondary", emit a warning. because, why are they doing that.

then, remove most mention of "foreign_keys" from these messages - the ForeignKeys should be on table metadata. don't encourage working around it.

then, do this:

diff -r 252ab17c7dc5f1cfd1a5173fb6d9e4b819e1ebb7 lib/sqlalchemy/orm/properties.py
--- a/lib/sqlalchemy/orm/properties.py  Fri Aug 13 14:25:58 2010 -0400
+++ b/lib/sqlalchemy/orm/properties.py  Fri Aug 13 14:26:42 2010 -0400
@@ -1023,6 +1023,16 @@
                     self.synchronize_pairs.append((l, r))
                 elif l in self._foreign_keys:
                     self.synchronize_pairs.append((r, l))
+        elif self.secondary is not None:
+            fks = self._foreign_keys or set(self.secondary.c)
+            eq_pairs = criterion_as_pairs(self.primaryjoin,
+                    consider_as_foreign_keys=fks,
+                    any_operator=self.viewonly)
+            eq_pairs = [r) for (l, r) in eq_pairs
+                        if self._col_is_part_of_mappings(l)
+                        and self._col_is_part_of_mappings(r)
+                        or self.viewonly and r in fks]((l,)
+            self.synchronize_pairs = eq_pairs
         else:
             eq_pairs = criterion_as_pairs(self.primaryjoin,
                     consider_as_foreign_keys=self._foreign_keys,
@@ -1063,13 +1073,14 @@
                                 "foreign." % (self.primaryjoin, self))
             self.synchronize_pairs = eq_pairs
         if self.secondaryjoin is not None:
+            fks = self._foreign_keys or set(self.secondary.c)
             sq_pairs = criterion_as_pairs(self.secondaryjoin,
-                    consider_as_foreign_keys=self._foreign_keys,
+                    consider_as_foreign_keys=fks,
                     any_operator=self.viewonly)
             sq_pairs = [r) for (l, r) in sq_pairs
                         if self._col_is_part_of_mappings(l)
                         and self._col_is_part_of_mappings(r) or r
-                        in self._foreign_keys]((l,)
+                        in fks]
             if not sq_pairs:
                 if not self.viewonly \
                     and criterion_as_pairs(self.secondaryjoin,

try to find any possible weird setup where the foreign keys collection shouldn't be limited to the secondary table. It certainly wasn't meant to work any other way. We should not have anyone confused by things like this.

Comments (4)

  1. Mike Bayer reporter

    it seems so far like it's impossible anyone is making usage of "secondary" with fks on the other side. the test below fails for both the persistence and the query side, hitting codepaths that are assuming fks are on "secondary". trace out these paths to ensure this is the case.

    from sqlalchemy import *
    
    from sqlalchemy.ext.declarative import declarative_base
    from sqlalchemy.orm import *
    
    Base = declarative_base()
    metadata = Base.metadata
    
    assoc = Table('assoc', metadata,
        Column('id1', Integer, primary_key=True),
        Column('id2', Integer, primary_key=True)
    )
    
    class Foo(Base):
        __tablename__ ='foo'
        id = Column(Integer, primary_key=True)
    
        assoc_id = Column(Integer, ForeignKey('assoc.id1'))
    
        bars = relationship("Bar", secondary=assoc)
    
    class Bar(Base):
        __tablename__ ='bar'
        id = Column(Integer, primary_key=True)
    
        assoc_id = Column(Integer, ForeignKey('assoc.id2'))
    
    compile_mappers()
    
    engine = create_engine('sqlite://', echo=True)
    
    metadata.create_all(engine)
    
    session = sessionmaker(engine)()
    
    # doesn't work
    session.execute(assoc.insert().values(id1=1, id2=1))
    
    f1 = Foo(assoc_id=1)
    f1.bars = [Bar(assoc_id=1)](Bar(assoc_id=1),)
    session.add(f1)
    session.commit()
    
    session.execute(assoc.insert().values(id1=1, id2=1))
    session.execute(Foo.__table__.insert().values(id=1, assoc_id=1))
    session.execute(Bar.__table__.insert().values(id=1, assoc_id=1))
    
    # doens't work
    f = session.query(Foo).first()
    print f.bars
    
  2. Log in to comment