]> git.ipfire.org Git - thirdparty/sqlalchemy/sqlalchemy.git/commitdiff
- the usual adjustments to relationships between inheriting mappers,
authorMike Bayer <mike_mp@zzzcomputing.com>
Fri, 27 Apr 2007 17:19:39 +0000 (17:19 +0000)
committerMike Bayer <mike_mp@zzzcomputing.com>
Fri, 27 Apr 2007 17:19:39 +0000 (17:19 +0000)
in this case establishing relation()s to subclass mappers where
the join conditions come from the superclass' table
- specifically, places where PropertyLoader limits its search to mapper.local_table had to be expanded
to search separately within mapper.mapped_table as well.  in the case of determining primary/secondaryjoin, it starts more specifically first with local table then out to mapped table if not found.  in the case of determining direction, it starts more generally with mapped table, then if ambiguous direction, goes to the local tables.

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

diff --git a/CHANGES b/CHANGES
index 07c65aac85afe4f99bf34c4292a754f2e3272889..c9c66e42ff988ebcd77ac2f03c32a5baea30a8b6 100644 (file)
--- a/CHANGES
+++ b/CHANGES
@@ -97,6 +97,9 @@
       [ticket:533]
     - making progress with session.merge() as well as combining its
       usage with entity_name [ticket:543]
+    - the usual adjustments to relationships between inheriting mappers,
+      in this case establishing relation()s to subclass mappers where
+      the join conditions come from the superclass' table
 - informix:
     - informix support added !  courtesy James Zhang, who put a ton
       of effort in.
index 51fb8fb2eb510186c2c7accb52c988a5e2bbf7b3..171021da824b53733636966aac42566c507471ae 100644 (file)
@@ -216,17 +216,28 @@ class PropertyLoader(StrategizedProperty):
         if self.secondaryjoin is not None and self.secondary is None:
             raise exceptions.ArgumentError("Property '" + self.key + "' specified with secondary join condition but no secondary argument")
         # if join conditions were not specified, figure them out based on foreign keys
+        
+        def _search_for_join(mapper, table):
+            """find a join between the given mapper's mapped table and the given table.
+            will try the mapper's local table first for more specificity, then if not 
+            found will try the more general mapped table, which in the case of inheritance
+            is a join."""
+            try:
+                return sql.join(mapper.local_table, table)
+            except exceptions.ArgumentError, e:
+                return sql.join(mapper.mapped_table, table)
+        
         try:
             if self.secondary is not None:
                 if self.secondaryjoin is None:
-                    self.secondaryjoin = sql.join(self.mapper.unjoined_table, self.secondary).onclause
+                    self.secondaryjoin = _search_for_join(self.mapper, self.secondary).onclause
                 if self.primaryjoin is None:
-                    self.primaryjoin = sql.join(self.parent.unjoined_table, self.secondary).onclause
+                    self.primaryjoin = _search_for_join(self.parent, self.secondary).onclause
             else:
                 if self.primaryjoin is None:
-                    self.primaryjoin = sql.join(self.parent.unjoined_table, self.target).onclause
+                    self.primaryjoin = _search_for_join(self.parent, self.target).onclause
         except exceptions.ArgumentError, e:
-            raise exceptions.ArgumentError("Error determining primary and/or secondary join for relationship '%s'.  If the underlying error cannot be corrected, you should specify the 'primaryjoin' (and 'secondaryjoin', if there is an association table present) keyword arguments to the relation() function (or for backrefs, by specifying the backref using the backref() function with keyword arguments) to explicitly specify the join conditions.  Nested error is \"%s\"" % (str(self), str(e)))
+            raise exceptions.ArgumentError("""Error determining primary and/or secondary join for relationship '%s'. If the underlying error cannot be corrected, you should specify the 'primaryjoin' (and 'secondaryjoin', if there is an association table present) keyword arguments to the relation() function (or for backrefs, by specifying the backref using the backref() function with keyword arguments) to explicitly specify the join conditions. Nested error is \"%s\"""" % (str(self), str(e)))
 
         # if using polymorphic mapping, the join conditions must be agasint the base tables of the mappers,
         # as the loader strategies expect to be working with those now (they will adapt the join conditions
@@ -246,10 +257,10 @@ class PropertyLoader(StrategizedProperty):
 
         def col_is_part_of_mappings(col):
             if self.secondary is None:
