From 8fa3becd5fac57bb898a0090bafaac377b60f070 Mon Sep 17 00:00:00 2001 From: Mike Bayer Date: Sun, 25 Feb 2007 01:27:15 +0000 Subject: [PATCH] - more fixes to polymorphic relations, involving proper lazy-clause generation on many-to-one relationships to polymorphic mappers [ticket:493] --- CHANGES | 4 ++ lib/sqlalchemy/orm/properties.py | 11 +++- test/orm/inheritance5.py | 108 ++++++++++++++++++++++++++++++- 3 files changed, 118 insertions(+), 5 deletions(-) diff --git a/CHANGES b/CHANGES index 440d91a4ee..9c7d45f29e 100644 --- a/CHANGES +++ b/CHANGES @@ -8,6 +8,10 @@ will be used as is (i.e. no query is compiled). works similarly to sending the results to instances(). - added "refresh-expire" cascade [ticket:492] + - more fixes to polymorphic relations, involving proper lazy-clause + generation on many-to-one relationships to polymorphic mappers + [ticket:493] + 0.3.5 - sql: - the value of "case_sensitive" defaults to True now, regardless of the diff --git a/lib/sqlalchemy/orm/properties.py b/lib/sqlalchemy/orm/properties.py index cd9624e96b..930ea10413 100644 --- a/lib/sqlalchemy/orm/properties.py +++ b/lib/sqlalchemy/orm/properties.py @@ -330,10 +330,15 @@ class PropertyLoader(StrategizedProperty): elif self.direction is sync.MANYTOONE: self.polymorphic_primaryjoin.accept_visitor(sql_util.ClauseAdapter(self.mapper.select_table, exclude=self.foreign_keys, equivalents=target_equivalents)) self.polymorphic_secondaryjoin = None + # load "polymorphic" versions of the columns present in "remote_side" - this is + # important for lazy-clause generation which goes off the polymorphic target selectable for c in list(self.remote_side): - corr = self.mapper.select_table.corresponding_column(c, raiseerr=False) + corr = self.mapper.select_table.corresponding_column(c, raiseerr=False) or \ + (c in target_equivalents and self.mapper.select_table.corresponding_column(target_equivalents[c], raiseerr=False)) if corr: self.remote_side.add(corr) + else: + raise exceptions.AssertionError("Could not find corresponding column for " + str(c) + " in selectable " + str(self.mapper.select_table)) else: self.polymorphic_primaryjoin = self.primaryjoin.copy_container() self.polymorphic_secondaryjoin = self.secondaryjoin and self.secondaryjoin.copy_container() or None @@ -342,8 +347,8 @@ class PropertyLoader(StrategizedProperty): if logging.is_info_enabled(self.logger): self.logger.info(str(self) + " setup primary join " + str(self.primaryjoin)) self.logger.info(str(self) + " setup polymorphic primary join " + str(self.polymorphic_primaryjoin)) - self.logger.info(str(self) + " foreign keys " + str([c.key for c in self.foreign_keys])) - self.logger.info(str(self) + " remote columns " + str([c.key for c in self.remote_side])) + self.logger.info(str(self) + " foreign keys " + str([str(c) for c in self.foreign_keys])) + self.logger.info(str(self) + " remote columns " + str([str(c) for c in self.remote_side])) self.logger.info(str(self) + " relation direction " + (self.direction is sync.ONETOMANY and "one-to-many" or (self.direction is sync.MANYTOONE and "many-to-one" or "many-to-many"))) if self.uselist is None and self.direction is sync.MANYTOONE: diff --git a/test/orm/inheritance5.py b/test/orm/inheritance5.py index b81ae47a97..d25d630351 100644 --- a/test/orm/inheritance5.py +++ b/test/orm/inheritance5.py @@ -348,9 +348,12 @@ class RelationTest4(testbase.ORMTest): session.flush() engineer4 = session.query(Engineer).selectfirst_by(name="E4") - - car1 = Car(owner=engineer4.person_id) + manager3 = session.query(Manager).selectfirst_by(name="M3") + + car1 = Car(employee=engineer4) session.save(car1) + car2 = Car(employee=manager3) + session.save(car2) session.flush() session.clear() @@ -478,6 +481,107 @@ class RelationTest6(testbase.ORMTest): m2 = sess.query(Manager).get(m2.person_id) assert m.colleague is m2 +class RelationTest7(testbase.ORMTest): + def define_tables(self, metadata): + global people, engineers, managers, cars, offroad_cars + cars = Table('cars', metadata, + Column('car_id', Integer, primary_key=True), + Column('name', String(30))) + + offroad_cars = Table('offroad_cars', metadata, + Column('car_id',Integer, ForeignKey('cars.car_id'),nullable=False,primary_key=True)) + + people = Table('people', metadata, + Column('person_id', Integer, primary_key=True), + Column('car_id', Integer, ForeignKey('cars.car_id'), nullable=False), + Column('name', String(50))) + + engineers = Table('engineers', metadata, + Column('person_id', Integer, ForeignKey('people.person_id'), primary_key=True), + Column('field', String(30))) + + + managers = Table('managers', metadata, + Column('person_id', Integer, ForeignKey('people.person_id'), primary_key=True), + Column('category', String(70))) + + def test_manytoone_lazyload(self): + """test that lazy load clause to a polymorphic child mapper generates correctly [ticket:493]""" + class PersistentObject(object): + def __init__(self, **kwargs): + for key, value in kwargs.iteritems(): + setattr(self, key, value) + + class Status(PersistentObject): + def __repr__(self): + return "Status %s" % self.name + + class Person(PersistentObject): + def __repr__(self): + return "Ordinary person %s" % self.name + + class Engineer(Person): + def __repr__(self): + return "Engineer %s, field %s" % (self.name, self.field) + + class Manager(Person): + def __repr__(self): + return "Manager %s, category %s" % (self.name, self.category) + + class Car(PersistentObject): + def __repr__(self): + return "Car number %d, name %s" % i(self.car_id, self.name) + + class Offraod_Car(Car): + def __repr__(self): + return "Offroad Car number %d, name %s" % (self.car_id,self.name) + + employee_join = polymorphic_union( + { + 'engineer':people.join(engineers), + 'manager':people.join(managers), + }, "type", 'employee_join') + + car_join = polymorphic_union( + { + 'car' : cars.select(offroad_cars.c.car_id == None, from_obj=[cars.outerjoin(offroad_cars)]), + # cant do this one because "car_id" from both tables conflicts on pg +# 'car' : cars.outerjoin(offroad_cars).select(offroad_cars.c.car_id == None), + 'offroad' : cars.join(offroad_cars) + }, "type", 'car_join') + + car_mapper = mapper(Car, cars, + select_table=car_join,polymorphic_on=car_join.c.type, + polymorphic_identity='car', + ) + offroad_car_mapper = mapper(Offraod_Car, offroad_cars, inherits=car_mapper, polymorphic_identity='offroad') + person_mapper = mapper(Person, people, + select_table=employee_join,polymorphic_on=employee_join.c.type, + polymorphic_identity='person', + properties={ + 'car':relation(car_mapper) + }) + engineer_mapper = mapper(Engineer, engineers, inherits=person_mapper, polymorphic_identity='engineer') + manager_mapper = mapper(Manager, managers, inherits=person_mapper, polymorphic_identity='manager') + + session = create_session() + basic_car=Car(name="basic") + offroad_car=Offraod_Car(name="offroad") + + for i in range(1,4): + if i%2: + car=Car() + else: + car=Offraod_Car() + session.save(Manager(name="M%d" % i,category="YYYYYYYYY",car=car)) + session.save(Engineer(name="E%d" % i,field="X",car=car)) + session.flush() + session.clear() + + r = session.query(Person).select() + for p in r: + assert p.car_id == p.car.car_id + class SelectResultsTest(testbase.AssertMixin): def setUpAll(self): # cars---owned by--- people (abstract) --- has a --- status -- 2.47.2