From fa1153405c4df9f20c23734f5bfb9ffca71a48f9 Mon Sep 17 00:00:00 2001 From: Mike Bayer Date: Sun, 18 Sep 2005 23:30:50 +0000 Subject: [PATCH] --- lib/sqlalchemy/attributes.py | 13 +++- lib/sqlalchemy/mapper.py | 118 ++++++++++++++++++---------------- lib/sqlalchemy/objectstore.py | 63 ++++++++++++++---- test/objectstore.py | 15 ++++- 4 files changed, 138 insertions(+), 71 deletions(-) diff --git a/lib/sqlalchemy/attributes.py b/lib/sqlalchemy/attributes.py index 06ed30605e..959a39ad94 100644 --- a/lib/sqlalchemy/attributes.py +++ b/lib/sqlalchemy/attributes.py @@ -148,6 +148,7 @@ class AttributeManager(object): self.get_history(obj, key).delattr() self.value_changed(obj, key, value) + def delete_list_attribute(self, obj, key): pass @@ -176,7 +177,13 @@ class AttributeManager(object): hist.commit() except KeyError: pass - + + def remove(self, obj): + try: + del self.attribute_history[obj] + except KeyError: + pass + def get_history(self, obj, key): try: return self.attribute_history[obj][key] @@ -192,12 +199,14 @@ class AttributeManager(object): self.attribute_history[obj][key] = p return p - def get_list_history(self, obj, key): + def get_list_history(self, obj, key, passive = False): try: return self.attribute_history[obj][key] except KeyError, e: list_ = obj.__dict__.get(key, None) if callable(list_): + if passive: + return None list_ = list_() if e.args[0] is obj: d = {} diff --git a/lib/sqlalchemy/mapper.py b/lib/sqlalchemy/mapper.py index 53f4596d99..b56a2753da 100644 --- a/lib/sqlalchemy/mapper.py +++ b/lib/sqlalchemy/mapper.py @@ -286,7 +286,25 @@ class Mapper(object): found = True def delete_obj(self, objects, uow): - pass + for table in self.tables: + delete = [] + for obj in objects: + params = {} + if not hasattr(obj, "_instance_key"): + continue + else: + delete.append(params) + for col in table.primary_keys: + params[col.key] = self._getattrbycolumn(obj, col) + uow.register_deleted_object(obj) + if len(delete): + clause = sql.and_() + for col in self.primary_keys[table]: + clause.clauses.append(col == sql.bindparam(col.key)) + statement = table.delete(clause) + c = statement.execute(*delete) + if c.rowcount != len(delete): + raise "ConcurrencyError - updated rowcount does not match number of objects updated" def register_dependencies(self, *args, **kwargs): for prop in self.props.values(): @@ -517,57 +535,52 @@ class PropertyLoader(MapperProperty): # if only a list changes, the parent mapper is the only mapper that # gets added to the "todo" list uowcommit.register_dependency(self.mapper, self.parent) - uowcommit.register_task(self.parent, self, uowcommit.get_objects(self.parent), False) + uowcommit.register_task(self.parent, False, self, self.parent, False) elif self.foreignkey.table == self.target: uowcommit.register_dependency(self.parent, self.mapper) - uowcommit.register_task(self.parent, self, uowcommit.get_objects(self.parent), False) + uowcommit.register_task(self.parent, False, self, self.parent, False) + uowcommit.register_task(self.parent, True, self, self.mapper, False) + elif self.foreignkey.table == self.parent.table: uowcommit.register_dependency(self.mapper, self.parent) - uowcommit.register_task(self.mapper, self, uowcommit.get_objects(self.parent), False) + uowcommit.register_task(self.mapper, False, self, self.parent, False) + #uowcommit.register_task(self.mapper, True, self, self.parent, False) else: raise " no foreign key ?" def process_dependencies(self, deplist, uowcommit, delete = False): - print self.mapper.table.name + " process_dep" - def getlist(obj): + print self.mapper.table.name + " process_dep isdelete " + repr(delete) + def getlist(obj, passive = True): if self.uselist: - return uowcommit.uow.attributes.get_list_history(obj, self.key) + return uowcommit.uow.attributes.get_list_history(obj, self.key, passive = passive) else: return uowcommit.uow.attributes.get_history(obj, self.key) - clearkeys = False - def sync_foreign_keys(binary): self._sync_foreign_keys(binary, obj, child, associationrow, clearkeys) setter = BinaryVisitor(sync_foreign_keys) + associationrow = {} + if self.secondaryjoin is not None: secondary_delete = [] secondary_insert = [] for obj in deplist: childlist = getlist(obj) - if delete: - clearkeys = True - for child in childlist.deleted_items() + childlist.unchanged_items(): - associationrow = {} - self.primaryjoin.accept_visitor(setter) - self.secondaryjoin.accept_visitor(setter) - secondary_delete.append(associationrow) - uowcommit.register_removed_list(childlist) - else: - clearkeys = False - for child in childlist.added_items(): - associationrow = {} - self.primaryjoin.accept_visitor(setter) - self.secondaryjoin.accept_visitor(setter) - secondary_insert.append(associationrow) - clearkeys = True - for child in childlist.deleted_items(): - associationrow = {} - self.primaryjoin.accept_visitor(setter) - self.secondaryjoin.accept_visitor(setter) - secondary_delete.append(associationrow) - uowcommit.register_saved_list(childlist) + if childlist is None: return + clearkeys = False + for child in childlist.added_items(): + associationrow = {} + self.primaryjoin.accept_visitor(setter) + self.secondaryjoin.accept_visitor(setter) + secondary_insert.append(associationrow) + clearkeys = True + for child in childlist.deleted_items(): + associationrow = {} + self.primaryjoin.accept_visitor(setter) + self.secondaryjoin.accept_visitor(setter) + secondary_delete.append(associationrow) + uowcommit.register_saved_list(childlist) if len(secondary_delete): statement = self.secondary.delete(sql.and_(*[c == sql.bindparam(c.key) for c in self.secondary.c])) statement.execute(*secondary_delete) @@ -575,40 +588,38 @@ class PropertyLoader(MapperProperty): statement = self.secondary.insert() statement.execute(*secondary_insert) elif self.foreignkey.table == self.target: - associationrow = {} - for obj in deplist: - childlist = getlist(obj) - if delete: - clearkeys = True - for child in childlist.deleted_items() + childlist.current_items(): - self.primaryjoin.accept_visitor(setter) - uowcommit.register_saved_list(childlist) - else: + if delete: + updates = [] + for obj in deplist: + childlist = getlist(obj, False) + #if len(updates): + # statement = self.secondary.update(sql.and_(*[c == sql.bindparam(c.key) for c in self.secondary.c])) + # statement.execute(*secondary_delete) + else: + for obj in deplist: + childlist = getlist(obj) + if childlist is None: return + uowcommit.register_saved_list(childlist) clearkeys = False for child in childlist.added_items(): self.primaryjoin.accept_visitor(setter) - uowcommit.register_saved_list(childlist) clearkeys = True for child in childlist.deleted_items(): self.primaryjoin.accept_visitor(setter) - uowcommit.register_saved_list(childlist) elif self.foreignkey.table == self.parent.table: - associationrow = {} for child in deplist: childlist = getlist(child) - if delete: - for obj in childlist.deleted_items() + childlist.current_items(): + if childlist is None: return + uowcommit.register_saved_list(childlist) + clearkeys = False + added = childlist.added_items() + if len(added): + for obj in added: self.primaryjoin.accept_visitor(setter) - uowcommit.register_saved_list(childlist) else: - clearkeys = False - for obj in childlist.added_items(): - self.primaryjoin.accept_visitor(setter) - uowcommit.register_saved_list(childlist) clearkeys = True for obj in childlist.deleted_items(): self.primaryjoin.accept_visitor(setter) - uowcommit.register_saved_list(childlist) else: raise " no foreign key ?" @@ -629,10 +640,9 @@ class PropertyLoader(MapperProperty): elif colmap.has_key(self.target) and colmap.has_key(self.secondary): associationrow[colmap[self.secondary].key] = self.mapper._getattrbycolumn(child, colmap[self.target]) - def delete(self): - self.mapper.delete() - +# TODO: break out the lazywhere capability so that the main PropertyLoader can use it +# to do child deletes class LazyLoader(PropertyLoader): def init(self, key, parent): diff --git a/lib/sqlalchemy/objectstore.py b/lib/sqlalchemy/objectstore.py index 2b00e7d66f..a22178fcab 100644 --- a/lib/sqlalchemy/objectstore.py +++ b/lib/sqlalchemy/objectstore.py @@ -105,6 +105,12 @@ class UnitOfWork(object): def _put(self, key, obj): self.identity_map[key] = obj + + def _remove_deleted(self, obj): + if hasattr(obj, "_instancekey"): + del self.identity_map[obj._instancekey] + del self.deleted[obj] + self.attributes.remove(obj) def update(self, obj): """called to add an object to this UnitOfWork as though it were loaded from the DB, but is @@ -207,13 +213,16 @@ class UnitOfWork(object): class UOWTransaction(object): def __init__(self, uow): self.uow = uow + # links objects to their mappers self.object_mappers = {} + # unique list of all the mappers we come across self.mappers = util.HashSet() self.dependencies = {} self.tasks = {} self.saved_objects = util.HashSet() self.saved_lists = util.HashSet() self.deleted_objects = util.HashSet() + self.deleted_lists = util.HashSet() def append_task(self, obj): mapper = self.object_mapper(obj) @@ -222,8 +231,8 @@ class UOWTransaction(object): def add_item_to_delete(self, obj): mapper = self.object_mapper(obj) - task = self.get_task_by_mapper(mapper) - task.todelete.append(obj) + task = self.get_task_by_mapper(mapper, True) + task.objects.append(obj) def get_task_by_mapper(self, mapper, isdelete = False): try: @@ -239,13 +248,13 @@ class UOWTransaction(object): return task.objects - # TODO: better interface for tasks with no object save, or multiple dependencies def register_dependency(self, mapper, dependency): self.dependencies[(mapper, dependency)] = True - def register_task(self, mapper, processor, objects, isdelete): + def register_task(self, mapper, isdelete, processor, mapperfrom, isdeletefrom): task = self.get_task_by_mapper(mapper, isdelete) - task.dependencies.append((processor, objects)) + targettask = self.get_task_by_mapper(mapperfrom, isdeletefrom) + task.dependencies.append((processor, targettask)) def register_saved_object(self, obj): self.saved_objects.append(obj) @@ -253,7 +262,10 @@ class UOWTransaction(object): def register_saved_list(self, listobj): self.saved_lists.append(listobj) - def register_deleted(self, obj): + def register_deleted_list(self, listobj): + self.deleted_lists.append(listobj) + + def register_deleted_object(self, obj): self.deleted_objects.append(obj) @@ -273,24 +285,33 @@ class UOWTransaction(object): tasklist = self.tasks.values() def compare(a, b): - if self.dependencies.has_key((a.mapper, b.mapper)): - return -1 + if a.mapper is b.mapper: + return a.isdelete and 1 or -1 + elif self.dependencies.has_key((a.mapper, b.mapper)): + if a.isdelete is not b.isdelete: + return a.isdelete and 1 or -1 + else: + return -1 elif self.dependencies.has_key((b.mapper, a.mapper)): - return 1 + if a.isdelete is not b.isdelete: + return a.isdelete and 1 or -1 + else: + return 1 else: return 0 + return c tasklist.sort(compare) import string + print string.join([str(t) for t in tasklist], ',') + for task in tasklist: obj_list = task.objects - if len(obj_list): - print "t:" + string.join([o.__class__.__name__ for o in obj_list]) if not task.isdelete: task.mapper.save_obj(obj_list, self) for dep in task.dependencies: - (processor, stuff) = dep - processor.process_dependencies(stuff, self, delete = task.isdelete) + (processor, targettask) = dep + processor.process_dependencies(targettask.objects, self, delete = task.isdelete) if task.isdelete: task.mapper.delete_obj(obj_list, self) @@ -306,12 +327,26 @@ class UOWTransaction(object): except KeyError: pass + for obj in self.deleted_objects: + self.uow._remove_deleted(obj) + for obj in self.deleted_lists: + try: + del self.uow.modified_lists[obj] + except KeyError: + pass + class UOWTask(object): def __init__(self, mapper, isdelete = False): self.mapper = mapper self.isdelete = isdelete self.objects = util.HashSet() self.dependencies = [] - + + def __str__(self): + if self.isdelete: + return self.mapper.table.name + " deletes" + else: + return self.mapper.table.name + " saves" + uow = util.ScopedRegistry(lambda: UnitOfWork(), "thread") \ No newline at end of file diff --git a/test/objectstore.py b/test/objectstore.py index 262fa0c465..1c14f9aaaf 100644 --- a/test/objectstore.py +++ b/test/objectstore.py @@ -126,6 +126,20 @@ class SaveTest(AssertMixin): u.address.email_address = 'imnew@foo.com' objectstore.uow().commit() + def testdelete(self): + m = mapper(User, users, properties = dict( + address = relation(Address, addresses, lazy = True, uselist = False) + )) + u = User() + u.user_name = 'one2onetester' + u.address = Address() + u.address.email_address = 'myonlyaddress@foo.com' + objectstore.uow().commit() + + print "OK" + objectstore.uow().register_deleted(u) + objectstore.uow().commit() + def testbackwardsonetoone(self): # test 'backwards' # m = mapper(Address, addresses, properties = dict( @@ -151,7 +165,6 @@ class SaveTest(AssertMixin): objects.append(a) objectstore.uow().commit() - objects[2].email_address = 'imnew@foo.bar' objects[3].user = User() objects[3].user.user_name = 'imnewlyadded' -- 2.47.2