[Zodb-checkins] SVN: ZODB/trunk/src/ZODB/ Reimplemented DemoStorage to be just a wrapper around a base storage

Jim Fulton jim at zope.com
Sat Oct 25 20:36:07 EDT 2008


Log message for revision 92553:
  Reimplemented DemoStorage to be just a wrapper around a base storage
  and a changes storage.
  

Changed:
  U   ZODB/trunk/src/ZODB/DemoStorage.py
  A   ZODB/trunk/src/ZODB/DemoStorage.test
  U   ZODB/trunk/src/ZODB/component.xml
  U   ZODB/trunk/src/ZODB/config.py
  U   ZODB/trunk/src/ZODB/tests/testDemoStorage.py

-=-
Modified: ZODB/trunk/src/ZODB/DemoStorage.py
===================================================================
--- ZODB/trunk/src/ZODB/DemoStorage.py	2008-10-26 00:36:04 UTC (rev 92552)
+++ ZODB/trunk/src/ZODB/DemoStorage.py	2008-10-26 00:36:06 UTC (rev 92553)
@@ -1,6 +1,6 @@
 ##############################################################################
 #
-# Copyright (c) 2001, 2002 Zope Corporation and Contributors.
+# Copyright (c) Zope Corporation and Contributors.
 # All Rights Reserved.
 #
 # This software is subject to the provisions of the Zope Public License,
@@ -13,592 +13,153 @@
 ##############################################################################
 """Demo ZODB storage
 
-The Demo storage serves two purposes:
+A demo storage supports demos by allowing a volatile changed database
+to be layered over a base database.
 
-  - Provide an example implementation of a full storage without
-    distracting storage details,
+The base storage must not change.
 
-  - Provide a volatile storage that is useful for giving demonstrations.
-
-The demo storage can have a "base" storage that is used in a
-read-only fashion. The base storage must not contain version
-data.
-
-There are three main data structures:
-
-  _data -- Transaction logging information necessary for undo
-
-      This is a mapping from transaction id to transaction, where
-      a transaction is simply a 5-tuple:
-
-        packed, user, description, extension_data, records
-
-      where extension_data is a dictionary or None and records are the
-      actual records in chronological order. Packed is a flag
-      indicating whethe the transaction has been packed or not
-
-  _index -- A mapping from oid to record
-
-  _vindex -- A mapping from version name to version data
-
-      where version data is a mapping from oid to record
-
-A record is a tuple:
-
-  oid, pre, vdata, p, tid
-
-where:
-
-     oid -- object id
-
-     pre -- The previous record for this object (or None)
-
-     vdata -- version data
-
-        None if not a version, ortherwise:
-           version, non-version-record
-
-     p -- the pickle data or None
-
-     tid -- the transaction id that wrote the record
-
-The pickle data will be None for a record for an object created in
-an aborted version.
-
-It is instructive to watch what happens to the internal data structures
-as changes are made.  For example, in Zope, you can create an external
-method::
-
-  import Zope2
-
-  def info(RESPONSE):
-      RESPONSE['Content-type']= 'text/plain'
-
-      return Zope2.DB._storage._splat()
-
-and call it to monitor the storage.
-
 """
+import random
+import threading
+import ZODB.MappingStorage
+import ZODB.POSException
+import ZODB.utils
 
-import cPickle
-import base64, time
+class DemoStorage:
 
-import ZODB.BaseStorage
-import ZODB.interfaces
-import zope.interface
-from ZODB import POSException
-from ZODB.utils import z64, oid_repr
-from persistent.TimeStamp import TimeStamp
-from BTrees import OOBTree
+    def __init__(self, name=None, base=None, changes=None):
+        if base is None:
+            base = ZODB.MappingStorage.MappingStorage()
+        self.base = base
+            
+        if changes is None:
+            changes = ZODB.MappingStorage.MappingStorage()
+        self.changes = changes
 
+        if name is None:
+            name = 'DemoStorage(%r, %r)' % (base.getName(), changes.getName())
+        self.__name__ = name
 
-class DemoStorage(ZODB.BaseStorage.BaseStorage):
-    """Demo storage
+        supportsUndo = getattr(changes, 'supportsUndo', None)
+        if supportsUndo is not None and supportsUndo():
+            for meth in ('supportsUndo', 'undo', 'undoLog', 'undoInfo'):
+                setattr(self, meth, getattr(changes, meth))
 
-    Demo storages provide useful storages for writing tests because
-    they store their data in memory and throw away their data
-    (implicitly) when they are closed.
+        for meth in (
+            '_lock_acquire', '_lock_release', 
+            'getSize', 'history', 'isReadOnly', 'registerDB',
+            'sortKey', 'tpc_begin', 'tpc_abort', 'tpc_finish',
+            'tpc_transaction', 'tpc_vote',
+            ):
+            setattr(self, meth, getattr(changes, meth))
 
-    They were originally designed to allow demonstrations using base
-    data provided on a CD.  They can optionally wrap an *unchanging*
-    base storage.  It is critical that the base storage does not
-    change. Using a changing base storage is not just unsupported, it
-    is known not to work and can even lead to serious errors and even
-    core dumps.
+        lastInvalidations = getattr(changes, 'lastInvalidations', None)
+        if lastInvalidations is not None:
+            self.lastInvalidations = lastInvalidations
     
-    """
-    
-    zope.interface.implements(ZODB.interfaces.IStorageIteration)
+    def cleanup(self):
+        self.base.cleanup()
+        self.changes.cleanup()
 
-    def __init__(self, name='Demo Storage', base=None, quota=None):
-        ZODB.BaseStorage.BaseStorage.__init__(self, name, base)
+    def close(self):
+        self.base.close()
+        self.changes.close()
 
-        # We use a BTree because the items are sorted!
-        self._data = OOBTree.OOBTree()
-        self._index = {}
-        self._vindex = {}
-        self._base = base
-        self._size = 0
-        self._quota = quota
-        self._ltid = None
-        self._clear_temp()
+    def getName(self):
+        return self.__name__
+    __repr__ = getName
 
+    def getTid(self, oid):
         try:
-            versions = base.versions
-        except AttributeError:
-            pass
-        else:
-            if base.versions():
-                raise POSException.StorageError(
-                    "Demo base storage has version data")
+            return self.changes.getTid(oid)
+        except ZODB.POSException.POSKeyError:
+            return self.base.getTid(oid)
 
-    # When DemoStorage needs to create a new oid, and there is a base
-    # storage, it must use that storage's new_oid() method.  Else
-    # DemoStorage may end up assigning "new" oids that are already in use
-    # by the base storage, leading to a variety of "impossible" problems.
-    def new_oid(self):
-        if self._base is None:
-            return ZODB.BaseStorage.BaseStorage.new_oid(self)
-        else:
-            return self._base.new_oid()
+    def iterator(self, start=None, end=None):
+        for t in self.base.iterator(start, end):
+            yield t
+        for t in self.changes.iterator(start, end):
+            yield t
 
+    def lastTransaction(self):
+        t = self.changes.lastTransaction()
+        if t == ZODB.utils.z64:
+            t = self.base.lastTransaction()
+        return t
+
     def __len__(self):
-        base=self._base
-        return (base and len(base) or 0) + len(self._index)
+        return len(self.changes)
 
-    def getSize(self):
-        s=100
-        for tid, (p, u, d, e, t) in self._data.items():
-            s=s+16+24+12+4+16+len(u)+16+len(d)+16+len(e)+16
-            for oid, pre, vdata, p, tid in t:
-                s=s+16+24+24+4+4+(p and (16+len(p)) or 4)
-                if vdata: s=s+12+16+len(vdata[0])+4
-
-        s=s+16*len(self._index)
-
-        for v in self._vindex.values():
-            s=s+32+16*len(v)
-
-        self._size=s
-        return s
-
-    def abortVersion(self, src, transaction):
-        if transaction is not self._transaction:
-            raise POSException.StorageTransactionError(self, transaction)
-        if not src:
-            raise POSException.VersionCommitError("Invalid version")
-
-        self._lock_acquire()
+    def load(self, oid, version=''):
         try:
-            v = self._vindex.get(src, None)
-            if not v:
-                return
+            return self.changes.load(oid, version)
+        except ZODB.POSException.POSKeyError:
+            return self.base.load(oid, version)
 
-            oids = []
-            for r in v.values():
-                oid, pre, (version, nv), p, tid = r
-                oids.append(oid)
-                if nv:
-                    oid, pre, vdata, p, tid = nv
-                    self._tindex.append([oid, r, None, p, self._tid])
-                else:
-                    # effectively, delete the thing
-                    self._tindex.append([oid, r, None, None, self._tid])
-
-            return self._tid, oids
-
-        finally: self._lock_release()
-
-    def commitVersion(self, src, dest, transaction):
-        if transaction is not self._transaction:
-            raise POSException.StorageTransactionError(self, transaction)
-
-        if not src:
-            raise POSException.VersionCommitError("Invalid source version")
-        if src == dest:
-            raise POSException.VersionCommitError(
-                "Can't commit to same version: %s" % repr(src))
-
-        self._lock_acquire()
+    def loadBefore(self, oid, tid):
         try:
-            v = self._vindex.get(src)
-            if v is None:
-                return
+            result = self.changes.loadBefore(oid, tid)
+        except ZODB.POSException.POSKeyError:
+            # The oid isn't in the changes, so defer to base
+            return self.base.loadBefore(oid, tid)
 
-            newserial = self._tid
-            tindex = self._tindex
-            oids = []
-            for r in v.values():
-                oid, pre, vdata, p, tid = r
-                assert vdata is not None
-                oids.append(oid)
-                if dest:
-                    new_vdata = dest, vdata[1]
-                else:
-                    new_vdata = None
-                tindex.append([oid, r, new_vdata, p, self._tid])
+        if result is None:
+            # The oid *was* in the changes, but there aren't any
+            # earlier records. Maybe there are in the base.
+            try:
+                return self.base.loadBefore(oid, tid)
+            except ZODB.POSException.POSKeyError:
+                # The oid isn't in the base, so None will be the right result
+                pass
 
-            return self._tid, oids
-
-        finally:
-            self._lock_release()
-
-    def load(self, oid, version):
-        self._lock_acquire()
+        return result
+            
+    def loadSerial(self, oid, serial):
         try:
+            return self.changes.loadSerial(oid, serial)
+        except ZODB.POSException.POSKeyError:
+            return self.base.loadSerial(oid, serial)
+
+    def new_oid(self):
+        while 1:
+            oid = ZODB.utils.p64(random.randint(1, 9223372036854775807))
             try:
-                oid, pre, vdata, p, tid = self._index[oid]
-            except KeyError:
-                if self._base:
-                    return self._base.load(oid, version)
-                raise KeyError(oid)
+                self.changes.load(oid, '')
+            except ZODB.POSException.POSKeyError:
+                pass
+            else:
+                continue
+            try:
+                self.base.load(oid, '')
+            except ZODB.POSException.POSKeyError:
+                pass
+            else:
+                continue
+            
+            return oid
 
-            if vdata:
-                oversion, nv = vdata
-                if oversion != version:
-                    if nv:
-                        # Return the current txn's tid with the non-version
-                        # data.
-                        p = nv[3]
-                    else:
-                        raise KeyError(oid)
-
-            if p is None:
-                raise KeyError(oid)
-
-            return p, tid
-        finally: self._lock_release()
-
-    def modifiedInVersion(self, oid):
-        self._lock_acquire()
+    def pack(self, t, referencesf, gc=False):
         try:
-            try:
-                oid, pre, vdata, p, tid = self._index[oid]
-                if vdata: return vdata[0]
-                return ''
-            except: return ''
-        finally: self._lock_release()
+            self.changes.pack(t, referencesf, gc=False)
+        except TypeError, v:
+            if 'gc' in str(v):
+                pass # The gc arg isn't supported. Don't pack
+            raise
 
     def store(self, oid, serial, data, version, transaction):
-        if transaction is not self._transaction:
-            raise POSException.StorageTransactionError(self, transaction)
+        assert version=='', "versions aren't supported"
 
-        self._lock_acquire()
+        # See if we already have changes for this oid
         try:
-            old = self._index.get(oid, None)
-            if old is None:
-                # Hm, nothing here, check the base version:
-                if self._base:
-                    try:
-                        p, tid = self._base.load(oid, '')
-                    except KeyError:
-                        pass
-                    else:
-                        old = oid, None, None, p, tid
+            old = self.changes.load(oid, '')[1]
+        except ZODB.POSException.POSKeyError:
+            try:
+                old = self.base.load(oid, '')[1]
+            except ZODB.POSException.POSKeyError:
+                old = serial
+                
+        if old != serial:
+            raise ZODB.POSException.ConflictError(
+                oid=oid, serials=(old, serial)) # XXX untested branch
 
