[Zope3-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