[Zope3-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:02 -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"""