summaryrefslogtreecommitdiff
path: root/lib/sqlalchemy
diff options
context:
space:
mode:
authorMike Bayer <mike_mp@zzzcomputing.com>2006-08-18 17:21:01 +0000
committerMike Bayer <mike_mp@zzzcomputing.com>2006-08-18 17:21:01 +0000
commitc48177f0fed3a43b3b8b02c18243cb1664ce0abb (patch)
tree1b4e70a8e4d64059fff906533cf14b35b8845946 /lib/sqlalchemy
parentbd04cffad83848e3edc5cb51c51ca6232270477a (diff)
downloadsqlalchemy-c48177f0fed3a43b3b8b02c18243cb1664ce0abb.tar.gz
- unit-of-work does a better check for "orphaned" objects that are
part of a "delete-orphan" cascade, for certain conditions where the parent isnt available to cascade from. - it is now invalid to declare a self-referential relationship with "delete-orphan" (as the abovementioned check would make them impossible to save) - improved the check for objects being part of a session when the unit of work seeks to flush() them as part of a relationship..
Diffstat (limited to 'lib/sqlalchemy')
-rw-r--r--lib/sqlalchemy/orm/mapper.py8
-rw-r--r--lib/sqlalchemy/orm/properties.py5
-rw-r--r--lib/sqlalchemy/orm/session.py6
-rw-r--r--lib/sqlalchemy/orm/unitofwork.py12
4 files changed, 27 insertions, 4 deletions
diff --git a/lib/sqlalchemy/orm/mapper.py b/lib/sqlalchemy/orm/mapper.py
index ab0226a41..2347da2ba 100644
--- a/lib/sqlalchemy/orm/mapper.py
+++ b/lib/sqlalchemy/orm/mapper.py
@@ -86,6 +86,7 @@ class Mapper(object):
self.properties = properties or {}
self.allow_column_override = allow_column_override
self.allow_null_pks = allow_null_pks
+ self.delete_orphans = []
# a Column which is used during a select operation to retrieve the
# "polymorphic identity" of the row, which indicates which Mapper should be used
@@ -139,6 +140,13 @@ class Mapper(object):
# of dependency
#self.compile()
+ def _is_orphan(self, obj):
+ for (key,klass) in self.delete_orphans:
+ if not getattr(klass, key).hasparent(obj):
+ return True
+ else:
+ return False
+
def _get_props(self):
self.compile()
return self.__props
diff --git a/lib/sqlalchemy/orm/properties.py b/lib/sqlalchemy/orm/properties.py
index b5d66c160..16760dc06 100644
--- a/lib/sqlalchemy/orm/properties.py
+++ b/lib/sqlalchemy/orm/properties.py
@@ -216,6 +216,11 @@ class PropertyLoader(mapper.MapperProperty):
self.target = self.mapper.mapped_table
+ if self.cascade.delete_orphan:
+ if self.parent.class_ is self.mapper.class_:
+ raise exceptions.ArgumentError("Cant establish 'delete-orphan' cascade rule on a self-referential relationship. You probably want cascade='all', which includes delete cascading but not orphan detection.")
+ self.mapper.primary_mapper().delete_orphans.append((self.key, self.parent.class_))
+
if self.secondaryjoin is not None and self.secondary is None:
raise exceptions.ArgumentError("Property '" + self.key + "' specified with secondary join condition but no secondary argument")
# if join conditions were not specified, figure them out based on foreign keys
diff --git a/lib/sqlalchemy/orm/session.py b/lib/sqlalchemy/orm/session.py
index 5c962aa39..e5c95cbe4 100644
--- a/lib/sqlalchemy/orm/session.py
+++ b/lib/sqlalchemy/orm/session.py
@@ -350,6 +350,12 @@ class Session(object):
raise exceptions.InvalidRequestError("Instance '%s' is a detached instance or is already persistent in a different Session" % repr(object))
else:
m = class_mapper(object.__class__, entity_name=kwargs.get('entity_name', None))
+
+ # this would be a nice exception to raise...however this is incompatible with a contextual
+ # session which puts all objects into the session upon construction.
+ #if m._is_orphan(object):
+ # raise exceptions.InvalidRequestError("Instance '%s' is an orphan, and must be attached to a parent object to be saved" % (repr(object)))
+
m._assign_entity_name(object)
self._register_new(object)
diff --git a/lib/sqlalchemy/orm/unitofwork.py b/lib/sqlalchemy/orm/unitofwork.py
index 33e0149e4..00d34a104 100644
--- a/lib/sqlalchemy/orm/unitofwork.py
+++ b/lib/sqlalchemy/orm/unitofwork.py
@@ -106,9 +106,10 @@ class UnitOfWork(object):
pass
def _validate_obj(self, obj):
- if hasattr(obj, '_instance_key') and not self.identity_map.has_key(obj._instance_key):
- raise InvalidRequestError("Instance '%s' is not attached or pending within this session" % repr(obj._instance_key))
-
+ if (hasattr(obj, '_instance_key') and not self.identity_map.has_key(obj._instance_key)) or \
+ (not hasattr(obj, '_instance_key') and obj not in self.new):
+ raise InvalidRequestError("Instance '%s' is not attached or pending within this session" % repr(obj))
+
def update(self, obj):
"""called to add an object to this UnitOfWork as though it were loaded from the DB,
but is actually coming from somewhere else, like a web session or similar."""
@@ -184,7 +185,10 @@ class UnitOfWork(object):
continue
if obj in self.deleted:
continue
- flush_context.register_object(obj)
+ if object_mapper(obj)._is_orphan(obj):
+ flush_context.register_object(obj, isdelete=True)
+ else:
+ flush_context.register_object(obj)
for obj in self.deleted:
if objset is not None and not obj in objset: