]> git.ipfire.org Git - thirdparty/sqlalchemy/sqlalchemy.git/commitdiff
- re-established viewonly relation() configurations that
authorMike Bayer <mike_mp@zzzcomputing.com>
Fri, 11 Apr 2008 15:58:18 +0000 (15:58 +0000)
committerMike Bayer <mike_mp@zzzcomputing.com>
Fri, 11 Apr 2008 15:58:18 +0000 (15:58 +0000)
join across multiple tables.

CHANGES
lib/sqlalchemy/orm/properties.py
test/orm/relationships.py

diff --git a/CHANGES b/CHANGES
index 9abc4a249aaf2aca44faef8512f4bfcdeb632a07..a045b0bf5a21cb3fc16d4405197993c80d3e4697 100644 (file)
--- a/CHANGES
+++ b/CHANGES
@@ -6,9 +6,12 @@ CHANGES
 =====
 - orm
     - Fix to the recent relation() refactoring which fixes
-      exotic relations which join between local and remote table
+      exotic viewonly relations which join between local and remote table
       multiple times, with a common column shared between the 
       joins.
+    
+    - Also re-established viewonly relation() configurations that
+      join across multiple tables.
       
     - removed ancient assertion that mapped selectables require
       "alias names" - the mapper creates its own alias now if
index c7a10ccf35c04ba4951dfed082414a41eb593ab7..c4bb323de5eebf8f4a4a84e3f1fa160b260cff97 100644 (file)
@@ -544,12 +544,12 @@ class PropertyLoader(StrategizedProperty):
         arg_foreign_keys = self.foreign_keys
         
         eq_pairs = criterion_as_pairs(self.primaryjoin, consider_as_foreign_keys=arg_foreign_keys, any_operator=self.viewonly)
-        eq_pairs = [(l, r) for l, r in eq_pairs if self.__col_is_part_of_mappings(l) and self.__col_is_part_of_mappings(r)]
+        eq_pairs = [(l, r) for l, r in eq_pairs if (self.__col_is_part_of_mappings(l) and self.__col_is_part_of_mappings(r)) or r in arg_foreign_keys]
 
         if not eq_pairs:
             if not self.viewonly and criterion_as_pairs(self.primaryjoin, consider_as_foreign_keys=arg_foreign_keys, any_operator=True):
