Commit c2eed46c authored by Jeremy Hylton's avatar Jeremy Hylton

Merge Standby-branch to trunk (mostly).

The Standby-branch was branched from the StandaloneZODB-1_0-branch,
which includes the BTrees-fsIndex code.  I didn't include that change
in the merge, but everything else.  Terse summary follows:

BaseStorage.py:
    Add read-only storage feature.
    Add TransactionRecord and DataRecord marker classes for iteration.
    Reformat some lines.

FileStorage.py:
    Add read-only storage feature.
    Greg Ward's ConflictError patch
    Reformat some lines.
    Add lastTransaction(), lastSerialno().
    Add bounds support to iterator().
    Use TransactionRecord and DataRecord.

Connection.py:
DemoStorage.py:
MappingStorage.py:
    Greg Ward's ConflictError patch

POSException.py:
    Greg Ward's ConflictError patch
    Add ReadOnlyError.
parent 9e154d03
...@@ -14,7 +14,7 @@ ...@@ -14,7 +14,7 @@
""" """
# Do this portably in the face of checking out with -kv # Do this portably in the face of checking out with -kv
import string import string
__version__ = string.split('$Revision: 1.16 $')[-2:][0] __version__ = string.split('$Revision: 1.17 $')[-2:][0]
import ThreadLock, bpthread import ThreadLock, bpthread
import time, UndoLogCompatible import time, UndoLogCompatible
...@@ -26,6 +26,7 @@ class BaseStorage(UndoLogCompatible.UndoLogCompatible): ...@@ -26,6 +26,7 @@ class BaseStorage(UndoLogCompatible.UndoLogCompatible):
_transaction=None # Transaction that is being committed _transaction=None # Transaction that is being committed
_serial=z64 # Transaction serial number _serial=z64 # Transaction serial number
_tstatus=' ' # Transaction status, used for copying data _tstatus=' ' # Transaction status, used for copying data
_is_read_only = 0
def __init__(self, name, base=None): def __init__(self, name, base=None):
...@@ -42,8 +43,10 @@ class BaseStorage(UndoLogCompatible.UndoLogCompatible): ...@@ -42,8 +43,10 @@ class BaseStorage(UndoLogCompatible.UndoLogCompatible):
t=time.time() t=time.time()
t=self._ts=apply(TimeStamp,(time.gmtime(t)[:5]+(t%60,))) t=self._ts=apply(TimeStamp,(time.gmtime(t)[:5]+(t%60,)))
self._serial=`t` self._serial=`t`
if base is None: self._oid='\0\0\0\0\0\0\0\0' if base is None:
else: self._oid=base._oid self._oid='\0\0\0\0\0\0\0\0'
else:
self._oid=base._oid
def abortVersion(self, src, transaction): def abortVersion(self, src, transaction):
if transaction is not self._transaction: if transaction is not self._transaction:
...@@ -55,15 +58,24 @@ class BaseStorage(UndoLogCompatible.UndoLogCompatible): ...@@ -55,15 +58,24 @@ class BaseStorage(UndoLogCompatible.UndoLogCompatible):
raise POSException.StorageTransactionError(self, transaction) raise POSException.StorageTransactionError(self, transaction)
return [] return []
def close(self): pass def close(self):
pass
def getName(self): return self.__name__ def getName(self):
def getSize(self): return len(self)*300 # WAG! return self.__name__
def history(self, oid, version, length=1): pass
def getSize(self):
return len(self)*300 # WAG!
def history(self, oid, version, length=1):
pass
def modifiedInVersion(self, oid): return '' def modifiedInVersion(self, oid):
return ''
def new_oid(self, last=None): def new_oid(self, last=None):
if self._is_read_only:
raise POSException.ReadOnlyError()
if last is None: if last is None:
self._lock_acquire() self._lock_acquire()
try: try:
...@@ -79,10 +91,17 @@ class BaseStorage(UndoLogCompatible.UndoLogCompatible): ...@@ -79,10 +91,17 @@ class BaseStorage(UndoLogCompatible.UndoLogCompatible):
if d < 255: return last[:-1]+chr(d+1)+'\0'*(8-len(last)) if d < 255: return last[:-1]+chr(d+1)+'\0'*(8-len(last))
else: return self.new_oid(last[:-1]) else: return self.new_oid(last[:-1])
def registerDB(self, db, limit): pass # we don't care def registerDB(self, db, limit):
pass # we don't care
def supportsUndo(self): return 0
def supportsVersions(self): return 0 def isReadOnly(self):
return self._is_read_only
def supportsUndo(self):
return 0
def supportsVersions(self):
return 0
def tpc_abort(self, transaction): def tpc_abort(self, transaction):
self._lock_acquire() self._lock_acquire()
...@@ -171,15 +190,22 @@ class BaseStorage(UndoLogCompatible.UndoLogCompatible): ...@@ -171,15 +190,22 @@ class BaseStorage(UndoLogCompatible.UndoLogCompatible):
pass pass
def undo(self, transaction_id): def undo(self, transaction_id):
if self._is_read_only:
raise POSException.ReadOnlyError()
raise POSException.UndoError, 'non-undoable transaction' raise POSException.UndoError, 'non-undoable transaction'
def undoLog(self, first, last, filter=None): return () def undoLog(self, first, last, filter=None):
return ()
def versionEmpty(self, version): return 1 def versionEmpty(self, version):
return 1
def versions(self, max=None): return () def versions(self, max=None):
return ()
def pack(self, t, referencesf): pass def pack(self, t, referencesf):
if self._is_read_only:
raise POSException.ReadOnlyError()
def getSerial(self, oid): def getSerial(self, oid):
self._lock_acquire() self._lock_acquire()
...@@ -232,3 +258,9 @@ class BaseStorage(UndoLogCompatible.UndoLogCompatible): ...@@ -232,3 +258,9 @@ class BaseStorage(UndoLogCompatible.UndoLogCompatible):
self.tpc_vote(transaction) self.tpc_vote(transaction)
self.tpc_finish(transaction) self.tpc_finish(transaction)
class TransactionRecord:
"""Abstract base class for iterator protocol"""
class DataRecord:
"""Abstract base class for iterator protocol"""
...@@ -12,11 +12,11 @@ ...@@ -12,11 +12,11 @@
############################################################################## ##############################################################################
"""Database connection support """Database connection support
$Id: Connection.py,v 1.61 2001/11/28 15:51:18 matt Exp $""" $Id: Connection.py,v 1.62 2002/01/17 17:34:33 jeremy Exp $"""
__version__='$Revision: 1.61 $'[11:-2] __version__='$Revision: 1.62 $'[11:-2]
from cPickleCache import PickleCache from cPickleCache import PickleCache
from POSException import ConflictError from POSException import ConflictError, ReadConflictError
from ExtensionClass import Base from ExtensionClass import Base
import ExportImport, TmpStore import ExportImport, TmpStore
from zLOG import LOG, ERROR, BLATHER from zLOG import LOG, ERROR, BLATHER
...@@ -248,7 +248,7 @@ class Connection(ExportImport.ExportImport): ...@@ -248,7 +248,7 @@ class Connection(ExportImport.ExportImport):
or or
invalid(None) invalid(None)
): ):
raise ConflictError, `oid` raise ConflictError(object=object)
self._invalidating.append(oid) self._invalidating.append(oid)
else: else:
...@@ -315,7 +315,7 @@ class Connection(ExportImport.ExportImport): ...@@ -315,7 +315,7 @@ class Connection(ExportImport.ExportImport):
or or
invalid(None) invalid(None)
): ):
raise ConflictError, `oid` raise ConflictError(object=object)
self._invalidating.append(oid) self._invalidating.append(oid)
klass = object.__class__ klass = object.__class__
...@@ -459,7 +459,7 @@ class Connection(ExportImport.ExportImport): ...@@ -459,7 +459,7 @@ class Connection(ExportImport.ExportImport):
if invalid(oid) or invalid(None): if invalid(oid) or invalid(None):
if not hasattr(object.__class__, '_p_independent'): if not hasattr(object.__class__, '_p_independent'):
get_transaction().register(self) get_transaction().register(self)
raise ConflictError(`oid`, `object.__class__`) raise ReadConflictError(object=object)
invalid=1 invalid=1
else: else:
invalid=0 invalid=0
...@@ -484,7 +484,7 @@ class Connection(ExportImport.ExportImport): ...@@ -484,7 +484,7 @@ class Connection(ExportImport.ExportImport):
except KeyError: pass except KeyError: pass
else: else:
get_transaction().register(self) get_transaction().register(self)
raise ConflictError(`oid`, `object.__class__`) raise ConflictError(object=object)
except ConflictError: except ConflictError:
raise raise
...@@ -544,7 +544,7 @@ class Connection(ExportImport.ExportImport): ...@@ -544,7 +544,7 @@ class Connection(ExportImport.ExportImport):
def tpc_begin(self, transaction, sub=None): def tpc_begin(self, transaction, sub=None):
if self._invalid(None): # Some nitwit invalidated everything! if self._invalid(None): # Some nitwit invalidated everything!
raise ConflictError, "transaction already invalidated" raise ConflictError("transaction already invalidated")
self._invalidating=[] self._invalidating=[]
self._creating=[] self._creating=[]
......
...@@ -78,7 +78,7 @@ method:: ...@@ -78,7 +78,7 @@ method::
and call it to monitor the storage. and call it to monitor the storage.
""" """
__version__='$Revision: 1.8 $'[11:-2] __version__='$Revision: 1.9 $'[11:-2]
import base64, time, string import base64, time, string
from ZODB import POSException, BaseStorage, utils from ZODB import POSException, BaseStorage, utils
...@@ -244,7 +244,8 @@ class DemoStorage(BaseStorage.BaseStorage): ...@@ -244,7 +244,8 @@ class DemoStorage(BaseStorage.BaseStorage):
else: else:
nv=old nv=old
if serial != oserial: raise POSException.ConflictError if serial != oserial:
raise POSException.ConflictError(serials=(oserial, serial))
serial=self._serial serial=self._serial
r=[oid, serial, old, version and (version, nv) or None, data] r=[oid, serial, old, version and (version, nv) or None, data]
......
...@@ -114,20 +114,19 @@ ...@@ -114,20 +114,19 @@
# may have a back pointer to a version record or to a non-version # may have a back pointer to a version record or to a non-version
# record. # record.
# #
__version__='$Revision: 1.76 $'[11:-2] __version__='$Revision: 1.77 $'[11:-2]
import struct, time, os, bpthread, string, base64, sys import struct, time, os, string, base64, sys
from struct import pack, unpack from struct import pack, unpack
from cPickle import loads
import POSException import POSException
from POSException import UndoError from POSException import UndoError
from TimeStamp import TimeStamp from TimeStamp import TimeStamp
from lock_file import lock_file from lock_file import lock_file
from utils import t32, p64, U64, cp from utils import t32, p64, U64, cp
from zLOG import LOG, WARNING, ERROR, PANIC, register_subsystem from zLOG import LOG, BLATHER, WARNING, ERROR, PANIC, register_subsystem
register_subsystem('ZODB FS') register_subsystem('ZODB FS')
import BaseStorage import BaseStorage
from cPickle import Pickler, Unpickler from cPickle import Pickler, Unpickler, loads
import ConflictResolution import ConflictResolution
try: from posix import fsync try: from posix import fsync
...@@ -185,6 +184,7 @@ class FileStorage(BaseStorage.BaseStorage, ...@@ -185,6 +184,7 @@ class FileStorage(BaseStorage.BaseStorage,
create = 1 create = 1
if read_only: if read_only:
self._is_read_only = 1
if create: if create:
raise ValueError, "can\'t create a read-only file" raise ValueError, "can\'t create a read-only file"
elif stop is not None: elif stop is not None:
...@@ -242,6 +242,7 @@ class FileStorage(BaseStorage.BaseStorage, ...@@ -242,6 +242,7 @@ class FileStorage(BaseStorage.BaseStorage,
self._file, file_name, index, vindex, tindex, stop, self._file, file_name, index, vindex, tindex, stop,
read_only=read_only, read_only=read_only,
) )
self._ltid = tid
self._ts = tid = TimeStamp(tid) self._ts = tid = TimeStamp(tid)
t = time.time() t = time.time()
...@@ -262,7 +263,8 @@ class FileStorage(BaseStorage.BaseStorage, ...@@ -262,7 +263,8 @@ class FileStorage(BaseStorage.BaseStorage,
self._index_get=index.get self._index_get=index.get
self._vindex_get=vindex.get self._vindex_get=vindex.get
def __len__(self): return len(self._index) def __len__(self):
return len(self._index)
def _newIndexes(self): def _newIndexes(self):
# hook to use something other than builtin dict # hook to use something other than builtin dict
...@@ -389,13 +391,20 @@ class FileStorage(BaseStorage.BaseStorage, ...@@ -389,13 +391,20 @@ class FileStorage(BaseStorage.BaseStorage,
def close(self): def close(self):
self._file.close() self._file.close()
if hasattr(self,'_lock_file'): self._lock_file.close() if hasattr(self,'_lock_file'):
if self._tfile: self._tfile.close() self._lock_file.close()
try: self._save_index() if self._tfile:
except: pass # We don't care if this fails. self._tfile.close()
try:
self._save_index()
except:
# XXX should log the error, though
pass # We don't care if this fails.
def commitVersion(self, src, dest, transaction, abort=None): def commitVersion(self, src, dest, transaction, abort=None):
# We are going to commit by simply storing back pointers. # We are going to commit by simply storing back pointers.
if self._is_read_only:
raise POSException.ReadOnlyError()
if not (src and isinstance(src, StringType) if not (src and isinstance(src, StringType)
and isinstance(dest, StringType)): and isinstance(dest, StringType)):
raise POSException.VersionCommitError('Invalid source version') raise POSException.VersionCommitError('Invalid source version')
...@@ -413,9 +422,8 @@ class FileStorage(BaseStorage.BaseStorage, ...@@ -413,9 +422,8 @@ class FileStorage(BaseStorage.BaseStorage,
self._lock_acquire() self._lock_acquire()
try: try:
file=self._file read=self._file.read
read=file.read seek=self._file.seek
seek=file.seek
tfile=self._tfile tfile=self._tfile
write=tfile.write write=tfile.write
tindex=self._tindex tindex=self._tindex
...@@ -607,6 +615,8 @@ class FileStorage(BaseStorage.BaseStorage, ...@@ -607,6 +615,8 @@ class FileStorage(BaseStorage.BaseStorage,
finally: self._lock_release() finally: self._lock_release()
def store(self, oid, serial, data, version, transaction): def store(self, oid, serial, data, version, transaction):
if self._is_read_only:
raise POSException.ReadOnlyError()
if transaction is not self._transaction: if transaction is not self._transaction:
raise POSException.StorageTransactionError(self, transaction) raise POSException.StorageTransactionError(self, transaction)
...@@ -632,8 +642,8 @@ class FileStorage(BaseStorage.BaseStorage, ...@@ -632,8 +642,8 @@ class FileStorage(BaseStorage.BaseStorage,
if serial != oserial: if serial != oserial:
data=self.tryToResolveConflict(oid, oserial, serial, data) data=self.tryToResolveConflict(oid, oserial, serial, data)
if not data: if not data:
raise POSException.ConflictError, ( raise POSException.ConflictError(oid=oid,
serial, oserial) serials=(oserial, serial))
else: else:
oserial=serial oserial=serial
...@@ -672,13 +682,17 @@ class FileStorage(BaseStorage.BaseStorage, ...@@ -672,13 +682,17 @@ class FileStorage(BaseStorage.BaseStorage,
finally: finally:
self._lock_release() self._lock_release()
def supportsUndo(self): return 1 def supportsUndo(self):
def supportsVersions(self): return 1 return 1
def supportsVersions(self):
return 1
def _clear_temp(self): def _clear_temp(self):
self._tindex.clear() self._tindex.clear()
self._tvindex.clear() self._tvindex.clear()
self._tfile.seek(0) if self._tfile is not None:
self._tfile.seek(0)
def _begin(self, tid, u, d, e): def _begin(self, tid, u, d, e):
self._thl=23+len(u)+len(d)+len(e) self._thl=23+len(u)+len(d)+len(e)
...@@ -702,9 +716,9 @@ class FileStorage(BaseStorage.BaseStorage, ...@@ -702,9 +716,9 @@ class FileStorage(BaseStorage.BaseStorage,
# We have to check lengths here because struct.pack # We have to check lengths here because struct.pack
# doesn't raise an exception on overflow! # doesn't raise an exception on overflow!
if luser > 65535: raise FileStorageError, 'user name too long' if luser > 65535: raise FileStorageError('user name too long')
if ldesc > 65535: raise FileStorageError, 'description too long' if ldesc > 65535: raise FileStorageError('description too long')
if lext > 65535: raise FileStorageError, 'too much extension data' if lext > 65535: raise FileStorageError('too much extension data')
tlen=self._thl tlen=self._thl
pos=self._pos pos=self._pos
...@@ -718,7 +732,7 @@ class FileStorage(BaseStorage.BaseStorage, ...@@ -718,7 +732,7 @@ class FileStorage(BaseStorage.BaseStorage,
# suspect. # suspect.
write(pack( write(pack(
">8s" "8s" "c" "H" "H" "H" ">8s" "8s" "c" "H" "H" "H"
,tid, stl, 'c', luser, ldesc, lext, ,tid, stl,'c', luser, ldesc, lext,
)) ))
if user: write(user) if user: write(user)
if desc: write(desc) if desc: write(desc)
...@@ -754,6 +768,7 @@ class FileStorage(BaseStorage.BaseStorage, ...@@ -754,6 +768,7 @@ class FileStorage(BaseStorage.BaseStorage,
self._index.update(self._tindex) self._index.update(self._tindex)
self._vindex.update(self._tvindex) self._vindex.update(self._tvindex)
self._ltid = tid
def _abort(self): def _abort(self):
if self._nextpos: if self._nextpos:
...@@ -761,6 +776,8 @@ class FileStorage(BaseStorage.BaseStorage, ...@@ -761,6 +776,8 @@ class FileStorage(BaseStorage.BaseStorage,
self._nextpos=0 self._nextpos=0
def undo(self, transaction_id): def undo(self, transaction_id):
if self._is_read_only:
raise POSException.ReadOnlyError()
self._lock_acquire() self._lock_acquire()
try: try:
self._clear_index() self._clear_index()
...@@ -808,7 +825,8 @@ class FileStorage(BaseStorage.BaseStorage, ...@@ -808,7 +825,8 @@ class FileStorage(BaseStorage.BaseStorage,
return t.keys() return t.keys()
finally: self._lock_release() finally: self._lock_release()
def supportsTransactionalUndo(self): return 1 def supportsTransactionalUndo(self):
return 1
def _undoDataInfo(self, oid, pos, tpos): def _undoDataInfo(self, oid, pos, tpos):
"""Return the serial, data pointer, data, and version for the oid """Return the serial, data pointer, data, and version for the oid
...@@ -930,7 +948,9 @@ class FileStorage(BaseStorage.BaseStorage, ...@@ -930,7 +948,9 @@ class FileStorage(BaseStorage.BaseStorage,
# writing a file position rather than a pickle. Sometimes, we # writing a file position rather than a pickle. Sometimes, we
# may do conflict resolution, in which case we actually copy # may do conflict resolution, in which case we actually copy
# new data that results from resolution. # new data that results from resolution.
if self._is_read_only:
raise POSException.ReadOnlyError()
if transaction is not self._transaction: if transaction is not self._transaction:
raise POSException.StorageTransactionError(self, transaction) raise POSException.StorageTransactionError(self, transaction)
...@@ -1175,6 +1195,8 @@ class FileStorage(BaseStorage.BaseStorage, ...@@ -1175,6 +1195,8 @@ class FileStorage(BaseStorage.BaseStorage,
the associated data are copied, since the old records are not copied. the associated data are copied, since the old records are not copied.
""" """
if self._is_read_only:
raise POSException.ReadOnlyError()
# Ugh, this seems long # Ugh, this seems long
packing=1 # are we in the packing phase (or the copy phase) packing=1 # are we in the packing phase (or the copy phase)
...@@ -1551,8 +1573,32 @@ class FileStorage(BaseStorage.BaseStorage, ...@@ -1551,8 +1573,32 @@ class FileStorage(BaseStorage.BaseStorage,
self._packt=z64 self._packt=z64
_lock_release() _lock_release()
def iterator(self): def iterator(self, start=None, stop=None):
return FileIterator(self._file_name) return FileIterator(self._file_name, start, stop)
def lastTransaction(self):
"""Return transaction id for last committed transaction"""
return self._ltid
def lastSerial(self, oid):
"""Return last serialno committed for object oid.
If there is no serialno for this oid -- which can only occur
if it is a new object -- return None.
"""
try:
pos = self._index[oid]
except KeyError:
return None
self._file.seek(pos)
# first 8 bytes are oid, second 8 bytes are serialno
h = self._file.read(16)
if len(h) < 16:
raise CorruptedDataError, h
if h[:8] != oid:
h = h + self._file.read(26) # get rest of header
raise CorruptedDataError, h
return h[8:]
def shift_transactions_forward(index, vindex, tindex, file, pos, opos): def shift_transactions_forward(index, vindex, tindex, file, pos, opos):
"""Copy transactions forward in the data file """Copy transactions forward in the data file
...@@ -1969,14 +2015,46 @@ class FileIterator(Iterator): ...@@ -1969,14 +2015,46 @@ class FileIterator(Iterator):
""" """
_ltid=z64 _ltid=z64
def __init__(self, file): def __init__(self, file, start=None, stop=None):
if isinstance(file, StringType): if isinstance(file, StringType):
file=open(file, 'rb') file = open(file, 'rb')
self._file=file self._file = file
if file.read(4) != packed_version: raise FileStorageFormatError, name if file.read(4) != packed_version:
raise FileStorageFormatError, name
file.seek(0,2) file.seek(0,2)
self._file_size=file.tell() self._file_size = file.tell()
self._pos=4L self._pos = 4L
assert start is None or isinstance(start, StringType)
assert stop is None or isinstance(stop, StringType)
if start:
self._skip_to_start(start)
self._stop = stop
def _skip_to_start(self, start):
# Scan through the transaction records doing almost no sanity
# checks.
while 1:
self._file.seek(self._pos)
h = self._file.read(16)
if len(h) < 16:
return
tid, stl = unpack(">8s8s", h)
if tid >= start:
return
tl = U64(stl)
try:
self._pos += tl + 8
except OverflowError:
self._pos = long(self._pos) + tl + 8
if __debug__:
# Sanity check
self._file.seek(self._pos - 8, 0)
rtl = self._file.read(8)
if rtl != stl:
pos = self._file.tell() - 8
panic("%s has inconsistent transaction length at %s "
"(%s != %s)",
self._file.name, pos, U64(rtl), U64(stl))
def next(self, index=0): def next(self, index=0):
file=self._file file=self._file
...@@ -1984,6 +2062,7 @@ class FileIterator(Iterator): ...@@ -1984,6 +2062,7 @@ class FileIterator(Iterator):
read=file.read read=file.read
pos=self._pos pos=self._pos
LOG("ZODB FS", BLATHER, "next(%d)" % index)
while 1: while 1:
# Read the transaction record # Read the transaction record
seek(pos) seek(pos)
...@@ -1994,7 +2073,7 @@ class FileIterator(Iterator): ...@@ -1994,7 +2073,7 @@ class FileIterator(Iterator):
if el < 0: el=t32-el if el < 0: el=t32-el
if tid <= self._ltid: if tid <= self._ltid:
warn("%s time-stamp reduction at %s", name, pos) warn("%s time-stamp reduction at %s", self._file.name, pos)
self._ltid=tid self._ltid=tid
tl=U64(stl) tl=U64(stl)
...@@ -2004,11 +2083,12 @@ class FileIterator(Iterator): ...@@ -2004,11 +2083,12 @@ class FileIterator(Iterator):
# cleared. They may also be corrupted, # cleared. They may also be corrupted,
# in which case, we don't want to totally lose the data. # in which case, we don't want to totally lose the data.
warn("%s truncated, possibly due to damaged records at %s", warn("%s truncated, possibly due to damaged records at %s",
name, pos) self._file.name, pos)
break break
if status not in ' up': if status not in ' up':
warn('%s has invalid status, %s, at %s', name, status, pos) warn('%s has invalid status, %s, at %s', self._file.name,
status, pos)
if tl < (23+ul+dl+el): if tl < (23+ul+dl+el):
# We're in trouble. Find out if this is bad data in # We're in trouble. Find out if this is bad data in
...@@ -2022,16 +2102,24 @@ class FileIterator(Iterator): ...@@ -2022,16 +2102,24 @@ class FileIterator(Iterator):
# reasonable: # reasonable:
if self._file_size - rtl < pos or rtl < 23: if self._file_size - rtl < pos or rtl < 23:
nearPanic('%s has invalid transaction header at %s', nearPanic('%s has invalid transaction header at %s',
name, pos) self._file.name, pos)
warn("It appears that there is invalid data at the end of " warn("It appears that there is invalid data at the end of "
"the file, possibly due to a system crash. %s " "the file, possibly due to a system crash. %s "
"truncated to recover from bad data at end." "truncated to recover from bad data at end."
% name) % self._file.name)
break break
else: else:
warn('%s has invalid transaction header at %s', name, pos) warn('%s has invalid transaction header at %s',
self._file.name, pos)
break break
if self._stop is not None:
LOG("ZODB FS", BLATHER,
("tid %x > stop %x ? %d" %
(U64(tid), U64(self._stop), tid > self._stop)))
if self._stop is not None and tid > self._stop:
raise IndexError, index
tpos=pos tpos=pos
tend=tpos+tl tend=tpos+tl
...@@ -2041,7 +2129,7 @@ class FileIterator(Iterator): ...@@ -2041,7 +2129,7 @@ class FileIterator(Iterator):
h=read(8) h=read(8)
if h != stl: if h != stl:
panic('%s has inconsistent transaction length at %s', panic('%s has inconsistent transaction length at %s',
name, pos) self._file.name, pos)
pos=tend+8 pos=tend+8
continue continue
...@@ -2067,7 +2155,7 @@ class FileIterator(Iterator): ...@@ -2067,7 +2155,7 @@ class FileIterator(Iterator):
h=read(8) h=read(8)
if h != stl: if h != stl:
warn("%s redundant transaction length check failed at %s", warn("%s redundant transaction length check failed at %s",
name, pos) self._file.name, pos)
break break
self._pos=pos+8 self._pos=pos+8
...@@ -2075,7 +2163,7 @@ class FileIterator(Iterator): ...@@ -2075,7 +2163,7 @@ class FileIterator(Iterator):
raise IndexError, index raise IndexError, index
class RecordIterator(Iterator): class RecordIterator(Iterator, BaseStorage.TransactionRecord):
"""Iterate over the transactions in a FileStorage file. """Iterate over the transactions in a FileStorage file.
""" """
def __init__(self, tid, status, user, desc, ext, pos, stuff): def __init__(self, tid, status, user, desc, ext, pos, stuff):
...@@ -2127,9 +2215,8 @@ class RecordIterator(Iterator): ...@@ -2127,9 +2215,8 @@ class RecordIterator(Iterator):
return r return r
raise IndexError, index raise IndexError, index
class Record: class Record(BaseStorage.DataRecord):
"""An abstract database record """An abstract database record
""" """
def __init__(self, *args): def __init__(self, *args):
......
...@@ -86,7 +86,7 @@ method:: ...@@ -86,7 +86,7 @@ method::
and call it to minotor the storage. and call it to minotor the storage.
""" """
__version__='$Revision: 1.4 $'[11:-2] __version__='$Revision: 1.5 $'[11:-2]
import POSException, BaseStorage, string, utils import POSException, BaseStorage, string, utils
from TimeStamp import TimeStamp from TimeStamp import TimeStamp
...@@ -136,7 +136,8 @@ class MappingStorage(BaseStorage.BaseStorage): ...@@ -136,7 +136,8 @@ class MappingStorage(BaseStorage.BaseStorage):
if self._index.has_key(oid): if self._index.has_key(oid):
old=self._index[oid] old=self._index[oid]
oserial=old[:8] oserial=old[:8]
if serial != oserial: raise POSException.ConflictError if serial != oserial:
raise POSException.ConflictError(serials=(oserial, serial))
serial=self._serial serial=self._serial
self._tindex.append((oid,serial+data)) self._tindex.append((oid,serial+data))
......
...@@ -10,14 +10,14 @@ ...@@ -10,14 +10,14 @@
# FOR A PARTICULAR PURPOSE # FOR A PARTICULAR PURPOSE
# #
############################################################################## ##############################################################################
'''BoboPOS-defined exceptions """BoboPOS-defined exceptions
$Id: POSException.py,v 1.8 2001/11/28 15:51:20 matt Exp $''' $Id: POSException.py,v 1.9 2002/01/17 17:34:33 jeremy Exp $"""
__version__='$Revision: 1.8 $'[11:-2] __version__ = '$Revision: 1.9 $'.split()[-2:][0]
from string import join from string import join
StringType=type('') from types import StringType, DictType
DictType=type({}) from ZODB import utils
class POSError(Exception): class POSError(Exception):
"""Persistent object system error """Persistent object system error
...@@ -28,10 +28,94 @@ class TransactionError(POSError): ...@@ -28,10 +28,94 @@ class TransactionError(POSError):
""" """
class ConflictError(TransactionError): class ConflictError(TransactionError):
"""Two transactions tried to modify the same object at once """Two transactions tried to modify the same object at once. This
transaction should be resubmitted.
Instance attributes:
oid : string
the OID (8-byte packed string) of the object in conflict
class_name : string
the fully-qualified name of that object's class
message : string
a human-readable explanation of the error
serials : (string, string)
a pair of 8-byte packed strings; these are the serial numbers
(old and new) of the object in conflict. (Serial numbers are
closely related [equal?] to transaction IDs; a ConflictError may
be triggered by a serial number mismatch.)
The caller should pass either object or oid as a keyword argument,
but not both of them. If object is passed, it should be a
persistent object with an _p_oid attribute.
"""
This transaction should be resubmitted. def __init__(self, message=None, object=None, oid=None, serials=None):
if message is None:
self.message = "database conflict error"
else:
self.message = message
if object is None:
self.oid = None
self.class_name = None
else:
self.oid = object._p_oid
klass = object.__class__
self.class_name = klass.__module__ + "." + klass.__name__
if oid is not None:
assert self.oid is None
self.oid = oid
self.serials = serials
def __str__(self):
extras = []
if self.oid:
extras.append("oid %016x" % utils.U64(self.oid))
if self.class_name:
extras.append("class %s" % self.class_name)
if self.serials:
extras.append("serial was %016x, now %016x" %
tuple(map(utils.U64, self.serials)))
if extras:
return "%s (%s)" % (self.message, ", ".join(extras))
else:
return self.message
def get_oid(self):
return self.oid
def get_class_name(self):
return self.class_name
def get_old_serial(self):
return self.serials[0]
def get_new_serial(self):
return self.serials[1]
def get_serials(self):
return self.serials
class ReadConflictError(ConflictError):
"""A conflict detected at read time -- attempt to read an object
that has changed in another transaction (eg. another thread
or process).
""" """
def __init__(self, message=None, object=None, serials=None):
if message is None:
message = "database read conflict error"
ConflictError.__init__(self, message=message, object=object,
serials=serials)
class BTreesConflictError(ConflictError):
"""A special subclass for BTrees conflict errors, which return
an undocumented four-tuple."""
def __init__(self, *btree_args):
ConflictError.__init__(self, message="BTrees conflict error")
self.btree = btree_args
class VersionError(POSError): class VersionError(POSError):
"""An error in handling versions occurred """An error in handling versions occurred
...@@ -84,6 +168,10 @@ class MountedStorageError(StorageError): ...@@ -84,6 +168,10 @@ class MountedStorageError(StorageError):
"""Unable to access mounted storage. """Unable to access mounted storage.
""" """
class ReadOnlyError(StorageError):
"""Unable to modify objects in a read-only storage.
"""
class ExportError(POSError): class ExportError(POSError):
"""An export file doesn't have the right format. """An export file doesn't have the right format.
""" """
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment