[Zodb-checkins] SVN: ZODB/trunk/src/ZODB/ We check for implicitly adding objects by looking for "new" objects

Jim Fulton jim at zope.com
Fri Jun 17 17:16:17 EDT 2005


Log message for revision 30832:
  We check for implicitly adding objects by looking for "new" objects
  reachable from multiple connections.  Previously, we thought that we
  could limit the time that an object was new to a single savepoint, but
  that didn't work because savepoints of different connections are too
  independent.   Now an object is considered new for the full extent of
  the transaction in which it was created.
  
  Made it possible to use connection add methods to explicitly control
  the database an object is added too.
  

Changed:
  U   ZODB/trunk/src/ZODB/Connection.py
  U   ZODB/trunk/src/ZODB/cross-database-references.txt
  U   ZODB/trunk/src/ZODB/serialize.py
  U   ZODB/trunk/src/ZODB/tests/testcrossdatabasereferences.py

-=-
Modified: ZODB/trunk/src/ZODB/Connection.py
===================================================================
--- ZODB/trunk/src/ZODB/Connection.py	2005-06-17 17:43:11 UTC (rev 30831)
+++ ZODB/trunk/src/ZODB/Connection.py	2005-06-17 21:16:16 UTC (rev 30832)
@@ -311,6 +311,22 @@
             connection = new_con
         return connection
 
+    def _implicitlyAdding(self, oid):
+        """Are we implicitly adding an object within the current transaction
+
+        This is used in a check to avoid implicitly adding an object
+        to a database in a multi-database situation.
+        See serialize.ObjectWriter.persistent_id.
+        
+        """
+        return (self._creating.get(oid, 0)
+                or
+                ((self._savepoint_storage is not None)
+                 and
+                 self._savepoint_storage.creating.get(oid, 0)
+                 )
+                )
+
     def sync(self):
         """Manually update the view on the database."""
         self.transaction_manager.abort()
@@ -520,10 +536,15 @@
 
             if serial == z64:
                 # obj is a new object
-                self._creating[oid] = 1
-                # Because obj was added, it is now in _creating, so it can
-                # be removed from _added.
-                self._added.pop(oid, None)
+
+                # Because obj was added, it is now in _creating, so it
+                # can be removed from _added.  If oid wasn't in
+                # adding, then we are adding it implicitly.
+                
+                implicitly_adding = self._added.pop(oid, None) is None
+
+                self._creating[oid] = implicitly_adding
+                
             else:
                 if (oid in self._invalidated
                     and not hasattr(obj, '_p_resolveConflict')):

Modified: ZODB/trunk/src/ZODB/cross-database-references.txt
===================================================================
--- ZODB/trunk/src/ZODB/cross-database-references.txt	2005-06-17 17:43:11 UTC (rev 30831)
+++ ZODB/trunk/src/ZODB/cross-database-references.txt	2005-06-17 21:16:16 UTC (rev 30832)
@@ -91,19 +91,48 @@
 
     >>> tm.abort()
 
-To resolve this ambiguity, we need to commit, or create a savepoint
-before an object becomes reachable from multiple databases.  Here
-we'll use a savepoint to make sure that p4 lands in database 1:
+To resolve this ambiguity, we can commit before an object becomes
+reachable from multiple databases.
 
     >>> p4 = MyClass()
     >>> p1.p4 = p4
-    >>> s = tm.savepoint()
+    >>> tm.commit()
     >>> p2.p4 = p4
+    >>> tm.commit()
+    >>> p4._p_jar.db().database_name
+    '1'
 
-The advantage of using a savepoint is that we aren't making a
-commitment.  Changes made in the savepoint will be rolled back if the
-transaction is aborted.
+This doesn't work with a savepoint:
 
+    >>> p5 = MyClass()
+    >>> p1.p5 = p5
+    >>> s = tm.savepoint()
+    >>> p2.p5 = p5
+    >>> tm.commit() # doctest: +NORMALIZE_WHITESPACE
+    Traceback (most recent call last):
+    ...
+    InvalidObjectReference: A new object is reachable from multiple
+    databases. Won't try to guess which one was correct!
+
+    >>> tm.abort()
+
+(Maybe it should.)
+
+We can disambiguate this situation by using the connection add method
+to explicitly say waht database an object belongs to:
+
+
+    >>> p5 = MyClass()
+    >>> p1.p5 = p5
+    >>> p2.p5 = p5
+    >>> conn1.add(p5)
+    >>> tm.commit() 
+    >>> p5._p_jar.db().database_name
+    '1'
+
+This the most explicit and thus the best way, when practical, to avoid
+the ambiguity.
+
 NOTE
 ----
 

Modified: ZODB/trunk/src/ZODB/serialize.py
===================================================================
--- ZODB/trunk/src/ZODB/serialize.py	2005-06-17 17:43:11 UTC (rev 30831)
+++ ZODB/trunk/src/ZODB/serialize.py	2005-06-17 21:16:16 UTC (rev 30832)
@@ -347,8 +347,7 @@
             # OK, we have an object from another database.
             # Lets make sure the object ws not *just* loaded.
 
-            # TODO: shouldn't depend on underware (_creating)
-            if oid in obj._p_jar._creating:
+            if obj._p_jar._implicitlyAdding(oid):
                 raise InvalidObjectReference(
                     "A new object is reachable from multiple databases. "
                     "Won't try to guess which one was correct!"

Modified: ZODB/trunk/src/ZODB/tests/testcrossdatabasereferences.py
===================================================================
--- ZODB/trunk/src/ZODB/tests/testcrossdatabasereferences.py	2005-06-17 17:43:11 UTC (rev 30831)
+++ ZODB/trunk/src/ZODB/tests/testcrossdatabasereferences.py	2005-06-17 21:16:16 UTC (rev 30832)
@@ -121,7 +121,31 @@
     >>> db2.close()
 """
 
+def test_explicit_adding_with_savepoint():
+    """
 
+    >>> import ZODB.tests.util, transaction, persistent
+    >>> databases = {}
+    >>> db1 = ZODB.tests.util.DB(databases=databases, database_name='1')
+    >>> db2 = ZODB.tests.util.DB(databases=databases, database_name='2')
+    >>> tm = transaction.TransactionManager()
+    >>> conn1 = db1.open(transaction_manager=tm)
+    >>> conn2 = conn1.get_connection('2')
+    >>> z = MyClass()
+
+    >>> conn1.root()['z'] = z
+    >>> conn1.add(z)
+    >>> s = tm.savepoint()
+    >>> conn2.root()['z'] = z
+    >>> tm.commit()
+    >>> z._p_jar.db().database_name
+    '1'
+    
+    >>> db1.close()
+    >>> db2.close()
+
+"""
+
 def tearDownDbs(test):
     test.globs['db1'].close()
     test.globs['db2'].close()



More information about the Zodb-checkins mailing list