-                raise exceptions.ArgumentError("Could not locate any equated column pairs for primaryjoin condition '%s' on relation %s. "
-                    "If no equated pairs exist, the relation must be marked as viewonly=True." % (self.primaryjoin, self)
+                raise exceptions.ArgumentError("Could not locate any equated, locally mapped column pairs for primaryjoin condition '%s' on relation %s. "
+                    "For more relaxed rules on join conditions, the relation may be marked as viewonly=True." % (self.primaryjoin, self)
                 )
             else:
                 raise exceptions.ArgumentError("Could not determine relation direction for primaryjoin condition '%s', on relation %s. "
@@ -561,12 +561,12 @@ class PropertyLoader(StrategizedProperty):
         
         if self.secondaryjoin:
             sq_pairs = criterion_as_pairs(self.secondaryjoin, consider_as_foreign_keys=arg_foreign_keys, any_operator=self.viewonly)
-            sq_pairs = [(l, r) for l, r in sq_pairs if self.__col_is_part_of_mappings(l) and self.__col_is_part_of_mappings(r)]
+            sq_pairs = [(l, 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 arg_foreign_keys]
             
             if not sq_pairs:
                 if not self.viewonly and criterion_as_pairs(self.secondaryjoin, consider_as_foreign_keys=arg_foreign_keys, any_operator=True):
-                    raise exceptions.ArgumentError("Could not locate any equated column pairs for secondaryjoin condition '%s' on relation %s. "
-                        "If no equated pairs exist, the relation must be marked as viewonly=True." % (self.secondaryjoin, self)
+                    raise exceptions.ArgumentError("Could not locate any equated, locally mapped column pairs for secondaryjoin condition '%s' on relation %s. "
+                        "For more relaxed rules on join conditions, the relation may be marked as viewonly=True." % (self.secondaryjoin, self)
                     )
                 else:
                     raise exceptions.ArgumentError("Could not determine relation direction for secondaryjoin condition '%s', on relation %s. "
@@ -599,6 +599,15 @@ class PropertyLoader(StrategizedProperty):
             self.remote_side, self.local_side = [util.OrderedSet(s) for s in zip(*eq_pairs)]
         else:
             self.local_side, self.remote_side = [util.OrderedSet(s) for s in zip(*eq_pairs)]
+        
+        if self.direction is ONETOMANY:
+            for l in self.local_side:
+                if not self.__col_is_part_of_mappings(l):
+                    raise exceptions.ArgumentError("Local column '%s' is not part of mapping %s.  Specify remote_side argument to indicate which column lazy join condition should compare against." % (l, self.parent))
+        elif self.direction is MANYTOONE:
+            for r in self.remote_side:
+                if not self.__col_is_part_of_mappings(r):
+                    raise exceptions.ArgumentError("Remote column '%s' is not part of mapping %s.  Specify remote_side argument to indicate which column lazy join condition should bind." % (r, self.mapper))
             
     def __determine_direction(self):
         """Determine our *direction*, i.e. do we represent one to
index 6cc24f411a002ba32a4ab52cd55ad196b472247c..22b69af2aa2e0e2c2bbe0e33381aad82240adc4d 100644 (file)
@@ -977,6 +977,81 @@ class ViewOnlyTest5(ORMTest):
         sess.clear()
         self.assertEquals(sess.query(Foo).filter_by(id=f1.id).one(), Foo(bars=[Bar(data='b1'), Bar(data='b2'), Bar(data='b4')]))
         self.assertEquals(sess.query(Foo).filter_by(id=f2.id).one(), Foo(bars=[Bar(data='b3'), Bar(data='b4')]))
+
+class ViewOnlyTest6(ORMTest):
+    """test a long primaryjoin condition"""
+    def define_tables(self, metadata):
+        global t1, t2, t3, t2tot3
+        t1 = Table('t1', metadata, 
+            Column('id', Integer, primary_key=True),
+            Column('data', String(50))
+            )
+        t2 = Table('t2', metadata,     
+            Column('id', Integer, primary_key=True),
+            Column('data', String(50)),
+            Column('t1id', Integer, ForeignKey('t1.id')),
+        )
+        t3 = Table('t3', metadata, 
+            Column('id', Integer, primary_key=True),
+            Column('data', String(50))
+            )
+        t2tot3 = Table('t2tot3', metadata, 
+            Column('t2id', Integer, ForeignKey('t2.id')),
+            Column('t3id', Integer, ForeignKey('t3.id')),
+        )
+    
+    def test_basic(self):
+        class T1(fixtures.Base):
+            pass
+        class T2(fixtures.Base):
+            pass
+        class T3(fixtures.Base):
+            pass
+    
+        mapper(T1, t1, properties={
+            't3s':relation(T3, primaryjoin=and_(
+                t1.c.id==t2.c.t1id,
+                t2.c.id==t2tot3.c.t2id,
+                t3.c.id==t2tot3.c.t3id
+            ),
+            viewonly=True, 
+            foreign_keys=t3.c.id, remote_side=t2.c.t1id)
+        })
+        mapper(T2, t2, properties={
+            't1':relation(T1),
+            't3s':relation(T3, secondary=t2tot3)
+        })
+        mapper(T3, t3)
+        
+        sess = create_session()
+        sess.save(T2(data='t2', t1=T1(data='t1'), t3s=[T3(data='t3')]))
+        sess.flush()
+        sess.clear()
+        
+        a = sess.query(T1).first()
+        self.assertEquals(a.t3s, [T3(data='t3')])
+        
+    def test_remote_side_escalation(self):
+        class T1(fixtures.Base):
+            pass
+        class T2(fixtures.Base):
+            pass
+        class T3(fixtures.Base):
+            pass
+
+        mapper(T1, t1, properties={
+            't3s':relation(T3, primaryjoin=and_(
+                t1.c.id==t2.c.t1id,
+                t2.c.id==t2tot3.c.t2id,
+                t3.c.id==t2tot3.c.t3id
+            ),viewonly=True, foreign_keys=t3.c.id) 
+        })
+        mapper(T2, t2, properties={
+            't1':relation(T1),
+            't3s':relation(T3, secondary=t2tot3)
+        })
+        mapper(T3, t3)
+        self.assertRaisesMessage(exceptions.ArgumentError, "Specify remote_side argument", compile_mappers)
     
 class InvalidRelationEscalationTest(ORMTest):
     def define_tables(self, metadata):
@@ -1018,7 +1093,7 @@ class InvalidRelationEscalationTest(ORMTest):
         })
 
         mapper(Bar, bars)
-        self.assertRaisesMessage(exceptions.ArgumentError, "Could not locate any equated column pairs for primaryjoin condition", compile_mappers)
+        self.assertRaisesMessage(exceptions.ArgumentError, "Could not locate any equated, locally mapped column pairs for primaryjoin condition", compile_mappers)
 
     def test_no_equated_self_ref(self):
         mapper(Foo, foos, properties={
@@ -1034,7 +1109,7 @@ class InvalidRelationEscalationTest(ORMTest):
         })
 
         mapper(Bar, bars)
-        self.assertRaisesMessage(exceptions.ArgumentError, "Could not locate any equated column pairs for primaryjoin condition", compile_mappers)
+        self.assertRaisesMessage(exceptions.ArgumentError, "Could not locate any equated, locally mapped column pairs for primaryjoin condition", compile_mappers)
 
     def test_no_equated_viewonly(self):
         mapper(Foo, foos, properties={
@@ -1130,7 +1205,7 @@ class InvalidRelationEscalationTestM2M(ORMTest):
         })
 
         mapper(Bar, bars)
-        self.assertRaisesMessage(exceptions.ArgumentError, "Could not locate any equated column pairs for secondaryjoin condition", compile_mappers)
+        self.assertRaisesMessage(exceptions.ArgumentError, "Could not locate any equated, locally mapped column pairs for secondaryjoin condition", compile_mappers)
 
 
 if __name__ == "__main__":