summaryrefslogtreecommitdiff
path: root/lib
diff options
context:
space:
mode:
authorMike Bayer <mike_mp@zzzcomputing.com>2010-03-29 17:56:02 -0400
committerMike Bayer <mike_mp@zzzcomputing.com>2010-03-29 17:56:02 -0400
commit75e14f855ee64a01bb79e66f8a868911f6c9e583 (patch)
tree57e0fe600809b12db44040beed4b2e19cde22d6c /lib
parentf35132267e2245f44f44d94c25a3d2015c224ac2 (diff)
downloadsqlalchemy-75e14f855ee64a01bb79e66f8a868911f6c9e583.tar.gz
- Session.refresh() now does an equivalent expire()
on the given instance first, so that the "refresh-expire" cascade is propagated. Previously, refresh() was not affected in any way by the presence of "refresh-expire" cascade. This is a change in behavior versus that of 0.6beta2, where the "lockmode" flag passed to refresh() would cause a version check to occur. Since the instance is first expired, refresh() always upgrades the object to the most recent version. - The 'refresh-expire' cascade, when reaching a pending object, will expunge the object if the cascade also includes "delete-orphan", or will simply detach it otherwise. [ticket:1754]
Diffstat (limited to 'lib')
-rw-r--r--lib/sqlalchemy/orm/properties.py11
-rw-r--r--lib/sqlalchemy/orm/session.py27
-rw-r--r--lib/sqlalchemy/test/requires.py12
3 files changed, 42 insertions, 8 deletions
diff --git a/lib/sqlalchemy/orm/properties.py b/lib/sqlalchemy/orm/properties.py
index a8295e2cd..2a5e92c1a 100644
--- a/lib/sqlalchemy/orm/properties.py
+++ b/lib/sqlalchemy/orm/properties.py
@@ -742,6 +742,8 @@ class RelationshipProperty(StrategizedProperty):
else:
instances = state.value_as_iterable(self.key, passive=passive)
+ skip_pending = type_ == 'refresh-expire' and 'delete-orphan' not in self.cascade
+
if instances:
for c in instances:
if c is not None and \
@@ -757,12 +759,17 @@ class RelationshipProperty(StrategizedProperty):
str(self.parent.class_),
str(c.__class__)
))
+ instance_state = attributes.instance_state(c)
+
+ if skip_pending and not instance_state.key:
+ continue
+
visited_instances.add(c)
# cascade using the mapper local to this
# object, so that its individual properties are located
- instance_mapper = object_mapper(c)
- yield (c, instance_mapper, attributes.instance_state(c))
+ instance_mapper = instance_state.manager.mapper
+ yield (c, instance_mapper, instance_state)
def _add_reverse_property(self, key):
other = self.mapper._get_property(key)
diff --git a/lib/sqlalchemy/orm/session.py b/lib/sqlalchemy/orm/session.py
index 0a3fbe79e..0810175bf 100644
--- a/lib/sqlalchemy/orm/session.py
+++ b/lib/sqlalchemy/orm/session.py
@@ -883,7 +883,7 @@ class Session(object):
state.commit_all(dict_, self.identity_map)
def refresh(self, instance, attribute_names=None, lockmode=None):
- """Refresh the attributes on the given instance.
+ """Expire and refresh the attributes on the given instance.
A query will be issued to the database and all attributes will be
refreshed with their current database value.
@@ -907,7 +907,9 @@ class Session(object):
state = attributes.instance_state(instance)
except exc.NO_STATE:
raise exc.UnmappedInstanceError(instance)
- self._validate_persistent(state)
+
+ self._expire_state(state, attribute_names)
+
if self.query(_object_mapper(instance))._get(
state.key, refresh_state=state,
lockmode=lockmode,
@@ -939,18 +941,31 @@ class Session(object):
state = attributes.instance_state(instance)
except exc.NO_STATE:
raise exc.UnmappedInstanceError(instance)
+ self._expire_state(state, attribute_names)
+
+ def _expire_state(self, state, attribute_names):
self._validate_persistent(state)
if attribute_names:
_expire_state(state, state.dict,
- attribute_names=attribute_names, instance_dict=self.identity_map)
+ attribute_names=attribute_names,
+ instance_dict=self.identity_map)
else:
# pre-fetch the full cascade since the expire is going to
# remove associations
cascaded = list(_cascade_state_iterator('refresh-expire', state))
- _expire_state(state, state.dict, None, instance_dict=self.identity_map)
+ self._conditional_expire(state)
for (state, m, o) in cascaded:
- _expire_state(state, state.dict, None, instance_dict=self.identity_map)
-
+ self._conditional_expire(state)
+
+ def _conditional_expire(self, state):
+ """Expire a state if persistent, else expunge if pending"""
+
+ if state.key:
+ _expire_state(state, state.dict, None, instance_dict=self.identity_map)
+ elif state in self._new:
+ self._new.pop(state)
+ state.detach()
+
def prune(self):
"""Remove unreferenced instances cached in the identity map.
diff --git a/lib/sqlalchemy/test/requires.py b/lib/sqlalchemy/test/requires.py
index 73b212095..bf911c2c2 100644
--- a/lib/sqlalchemy/test/requires.py
+++ b/lib/sqlalchemy/test/requires.py
@@ -149,6 +149,18 @@ def sequences(fn):
no_support('sybase', 'no SEQUENCE support'),
)
+def update_nowait(fn):
+ """Target database must support SELECT...FOR UPDATE NOWAIT"""
+ return _chain_decorators_on(
+ fn,
+ no_support('access', 'no FOR UPDATE NOWAIT support'),
+ no_support('firebird', 'no FOR UPDATE NOWAIT support'),
+ no_support('mssql', 'no FOR UPDATE NOWAIT support'),
+ no_support('mysql', 'no FOR UPDATE NOWAIT support'),
+ no_support('sqlite', 'no FOR UPDATE NOWAIT support'),
+ no_support('sybase', 'no FOR UPDATE NOWAIT support'),
+ )
+
def subqueries(fn):
"""Target database must support subqueries."""
return _chain_decorators_on(