summaryrefslogtreecommitdiff
path: root/lib/sqlalchemy/orm
diff options
context:
space:
mode:
authorMike Bayer <mike_mp@zzzcomputing.com>2010-09-06 10:55:53 -0400
committerMike Bayer <mike_mp@zzzcomputing.com>2010-09-06 10:55:53 -0400
commitccde5319358aa3eb0e075dbcf01024001230b02b (patch)
tree5173442c847b06ad5c353184621e4e848b60f0d1 /lib/sqlalchemy/orm
parentbe5207a98f62090aa5200d7808730a40dd840dac (diff)
downloadsqlalchemy-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/orm')
-rw-r--r--lib/sqlalchemy/orm/properties.py18
-rw-r--r--lib/sqlalchemy/orm/query.py47
-rw-r--r--lib/sqlalchemy/orm/strategies.py34
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(