[Zodb-checkins] SVN: ZODB/trunk/src/ZODB/ Added cross-database-reference support.

Jim Fulton jim at zope.com
Thu Jun 9 15:15:20 EDT 2005


Log message for revision 30724:
  Added cross-database-reference support.
  

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

-=-
Added: ZODB/trunk/src/ZODB/cross-database-references.txt
===================================================================
--- ZODB/trunk/src/ZODB/cross-database-references.txt	2005-06-09 19:15:18 UTC (rev 30723)
+++ ZODB/trunk/src/ZODB/cross-database-references.txt	2005-06-09 19:15:20 UTC (rev 30724)
@@ -0,0 +1,82 @@
+Cross-Database References
+=========================
+
+Persistent references to objects in different databases within a
+multi-database are allowed.  
+
+Lets set up a multi-database with 2 databases:
+
+    >>> 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')
+
+And create a persistent object in the first database:
+
+    >>> tm = transaction.TransactionManager()
+    >>> conn1 = db1.open(transaction_manager=tm)
+    >>> p1 = MyClass()
+    >>> conn1.root()['p'] = p1
+    >>> tm.commit()
+
+Now, we'll create a second persistent object in the second database.
+We'll have a reference to the first object:
+
+    >>> tm = transaction.TransactionManager()
+    >>> conn2 = db2.open(transaction_manager=tm)
+    >>> p2 = MyClass()
+    >>> conn2.root()['p'] = p2
+    >>> p2.p1 = p1
+    >>> tm.commit()
+
+Now, let's open a separate connection to database 2.  We use it to
+read p2, use p2 to get to p1, and verify that it is in database 1:
+
+    >>> conn = db2.open()
+    >>> p2x = conn.root()['p']
+    >>> p1x = p2x.p1
+
+    >>> p2x is p2, p2x._p_oid == p2._p_oid, p2x._p_jar.db() is db2
+    (False, True, True)
+
+    >>> p1x is p1, p1x._p_oid == p1._p_oid, p1x._p_jar.db() is db1
+    (False, True, True)
+
+It isn't valid to create references outside a multi database:
+
+    >>> db3 = ZODB.tests.util.DB()
+    >>> conn3 = db3.open(transaction_manager=tm)
+    >>> p3 = MyClass()
+    >>> conn3.root()['p'] = p3
+    >>> tm.commit()
+
+    >>> p2.p3 = p3
+    >>> tm.commit() # doctest: +NORMALIZE_WHITESPACE
+    Traceback (most recent call last):
+    ...
+    InvalidObjectReference: 
+    Attempt to store an object from a foreign database connection
+
+NOTE
+----
+
+This implementation is incomplete.  It allows creatting and using
+cross-database references, however, there are a number of facilities
+missing:
+
+cross-database garbage collection
+    Garbage collection is done on a database by database basis.
+    If an object on a database only has references to it from other
+    databases, then the object will be garbage collected when it's
+    database is packed.  The cross-database references to it will be
+    broken.
+
+cross-database undo
+    Undo is only applied to a single database.  Fixing this for
+    mutliple databases is going to be extremely difficult.  Undo
+    currently poses consistency problems, so it is not (or should not
+    be) widely used.
+
+Cross-database aware (tolerant) export/import
+    The export/import facility needs to be aware, at least, of cross-database
+    references.


Property changes on: ZODB/trunk/src/ZODB/cross-database-references.txt
___________________________________________________________________
Name: svn:eol-style
   + native

Modified: ZODB/trunk/src/ZODB/serialize.py
===================================================================
--- ZODB/trunk/src/ZODB/serialize.py	2005-06-09 19:15:18 UTC (rev 30723)
+++ ZODB/trunk/src/ZODB/serialize.py	2005-06-09 19:15:20 UTC (rev 30724)
@@ -188,6 +188,10 @@
         >>> class DummyJar:
         ...     def new_oid(self):
         ...         return 42
+        ...     def db(self):
+        ...         return self
+        ...     databases = {}
+        
         >>> jar = DummyJar()
         >>> class O:
         ...     _p_jar = jar
@@ -312,16 +316,27 @@
 
         # NOTE! Persistent classes don't (and can't) subclass persistent.
 
