[Zodb-checkins] CVS: Zope3/src/zodb - serialize.py:1.6 interfaces.py:1.4 db.py:1.4

Jeremy Hylton jeremy@zope.com
Fri, 24 Jan 2003 18:21:03 -0500


Update of /cvs-repository/Zope3/src/zodb
In directory cvs.zope.org:/tmp/cvs-serv31712/zodb

Modified Files:
	serialize.py interfaces.py db.py 
Log Message:
Merge new-pickle-branch to trunk.  Yee ha!


=== Zope3/src/zodb/serialize.py 1.5 => 1.6 ===
--- Zope3/src/zodb/serialize.py:1.5	Tue Jan 21 16:00:31 2003
+++ Zope3/src/zodb/serialize.py	Fri Jan 24 18:20:58 2003
@@ -21,31 +21,38 @@
 ghost allows many persistent objects to be loaded while minimizing the
 memory consumption of referenced but otherwise unused objects.
 
-Object introspection
---------------------
-
-XXX Need to define what properties an object must have to be usable
-with the ObjectWriter.  Should document how it determines what the
-class and state of an object are.
-
 Pickle format
 -------------
 
 ZODB pickles objects using a custom format.  Each object pickle had
-two parts: the class description and the object state.  The class
+two parts: the class metadata and the object state.  The class
 description must provide enough information to call the class's
-``__new__`` and create an empty object.  Once the object exists, its
-state is passed to ``__getstate__``.
+``__new__`` and create an empty object.  Once the object exists as a
+ghost, its state is passed to ``__setstate__``.
 
-The class metadata is a three-tuple contained the module name, the
-class name, and a tuple of arguments to pass to ``__new__``.  The last
-element may be None if the only argument to ``__new__`` is the class.
+The class metadata is a two-tuple containing the class object and a
+tuple of arguments to pass to ``__new__``.  The second element may be
+None if the only argument to ``__new__`` is the class.  Since the
+first argument is a class, it will normally be pickled as a global
+reference.  If the class is itself a persistent object, then the first
+part of its instances class metadata will be a persistent reference to
+the class.
 
 Persistent references
 ---------------------
+
 A persistent reference is a pair containing an oid and class metadata.
-XXX Need to write more about when they are used and when plain oids
-are used.
+When one persistent object pickle refers to another persistent object,
+the database uses a persistent reference.  The format allows a
+significant optimization, because ghosts can be created directly from
+persistent references.  If the reference was just an oid, a database
+access would be required to determine the class of the ghost.
+
+Because the persistent reference includes the class, it is not
+possible to change the class of a persistent object.  If a transaction
+changed the class of an object, a new record with new class metadata
+would be written but all the old references would still include the
+old class.
 """
 
 __metaclass__ = type
@@ -55,24 +62,14 @@
 from types import StringType, TupleType
 import logging
 
-def getClass(module, name):
-    mod = __import__(module)
-    parts = module.split(".")
-    for part in parts[1:]:
-        mod = getattr(mod, part)
-    return getattr(mod, name)
-
 def getClassMetadata(obj=None, klass=None):
     if klass is None:
         klass = obj.__class__
-    module = klass.__module__
-    classname = klass.__name__
-    # XXX what if obj is None and we were passed klass?
-    if hasattr(obj, "__getnewargs__"):
-        newargs = obj.__getnewargs__()
-    else:
-        newargs = None
-    return module, classname, newargs
+    # XXX Not sure I understand the obj==None casse
+    newargs = None
+    if obj is not None and hasattr(obj, "__getnewargs__"):
+            newargs = obj.__getnewargs__()
+    return klass, newargs
 
 class RootJar:
     def new_oid(self):
@@ -181,8 +178,7 @@
         unpickler.persistent_load = self._persistent_load
         return unpickler
 
-    def _new_object(self, module, classname, newargs=None):
-        klass = getClass(module, classname)
+    def _new_object(self, klass, newargs=None):
         if newargs is None:
             obj = klass.__new__(klass)
         else:
@@ -197,8 +193,8 @@
 
     def getGhost(self, pickle):
         unpickler = self._get_unpickler(pickle)
-        module, classname, newargs = unpickler.load()
-        return self._new_object(module, classname, newargs)
+        klass, newargs = unpickler.load()
+        return self._new_object(klass, newargs)
 
     def getState(self, pickle):
         unpickler = self._get_unpickler(pickle)
@@ -212,8 +208,8 @@
 
     def getObject(self, pickle):
         unpickler = self._get_unpickler(pickle)
-        module, classname, newargs = unpickler.load()
-        obj = self._new_object(module, classname, newargs)
+        klass, newargs = unpickler.load()
+        obj = self._new_object(klass, newargs)
         state = unpickler.load()
         obj.__setstate__(state)
         return obj


=== Zope3/src/zodb/interfaces.py 1.3 => 1.4 ===
--- Zope3/src/zodb/interfaces.py:1.3	Wed Jan 15 17:59:06 2003
+++ Zope3/src/zodb/interfaces.py	Fri Jan 24 18:20:58 2003
@@ -16,11 +16,14 @@
 $Id$
 """
 
