summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--lib/sqlalchemy/mapping/__init__.py15
-rw-r--r--lib/sqlalchemy/mapping/mapper.py16
-rw-r--r--lib/sqlalchemy/mapping/objectstore.py28
-rw-r--r--lib/sqlalchemy/mapping/properties.py45
-rw-r--r--test/cycles.py10
5 files changed, 69 insertions, 45 deletions
diff --git a/lib/sqlalchemy/mapping/__init__.py b/lib/sqlalchemy/mapping/__init__.py
index 64258fa65..2de644f43 100644
--- a/lib/sqlalchemy/mapping/__init__.py
+++ b/lib/sqlalchemy/mapping/__init__.py
@@ -88,21 +88,6 @@ def undefer(name, **kwargs):
name into a non-deferred (regular column) load. Used with mapper.options."""
return DeferredOption(name, defer=False)
-def object_mapper(object):
- """given an object, returns the primary Mapper associated with the object
- or the object's class."""
- return class_mapper(object.__class__)
-
-def class_mapper(class_):
- """given a class, returns the primary Mapper associated with the class."""
- return mapper_registry[class_]
- try:
- return mapper_registry[class_]
- except KeyError:
- pass
- except AttributeError:
- pass
- raise InvalidRequestError("Class '%s' has no mapper associated with it" % class_.__name__)
def assign_mapper(class_, *args, **params):
diff --git a/lib/sqlalchemy/mapping/mapper.py b/lib/sqlalchemy/mapping/mapper.py
index 88b803b37..2e69a1e66 100644
--- a/lib/sqlalchemy/mapping/mapper.py
+++ b/lib/sqlalchemy/mapping/mapper.py
@@ -893,6 +893,22 @@ def hash_key(obj):
return obj.hash_key()
else:
return repr(obj)
+
+def object_mapper(object):
+ """given an object, returns the primary Mapper associated with the object
+ or the object's class."""
+ return class_mapper(object.__class__)
+
+def class_mapper(class_):
+ """given a class, returns the primary Mapper associated with the class."""
+ return mapper_registry[class_]
+ try:
+ return mapper_registry[class_]
+ except KeyError:
+ pass
+ except AttributeError:
+ pass
+ raise InvalidRequestError("Class '%s' has no mapper associated with it" % class_.__name__)
diff --git a/lib/sqlalchemy/mapping/objectstore.py b/lib/sqlalchemy/mapping/objectstore.py
index 94e2f966b..e68c59e30 100644
--- a/lib/sqlalchemy/mapping/objectstore.py
+++ b/lib/sqlalchemy/mapping/objectstore.py
@@ -454,8 +454,9 @@ class UOWTransaction(object):
self.dependencies = {}
self.tasks = {}
self.saved_histories = util.HashSet()
-
- def register_object(self, obj, isdelete = False, listonly = False, **kwargs):
+ self.__modified = False
+
+ def register_object(self, obj, isdelete = False, listonly = False, postupdate=False, **kwargs):
"""adds an object to this UOWTransaction to be updated in the database.
'isdelete' indicates whether the object is to be deleted or saved (update/inserted).
@@ -474,15 +475,19 @@ class UOWTransaction(object):
self.mappers.append(mapper)
task = self.get_task_by_mapper(mapper)
+ if postupdate:
+ mod = task.append_postupdate(obj)
+ self.__modified = self.__modified or mod
+ return
+
# for a cyclical task, things need to be sorted out already,
# so this object should have already been added to the appropriate sub-task
# can put an assertion here to make sure....
if task.circular:
return
- if obj not in task.objects:
- self.__modified = True
- task.append(obj, listonly, isdelete=isdelete, **kwargs)
+ mod = task.append(obj, listonly, isdelete=isdelete, **kwargs)
+ self.__modified = self.__modified or mod
def unregister_object(self, obj):
mapper = object_mapper(obj)
@@ -665,16 +670,24 @@ class UOWTask(object):
dependent operations at the per-object instead of per-task level. """
try:
rec = self.objects[obj]
+ retval = False
except KeyError:
rec = UOWTaskElement(obj)
self.objects[obj] = rec
+ retval = True
if not listonly:
rec.listonly = False
if childtask:
rec.childtasks.append(childtask)
if isdelete:
rec.isdelete = True
-
+ return retval
+
+ def append_postupdate(self, obj):
+ # postupdates are UPDATED immeditely (for now)
+ self.mapper.save_obj([obj], self.uowtransaction, postupdate=True)
+ return True
+
def delete(self, obj):
try:
del self.objects[obj]
@@ -974,6 +987,9 @@ class UOWTask(object):
for child in self.childtasks:
header(buf, _indent() + " |- Child tasks\n")
child._dump(buf, indent + 1)
+# for obj in self.postupdate:
+# header(buf, _indent() + " |- Post Update objects\n")
+# buf.write(_repr(obj) + "\n")
for element in self.todelete_elements():
for task in element.childtasks:
header(buf, _indent() + " |- Delete subelements of UOWTaskElement(%s)\n" % id(element))
diff --git a/lib/sqlalchemy/mapping/properties.py b/lib/sqlalchemy/mapping/properties.py
index 571bfc0b5..036dfe440 100644
--- a/lib/sqlalchemy/mapping/properties.py
+++ b/lib/sqlalchemy/mapping/properties.py
@@ -358,14 +358,20 @@ class PropertyLoader(MapperProperty):
uowcommit.register_processor(stub, self, self.parent, True)
elif self.direction == PropertyLoader.ONETOMANY:
if self.post_update:
- raise InvalidRequestError("post_update not yet supported with one-to-many relation")
- uowcommit.register_dependency(self.parent, self.mapper)
- uowcommit.register_processor(self.parent, self, self.parent, False)
- uowcommit.register_processor(self.parent, self, self.parent, True)
+ stub = PropertyLoader.MapperStub(self.mapper)
+ uowcommit.register_dependency(self.mapper, stub)
+ uowcommit.register_dependency(self.parent, stub)
+ uowcommit.register_processor(stub, self, self.parent, False)
+ uowcommit.register_processor(stub, self, self.parent, True)
+ else:
+ uowcommit.register_dependency(self.parent, self.mapper)
+ uowcommit.register_processor(self.parent, self, self.parent, False)
+ uowcommit.register_processor(self.parent, self, self.parent, True)
elif self.direction == PropertyLoader.MANYTOONE:
if self.post_update:
stub = PropertyLoader.MapperStub(self.mapper)
uowcommit.register_dependency(self.mapper, stub)
+ uowcommit.register_dependency(self.parent, stub)
uowcommit.register_processor(stub, self, self.parent, False)
uowcommit.register_processor(stub, self, self.parent, True)
else:
@@ -433,12 +439,12 @@ class PropertyLoader(MapperProperty):
statement = self.secondary.insert()
statement.execute(*secondary_insert)
elif self.direction == PropertyLoader.MANYTOONE and delete:
- # head object is being deleted, and we manage a foreign key object.
- # dont have to do anything to it.
if self.post_update:
+ # post_update means we have to update our row to not reference the child object
+ # before we can DELETE the row
for obj in deplist:
self._synchronize(obj, None, None, True)
- task.mapper.save_obj([obj], uowcommit, postupdate=True)
+ uowcommit.register_object(obj, postupdate=True)
elif self.direction == PropertyLoader.ONETOMANY and delete:
# head object is being deleted, and we manage its list of child objects
# the child objects have to have their foreign key to the parent set to NULL
@@ -450,7 +456,7 @@ class PropertyLoader(MapperProperty):
childlist = getlist(obj, False)
for child in childlist.deleted_items() + childlist.unchanged_items():
self._synchronize(obj, child, None, True)
- uowcommit.register_object(child)
+ uowcommit.register_object(child, postupdate=self.post_update)
elif self.association is not None:
# manage association objects.
for obj in deplist:
@@ -488,21 +494,16 @@ class PropertyLoader(MapperProperty):
#print "DELETE ASSOC OBJ", repr(child)
uowcommit.register_object(child, isdelete=True)
else:
- for obj in deplist:
- if self.direction == PropertyLoader.MANYTOONE:
- uowcommit.register_object(obj)
+ for obj in deplist:
childlist = getlist(obj, passive=True)
- if childlist is None: continue
- for child in childlist.added_items():
- self._synchronize(obj, child, None, False)
- if self.direction == PropertyLoader.ONETOMANY and child is not None:
- if self.post_update:
- task.mapper.save_obj([child],uowcommit, postupdate=True)
- else:
- uowcommit.register_object(child)
- if self.post_update:
- task.mapper.save_obj([obj], uowcommit, postupdate=True)
- if self.direction != PropertyLoader.MANYTOONE or len(childlist.added_items()) == 0:
+ if childlist is not None:
+ for child in childlist.added_items():
+ self._synchronize(obj, child, None, False)
+ if self.direction == PropertyLoader.ONETOMANY and child is not None:
+ uowcommit.register_object(child, postupdate=self.post_update)
+ if self.direction == PropertyLoader.MANYTOONE:
+ uowcommit.register_object(obj, postupdate=self.post_update)
+ if self.direction != PropertyLoader.MANYTOONE:
for child in childlist.deleted_items():
if not self.private:
self._synchronize(obj, child, None, True)
diff --git a/test/cycles.py b/test/cycles.py
index 863d30fd9..f30ac0297 100644
--- a/test/cycles.py
+++ b/test/cycles.py
@@ -238,8 +238,8 @@ class CycleTest2(AssertMixin):
Ball.mapper = mapper(Ball, ball)
Person.mapper = mapper(Person, person, properties= dict(
- balls = relation(Ball.mapper, primaryjoin=ball.c.person_id==person.c.id, foreignkey=ball.c.person_id),
- favorateBall = relation(Ball.mapper, primaryjoin=person.c.favoriteBall_id==ball.c.id, foreignkey=person.c.favoriteBall_id),
+ balls = relation(Ball.mapper, primaryjoin=ball.c.person_id==person.c.id, foreignkey=ball.c.person_id, post_update=False, private=True),
+ favorateBall = relation(Ball.mapper, primaryjoin=person.c.favoriteBall_id==ball.c.id, foreignkey=person.c.favoriteBall_id, post_update=True),
)
)
@@ -248,8 +248,14 @@ class CycleTest2(AssertMixin):
b = Ball()
p = Person()
p.balls.append(b)
+ p.balls.append(Ball())
+ p.balls.append(Ball())
+ p.balls.append(Ball())
p.favorateBall = b
objectstore.commit()
+
+ objectstore.delete(p)
+ objectstore.commit()
if __name__ == "__main__":