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
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
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):
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.