-            nv=None
-            if old:
-                oid, pre, vdata, p, tid = old
-
-                if vdata:
-                    if vdata[0] != version:
-                        raise POSException.VersionLockError(oid)
-
-                    nv=vdata[1]
-                else:
-                    nv=old
-
-                if serial != tid:
-                    raise POSException.ConflictError(
-                        oid=oid, serials=(tid, serial), data=data)
-
-            r = [oid, old, version and (version, nv) or None, data, self._tid]
-            self._tindex.append(r)
-
-            s=self._tsize
-            s=s+72+(data and (16+len(data)) or 4)
-            if version: s=s+32+len(version)
-
-            if self._quota is not None and s > self._quota:
-                raise POSException.StorageError(
-                    '''<b>Quota Exceeded</b><br>
-                    The maximum quota for this demonstration storage
-                    has been exceeded.<br>Have a nice day.''')
-
-        finally: self._lock_release()
-        return self._tid
-
-    def supportsVersions(self):
-        return 1
-
-    def _clear_temp(self):
-        self._tindex = []
-        self._tsize = self._size + 160
-
-    def lastTransaction(self):
-        return self._ltid
-
-    def _begin(self, tid, u, d, e):
-        self._tsize = self._size + 120 + len(u) + len(d) + len(e)
-
-    def _finish(self, tid, user, desc, ext):
-        if not self._tindex:
-            # No data, so we don't update anything.
-            return
-
-        self._size = self._tsize
-
-        self._data[tid] = None, user, desc, ext, tuple(self._tindex)
-        for r in self._tindex:
-            oid, pre, vdata, p, tid = r
-            old = self._index.get(oid)
-            # If the object had version data, remove the version data.
-            if old is not None:
-                oldvdata = old[2]
-                if oldvdata:
-                    v = self._vindex[oldvdata[0]]
-                    del v[oid]
-                    if not v:
-                        # If the version info is now empty, remove it.
-                        del self._vindex[oldvdata[0]]
-
-            self._index[oid] = r
-
-            # If there is version data, then udpate self._vindex, too.
-            if vdata:
-                version = vdata[0]
-                v = self._vindex.get(version)
-                if v is None:
-                    v = self._vindex[version] = {}
-                v[oid] = r
-        self._ltid = self._tid
-
-    def undoLog(self, first, last, filter=None):
-        if last < 0:  # abs(last) is an upper bound on the # to return
-            last = first - last
-        self._lock_acquire()
-        try:
-            transactions = self._data.items()
-            pos = len(transactions)
-            r = []
-            i = 0
-            while i < last and pos:
-                pos -= 1
-                tid, (p, u, d, e, t) = transactions[pos]
-                # Bug alert:  why do we skip this if the transaction
-                # has been packed?
-                if p:
-                    continue
-                d = {'id': base64.encodestring(tid)[:-1],
-                     'time': TimeStamp(tid).timeTime(),
-                     'user_name': u, 'description': d}
-                if e:
-                    d.update(cPickle.loads(e))
-                if filter is None or filter(d):
-                    if i >= first:
-                        r.append(d)
-                    i += 1
-            return r
-        finally:
-            self._lock_release()
-
-    def versionEmpty(self, version):
-        return not self._vindex.get(version, None)
-
-    def versions(self, max=None):
-        r = []
-        for v in self._vindex.keys():
-            if self.versionEmpty(v):
-                continue
-            r.append(v)
-            if max is not None and len(r) >= max:
-                break
-        return r
-
-    def _build_indexes(self, stop='\377\377\377\377\377\377\377\377'):
-        # Rebuild index structures from transaction data
-        index = {}
-        vindex = {}
-        for tid, (p, u, d, e, t) in self._data.items():
-            if tid >= stop:
-                break
-            for r in t:
-                oid, pre, vdata, p, tid = r
-                old=index.get(oid, None)
-
-                if old is not None:
-                    oldvdata=old[2]
-                    if oldvdata:
-                        v=vindex[oldvdata[0]]
-                        del v[oid]
-                        if not v: del vindex[oldvdata[0]]
-
-                index[oid]=r
-
-                if vdata:
-                    version=vdata[0]
-                    v=vindex.get(version, None)
-                    if v is None: v=vindex[version]={}
-                    vindex[vdata[0]][oid]=r
-
-        return index, vindex
-
-    def pack(self, t, referencesf):
-        # Packing is hard, at least when undo is supported.
-        # Even for a simple storage like this one, packing
-        # is pretty complex.
-
-        self._lock_acquire()
-        try:
-
-            stop=`TimeStamp(*time.gmtime(t)[:5]+(t%60,))`
-
-            # Build indexes up to the pack time:
-            index, vindex = self._build_indexes(stop)
-
-
-            # TODO:  This packing algorithm is flawed. It ignores
-            # references from non-current records after the pack
-            # time.
-
-            # Now build an index of *only* those objects reachable
-            # from the root.
-            rootl = [z64]
-            pindex = {}
-            while rootl:
-                oid = rootl.pop()
-                if oid in pindex:
-                    continue
-
-                # Scan non-version pickle for references
-                r = index.get(oid, None)
-                if r is None:
-                    if self._base:
-                        p, s = self._base.load(oid, '')
-                        referencesf(p, rootl)
-                else:
-                    pindex[oid] = r
-                    oid, pre, vdata, p, tid = r
-                    referencesf(p, rootl)
-                    if vdata:
-                        nv = vdata[1]
-                        if nv:
-                            oid, pre, vdata, p, tid = nv
-                            referencesf(p, rootl)
-
-            # Now we're ready to do the actual packing.
-            # We'll simply edit the transaction data in place.
-            # We'll defer deleting transactions till the end
-            # to avoid messing up the BTree items.
-            deleted = []
-            for tid, (p, u, d, e, records) in self._data.items():
-                if tid >= stop:
-                    break
-                o = []
-                for r in records:
-                    c = pindex.get(r[0])
-                    if c is None:
-                        # GC this record, no longer referenced
-                        continue
-                    if c == r:
-                        # This is the most recent revision.
-                        o.append(r)
-                    else:
-                        # This record is not the indexed record,
-                        # so it may not be current. Let's see.
-                        vdata = r[3]
-                        if vdata:
-                            # Version record are current *only* if they
-                            # are indexed
-                            continue
-                        else:
-                            # OK, this isn't a version record, so it may be the
-                            # non-version record for the indexed record.
-                            vdata = c[3]
-                            if vdata:
-                                if vdata[1] != r:
-                                    # This record is not the non-version
-                                    # record for the indexed record
-                                    continue
-                            else:
-                                # The indexed record is not a version record,
-                                # so this record can not be the non-version
-                                # record for it.
-                                continue
-                        o.append(r)
-
-                if o:
-                    if len(o) != len(records):
-                        self._data[tid] = 1, u, d, e, tuple(o) # Reset data
-                else:
-                    deleted.append(tid)
-
-            # Now delete empty transactions
-            for tid in deleted:
-                del self._data[tid]
-
-            # Now reset previous pointers for "current" records:
-            for r in pindex.values():
-                r[1] = None # Previous record
-                if r[2] and r[2][1]: # vdata
-                    # If this record contains version data and
-                    # non-version data, then clear it out.
-                    r[2][1][2] = None
-
-            # Finally, rebuild indexes from transaction data:
-            self._index, self._vindex = self._build_indexes()
-
-        finally:
-            self._lock_release()
-        self.getSize()
-
-    def _splat(self):
-        """Spit out a string showing state.
-        """
-        o=[]
-
-        o.append('Transactions:')
-        for tid, (p, u, d, e, t) in self._data.items():
-            o.append("  %s %s" % (TimeStamp(tid), p))
-            for r in t:
-                oid, pre, vdata, p, tid = r
-                oid = oid_repr(oid)
-                tid = oid_repr(tid)
-##                if serial is not None: serial=str(TimeStamp(serial))
-                pre=id(pre)
-                if vdata and vdata[1]: vdata=vdata[0], id(vdata[1])
-                if p: p=''
-                o.append('    %s: %s' %
-                         (id(r), `(oid, pre, vdata, p, tid)`))
-
-        o.append('\nIndex:')
-        items=self._index.items()
-        items.sort()
-        for oid, r in items:
-            if r: r=id(r)
-            o.append('  %s: %s' % (oid_repr(oid), r))
-
-        o.append('\nVersion Index:')
-        items=self._vindex.items()
-        items.sort()
-        for version, v in items:
-            o.append('  '+version)
-            vitems=v.items()
-            vitems.sort()
-            for oid, r in vitems:
-                if r: r=id(r)
-                o.append('    %s: %s' % (oid_repr(oid), r))
-
-        return '\n'.join(o)
-
-    def cleanup(self):
-        if self._base is not None:
-            self._base.cleanup()
-
-    def close(self):
-        if self._base is not None:
-            self._base.close()
-
-    def iterator(self, start=None, end=None):
-        # First iterate over the base storage
-        if self._base is not None:
-            for transaction in self._base.iterator(start, end):
-                yield transaction
-        # Then iterate over our local transactions
-        for tid, transaction in self._data.items():
-            if tid >= start and tid <= end:
-                yield TransactionRecord(tid, transaction)
-
-
-class TransactionRecord(ZODB.BaseStorage.TransactionRecord):
-    
-    def __init__(self, tid, transaction):
-        packed, user, description, extension, records = transaction
-        super(TransactionRecord, self).__init__(
-            tid, packed, user, description, extension)
-        self.records = transaction
-
-    def __iter__(self):
-        for record in self.records:
-            oid, prev, version, data, tid = record
-            yield ZODB.BaseStorage.DataRecord(oid, tid, data, version, prev)
+        return self.changes.store(oid, serial, data, '', transaction)

