From 190436e58df15a994fd57699e00bbfdbcc0e1848 Mon Sep 17 00:00:00 2001 From: Mike Bayer Date: Sun, 16 Mar 2008 23:49:55 +0000 Subject: [PATCH] - fixed "cascade delete" operation of dynamic relations, which had only been implemented for foreign-key nulling behavior in 0.4.2 and not actual cascading deletes [ticket:895] --- CHANGES | 7 +++++- lib/sqlalchemy/orm/attributes.py | 2 +- lib/sqlalchemy/orm/dynamic.py | 26 +++++++++++++--------- test/orm/dynamic.py | 38 +++++++++++++++++++++++++++++++- 4 files changed, 59 insertions(+), 14 deletions(-) diff --git a/CHANGES b/CHANGES index 006849a420..00c5485e2c 100644 --- a/CHANGES +++ b/CHANGES @@ -16,7 +16,12 @@ CHANGES from being used with inheritance - Session.execute can now find binds from metadata - + + - fixed "cascade delete" operation of dynamic relations, + which had only been implemented for foreign-key nulling + behavior in 0.4.2 and not actual cascading deletes + [ticket:895] + - extensions - the "synonym" function is now directly usable with "declarative". Pass in the decorated property using diff --git a/lib/sqlalchemy/orm/attributes.py b/lib/sqlalchemy/orm/attributes.py index 3173f82740..b0c2ac8261 100644 --- a/lib/sqlalchemy/orm/attributes.py +++ b/lib/sqlalchemy/orm/attributes.py @@ -1160,7 +1160,7 @@ def get_as_list(state, key, passive=False): if x is PASSIVE_NORESULT: return None elif hasattr(attr, 'get_collection'): - return attr.get_collection(state, x) + return attr.get_collection(state, x, passive=passive) elif isinstance(x, list): return x else: diff --git a/lib/sqlalchemy/orm/dynamic.py b/lib/sqlalchemy/orm/dynamic.py index 0d72beddb6..09de60db2d 100644 --- a/lib/sqlalchemy/orm/dynamic.py +++ b/lib/sqlalchemy/orm/dynamic.py @@ -14,12 +14,16 @@ class DynamicAttributeImpl(attributes.AttributeImpl): def get(self, state, passive=False): if passive: - return self._get_collection(state, passive=True).added_items + return self._get_collection_history(state, passive=True).added_items else: return AppenderQuery(self, state) - def get_collection(self, state, user_data=None): - return self._get_collection(state, passive=True).added_items + def get_collection(self, state, user_data=None, passive=True): + if passive: + return self._get_collection_history(state, passive=passive).added_items + else: + history = self._get_collection_history(state, passive=passive) + return history.added_items + history.unchanged_items def fire_append_event(self, state, value, initiator): state.modified = True @@ -53,10 +57,10 @@ class DynamicAttributeImpl(attributes.AttributeImpl): raise NotImplementedError() def get_history(self, state, passive=False): - c = self._get_collection(state, passive) + c = self._get_collection_history(state, passive) return (c.added_items, c.unchanged_items, c.deleted_items) - def _get_collection(self, state, passive=False): + def _get_collection_history(self, state, passive=False): try: c = state.dict[self.key] except KeyError: @@ -69,12 +73,12 @@ class DynamicAttributeImpl(attributes.AttributeImpl): def append(self, state, value, initiator, passive=False): if initiator is not self: - self._get_collection(state, passive=True).added_items.append(value) + self._get_collection_history(state, passive=True).added_items.append(value) self.fire_append_event(state, value, initiator) def remove(self, state, value, initiator, passive=False): if initiator is not self: - self._get_collection(state, passive=True).deleted_items.append(value) + self._get_collection_history(state, passive=True).deleted_items.append(value) self.fire_remove_event(state, value, initiator) @@ -100,21 +104,21 @@ class AppenderQuery(Query): def __iter__(self): sess = self.__session() if sess is None: - return iter(self.attr._get_collection(self.instance._state, passive=True).added_items) + return iter(self.attr._get_collection_history(self.instance._state, passive=True).added_items) else: return iter(self._clone(sess)) def __getitem__(self, index): sess = self.__session() if sess is None: - return self.attr._get_collection(self.instance._state, passive=True).added_items.__getitem__(index) + return self.attr._get_collection_history(self.instance._state, passive=True).added_items.__getitem__(index) else: return self._clone(sess).__getitem__(index) def count(self): sess = self.__session() if sess is None: - return len(self.attr._get_collection(self.instance._state, passive=True).added_items) + return len(self.attr._get_collection_history(self.instance._state, passive=True).added_items) else: return self._clone(sess).count() @@ -142,7 +146,7 @@ class AppenderQuery(Query): oldlist = list(self) else: oldlist = [] - self.attr._get_collection(self.instance._state, passive=True).replace(oldlist, collection) + self.attr._get_collection_history(self.instance._state, passive=True).replace(oldlist, collection) return oldlist def append(self, item): diff --git a/test/orm/dynamic.py b/test/orm/dynamic.py index ae8ef4e5ea..2b5c0eeecb 100644 --- a/test/orm/dynamic.py +++ b/test/orm/dynamic.py @@ -121,7 +121,7 @@ class FlushTest(FixtureTest): ] == sess.query(User).all() @testing.fails_on('maxdb') - def test_delete(self): + def test_delete_nocascade(self): mapper(User, users, properties={ 'addresses':dynamic_loader(mapper(Address, addresses), backref='user') }) @@ -141,6 +141,9 @@ class FlushTest(FixtureTest): sess.delete(u.addresses[3]) assert [Address(email_address='a'), Address(email_address='b'), Address(email_address='d')] == list(u.addresses) + sess.clear() + u = sess.query(User).get(u.id) + sess.delete(u) # u.addresses relation will have to force the load @@ -150,6 +153,39 @@ class FlushTest(FixtureTest): assert testing.db.scalar(addresses.count(addresses.c.user_id != None)) ==0 + @testing.fails_on('maxdb') + def test_delete_cascade(self): + mapper(User, users, properties={ + 'addresses':dynamic_loader(mapper(Address, addresses), backref='user', cascade="all, delete-orphan") + }) + sess = create_session(autoflush=True) + u = User(name='ed') + u.addresses.append(Address(email_address='a')) + u.addresses.append(Address(email_address='b')) + u.addresses.append(Address(email_address='c')) + u.addresses.append(Address(email_address='d')) + u.addresses.append(Address(email_address='e')) + u.addresses.append(Address(email_address='f')) + sess.save(u) + + assert Address(email_address='c') == u.addresses[2] + sess.delete(u.addresses[2]) + sess.delete(u.addresses[4]) + sess.delete(u.addresses[3]) + assert [Address(email_address='a'), Address(email_address='b'), Address(email_address='d')] == list(u.addresses) + + sess.clear() + u = sess.query(User).get(u.id) + + sess.delete(u) + + # u.addresses relation will have to force the load + # of all addresses so that they can be updated + sess.flush() + sess.close() + + assert testing.db.scalar(addresses.count()) ==0 + @testing.fails_on('maxdb') def test_remove_orphans(self): mapper(User, users, properties={ -- 2.47.3