[Zope-CVS] CVS: Products/AdaptableStorage/tests - SerialTestBase.py:1.7 testASStorage.py:1.4 testSerialization.py:1.6

Shane Hathaway shane@zope.com
Fri, 13 Dec 2002 15:42:04 -0500


Update of /cvs-repository/Products/AdaptableStorage/tests
In directory cvs.zope.org:/tmp/cvs-serv32288/tests

Modified Files:
	SerialTestBase.py testASStorage.py testSerialization.py 
Log Message:
Hopefully solved the problem of unmanaged persistent objects.

Because AdaptableStorage lets the mappers choose their own persistence
boundaries, some objects that inherit from Persistent may be unknown
to ZODB, even though they are in the database.  I call these unmanaged
persistent objects.

The problem with unmanaged persistent objects surfaces when you change
them without changing their container.  For example, if you add an
item to a BTree, the BTree object (which is managed) does not get
changed, but rather a contained Bucket object (which is often
unmanaged).  We really need the BTree to be notified when the Buckets
change.

To solve this, certain aspect serializers (currently only
RemainderSerializer) now detect unmanaged persistent objects and addq
them to a list.  ZODB looks over this list and assigns them a one-off
"_p_jar" with a register() method.  This special register() method
just sets the _p_changed attribute of the managed object, notifying
ZODB that it must be saved.

I think this is the best solution, even though it's awfully complex to
explain.  The change involved moving the RemainingState aspect to the
"zodb" subpackage, since it already depended on the particulars of the
Persistent base class anyway.  It also required changing
ObjectSerializer so that the event object gets constructed by the
caller, which is appropriate, I think.

Also made some minor changes.


=== Products/AdaptableStorage/tests/SerialTestBase.py 1.6 => 1.7 ===
--- Products/AdaptableStorage/tests/SerialTestBase.py:1.6	Mon Dec  9 17:11:07 2002
+++ Products/AdaptableStorage/tests/SerialTestBase.py	Fri Dec 13 15:42:03 2002
@@ -24,7 +24,8 @@
 from Products.AdaptableStorage.serial import public as sfw
 from Products.AdaptableStorage.serial_std.public import \
      StringDataAttribute, MappingGateway, FixedPersistentMapping, RollCall, \
-     FixedClassifier, RemainingState
+     FixedClassifier
+from Products.AdaptableStorage.zodb.public import RemainingState
 
 
 class SimpleItemsAspect:
@@ -67,6 +68,7 @@
         ser2 = sfw.ObjectSerializer('Persistence', 'PersistentMapping')
         fixed_items_aspect = FixedPersistentMapping()
         fixed_items_aspect.add('TestRoot', ('test',), ('test_mapper',))
+        fixed_items_aspect.add('TestRoot2', ('test2',), ('test_mapper_2',))
         ser2.addAspect('fixed_items', fixed_items_aspect)
         ser2.addAspect('roll_call', RollCall())
 
@@ -74,7 +76,7 @@
                                        classifier)
         self.root_mapper = root_mapper
 
-        # Create "test_mapper"
+        # Create "test_mapper", which allows a "strdata" attribute.
 
         ser1 = sfw.ObjectSerializer('Persistence', 'PersistentMapping')
         items_aspect = SimpleItemsAspect()
@@ -95,7 +97,7 @@
         self.om1 = om1
         root_mapper.addSubMapper('test_mapper', om1)
 
-        # Create "test_mapper_2"
+        # Create "test_mapper_2", which stores a remainder.
 
         ser = sfw.ObjectSerializer('Persistence', 'PersistentMapping')
         items_aspect = SimpleItemsAspect()


=== Products/AdaptableStorage/tests/testASStorage.py 1.3 => 1.4 ===
--- Products/AdaptableStorage/tests/testASStorage.py:1.3	Sat Dec  7 00:59:14 2002
+++ Products/AdaptableStorage/tests/testASStorage.py	Fri Dec 13 15:42:03 2002
@@ -47,6 +47,8 @@
         ob['a'] = 'b'
         ob['c'] = 'd'
 
+        dummy = PersistentMapping()
+
         conn1 = self.db.open()
         conn2 = None
         conn3 = None
@@ -56,6 +58,7 @@
             root = conn1.root()
             get_transaction().begin()
             root['TestRoot'] = ob
+            root['TestRoot2'] = dummy
             get_transaction().commit()
             ob1 = conn1.loadStub(('test',))
             self.assertEqual(ob1.strdata, ob.strdata)
@@ -92,6 +95,75 @@
                 conn2.close()
             if conn3 is not None:
                 conn3.close()
+
+
+    def testUnmanaged(self):
+        ob = PersistentMapping()
+        ob['a'] = 'b'
+        ob.stowaway = PersistentMapping()
+        ob.stowaway['c'] = 'd'
+
+        dummy = PersistentMapping()
+        dummy.strdata = 'foo'
+
+        conn1 = self.db.open()
+        conn2 = None
+        conn3 = None
+        try:
+
+            # Load the root and create a new object
+            root = conn1.root()
+            get_transaction().begin()
+            root['TestRoot'] = dummy
+            root['TestRoot2'] = ob
+            get_transaction().commit()
+            ob1 = conn1.loadStub(('test2',))
+            self.assert_(ob1 is ob)
+            self.assertEqual(ob1.items(), [('a', 'b')])
+            self.assertEqual(ob1.stowaway.items(), [('c', 'd')])
+
+            # Verify a new object was stored and make a change only to
+            # the unmanaged persistent object (the "stowaway").
+            get_transaction().begin()
+            conn2 = self.db.open()
+            ob2 = conn2.loadStub(('test2',))
+            self.assertEqual(ob2.items(), [('a', 'b')])
+            self.assertEqual(ob2.stowaway.items(), [('c', 'd')])
+            ob2.stowaway['c'] = 'e'
+            get_transaction().commit()
+
+            # Verify the change was stored and make a change to the
+            # managed persistent object.
+            conn3 = self.db.open()
+            ob3 = conn3.loadStub(('test2',))
+            self.assertEqual(ob3.items(), [('a', 'b')])
+            self.assertEqual(ob3.stowaway.items(), [('c', 'e')])
+            ob3['a'] = 'z'
+            get_transaction().commit()
+            conn3.close()
+            conn3 = None
+            conn3 = self.db.open()
+            ob3 = conn3.loadStub(('test2',))
+            self.assertEqual(ob3['a'], 'z')
+            self.assertEqual(ob3.stowaway.items(), [('c', 'e')])
+
+            # Verify we didn't accidentally change the original object.
+            self.assertEqual(ob['a'], 'b')
+
+            # sync and verify the current state.
+            conn1.sync()
+            self.assertEqual(ob1.items(), [('a', 'z')])
+            self.assertEqual(ob1.stowaway.items(), [('c', 'e')])
+
+        finally:
+            conn1.close()
+            if conn2 is not None:
+                conn2.close()
+            if conn3 is not None:
+                conn3.close()
+
+
+
 
 
 if __name__ == '__main__':


=== Products/AdaptableStorage/tests/testSerialization.py 1.5 => 1.6 ===
--- Products/AdaptableStorage/tests/testSerialization.py:1.5	Mon Dec  9 17:11:07 2002
+++ Products/AdaptableStorage/tests/testSerialization.py	Fri Dec 13 15:42:03 2002
@@ -44,10 +44,11 @@
         ob['c'] = 'd'
         kos = self.getKeyedObjectSystem()
         mapper = self.root_mapper.getSubMapper('test_mapper')
-        full_state, refs = mapper.getSerializer().serialize(
-            kos, mapper, '', ob)
+        event = sfw.SerializationEvent(kos, mapper, ('',), ob)
+        full_state = mapper.getSerializer().serialize(ob, event)
         ob2 = PersistentMapping()
-        mapper.getSerializer().deserialize(kos, mapper, '', ob2, full_state)
+        event = sfw.DeserializationEvent(kos, mapper, ('',), ob2)
+        mapper.getSerializer().deserialize(ob2, event, full_state)
         self.assertEqual(ob.strdata, ob2.strdata)
         self.assertEqual(ob.items(), ob2.items())
 
@@ -58,13 +59,14 @@
         ob['c'] = 'd'
         kos = self.getKeyedObjectSystem()
         mapper = self.root_mapper.getSubMapper('test_mapper')
-        full_state, refs = mapper.getSerializer().serialize(
-            kos, mapper, '', ob)
-        event = sfw.MapperEvent(mapper, '')
+        event = sfw.SerializationEvent(kos, mapper, ('',), ob)
+        full_state = mapper.getSerializer().serialize(ob, event)
+        event = sfw.MapperEvent(mapper, ('',))
         mapper.getGateway().store(event, full_state)
         full_state, serial = mapper.getGateway().load(event)
         ob2 = PersistentMapping()
-        mapper.getSerializer().deserialize(kos, mapper, '', ob2, full_state)
+        event = sfw.DeserializationEvent(kos, mapper, ('',), ob2)
+        mapper.getSerializer().deserialize(ob2, event, full_state)
         self.assertEqual(ob.strdata, ob2.strdata)
         self.assertEqual(ob.items(), ob2.items())
 
@@ -76,9 +78,9 @@
         ob['c'] = 'd'
         kos = self.getKeyedObjectSystem()
         mapper = self.root_mapper.getSubMapper('test_mapper')
+        event = sfw.SerializationEvent(kos, mapper, ('',), ob)
         self.assertRaises(sfw.SerializationError,
-                          mapper.getSerializer().serialize, kos, mapper,
-                          '', ob)
+                          mapper.getSerializer().serialize, ob, event)
 
     def testSharedAttribute(self):
         # Test of an attribute shared between an aspect and
@@ -89,14 +91,15 @@
         ob['a'] = data
         kos = self.getKeyedObjectSystem()
         mapper = self.root_mapper.getSubMapper('test_mapper_2')
-        full_state, refs = mapper.getSerializer().serialize(
-            kos, mapper, '', ob)
-        event = sfw.MapperEvent(mapper, '')
+        event = sfw.SerializationEvent(kos, mapper, ('',), ob)
+        full_state = mapper.getSerializer().serialize(ob, event)
+        event = sfw.MapperEvent(mapper, ('',))
         mapper.getGateway().store(event, full_state)
         # Now load the state into a different object
         full_state, serial = mapper.getGateway().load(event)
         ob2 = PersistentMapping()
-        mapper.getSerializer().deserialize(kos, mapper, '', ob2, full_state)
+        event = sfw.DeserializationEvent(kos, mapper, ('',), ob2)
+        mapper.getSerializer().deserialize(ob2, event, full_state)
         self.assertEqual(ob.extra.data, ob2.extra.data)
         self.assertEqual(ob.keys(), ob2.keys())
         # Check that both see the *same* object