Added: ZODB/trunk/src/ZODB/DemoStorage.test
===================================================================
--- ZODB/trunk/src/ZODB/DemoStorage.test	                        (rev 0)
+++ ZODB/trunk/src/ZODB/DemoStorage.test	2008-10-26 00:36:06 UTC (rev 92553)
@@ -0,0 +1,122 @@
+DemoStorage demo (doctest)
+--------------------------
+
+Note that most people will configure the storage through ZConfig.  If
+you are one of those people, you may want to stop here. :)  The
+examples below show you how to use the storage from Python, but they
+also exercise lots of details you might not be interested in.
+
+To see how this works, we'll start by creating a base storage and
+puting an object (in addition to the root object) in it:
+
+    >>> from ZODB.FileStorage import FileStorage
+    >>> base = FileStorage('base.fs')
+    >>> from ZODB.DB import DB
+    >>> db = DB(base)
+    >>> from persistent.mapping import PersistentMapping
+    >>> conn = db.open()
+    >>> conn.root()['1'] = PersistentMapping({'a': 1, 'b':2})
+    >>> import transaction
+    >>> transaction.commit()
+    >>> db.close()
+    >>> import os
+    >>> original_size = os.path.getsize('base.fs')
+
+Now, lets reopen the base storage in read-only mode:
+
+    >>> base = FileStorage('base.fs', read_only=True)
+
+And open a new storage to store changes:
+
+    >>> changes = FileStorage('changes.fs')
+
+and combine the 2 in a demofilestorage:
+
+    >>> from ZODB.DemoStorage import DemoStorage
+    >>> storage = DemoStorage(base=base, changes=changes)
+
+If there are no transactions, the storage reports the lastTransaction
+of the base database:
+
+    >>> storage.lastTransaction() == base.lastTransaction()
+    True
+
+Let's add some data:
+
+    >>> db = DB(storage)
+    >>> conn = db.open()
+    >>> items = conn.root()['1'].items()
+    >>> items.sort()
+    >>> items
+    [('a', 1), ('b', 2)]
+
+    >>> conn.root()['2'] = PersistentMapping({'a': 3, 'b':4})
+    >>> transaction.commit()
+
+    >>> conn.root()['2']['c'] = 5
+    >>> transaction.commit()
+
+Here we can see that we haven't modified the base storage:
+
+    >>> original_size == os.path.getsize('base.fs')
+    True
+
+But we have modified the changes database:
+
+    >>> len(changes)
+    2
+
+Our lastTransaction reflects the lastTransaction of the changes:
+
+    >>> storage.lastTransaction() > base.lastTransaction()
+    True
+
+    >>> storage.lastTransaction() == changes.lastTransaction()
+    True
+
+Let's walk over some of the methods so ewe can see how we delegate to
+the new underlying storages:
+
+    >>> from ZODB.utils import p64, u64
+    >>> storage.load(p64(0), '') == changes.load(p64(0), '')
+    True
+    >>> storage.load(p64(0), '') == base.load(p64(0), '')
+    False
+    >>> storage.load(p64(1), '') == base.load(p64(1), '')
+    True
+
+    >>> serial = base.getTid(p64(0))
+    >>> storage.loadSerial(p64(0), serial) == base.loadSerial(p64(0), serial)
+    True
+
+    >>> serial = changes.getTid(p64(0))
+    >>> storage.loadSerial(p64(0), serial) == changes.loadSerial(p64(0),
+    ...                                                          serial)
+    True
+
+The object id of the new object is quite random, and typically large:
+
+    >>> print u64(conn.root()['2']._p_oid)
+    7106521602475165646
+
+Let's look at some other methods:
+
+    >>> storage.getName()
+    "DemoStorage('base.fs', 'changes.fs')"
+
+    >>> storage.sortKey() == changes.sortKey()
+    True
+
+    >>> storage.getSize() == changes.getSize()
+    True
+    
+    >>> len(storage) == len(changes)
+    True
+
+    
+Undo methods are simply copied from the changes storage:
+
+    >>> [getattr(storage, name) == getattr(changes, name)
+    ...  for name in ('supportsUndo', 'undo', 'undoLog', 'undoInfo')
+    ...  ]
+    [True, True, True, True]