-                return self.parent.unjoined_table.corresponding_column(col, raiseerr=False) is not None or \
+                return self.parent.mapped_table.corresponding_column(col, raiseerr=False) is not None or \
                     self.target.corresponding_column(col, raiseerr=False) is not None
             else:
-                return self.parent.unjoined_table.corresponding_column(col, raiseerr=False) is not None or \
+                return self.parent.mapped_table.corresponding_column(col, raiseerr=False) is not None or \
                     self.target.corresponding_column(col, raiseerr=False) is not None or \
                     self.secondary.corresponding_column(col, raiseerr=False) is not None
 
@@ -326,24 +337,29 @@ class PropertyLoader(StrategizedProperty):
             else:
                 self.direction = sync.ONETOMANY
         else:
-            onetomany = len([c for c in self.foreign_keys if self.mapper.unjoined_table.c.contains_column(c)])
-            manytoone = len([c for c in self.foreign_keys if self.parent.unjoined_table.c.contains_column(c)])
-
-            if not onetomany and not manytoone:
-                raise exceptions.ArgumentError(
-                    "Can't determine relation direction for relationship '%s' "
-                    "- foreign key columns are not present in neither the "
-                    "parent nor the child's mapped tables" %(str(self)))
-            elif onetomany and manytoone:
+            for mappedtable, parenttable in [(self.mapper.mapped_table, self.parent.mapped_table), (self.mapper.local_table, self.parent.local_table)]:
+                onetomany = len([c for c in self.foreign_keys if mappedtable.c.contains_column(c)])
+                manytoone = len([c for c in self.foreign_keys if parenttable.c.contains_column(c)])
+
+                if not onetomany and not manytoone:
+                    raise exceptions.ArgumentError(
+                        "Can't determine relation direction for relationship '%s' "
+                        "- foreign key columns are present in neither the "
+                        "parent nor the child's mapped tables" %(str(self)))
+                elif onetomany and manytoone:
+                    continue
+                elif onetomany:
+                    self.direction = sync.ONETOMANY
+                    break
+                elif manytoone:
+                    self.direction = sync.MANYTOONE
+                    break
+            else:
                 raise exceptions.ArgumentError(
                     "Can't determine relation direction for relationship '%s' "
                     "- foreign key columns are present in both the parent and "
                     "the child's mapped tables.  Specify 'foreign_keys' "
                     "argument." % (str(self)))
-            elif onetomany:
-                self.direction = sync.ONETOMANY
-            elif manytoone:
-                self.direction = sync.MANYTOONE
 
     def _determine_remote_side(self):
         if len(self.remote_side):
index d2bd814d1e71f0463be846587d894a0400298920..9d886cf3f1f86dc3e315550a715c5406276cf5d6 100644 (file)
@@ -158,6 +158,40 @@ class InsertOrderTest(PolymorphTest):
 
         assert [e.get_name() for e in c.employees] == ['pointy haired boss', 'dilbert', 'joesmith', 'wally', 'jsmith']
 
+class RelationToSubclassTest(PolymorphTest):
+    def testrelationtosubclass(self):
+        """test a relation to an inheriting mapper where the relation is to a subclass
+        but the join condition is expressed by the parent table.  
+        
+        also test that backrefs work in this case.
+        
+        this test touches upon a lot of the join/foreign key determination code in properties.py
+        and creates the need for properties.py to search for conditions individually within 
+        the mapper's local table as well as the mapper's 'mapped' table, so that relations
+        requiring lots of specificity (like self-referential joins) as well as relations requiring
+        more generalization (like the example here) both come up with proper results."""
+        
+        mapper(Person, people)
+        
+        mapper(Engineer, engineers, inherits=Person)
+        mapper(Manager, managers, inherits=Person)
+
+        mapper(Company, companies, properties={
+            'managers': relation(Manager, lazy=True,backref="company")
+        })
+        
+        sess = create_session()
+
+        c = Company(name='company1')
+        c.managers.append(Manager(status='AAB', manager_name='manager1', name='pointy haired boss'))
+        sess.save(c)
+        sess.flush()
+        sess.clear()
+
+        sess.query(Company).get_by(company_id=c.company_id)
+        assert sets.Set([e.get_name() for e in c.managers]) == sets.Set(['pointy haired boss'])
+        assert c.managers[0].company is c
+        
 def generate_round_trip_test(include_base=False, lazy_relation=True, redefine_colprop=False, use_literal_join=False):
     """generates a round trip test.