[Zodb-checkins] CVS: Zope3/src/zodb/storage - demo.py:1.1

Jeremy Hylton jeremy@zope.com
Thu, 13 Mar 2003 17:05:04 -0500


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

Added Files:
	demo.py 
Log Message:
First steps towards reviving DemoStorage.


=== Added File Zope3/src/zodb/storage/demo.py ===
##############################################################################
#
# Copyright (c) 2003 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
#
##############################################################################
"""Demo ZODB storage

The Demo storage serves two purposes:

  - Provide an example implementation of a full storage without
    distracting storage details,

  - Provide a volatile storage that is useful for giving demonstrations.

The demo strorage can have a "base" storage that is used in a
read-only fashion. The base storage must not not to contain version
data.
"""

from zodb.interfaces import *
from zodb.storage.base import BaseStorage
from zodb.storage.interfaces import *

class TxnRecord(object):

    def __init__(self, tid, user, desc, ext):
        self.tid = tid
        self.user = user
        self.desc = desc
        self.ext = ext
        self.data = []

    def append(self, rec):
        self.data.append(rec)

    def __iter__(self):
        for rec in self.data:
            yield rec

class DataRecord(object):

    def __init__(self, oid, serial, data, refs, version, prev):
        self.oid = oid
        self.serial = serial
        self.data = data
        self.refs = refs
        self.version = version
        self.prev = prev
        self.rec_version = None
        self.rec_nonversion = None

    def setVersionData(self, rec):
        assert self.rec_nonversion is None
        self.rec_version = rec

    def setNonVersionData(self, rec):
        assert self.rec_version is None
        self.rec_nonversion = rec

class DemoStorage(BaseStorage):
    """Support ephemeral updates to a base storage.

    The DemoStorage extends a read-only base storage so that ephemeral
    transactions can be commited in the demo.  The transactions are
    only stored in memory; the base storage is not modified and
    updates are lost when the DemoStorage object is closed.
    """

    # The implementation uses three dictionaries to manage the
    # ephemeral transactions.
    # _data maps tid to TxnRecords
    # _index maps oid to DataRecords
    # _vindex maps version name to version indexes (like _index)
    # A version index maps oids to DataRecords.
    # _index is stored in _vindex under the key ""

    # XXX What if the base storage doesn't support undo or versions?

    __implements__ = IStorage, IUndoStorage, IVersionStorage

    def __init__(self, base, name=None):
        if name is None:
            name = "DemoStorage"
        BaseStorage.__init__(self, name)
        self._base = base
        # XXX waste an oid
        self._oid = base.newObjectId()

        self._data = {}
        self._index = {}
        self._vindex = {"": self._index}
        self._cur_txn = None

        # delegate some methods to _base, even though they may
        # be provided by BaseStorage.
        self.getVersion = self._base.getVersion
        self.setVersion = self._base.setVersion

    def close(self):
        self._base.close()

    def cleanup(self):
        # XXX Don't cleanup the base.
        pass

    def _load(self, oid, version=""):
        index = self._vindex.get(version)
        if index is None:
            rec = None
        else:
            rec = index.get(oid)
        if rec is None and version:
            rec = self._vindex[""].get(oid)
        return rec

    def _loadCurrent(self, oid):
        # Return the current record for oid.
        # If the most recent revision is on a version, return that.
        rec = self._load(oid)
        if rec is None:
            # Consult the base storage and create a faux DataRecord
            try:
                version = self._base.modifiedInVersion(oid)
            except KeyError:
                return None
            data, serial = self._base.load(oid, version)
            return DataRecord(oid, serial, data, version, None)
        # If there is a pointer to version data from the nonversion
        # date, then it must be current.
        if rec.rec_version:
            rec = rec.rec_version
        return rec

    def load(self, oid, version):
        self._lock_acquire()
        try:
            rec = self._load(oid, version)
            if rec is not None:
                return rec.data, rec.serial
            else:
                return self._base.load(oid, version)
        finally:
            self._lock_release()

    def loadSerial(self, oid, serial):
        rec = self._loadCurrent(oid)
        while rec is not None:
            if rec.serial == serial:
                return rec.data
            rec = rec.prev
            # XXX Need to fallback to base storage...
        raise POSKeyError(oid)

    def getSerial(self, oid):
        rec = self._loadCurrent(oid)
        if rec is None:
            raise POSKeyError(oid)
        return rec.serial

    def store(self, oid, serial, data, refs, version, txn):
        if txn is not self._transaction:
            raise StorageTransactionError(self, txn)
        prev = self._loadCurrent(oid)
        if prev:
            if prev.version and prev.version != version:
                raise VersionLockError(oid, prev.version)
            if prev.serial != serial:
                raise ConflictError(oid=oid, serials=(prev.serial, serial))
        rec = DataRecord(oid, self._serial, data, refs, version, prev)
        if prev and version:
            rec.setNonVersionData(prev.rec_nonversion or prev)
        self._cur_txn.append(rec)
        return self._serial

    def restore(self, oid, serial, data, version, prev_txn, txn):
        pass

    def _begin(self, tid, user, desc, ext):
        self._cur_txn = TxnRecord(tid, user, desc, ext)

    def _finish(self, tid, user, desc, ext):
        self._data[tid] = self._cur_txn
        for rec in self._cur_txn:
            vindex = self._vindex.setdefault(rec.version, {})
            vindex[rec.oid] = rec
            nvrec = self._index.get(rec.oid)
            if rec.prev and rec.prev.version != rec.version:
                # If the current version doesn't match the previous
                # version, we may need to remove the object from a
                # version index.
                version = rec.prev.version
                if version:
                    del self._vindex[version][rec.oid]
            if nvrec is not None:
                nvrec.setVersionData(rec)
        self._cur_txn = None

    def _abort(self):
        self._cur_txn = None

    def undo(self, tid):
        pass

    def undoLog(self, first, last, filter=None):
        pass

    def versionEmpty(self, version):
        if self._vindex.get(version):
            return False
        else:
            return True

    def versions(self):
        return [v for v in self._vindex.keys()
                if v and not self.versionEmpty(v)]

    def pack(self):
        pass

    def abortVersion(self, src, txn):
        if txn is not self._transaction:
            raise StorageTransactionError(self, txn)
        if not src:
            raise VersionError("Invalid version: %r" % src)
        vindex = self._vindex.get(src)
        if vindex is None:
            return []
        oids = []
        for rec in vindex.itervalues():
            if rec.rec_nonversion:
                data = rec.rec_nonversion.data
                refs = rec.rec_nonversion.refs
                new = DataRecord(rec.oid, self._serial, data, refs, "", rec)
            else:
                new = DataRecord(rec.oid, None, None, None, None, rec)
            self._cur_txn.append(new)
            oids.append(rec.oid)
        return oids

    def commitVersion(self, src, dest, txn):
        if txn is not self._transaction:
            raise StorageTransactionError(self, txn)
        if not src:
            raise VersionCommitError("Invalid source version")
        if src == dest:
            raise VersionCommitError("Can't commit to same version: %r" % src)
        src_index = self._vindex.get(src)
        if src_index is None:
            return []
        oids = []
        for rec in src_index.itervalues():
            new = DataRecord(rec.oid, self._serial, rec.data,
                             rec.refs, dest, rec)
            if dest and rec.rec_nonversion:
                new.setNonVersionData(rec.rec_nonversion)
            self._cur_txn.append(new)
            oids.append(rec.oid)
        return oids