Property changes on: ZODB/trunk/src/ZODB/DemoStorage.test
___________________________________________________________________
Name: svn:eol-style
   + native

Modified: ZODB/trunk/src/ZODB/component.xml
===================================================================
--- ZODB/trunk/src/ZODB/component.xml	2008-10-26 00:36:04 UTC (rev 92552)
+++ ZODB/trunk/src/ZODB/component.xml	2008-10-26 00:36:06 UTC (rev 92553)
@@ -174,9 +174,9 @@
 
   <sectiontype name="demostorage" datatype=".DemoStorage"
                implements="ZODB.storage">
-    <key name="name" default="Demo Storage"/>
-    <section type="ZODB.storage" name="*" attribute="base"/>
-    <key name="quota" datatype="integer"/>
+    <key name="name" />
+    <section type="ZODB.storage" name="*" attribute="base" />
+    <section type="ZODB.storage" name="changes" attribute="changes" />
   </sectiontype>
 
 

Modified: ZODB/trunk/src/ZODB/config.py
===================================================================
--- ZODB/trunk/src/ZODB/config.py	2008-10-26 00:36:04 UTC (rev 92552)
+++ ZODB/trunk/src/ZODB/config.py	2008-10-26 00:36:06 UTC (rev 92553)
@@ -125,9 +125,11 @@
             base = self.config.base.open()
         else:
             base = None
