Commit 1b7aef1d authored by Barry Warsaw's avatar Barry Warsaw

Fixes so that Full now passes the basic test suite (essentially the

same set of tests that Minimal also passes).  Not yet tested:
versions, undo, packing, garbage collection.

InternalInconsistencyError: the base class should be AssertionError.

Fix typo in name of table that keeps the pickle reference counts (it
should be _pickleRefcounts).

next_object() => next()

Use referencef() in a completely backwards compatible way (I happen to
have an older version of ZODB installed).

load(): Fix typo in name of table that stores the pickle data (it
should be _pickles).

store(): Use a local variable called `zero' for the 8-byte zero
pointer.  When there is no current serial number for the object
(i.e. it is being stored for the first time), initialize oserial to
zero (but not orevid).  Better printing of the ConflictError message.
When there is no version string (i.e. this is being stored in a
non-version), initialize nvrevid to zero.  Better printing of the
VersionLockError.  Fix typo in return value; we should return the
cached serial number, not the serial number that was passed in as an
argument.
parent 7e2aab94
...@@ -4,7 +4,7 @@ See Minimal.py for an implementation of Berkeley storage that does not support ...@@ -4,7 +4,7 @@ See Minimal.py for an implementation of Berkeley storage that does not support
undo or versioning. undo or versioning.
""" """
# $Revision: 1.4 $ # $Revision: 1.5 $
__version__ = '0.1' __version__ = '0.1'
import struct import struct
...@@ -35,7 +35,7 @@ PROTECTED_TRANSACTION = 'N' ...@@ -35,7 +35,7 @@ PROTECTED_TRANSACTION = 'N'
class InternalInconsistencyError(POSException.POSError, AssertError): class InternalInconsistencyError(POSException.POSError, AssertionError):
"""Raised when we detect an internal inconsistency in our tables.""" """Raised when we detect an internal inconsistency in our tables."""
...@@ -178,7 +178,7 @@ class Full(BerkeleyBase): ...@@ -178,7 +178,7 @@ class Full(BerkeleyBase):
self._txnOids.close() self._txnOids.close()
self._refcounts.close() self._refcounts.close()
self._references.close() self._references.close()
self._pickleReferenceCount.close() self._pickleRefcounts.close()
BerkeleyBase.close(self) BerkeleyBase.close(self)
def _begin(self, tid, u, d, e): def _begin(self, tid, u, d, e):
...@@ -236,7 +236,7 @@ class Full(BerkeleyBase): ...@@ -236,7 +236,7 @@ class Full(BerkeleyBase):
UNDOABLE_TRANSACTION + lengths + u + d + e, UNDOABLE_TRANSACTION + lengths + u + d + e,
txn=txn) txn=txn)
while 1: while 1:
rec = self._commitlog.next_object() rec = self._commitlog.next()
if rec is None: if rec is None:
break break
op, data = rec op, data = rec
...@@ -269,22 +269,22 @@ class Full(BerkeleyBase): ...@@ -269,22 +269,22 @@ class Full(BerkeleyBase):
self._txnOids.put(tid, oid, txn=txn) self._txnOids.put(tid, oid, txn=txn)
# Boost the refcount of all the objects referred to by # Boost the refcount of all the objects referred to by
# this pickle. referencesf() scans a pickle and returns # this pickle. referencesf() scans a pickle and returns
# the list of objects referenced by the pickle. BAW: In # the list of objects referenced by the pickle. BAW: the
# Zope 2.3.1, which we need to target, the signature of # signature of referencesf() has changed for Zope 2.4, to
# this function requires an empty list, but it returns # make it more convenient to use. Gotta stick with the
# that list. In future versions of Zope, there's a # backwards compatible version for now.
# default argument for that. refdoids = []
for roid in referencesf(pickle, []): referencesf(pickle, refdoids)
for roid in refdoids:
refcount = self._refcounts.get(roid, zero, txn=txn) refcount = self._refcounts.get(roid, zero, txn=txn)
refcount = utils.p64(utils.U64(refcount) + 1) refcount = utils.p64(utils.U64(refcount) + 1)
self._refcounts.put(roid, refcount, txn=txn) self._refcounts.put(roid, refcount, txn=txn)
# Update the pickle's reference count. Remember, the # Update the pickle's reference count. Remember, the
# refcount is stored as a string, so we have to do the # refcount is stored as a string, so we have to do the
# string->long->string dance. # string->long->string dance.
refcount = self._pickleReferenceCount.get(key, zero, refcount = self._pickleRefcounts.get(key, zero, txn=txn)
txn=txn)
refcount = utils.p64(utils.U64(refcount) + 1) refcount = utils.p64(utils.U64(refcount) + 1)
self._pickleReferenceCount.put(key, refcount, txn=txn) self._pickleRefcounts.put(key, refcount, txn=txn)
elif op == 'v': elif op == 'v':
# This is a "create-a-version" record # This is a "create-a-version" record
version, vid = data version, vid = data
...@@ -477,14 +477,14 @@ class Full(BerkeleyBase): ...@@ -477,14 +477,14 @@ class Full(BerkeleyBase):
# requested, then we can simply return the pickle referenced by # requested, then we can simply return the pickle referenced by
# the revid. # the revid.
if vid == '\0'*8 or self._versions[vid] == version: if vid == '\0'*8 or self._versions[vid] == version:
return self._pickle[oid+lrevid], revid return self._pickles[oid+lrevid], revid
# Otherwise, we recognize that an object cannot be stored in more # Otherwise, we recognize that an object cannot be stored in more
# than one version at a time (although this may change if/when # than one version at a time (although this may change if/when
# "Unlocked" versions are added). So we return the non-version # "Unlocked" versions are added). So we return the non-version
# revision of the object. BAW: should we assert that version is # revision of the object. BAW: should we assert that version is
# empty in this case? # empty in this case?
lrevid = self._metadata[oid+nvrevid][16:24] lrevid = self._metadata[oid+nvrevid][16:24]
return self._pickle[oid+lrevid], nvrevid return self._pickles[oid+lrevid], nvrevid
finally: finally:
self._lock_release() self._lock_release()
...@@ -522,6 +522,7 @@ class Full(BerkeleyBase): ...@@ -522,6 +522,7 @@ class Full(BerkeleyBase):
if transaction is not self._transaction: if transaction is not self._transaction:
raise POSException.StorageTransactionError(self, transaction) raise POSException.StorageTransactionError(self, transaction)
zero = '\0'*8
self._lock_acquire() self._lock_acquire()
try: try:
# Check for conflict errors. JF says: under some circumstances, # Check for conflict errors. JF says: under some circumstances,
...@@ -533,14 +534,15 @@ class Full(BerkeleyBase): ...@@ -533,14 +534,15 @@ class Full(BerkeleyBase):
if oserial is None: if oserial is None:
# There's never been a previous revision of this object, so # There's never been a previous revision of this object, so
# set its non-version revid to zero. # set its non-version revid to zero.
nvrevid = '\0'*8 nvrevid = zero
oserial = zero
elif serial <> oserial: elif serial <> oserial:
# The object exists in the database, but the serial number # The object exists in the database, but the serial number
# given in the call is not the same as the last stored serial # given in the call is not the same as the last stored serial
# number. Raise a ConflictError. # number. Raise a ConflictError.
raise POSException.ConflictError( raise POSException.ConflictError(
'serial number mismatch (was: %s, has: %s)' % 'serial number mismatch (was: %s, has: %s)' %
(oserial, utils.U64(serial))) (utils.U64(oserial), utils.U64(serial)))
# Do we already know about this version? If not, we need to # Do we already know about this version? If not, we need to
# record the fact that a new version is being created. `version' # record the fact that a new version is being created. `version'
# will be the empty string when the transaction is storing on the # will be the empty string when the transaction is storing on the
...@@ -549,7 +551,8 @@ class Full(BerkeleyBase): ...@@ -549,7 +551,8 @@ class Full(BerkeleyBase):
vid = self.__findcreatevid(version) vid = self.__findcreatevid(version)
else: else:
# vid 0 means no explicit version # vid 0 means no explicit version
vid = '\0'*8 vid = zero
nvrevid = zero
# A VersionLockError occurs when a particular object is being # A VersionLockError occurs when a particular object is being
# stored on a version different than the last version it was # stored on a version different than the last version it was
# previously stored on (as long as the previous version wasn't # previously stored on (as long as the previous version wasn't
...@@ -560,7 +563,7 @@ class Full(BerkeleyBase): ...@@ -560,7 +563,7 @@ class Full(BerkeleyBase):
if orevid: if orevid:
rec = self._metadata[oid+orevid] rec = self._metadata[oid+orevid]
ovid, onvrevid = struct.unpack('>8s8s', rec[:16]) ovid, onvrevid = struct.unpack('>8s8s', rec[:16])
if ovid == '\0'*8: if ovid == zero:
# The old revision's vid was zero any version is okay. # The old revision's vid was zero any version is okay.
# But if we're storing this on a version, then the # But if we're storing this on a version, then the
# non-version revid will be the previous revid for the # non-version revid will be the previous revid for the
...@@ -572,13 +575,13 @@ class Full(BerkeleyBase): ...@@ -572,13 +575,13 @@ class Full(BerkeleyBase):
# current version. That's a no no. # current version. That's a no no.
raise POSException.VersionLockError( raise POSException.VersionLockError(
'version mismatch for object %s (was: %s, got: %s)' % 'version mismatch for object %s (was: %s, got: %s)' %
(oid, ovid, vid)) map(utils.U64, (oid, ovid, vid)))
# Record the update to this object in the commit log. # Record the update to this object in the commit log.
self._commitlog.write_object(oid, vid, nvrevid, data, oserial) self._commitlog.write_object(oid, vid, nvrevid, data, oserial)
finally: finally:
self._lock_release() self._lock_release()
# Return our cached serial number for the object. # Return our cached serial number for the object.
return serial return self._serial
def _decref(self, oid, lrevid, txn): def _decref(self, oid, lrevid, txn):
# Decref the reference count of the pickle pointed to by oid+lrevid. # Decref the reference count of the pickle pointed to by oid+lrevid.
...@@ -586,17 +589,19 @@ class Full(BerkeleyBase): ...@@ -586,17 +589,19 @@ class Full(BerkeleyBase):
# pickle, and decref all the object pointed to by the pickle (with of # pickle, and decref all the object pointed to by the pickle (with of
# course, cascading garbage collection). # course, cascading garbage collection).
key = oid + lrevid key = oid + lrevid
refcount = self._pickleReferenceCount.get(key, txn=txn) refcount = self._pickleRefcounts.get(key, txn=txn)
refcount = utils.U64(refcount) - 1 refcount = utils.U64(refcount) - 1
if refcount > 0: if refcount > 0:
self._pickleReferenceCount.put(key, utils.p64(refcount), txn=txn) self._pickleRefcounts.put(key, utils.p64(refcount), txn=txn)
return return
# The refcount of this pickle has gone to zero, so we need to garbage # The refcount of this pickle has gone to zero, so we need to garbage
# collect it, and decref all the objects it points to. # collect it, and decref all the objects it points to.
self._pickleReferenceCount.delete(key, txn=txn) self._pickleRefcounts.delete(key, txn=txn)
pickle = self._pickles.get(key, txn=txn) pickle = self._pickles.get(key, txn=txn)
collectedOids = [] collectedOids = []
for roid in referencesf(pickle, []): refdoids = []
referencesf(pickle, refdoids)
for roid in refdoids:
refcount = self._refcounts.get(roid, txn=txn) refcount = self._refcounts.get(roid, txn=txn)
refcount = utils.U64(refcount) - 1 refcount = utils.U64(refcount) - 1
if refcount > 0: if refcount > 0:
......
...@@ -4,7 +4,7 @@ See Minimal.py for an implementation of Berkeley storage that does not support ...@@ -4,7 +4,7 @@ See Minimal.py for an implementation of Berkeley storage that does not support
undo or versioning. undo or versioning.
""" """
# $Revision: 1.4 $ # $Revision: 1.5 $
__version__ = '0.1' __version__ = '0.1'
import struct import struct
...@@ -35,7 +35,7 @@ PROTECTED_TRANSACTION = 'N' ...@@ -35,7 +35,7 @@ PROTECTED_TRANSACTION = 'N'
class InternalInconsistencyError(POSException.POSError, AssertError): class InternalInconsistencyError(POSException.POSError, AssertionError):
"""Raised when we detect an internal inconsistency in our tables.""" """Raised when we detect an internal inconsistency in our tables."""
...@@ -178,7 +178,7 @@ class Full(BerkeleyBase): ...@@ -178,7 +178,7 @@ class Full(BerkeleyBase):
self._txnOids.close() self._txnOids.close()
self._refcounts.close() self._refcounts.close()
self._references.close() self._references.close()
self._pickleReferenceCount.close() self._pickleRefcounts.close()
BerkeleyBase.close(self) BerkeleyBase.close(self)
def _begin(self, tid, u, d, e): def _begin(self, tid, u, d, e):
...@@ -236,7 +236,7 @@ class Full(BerkeleyBase): ...@@ -236,7 +236,7 @@ class Full(BerkeleyBase):
UNDOABLE_TRANSACTION + lengths + u + d + e, UNDOABLE_TRANSACTION + lengths + u + d + e,
txn=txn) txn=txn)
while 1: while 1:
rec = self._commitlog.next_object() rec = self._commitlog.next()
if rec is None: if rec is None:
break break
op, data = rec op, data = rec
...@@ -269,22 +269,22 @@ class Full(BerkeleyBase): ...@@ -269,22 +269,22 @@ class Full(BerkeleyBase):
self._txnOids.put(tid, oid, txn=txn) self._txnOids.put(tid, oid, txn=txn)
# Boost the refcount of all the objects referred to by # Boost the refcount of all the objects referred to by
# this pickle. referencesf() scans a pickle and returns # this pickle. referencesf() scans a pickle and returns
# the list of objects referenced by the pickle. BAW: In # the list of objects referenced by the pickle. BAW: the
# Zope 2.3.1, which we need to target, the signature of # signature of referencesf() has changed for Zope 2.4, to
# this function requires an empty list, but it returns # make it more convenient to use. Gotta stick with the
# that list. In future versions of Zope, there's a # backwards compatible version for now.
# default argument for that. refdoids = []
for roid in referencesf(pickle, []): referencesf(pickle, refdoids)
for roid in refdoids:
refcount = self._refcounts.get(roid, zero, txn=txn) refcount = self._refcounts.get(roid, zero, txn=txn)
refcount = utils.p64(utils.U64(refcount) + 1) refcount = utils.p64(utils.U64(refcount) + 1)
self._refcounts.put(roid, refcount, txn=txn) self._refcounts.put(roid, refcount, txn=txn)
# Update the pickle's reference count. Remember, the # Update the pickle's reference count. Remember, the
# refcount is stored as a string, so we have to do the # refcount is stored as a string, so we have to do the
# string->long->string dance. # string->long->string dance.
refcount = self._pickleReferenceCount.get(key, zero, refcount = self._pickleRefcounts.get(key, zero, txn=txn)
txn=txn)
refcount = utils.p64(utils.U64(refcount) + 1) refcount = utils.p64(utils.U64(refcount) + 1)
self._pickleReferenceCount.put(key, refcount, txn=txn) self._pickleRefcounts.put(key, refcount, txn=txn)
elif op == 'v': elif op == 'v':
# This is a "create-a-version" record # This is a "create-a-version" record
version, vid = data version, vid = data
...@@ -477,14 +477,14 @@ class Full(BerkeleyBase): ...@@ -477,14 +477,14 @@ class Full(BerkeleyBase):
# requested, then we can simply return the pickle referenced by # requested, then we can simply return the pickle referenced by
# the revid. # the revid.
if vid == '\0'*8 or self._versions[vid] == version: if vid == '\0'*8 or self._versions[vid] == version:
return self._pickle[oid+lrevid], revid return self._pickles[oid+lrevid], revid
# Otherwise, we recognize that an object cannot be stored in more # Otherwise, we recognize that an object cannot be stored in more
# than one version at a time (although this may change if/when # than one version at a time (although this may change if/when
# "Unlocked" versions are added). So we return the non-version # "Unlocked" versions are added). So we return the non-version
# revision of the object. BAW: should we assert that version is # revision of the object. BAW: should we assert that version is
# empty in this case? # empty in this case?
lrevid = self._metadata[oid+nvrevid][16:24] lrevid = self._metadata[oid+nvrevid][16:24]
return self._pickle[oid+lrevid], nvrevid return self._pickles[oid+lrevid], nvrevid
finally: finally:
self._lock_release() self._lock_release()
...@@ -522,6 +522,7 @@ class Full(BerkeleyBase): ...@@ -522,6 +522,7 @@ class Full(BerkeleyBase):
if transaction is not self._transaction: if transaction is not self._transaction:
raise POSException.StorageTransactionError(self, transaction) raise POSException.StorageTransactionError(self, transaction)
zero = '\0'*8
self._lock_acquire() self._lock_acquire()
try: try:
# Check for conflict errors. JF says: under some circumstances, # Check for conflict errors. JF says: under some circumstances,
...@@ -533,14 +534,15 @@ class Full(BerkeleyBase): ...@@ -533,14 +534,15 @@ class Full(BerkeleyBase):
if oserial is None: if oserial is None:
# There's never been a previous revision of this object, so # There's never been a previous revision of this object, so
# set its non-version revid to zero. # set its non-version revid to zero.
nvrevid = '\0'*8 nvrevid = zero
oserial = zero
elif serial <> oserial: elif serial <> oserial:
# The object exists in the database, but the serial number # The object exists in the database, but the serial number
# given in the call is not the same as the last stored serial # given in the call is not the same as the last stored serial
# number. Raise a ConflictError. # number. Raise a ConflictError.
raise POSException.ConflictError( raise POSException.ConflictError(
'serial number mismatch (was: %s, has: %s)' % 'serial number mismatch (was: %s, has: %s)' %
(oserial, utils.U64(serial))) (utils.U64(oserial), utils.U64(serial)))
# Do we already know about this version? If not, we need to # Do we already know about this version? If not, we need to
# record the fact that a new version is being created. `version' # record the fact that a new version is being created. `version'
# will be the empty string when the transaction is storing on the # will be the empty string when the transaction is storing on the
...@@ -549,7 +551,8 @@ class Full(BerkeleyBase): ...@@ -549,7 +551,8 @@ class Full(BerkeleyBase):
vid = self.__findcreatevid(version) vid = self.__findcreatevid(version)
else: else:
# vid 0 means no explicit version # vid 0 means no explicit version
vid = '\0'*8 vid = zero
nvrevid = zero
# A VersionLockError occurs when a particular object is being # A VersionLockError occurs when a particular object is being
# stored on a version different than the last version it was # stored on a version different than the last version it was
# previously stored on (as long as the previous version wasn't # previously stored on (as long as the previous version wasn't
...@@ -560,7 +563,7 @@ class Full(BerkeleyBase): ...@@ -560,7 +563,7 @@ class Full(BerkeleyBase):
if orevid: if orevid:
rec = self._metadata[oid+orevid] rec = self._metadata[oid+orevid]
ovid, onvrevid = struct.unpack('>8s8s', rec[:16]) ovid, onvrevid = struct.unpack('>8s8s', rec[:16])
if ovid == '\0'*8: if ovid == zero:
# The old revision's vid was zero any version is okay. # The old revision's vid was zero any version is okay.
# But if we're storing this on a version, then the # But if we're storing this on a version, then the
# non-version revid will be the previous revid for the # non-version revid will be the previous revid for the
...@@ -572,13 +575,13 @@ class Full(BerkeleyBase): ...@@ -572,13 +575,13 @@ class Full(BerkeleyBase):
# current version. That's a no no. # current version. That's a no no.
raise POSException.VersionLockError( raise POSException.VersionLockError(
'version mismatch for object %s (was: %s, got: %s)' % 'version mismatch for object %s (was: %s, got: %s)' %
(oid, ovid, vid)) map(utils.U64, (oid, ovid, vid)))
# Record the update to this object in the commit log. # Record the update to this object in the commit log.
self._commitlog.write_object(oid, vid, nvrevid, data, oserial) self._commitlog.write_object(oid, vid, nvrevid, data, oserial)
finally: finally:
self._lock_release() self._lock_release()
# Return our cached serial number for the object. # Return our cached serial number for the object.
return serial return self._serial
def _decref(self, oid, lrevid, txn): def _decref(self, oid, lrevid, txn):
# Decref the reference count of the pickle pointed to by oid+lrevid. # Decref the reference count of the pickle pointed to by oid+lrevid.
...@@ -586,17 +589,19 @@ class Full(BerkeleyBase): ...@@ -586,17 +589,19 @@ class Full(BerkeleyBase):
# pickle, and decref all the object pointed to by the pickle (with of # pickle, and decref all the object pointed to by the pickle (with of
# course, cascading garbage collection). # course, cascading garbage collection).
key = oid + lrevid key = oid + lrevid
refcount = self._pickleReferenceCount.get(key, txn=txn) refcount = self._pickleRefcounts.get(key, txn=txn)
refcount = utils.U64(refcount) - 1 refcount = utils.U64(refcount) - 1
if refcount > 0: if refcount > 0:
self._pickleReferenceCount.put(key, utils.p64(refcount), txn=txn) self._pickleRefcounts.put(key, utils.p64(refcount), txn=txn)
return return
# The refcount of this pickle has gone to zero, so we need to garbage # The refcount of this pickle has gone to zero, so we need to garbage
# collect it, and decref all the objects it points to. # collect it, and decref all the objects it points to.
self._pickleReferenceCount.delete(key, txn=txn) self._pickleRefcounts.delete(key, txn=txn)
pickle = self._pickles.get(key, txn=txn) pickle = self._pickles.get(key, txn=txn)
collectedOids = [] collectedOids = []
for roid in referencesf(pickle, []): refdoids = []
referencesf(pickle, refdoids)
for roid in refdoids:
refcount = self._refcounts.get(roid, txn=txn) refcount = self._refcounts.get(roid, txn=txn)
refcount = utils.U64(refcount) - 1 refcount = utils.U64(refcount) - 1
if refcount > 0: if refcount > 0:
......
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