diff options
| author | Mike Bayer <mike_mp@zzzcomputing.com> | 2010-09-06 10:55:53 -0400 |
|---|---|---|
| committer | Mike Bayer <mike_mp@zzzcomputing.com> | 2010-09-06 10:55:53 -0400 |
| commit | ccde5319358aa3eb0e075dbcf01024001230b02b (patch) | |
| tree | 5173442c847b06ad5c353184621e4e848b60f0d1 /lib/sqlalchemy | |
| parent | be5207a98f62090aa5200d7808730a40dd840dac (diff) | |
| download | sqlalchemy-ccde5319358aa3eb0e075dbcf01024001230b02b.tar.gz | |
- query.with_parent() now accepts transient objects
and will use the non-persistent values of their pk/fk
attributes in order to formulate the criterion.
Docs are also clarified as to the purpose of with_parent().
- fix for PG test executing an alias()
Diffstat (limited to 'lib/sqlalchemy')
| -rw-r--r-- | lib/sqlalchemy/orm/properties.py | 18 | ||||
| -rw-r--r-- | lib/sqlalchemy/orm/query.py | 47 | ||||
| -rw-r--r-- | lib/sqlalchemy/orm/strategies.py | 34 |
3 files changed, 68 insertions, 31 deletions
diff --git a/lib/sqlalchemy/orm/properties.py b/lib/sqlalchemy/orm/properties.py index 263b611a5..1b8a4b545 100644 --- a/lib/sqlalchemy/orm/properties.py +++ b/lib/sqlalchemy/orm/properties.py @@ -720,7 +720,10 @@ class RelationshipProperty(StrategizedProperty): self.prop.parent.compile() return self.prop - def compare(self, op, value, value_is_parent=False, alias_secondary=True): + def compare(self, op, value, + value_is_parent=False, + alias_secondary=True, + detect_transient_pending=False): if op == operators.eq: if value is None: if self.uselist: @@ -728,21 +731,26 @@ class RelationshipProperty(StrategizedProperty): else: return self._optimized_compare(None, value_is_parent=value_is_parent, + detect_transient_pending=detect_transient_pending, alias_secondary=alias_secondary) else: - return self._optimized_compare(value, - value_is_parent=value_is_parent, - alias_secondary=alias_secondary) + return self._optimized_compare(value, + value_is_parent=value_is_parent, + detect_transient_pending=detect_transient_pending, + alias_secondary=alias_secondary) else: return op(self.comparator, value) def _optimized_compare(self, value, value_is_parent=False, - adapt_source=None, alias_secondary=True): + adapt_source=None, + detect_transient_pending=False, + alias_secondary=True): if value is not None: value = attributes.instance_state(value) return self._get_strategy(strategies.LazyLoader).lazy_clause(value, reverse_direction=not value_is_parent, alias_secondary=alias_secondary, + detect_transient_pending=detect_transient_pending, adapt_source=adapt_source) def __str__(self): diff --git a/lib/sqlalchemy/orm/query.py b/lib/sqlalchemy/orm/query.py index 41e9a4890..9da7d3ea8 100644 --- a/lib/sqlalchemy/orm/query.py +++ b/lib/sqlalchemy/orm/query.py @@ -648,21 +648,35 @@ class Query(object): self._populate_existing = True def with_parent(self, instance, property=None): - """Add a join criterion corresponding to a relationship to the given - parent instance. - - instance - a persistent or detached instance which is related to class - represented by this query. - - property - string name of the property which relates this query's class to the - instance. if None, the method will attempt to find a suitable - property. - - Currently, this method only works with immediate parent relationships, - but in the future may be enhanced to work across a chain of parent - mappers. + """Add filtering criterion that relates this query's primary entity + to the given related instance, using established :func:`.relationship()` + configuration. + + The SQL rendered is the same as that rendered when a lazy loader + would fire off from the given parent on that attribute, meaning + that the appropriate state is taken from the parent object in + Python without the need to render joins to the parent table + in the rendered statement. + + As of 0.6.4, this method accepts parent instances in all + persistence states, including transient, persistent, and detached. + Only the requisite primary key/foreign key attributes need to + be populated. Previous versions didn't work with transient + instances. + + :param instance: + An instance which is related to the class represented by + this query via some :func:`.relationship`, that also + contains the appropriate attribute state that identifies + the child object or collection. + + :param property: + String property name, or class-bound attribute, which indicates + what relationship should be used to reconcile the parent/child + relationship. If None, the method will use the first relationship + that links them together - note that this is not deterministic + in the case of multiple relationships linking parent/child, + so using None is not recommended. """ from sqlalchemy.orm import properties @@ -684,7 +698,8 @@ class Query(object): prop = mapper.get_property(property, resolve_synonyms=True) return self.filter(prop.compare( operators.eq, - instance, value_is_parent=True)) + instance, value_is_parent=True, + detect_transient_pending=True)) @_generative() def add_entity(self, entity, alias=None): diff --git a/lib/sqlalchemy/orm/strategies.py b/lib/sqlalchemy/orm/strategies.py index a8c079113..1b8cf0852 100644 --- a/lib/sqlalchemy/orm/strategies.py +++ b/lib/sqlalchemy/orm/strategies.py @@ -404,7 +404,9 @@ class LazyLoader(AbstractRelationshipLoader): ) def lazy_clause(self, state, reverse_direction=False, - alias_secondary=False, adapt_source=None): + alias_secondary=False, + adapt_source=None, + detect_transient_pending=False): if state is None: return self._lazy_none_clause( reverse_direction, @@ -426,17 +428,29 @@ class LazyLoader(AbstractRelationshipLoader): else: mapper = self.parent_property.parent + o = state.obj() # strong ref + dict_ = attributes.instance_dict(o) + def visit_bindparam(bindparam): if bindparam.key in bind_to_col: - # use the "committed" (database) version to get - # query column values - # also its a deferred value; so that when used - # by Query, the committed value is used - # after an autoflush occurs - o = state.obj() # strong ref - bindparam.value = \ - lambda: mapper._get_committed_attr_by_column( - o, bind_to_col[bindparam.key]) + # using a flag to enable "detect transient pending" so that + # the slightly different usage paradigm of "dynamic" loaders + # continue to work as expected, i.e. that all pending objects + # should use the "post flush" attributes, and to limit this + # newer behavior to the query.with_parent() method. + # It would be nice to do away with this flag. + + if detect_transient_pending and \ + (not state.key or not state.session_id): + bindparam.value = mapper._get_state_attr_by_column( + state, dict_, bind_to_col[bindparam.key]) + else: + # send value as a lambda so that the value is + # acquired after any autoflush occurs. + bindparam.value = \ + lambda: mapper._get_committed_state_attr_by_column( + state, dict_, bind_to_col[bindparam.key]) + if self.parent_property.secondary is not None and alias_secondary: criterion = sql_util.ClauseAdapter( |
