From 154a831822c21ade7ff445381b1cacceb717e1e8 Mon Sep 17 00:00:00 2001 From: Mike Bayer Date: Thu, 1 Feb 2007 03:18:08 +0000 Subject: [PATCH] - the "polymorphic_primaryjoin" again goes against the parent's non-polymorphic local table. lazy load clause evaluation is plenty solid enough to handle it this time. - the join_to() method on PropertyLoader takes the parent mapper as an argument and alisiazes the primaryjoin against that mapper's selectable, so that the same primary join can be used against the base mapper, any inheriting mapper, etc., whether or not it uses a polymorphic union (although needs to be tested against alternate polymorphic unions added on subclasses). fixes [ticket:448] --- CHANGES | 2 +- lib/sqlalchemy/ext/selectresults.py | 4 +-- lib/sqlalchemy/orm/properties.py | 40 ++++++++++++++++++----------- lib/sqlalchemy/orm/query.py | 4 +-- lib/sqlalchemy/orm/strategies.py | 2 +- test/orm/inheritance5.py | 15 +++++------ 6 files changed, 38 insertions(+), 29 deletions(-) diff --git a/CHANGES b/CHANGES index 4ff29d3b00..9b12671a58 100644 --- a/CHANGES +++ b/CHANGES @@ -9,7 +9,7 @@ - further rework of the recent polymorphic relationship refactorings, as well as the mechanics of relationships overall. Allows more accurate ORM behavior with relationships from/to/between polymorphic mappers, as well as their usage - with Query, SelectResults. tickets include [ticket:439], [ticket:441]. + with Query, SelectResults. tickets include [ticket:439], [ticket:441], [ticket:448]. relationship mechanics are still a work in progress, more to come ! - eager relation to an inheriting mapper wont fail if no rows returned for the relationship. diff --git a/lib/sqlalchemy/ext/selectresults.py b/lib/sqlalchemy/ext/selectresults.py index 23fd988930..3e7fa93c87 100644 --- a/lib/sqlalchemy/ext/selectresults.py +++ b/lib/sqlalchemy/ext/selectresults.py @@ -134,9 +134,9 @@ class SelectResults(object): for key in keys: prop = mapper.props[key] if outerjoin: - clause = clause.outerjoin(prop.select_table, prop.get_join()) + clause = clause.outerjoin(prop.select_table, prop.get_join(mapper)) else: - clause = clause.join(prop.select_table, prop.get_join()) + clause = clause.join(prop.select_table, prop.get_join(mapper)) mapper = prop.mapper return (clause, mapper) diff --git a/lib/sqlalchemy/orm/properties.py b/lib/sqlalchemy/orm/properties.py index 09c90c6acc..5e8fab807e 100644 --- a/lib/sqlalchemy/orm/properties.py +++ b/lib/sqlalchemy/orm/properties.py @@ -87,6 +87,7 @@ class PropertyLoader(StrategizedProperty): self.collection_class = collection_class self.passive_deletes = passive_deletes self.remote_side = util.to_set(remote_side) + self._parent_join_cache = {} if cascade is not None: self.cascade = mapperutil.CascadeOptions(cascade) @@ -260,7 +261,6 @@ class PropertyLoader(StrategizedProperty): # as we will be using the polymorphic selectables (i.e. select_table argument to Mapper) to figure this out, # first create maps of all the "equivalent" columns, since polymorphic selectables will often munge # several "equivalent" columns (such as parent/child fk cols) into just one column. - parent_equivalents = self.parent._get_inherited_column_equivalents() target_equivalents = self.mapper._get_inherited_column_equivalents() # if the target mapper loads polymorphically, adapt the clauses to the target's selectable @@ -281,15 +281,7 @@ class PropertyLoader(StrategizedProperty): self.polymorphic_primaryjoin = self.primaryjoin.copy_container() self.polymorphic_secondaryjoin = self.secondaryjoin and self.secondaryjoin.copy_container() or None - # if the parent mapper loads polymorphically, adapt the clauses to the parent's selectable - if self.parent.select_table is not self.parent.mapped_table: - if self.direction is sync.ONETOMANY: - self.polymorphic_primaryjoin.accept_visitor(sql_util.ClauseAdapter(self.parent.select_table, exclude=self.foreignkey, equivalents=parent_equivalents)) - elif self.direction is sync.MANYTOONE: - self.polymorphic_primaryjoin.accept_visitor(sql_util.ClauseAdapter(self.parent.select_table, include=self.foreignkey, equivalents=parent_equivalents)) - elif self.secondaryjoin: - self.polymorphic_primaryjoin.accept_visitor(sql_util.ClauseAdapter(self.parent.select_table, exclude=self.foreignkey, equivalents=parent_equivalents)) - + #print "KEY", self.key, "PARENT", str(self.parent) #print "KEY", self.key, "REG PRIMARY JOIN", str(self.primaryjoin) #print "KEY", self.key, "POLY PRIMARY JOIN", str(self.polymorphic_primaryjoin) @@ -372,12 +364,30 @@ class PropertyLoader(StrategizedProperty): raise exceptions.ArgumentError("Cant determine relation direction for '%s' on mapper '%s' with primary join '%s' - no foreign key relationship is expressed within the join condition. Specify 'foreignkey' argument." %(self.key, str(self.parent), str(self.primaryjoin))) self.foreignkey = foreignkeys - def get_join(self): - if self.polymorphic_secondaryjoin is not None: - return self.polymorphic_primaryjoin & self.polymorphic_secondaryjoin - else: - return self.polymorphic_primaryjoin + def get_join(self, parent): + try: + return self._parent_join_cache[parent] + except KeyError: + parent_equivalents = parent._get_inherited_column_equivalents() + primaryjoin = self.polymorphic_primaryjoin.copy_container() + if self.secondaryjoin is not None: + secondaryjoin = self.polymorphic_secondaryjoin.copy_container() + else: + secondaryjoin = None + if self.direction is sync.ONETOMANY: + primaryjoin.accept_visitor(sql_util.ClauseAdapter(parent.select_table, exclude=self.foreignkey, equivalents=parent_equivalents)) + elif self.direction is sync.MANYTOONE: + primaryjoin.accept_visitor(sql_util.ClauseAdapter(parent.select_table, include=self.foreignkey, equivalents=parent_equivalents)) + elif self.secondaryjoin: + primaryjoin.accept_visitor(sql_util.ClauseAdapter(parent.select_table, exclude=self.foreignkey, equivalents=parent_equivalents)) + if secondaryjoin is not None: + j = primaryjoin & secondaryjoin + else: + j = primaryjoin + self._parent_join_cache[parent] = j + return j + def register_dependencies(self, uowcommit): if not self.viewonly: self._dependency_processor.register_dependencies(uowcommit) diff --git a/lib/sqlalchemy/orm/query.py b/lib/sqlalchemy/orm/query.py index adec69116b..09863dbab5 100644 --- a/lib/sqlalchemy/orm/query.py +++ b/lib/sqlalchemy/orm/query.py @@ -186,9 +186,9 @@ class Query(object): for key in keys: prop = mapper.props[key] if clause is None: - clause = prop.get_join() + clause = prop.get_join(mapper) else: - clause &= prop.get_join() + clause &= prop.get_join(mapper) mapper = prop.mapper return clause diff --git a/lib/sqlalchemy/orm/strategies.py b/lib/sqlalchemy/orm/strategies.py index 29b60a8f61..1cdf0b177e 100644 --- a/lib/sqlalchemy/orm/strategies.py +++ b/lib/sqlalchemy/orm/strategies.py @@ -156,7 +156,7 @@ class LazyLoader(AbstractRelationLoader): def init(self): super(LazyLoader, self).init() (self.lazywhere, self.lazybinds, self.lazyreverse) = self._create_lazy_clause( - self.parent.select_table, + self.parent.local_table, self.mapper.select_table, self.polymorphic_primaryjoin, self.polymorphic_secondaryjoin, diff --git a/test/orm/inheritance5.py b/test/orm/inheritance5.py index 49eca2fc3c..8a0ec165f9 100644 --- a/test/orm/inheritance5.py +++ b/test/orm/inheritance5.py @@ -546,14 +546,13 @@ class SelectResultsTest(testbase.AssertMixin): session.save(car2) session.flush() -# for activeCars in SelectResults(session.query(Car)).join_to('status').select(status.c.name=="active"): -# print activeCars - for activePerson in SelectResults(session.query(Person)).join_to('status').select(status.c.name=="active"): - print activePerson -# for activePerson in SelectResults(session.query(Person)).join_to('status').select_by(name="active"): -# print activePerson - - + # test these twice because theres caching involved + for x in range(0, 2): + r = SelectResults(session.query(Person)).select_by(people.c.name.like('%2')).join_to('status').select_by(name="active") + assert str(list(r)) == "[Manager M2, category YYYYYYYYY, status Status active, Engineer E2, field X, status Status active]" + r = SelectResults(session.query(Engineer)).join_to('status').select(people.c.name.in_('E2', 'E3', 'E4', 'M4', 'M2', 'M1') & (status.c.name=="active")) + assert str(list(r)) == "[Engineer E2, field X, status Status active, Engineer E3, field X, status Status active]" + class MultiLevelTest(testbase.ORMTest): def define_tables(self, metadata): global table_Employee, table_Engineer, table_Manager -- 2.47.2