-        return DemoStorage(self.config.name,
-                           base=base,
-                           quota=self.config.quota)
+        if self.config.changes:
+            changes = self.config.changes.open()
+        else:
+            changes = None
+        return DemoStorage(self.config.name, base=base, changes=changes)
 
 class FileStorage(BaseConfig):
 

Modified: ZODB/trunk/src/ZODB/tests/testDemoStorage.py
===================================================================
--- ZODB/trunk/src/ZODB/tests/testDemoStorage.py	2008-10-26 00:36:04 UTC (rev 92552)
+++ ZODB/trunk/src/ZODB/tests/testDemoStorage.py	2008-10-26 00:36:06 UTC (rev 92553)
@@ -12,18 +12,36 @@
 #
 ##############################################################################
 import unittest
-
+import random
 import transaction
 from ZODB.DB import DB
+from zope.testing import doctest
+import zope.testing.setupstack
 import ZODB.utils
 import ZODB.DemoStorage
-from ZODB.tests import StorageTestBase, BasicStorage
-from ZODB.tests import Synchronization
+from ZODB.tests import (
+    BasicStorage,
+    HistoryStorage,
+    IteratorStorage,
+    MTStorage,
+    PackableStorage,
+    RevisionStorage,
+    StorageTestBase,
+    Synchronization,
+    )
 
-class DemoStorageTests(StorageTestBase.StorageTestBase,
-                       BasicStorage.BasicStorage,
-                       Synchronization.SynchronizedStorage,
-                       ):
+class DemoStorageTests(
+    StorageTestBase.StorageTestBase,
+    BasicStorage.BasicStorage,
+        
+    HistoryStorage.HistoryStorage,
+    IteratorStorage.ExtendedIteratorStorage,
+    IteratorStorage.IteratorStorage,
+    MTStorage.MTStorage,
+    PackableStorage.PackableStorage,
+    RevisionStorage.RevisionStorage,
+    Synchronization.SynchronizedStorage,
+    ):
 
     def setUp(self):
         self._storage = ZODB.DemoStorage.DemoStorage()
@@ -44,7 +62,26 @@
         self.assertEqual(s2.load(ZODB.utils.z64, ''),
                          self._storage.load(ZODB.utils.z64, ''))
 
