From 089e9ce5978d081e9cb579b0298042a0efb57edd Mon Sep 17 00:00:00 2001 From: Mike Bayer Date: Fri, 10 Feb 2012 17:50:15 -0500 Subject: [PATCH] - move properties to use the new create_joins - fix up subquery eager loading --- lib/sqlalchemy/orm/properties.py | 197 ++++++++++------------------ lib/sqlalchemy/orm/relationships.py | 3 +- lib/sqlalchemy/orm/strategies.py | 23 +--- test/orm/test_rel_fn.py | 10 +- 4 files changed, 79 insertions(+), 154 deletions(-) diff --git a/lib/sqlalchemy/orm/properties.py b/lib/sqlalchemy/orm/properties.py index 38237b2d4b..f7a979d0e3 100644 --- a/lib/sqlalchemy/orm/properties.py +++ b/lib/sqlalchemy/orm/properties.py @@ -920,6 +920,61 @@ class RelationshipProperty(StrategizedProperty): self._generate_backref() super(RelationshipProperty, self).do_init() + def _process_dependent_arguments(self): + """Convert incoming configuration arguments to their + proper form. + + Callables are resolved, ORM annotations removed. + + """ + # accept callables for other attributes which may require + # deferred initialization. This technique is used + # by declarative "string configs" and some recipes. + for attr in ( + 'order_by', 'primaryjoin', 'secondaryjoin', + 'secondary', '_user_defined_foreign_keys', 'remote_side', + ): + attr_value = getattr(self, attr) + if util.callable(attr_value): + setattr(self, attr, attr_value()) + + # remove "annotations" which are present if mapped class + # descriptors are used to create the join expression. + for attr in 'primaryjoin', 'secondaryjoin': + val = getattr(self, attr) + if val is not None: + setattr(self, attr, _orm_deannotate( + expression._only_column_elements(val, attr)) + ) + + # ensure expressions in self.order_by, foreign_keys, + # remote_side are all columns, not strings. + if self.order_by is not False and self.order_by is not None: + self.order_by = [ + expression._only_column_elements(x, "order_by") + for x in + util.to_list(self.order_by)] + + self._user_defined_foreign_keys = \ + util.column_set( + expression._only_column_elements(x, "foreign_keys") + for x in util.to_column_set( + self._user_defined_foreign_keys + )) + + self.remote_side = \ + util.column_set( + expression._only_column_elements(x, "remote_side") + for x in + util.to_column_set(self.remote_side)) + + self.target = self.mapper.mapped_table + + if self.cascade.delete_orphan: + self.mapper.primary_mapper().delete_orphans.append( + (self.key, self.parent.class_) + ) + def _setup_join_conditions(self): self._join_condition = jc = relationships.JoinCondition( parent_selectable=self.parent.mapped_table, @@ -944,6 +999,7 @@ class RelationshipProperty(StrategizedProperty): self.direction = jc.direction self.local_remote_pairs = jc.local_remote_pairs self.remote_side = jc.remote_columns + self.local_columns = jc.local_columns self.synchronize_pairs = jc.synchronize_pairs self._calculated_foreign_keys = jc.foreign_key_columns self.secondary_synchronize_pairs = jc.secondary_synchronize_pairs @@ -975,64 +1031,6 @@ class RelationshipProperty(StrategizedProperty): "cause dependency issues during flush" % (self.key, self.parent, inheriting)) - def _process_dependent_arguments(self): - """Convert incoming configuration arguments to their - proper form. - - Callables are resolved, ORM annotations removed. - - """ - # accept callables for other attributes which may require - # deferred initialization. This technique is used - # by declarative "string configs" and some recipes. - for attr in ( - 'order_by', - 'primaryjoin', - 'secondaryjoin', - 'secondary', - '_user_defined_foreign_keys', - 'remote_side', - ): - attr_value = getattr(self, attr) - if util.callable(attr_value): - setattr(self, attr, attr_value()) - - # remove "annotations" which are present if mapped class - # descriptors are used to create the join expression. - for attr in 'primaryjoin', 'secondaryjoin': - val = getattr(self, attr) - if val is not None: - setattr(self, attr, _orm_deannotate( - expression._only_column_elements(val, attr)) - ) - - # ensure expressions in self.order_by, foreign_keys, - # remote_side are all columns, not strings. - if self.order_by is not False and self.order_by is not None: - self.order_by = [ - expression._only_column_elements(x, "order_by") - for x in - util.to_list(self.order_by)] - - self._user_defined_foreign_keys = \ - util.column_set( - expression._only_column_elements(x, "foreign_keys") - for x in util.to_column_set( - self._user_defined_foreign_keys - )) - - self.remote_side = \ - util.column_set( - expression._only_column_elements(x, "remote_side") - for x in - util.to_column_set(self.remote_side)) - - self.target = self.mapper.mapped_table - - if self.cascade.delete_orphan: - self.mapper.primary_mapper().delete_orphans.append( - (self.key, self.parent.class_) - ) def _check_cascade_settings(self): if self.cascade.delete_orphan and not self.single_parent \ @@ -1098,14 +1096,11 @@ class RelationshipProperty(StrategizedProperty): kwargs.setdefault('passive_updates', self.passive_updates) self.back_populates = backref_key relationship = RelationshipProperty( - parent, - self.secondary, - pj, - sj, + parent, self.secondary, + pj, sj, foreign_keys=foreign_keys, back_populates=self.key, - **kwargs - ) + **kwargs) mapper._configure_property(backref_key, relationship) if self.back_populates: @@ -1155,78 +1150,22 @@ class RelationshipProperty(StrategizedProperty): else: aliased = True - # place a barrier on the destination such that - # replacement traversals won't ever dig into it. - # its internal structure remains fixed - # regardless of context. - dest_selectable = _shallow_annotate( - dest_selectable, - {'no_replacement_traverse':True}) - - aliased = aliased or (source_selectable is not None) - - primaryjoin, secondaryjoin, secondary = self.primaryjoin, \ - self.secondaryjoin, self.secondary - - # adjust the join condition for single table inheritance, - # in the case that the join is to a subclass - # this is analogous to the "_adjust_for_single_table_inheritance()" - # method in Query. - dest_mapper = of_type or self.mapper single_crit = dest_mapper._single_table_criterion - if single_crit is not None: - if secondaryjoin is not None: - secondaryjoin = secondaryjoin & single_crit - else: - primaryjoin = primaryjoin & single_crit - - if aliased: - if secondary is not None: - secondary = secondary.alias() - primary_aliasizer = ClauseAdapter(secondary) - secondary_aliasizer = \ - ClauseAdapter(dest_selectable, - equivalents=self.mapper._equivalent_columns).\ - chain(primary_aliasizer) - if source_selectable is not None: - primary_aliasizer = \ - ClauseAdapter(secondary).\ - chain(ClauseAdapter(source_selectable, - equivalents=self.parent._equivalent_columns)) - secondaryjoin = \ - secondary_aliasizer.traverse(secondaryjoin) - else: - primary_aliasizer = ClauseAdapter(dest_selectable, - #exclude=self.local_side, - exclude_fn=lambda c: "local" in c._annotations, - equivalents=self.mapper._equivalent_columns) - if source_selectable is not None: - primary_aliasizer.chain( - ClauseAdapter(source_selectable, - #exclude=self.remote_side, - exclude_fn=lambda c: "remote" in c._annotations, - equivalents=self.parent._equivalent_columns)) - secondary_aliasizer = None - - primaryjoin = primary_aliasizer.traverse(primaryjoin) - target_adapter = secondary_aliasizer or primary_aliasizer - target_adapter.include = target_adapter.exclude = target_adapter.exclude_fn = None - else: - target_adapter = None + aliased = aliased or (source_selectable is not None) + + primaryjoin, secondaryjoin, secondary, target_adapter, dest_selectable = \ + self._join_condition.join_targets( + source_selectable, dest_selectable, aliased, single_crit + ) if source_selectable is None: source_selectable = self.parent.local_table if dest_selectable is None: dest_selectable = self.mapper.local_table - return ( - primaryjoin, - secondaryjoin, - source_selectable, - dest_selectable, - secondary, - target_adapter, - ) + return (primaryjoin, secondaryjoin, source_selectable, + dest_selectable, secondary, target_adapter) + PropertyLoader = RelationProperty = RelationshipProperty log.class_logger(RelationshipProperty) diff --git a/lib/sqlalchemy/orm/relationships.py b/lib/sqlalchemy/orm/relationships.py index 515ca773ba..413397fda1 100644 --- a/lib/sqlalchemy/orm/relationships.py +++ b/lib/sqlalchemy/orm/relationships.py @@ -599,7 +599,8 @@ class JoinCondition(object): target_adapter.exclude_fn = None else: target_adapter = None - return primaryjoin, secondaryjoin, secondary, target_adapter + return primaryjoin, secondaryjoin, secondary, target_adapter, dest_selectable + ################# everything below is TODO ################################ diff --git a/lib/sqlalchemy/orm/strategies.py b/lib/sqlalchemy/orm/strategies.py index 3202342819..00739ea03b 100644 --- a/lib/sqlalchemy/orm/strategies.py +++ b/lib/sqlalchemy/orm/strategies.py @@ -785,8 +785,8 @@ class SubqueryLoader(AbstractRelationshipLoader): leftmost_mapper, leftmost_prop = \ subq_mapper, \ subq_mapper._props[subq_path[1]] - # TODO: local cols might not be unique here - leftmost_cols, remote_cols = self._local_remote_columns(leftmost_prop) + + leftmost_cols = leftmost_prop.local_columns leftmost_attr = [ leftmost_mapper._columntoproperty[c].class_attribute @@ -847,9 +847,7 @@ class SubqueryLoader(AbstractRelationshipLoader): # self.parent is more specific than subq_path[-2] parent_alias = mapperutil.AliasedClass(self.parent) - # TODO: local cols might not be unique here - local_cols, remote_cols = \ - self._local_remote_columns(self.parent_property) + local_cols = self.parent_property.local_columns local_attr = [ getattr(parent_alias, self.parent._columntoproperty[c].key) @@ -883,18 +881,6 @@ class SubqueryLoader(AbstractRelationshipLoader): q = q.join(attr, aliased=middle, from_joinpoint=True) return q - def _local_remote_columns(self, prop): - if prop.secondary is None: - return zip(*prop.local_remote_pairs) - else: - # TODO: this isn't going to work for readonly.... - return \ - [p[0] for p in prop.synchronize_pairs],\ - [ - p[0] for p in prop. - secondary_synchronize_pairs - ] - def _setup_options(self, q, subq_path, orig_query): # propagate loader options etc. to the new query. # these will fire relative to subq_path. @@ -933,8 +919,7 @@ class SubqueryLoader(AbstractRelationshipLoader): if ('subquery', reduced_path) not in context.attributes: return None, None, None - # TODO: local_cols might not be unique here - local_cols, remote_cols = self._local_remote_columns(self.parent_property) + local_cols = self.parent_property.local_columns q = context.attributes[('subquery', reduced_path)] diff --git a/test/orm/test_rel_fn.py b/test/orm/test_rel_fn.py index 21fc681917..56cba3c44c 100644 --- a/test/orm/test_rel_fn.py +++ b/test/orm/test_rel_fn.py @@ -400,7 +400,7 @@ class AdaptedJoinTest(_JoinFixtures, fixtures.TestBase, AssertsCompiledSQL): def test_join_targets_o2m_selfref(self): joincond = self._join_fixture_o2m_selfref() left = select([joincond.parent_selectable]).alias('pj') - pj, sj, sec, adapter = joincond.join_targets( + pj, sj, sec, adapter, ds = joincond.join_targets( left, joincond.child_selectable, True) @@ -409,7 +409,7 @@ class AdaptedJoinTest(_JoinFixtures, fixtures.TestBase, AssertsCompiledSQL): ) right = select([joincond.child_selectable]).alias('pj') - pj, sj, sec, adapter = joincond.join_targets( + pj, sj, sec, adapter, ds = joincond.join_targets( joincond.parent_selectable, right, True) @@ -420,7 +420,7 @@ class AdaptedJoinTest(_JoinFixtures, fixtures.TestBase, AssertsCompiledSQL): def test_join_targets_o2m_plain(self): joincond = self._join_fixture_o2m() - pj, sj, sec, adapter = joincond.join_targets( + pj, sj, sec, adapter, ds = joincond.join_targets( joincond.parent_selectable, joincond.child_selectable, False) @@ -431,7 +431,7 @@ class AdaptedJoinTest(_JoinFixtures, fixtures.TestBase, AssertsCompiledSQL): def test_join_targets_o2m_left_aliased(self): joincond = self._join_fixture_o2m() left = select([joincond.parent_selectable]).alias('pj') - pj, sj, sec, adapter = joincond.join_targets( + pj, sj, sec, adapter, ds = joincond.join_targets( left, joincond.child_selectable, True) @@ -442,7 +442,7 @@ class AdaptedJoinTest(_JoinFixtures, fixtures.TestBase, AssertsCompiledSQL): def test_join_targets_o2m_right_aliased(self): joincond = self._join_fixture_o2m() right = select([joincond.child_selectable]).alias('pj') - pj, sj, sec, adapter = joincond.join_targets( + pj, sj, sec, adapter, ds = joincond.join_targets( joincond.parent_selectable, right, True) -- 2.47.3