+        database_name = None
+
         if oid is None:
             oid = obj._p_oid = self._jar.new_oid()
             obj._p_jar = self._jar
             self._stack.append(obj)
+            
         elif obj._p_jar is not self._jar:
-            raise InvalidObjectReference(
-                "Attempt to store an object from a foreign "
-                "database connection"
-                )
 
+            try:
+                otherdb = obj._p_jar.db()
+                database_name = otherdb.database_name
+            except AttributeError:
+                otherdb = self
+
+            if self._jar.db().databases.get(database_name) is not otherdb:
+                raise InvalidObjectReference(
+                    "Attempt to store an object from a foreign "
+                    "database connection"
+                    )
+
         klass = type(obj)
         if hasattr(klass, '__getnewargs__'):
             # We don't want to save newargs in object refs.
@@ -333,11 +348,15 @@
             # __getnewargs__ of its own, we'll lose the optimization
             # of caching the class info.
 
+            if database_name:
+                return ['n', (database_name, oid)]
             return oid
 
         # Note that we never get here for persistent classes.
         # We'll use driect refs for normal classes.
 
+        if database_name:
+            return ['m', (database_name, oid, klass)]
         return oid, klass
 
     def serialize(self, obj):
@@ -475,8 +494,15 @@
         self._cache[oid] = obj
         return obj
 
-    loaders['p'] = load_persistent
+    def load_multi_persistent(self, database_name, oid, klass):
+        conn = self._conn.get_connection(database_name)
+        # TODO, make connection _cache attr public
+        reader = ObjectReader(conn, conn._cache, self._factory)
+        return reader.load_persistent(oid, klass)
 
+    loaders['m'] = load_multi_persistent
+
+
     def load_persistent_weakref(self, oid):
         obj = WeakRef.__new__(WeakRef)
         obj.oid = oid
@@ -491,8 +517,14 @@
             return obj
         return self._conn.get(oid)
 
-    loaders['o'] = load_oid
+    def load_multi_oid(self, database_name, oid):
+        conn = self._conn.get_connection(database_name)
+        # TODO, make connection _cache attr public
+        reader = ObjectReader(conn, conn._cache, self._factory)
+        return reader.load_oid(oid)
 
+    loaders['n'] = load_multi_oid
+
     def _new_object(self, klass, args):
         if not args and not myhasattr(klass, "__getnewargs__"):
             obj = klass.__new__(klass)

Added: ZODB/trunk/src/ZODB/tests/testcrossdatabasereferences.py
===================================================================
--- ZODB/trunk/src/ZODB/tests/testcrossdatabasereferences.py	2005-06-09 19:15:18 UTC (rev 30723)
+++ ZODB/trunk/src/ZODB/tests/testcrossdatabasereferences.py	2005-06-09 19:15:20 UTC (rev 30724)
@@ -0,0 +1,41 @@
+##############################################################################
+#
+# Copyright (c) 2004 Zope Corporation and Contributors.
+# All Rights Reserved.
+#
+# This software is subject to the provisions of the Zope Public License,
+# Version 2.0 (ZPL).  A copy of the ZPL should accompany this distribution.
+# THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED
+# WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+# WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS
+# FOR A PARTICULAR PURPOSE.
+#
+##############################################################################
+"""
+$Id$
+"""
+import unittest
+from zope.testing import doctest
+import persistent
+
+class MyClass(persistent.Persistent):
+    pass
+
+class MyClass_w_getnewargs(persistent.Persistent):
+
+    def __getnewargs__(self):
+        return ()
+
+def test_suite():
+    return unittest.TestSuite((
+        doctest.DocFileSuite('../cross-database-references.txt',
+                             globs=dict(MyClass=MyClass),
+                             ),
+        doctest.DocFileSuite('../cross-database-references.txt',
+                             globs=dict(MyClass=MyClass_w_getnewargs),
+                             ),
+        ))
+
+if __name__ == '__main__':
+    unittest.main(defaultTest='test_suite')
+


Property changes on: ZODB/trunk/src/ZODB/tests/testcrossdatabasereferences.py
___________________________________________________________________
Name: svn:keywords
   + Id
Name: svn:eol-style
   + native



More information about the Zodb-checkins mailing list