+    def checkLengthAndBool(self):
+        self.assertEqual(len(self._storage), 0)
+        self.assert_(not self._storage)
+        db = DB(self._storage) # creates object 0. :)
+        self.assertEqual(len(self._storage), 1)
+        self.assert_(self._storage)
+        conn = db.open()
+        for i in range(10):
+            conn.root()[i] = conn.root().__class__()
+        transaction.commit()
+        self.assertEqual(len(self._storage), 11)
+        self.assert_(self._storage)
+        
+    def checkLoadBeforeUndo(self):
+        pass # we don't support undo yet
+    checkUndoZombie = checkLoadBeforeUndo
 
+    def checkPackWithMultiDatabaseReferences(self):
+        pass # we never do gc
+        
 class DemoStorageWrappedBase(DemoStorageTests):
 
     def setUp(self):
@@ -59,29 +96,92 @@
     def _makeBaseStorage(self):
         raise NotImplementedError
 
-class DemoStorageWrappedAroundFileStorage(DemoStorageWrappedBase):
+class DemoStorageWrappedAroundMappingStorage(DemoStorageWrappedBase):
 
     def _makeBaseStorage(self):
         from ZODB.MappingStorage import MappingStorage
         return MappingStorage()
 
-class DemoStorageWrappedAroundMappingStorage(DemoStorageWrappedBase):
+class DemoStorageWrappedAroundFileStorage(DemoStorageWrappedBase):
 
     def _makeBaseStorage(self):
         from ZODB.FileStorage import FileStorage
         return FileStorage('FileStorageTests.fs')
                        
 
+
+def setUp(test):
+    random.seed(0)
+    zope.testing.setupstack.setUpDirectory(test)
+    zope.testing.setupstack.register(test, transaction.abort)
+
+def testSomeDelegation():
+    r"""
+    >>> class S:
+    ...     def __init__(self, name):
+    ...         self.name = name
+    ...     def registerDB(self, db):
+    ...         print self.name, db
+    ...     def close(self):
+    ...         print self.name, 'closed'
+    ...     sortKey = getSize = __len__ = history = getTid = None
+    ...     tpc_finish = tpc_vote = tpc_transaction = None
+    ...     _lock_acquire = _lock_release = lambda: None
+    ...     getName = lambda self: 'S'
+    ...     isReadOnly = tpc_transaction = None
+    ...     supportsUndo = undo = undoLog = undoInfo = None
+    ...     supportsTransactionalUndo = None
+    ...     def new_oid(self):
+    ...         return '\0' * 8
+    ...     def tpc_begin(self, t, tid, status):
+    ...         print 'begin', tid, status
+    ...     def tpc_abort(self, t):
+    ...         pass
+
+    >>> from ZODB.DemoStorage import DemoStorage
+    >>> storage = DemoStorage(base=S(1), changes=S(2))
+
+    >>> storage.registerDB(1)
+    2 1
+
+    >>> storage.close()
+    1 closed
+    2 closed
+
+    >>> storage.tpc_begin(1, 2, 3)
+    begin 2 3
+    >>> storage.tpc_abort(1)
+
+    """
+
 def test_suite():
-    suite = unittest.TestSuite()
+    return unittest.TestSuite((
+        doctest.DocFileSuite('synchronized.txt'),
+        doctest.DocTestSuite(
+            setUp=setUp, tearDown=zope.testing.setupstack.tearDown,
+            ),
+        doctest.DocFileSuite(
+            'README.txt',
+            setUp=setUp, tearDown=zope.testing.setupstack.tearDown,
+            ),
+        ))
+
+
+
+
+def test_suite():
+    suite = unittest.TestSuite((
+        doctest.DocTestSuite(
+            setUp=setUp, tearDown=zope.testing.setupstack.tearDown,
+            ),
+        doctest.DocFileSuite(
+            '../DemoStorage.test',
+            setUp=setUp, tearDown=zope.testing.setupstack.tearDown,
+            ),
+        ))
     suite.addTest(unittest.makeSuite(DemoStorageTests, 'check'))
     suite.addTest(unittest.makeSuite(DemoStorageWrappedAroundFileStorage,
                                      'check'))
     suite.addTest(unittest.makeSuite(DemoStorageWrappedAroundMappingStorage,
                                      'check'))
     return suite
-
-if __name__ == "__main__":
-    loader = unittest.TestLoader()
-    loader.testMethodPrefix = "check"
-    unittest.main(testLoader=loader)



More information about the Zodb-checkins mailing list