-from transaction.interfaces \
-     import TransactionError, RollbackError, ConflictError as _ConflictError
-
 from types import StringType, DictType
+
 import zodb.utils
+from zope.interface import Interface, Attribute
+
+from transaction.interfaces import ITransaction as _ITransaction
+from transaction.interfaces \
+     import TransactionError, RollbackError, ConflictError as _ConflictError
 
 def _fmt_oid(oid):
     return "%016x" % zodb.utils.u64(oid)
@@ -184,6 +187,19 @@
 class StorageError(POSError):
     """Base class for storage based exceptions."""
 
+class StorageVersionError(StorageError):
+    """The storage version doesn't match the database version."""
+
+    def __init__(self, db_ver, storage_ver):
+        self.db_ver = db_ver
+        self.storage_ver = storage_ver
+
+    def __str__(self):
+        db = ".".join(self.db_ver)
+        storage = ".".join(self.storage_ver)
+        return ("Storage version %s passed to database version %s"
+                % (storage, db))
+
 class StorageTransactionError(StorageError):
     """An operation was invoked for an invalid transaction or state."""
 
@@ -214,12 +230,6 @@
 
     o A reference to an object in a different database connection.
     """
-
-
-from zope.interface import Interface
-from zope.interface import Attribute
-
-from transaction.interfaces import ITransaction as _ITransaction
 
 class IConnection(Interface):
     """Interface required of Connection by ZODB DB.


=== Zope3/src/zodb/db.py 1.3 => 1.4 ===
--- Zope3/src/zodb/db.py:1.3	Tue Jan 21 13:19:55 2003
+++ Zope3/src/zodb/db.py	Fri Jan 24 18:20:58 2003
@@ -24,7 +24,7 @@
 from types import StringType
 import logging
 
-from zodb.interfaces import StorageError
+from zodb.interfaces import StorageError, StorageVersionError
 from zodb.connection import Connection
 from zodb.serialize import getDBRoot
 from zodb.ztransaction import Transaction
@@ -40,6 +40,10 @@
     or more connections, which manage object spaces.  Most of the actual work
     of managing objects is done by the connections.
     """
+
+    # the database version number, a 4-byte string
+    version = "DB01"
+    
     def __init__(self, storage, pool_size=7, cache_size=400):
         """Create an object database.
 
@@ -71,6 +75,7 @@
 
         # Setup storage
         self._storage = storage
+        self._checkVersion()
         storage.registerDB(self)
         try:
             storage.load(z64, "")
@@ -86,6 +91,16 @@
         for m in ('history', 'supportsVersions', 'undoInfo', 'versionEmpty',
                   'versions', 'modifiedInVersion', 'versionEmpty'):
             setattr(self, m, getattr(storage, m))
+
+    def _checkVersion(self):
+        # Make sure the database version that created the storage is
+        # compatible with this version of the database.  If the storage
+        # doesn't have a database version, it's brand-new so set it.
+        ver = self._storage.getVersion()
+        if ver is None:
+            self._storage.setVersion(self.version)
+        elif ver != self.version:
+            raise StorageVersionError(self.version, ver)
 
     def _closeConnection(self, connection):
         """Return a connection to the pool"""