[Zodb-checkins] CVS: Packages/bsddb3Storage - Full.py:1.14

barry@digicool.com barry@digicool.com
Fri, 13 Apr 2001 15:31:57 -0400 (EDT)


Update of /cvs-repository/Packages/bsddb3Storage
In directory korak:/tmp/cvs-serv16944

Modified Files:
	Full.py 
Log Message:
Added some debugging aids:

    - DNE='nonexist' (it must be 8-bytes and this is easier to pick out)

    - _setupDBs(): use self._nextserial to choose the next serial
      number; this is easier to follow than the timestamps the base
      class uses.

    - _begin(): increment the next serial counter and assign that to
      self._serial

Also,

ObjectDoesNotExist: Removed, we use a straight KeyError instead since
undoInfo() obviates the need for this hack.

_vote(): Removed, base class implements this.

_finish(): At the point we increment the refcounts of the objects
refered to by the pickle, add a FIXME note.  Since objects can be
revised multiple times in a single transaction, we need to make sure
not to increment the refcounts too many times (not tested, so fix is
deferred for later).

abortVersion(): The API requires a VersionError if we try to abort the
empty version.

load(): The API requires that if we try to load an object on a
non-existant version, or a version on which the object hasn't been
changed, that we return the non-version revision of the object instead
of raising an exception.

transactionalUndo(): Fixed (as yet untested) bug in extraction of
pack-protection flag from transaction's metadata.

Rewrote the "revid <> tid" branch to check the commit log for any
promised revisions to the object committed earlier in this
transaction.  It's possible to revise an object multiple times in the
same transaction.  Along those lines, record modified oids in a set
(i.e. dictionary) so we don't get duplicates in the return value.

Use write_object_undo() instead of write_moved_object() so previous
revisions in the same transaction get recorded by the commit log.

undoLog(): The API defines default arguments, and their semantics, so
use them!  Also fix typos in cursor method calls and in unpacking the
user/desc/ext information in the transaction metadata.



--- Updated File Full.py in package Packages/bsddb3Storage --
--- Full.py	2001/04/11 22:11:15	1.13
+++ Full.py	2001/04/13 19:31:57	1.14
@@ -16,7 +16,7 @@
 from ZODB import POSException
 from ZODB import utils
 from ZODB.referencesf import referencesf
-from ZODB import TimeStamp
+from ZODB.TimeStamp import TimeStamp
 
 # BerkeleyBase.BerkeleyBase class provides some common functionality for both
 # the Full and Minimal implementations.  It in turn inherits from
@@ -34,17 +34,12 @@
 PROTECTED_TRANSACTION = 'N'
 
 ZERO = '\0'*8
-DNE = '\377'*8                                    # does not exist
+#DNE = '\377'*8
+# DEBUGGING
+DNE = 'nonexist'                                  # does not exist
 
 
 
-class ObjectDoesNotExist(KeyError):
-    def __init__(self, msg, revid):
-        POSException.VersionError.__init__(self, msg, revid)
-        self.revid = revid
-
-
-
 class Full(BerkeleyBase):
     #
     # Overrides of base class methods
@@ -164,6 +159,8 @@
             self.__nextvid = utils.U64(vid[0])
         else:
             self.__nextvid = 0L
+        # DEBUGGING
+        self._nextserial = 0L
         
     def close(self):
         self._serials.close()
@@ -179,6 +176,9 @@
         BerkeleyBase.close(self)
 
     def _begin(self, tid, u, d, e):
+        # DEBUGGING
+        self._nextserial += 1
+        self._serial = utils.p64(self._nextserial)
         # Begin the current transaction.  Currently, this just makes sure that
         # the commit log is in the proper state.
         if self._commitlog is None:
@@ -188,11 +188,6 @@
             self._commitlog = FullLog(dir=self._env.db_home)
         self._commitlog.start()
 
-    def _vote(self, transaction):
-        # From here on out, we promise to commit all the registered changes,
-        # so rewind and put our commit log in the PROMISED state.
-        self._commitlog.promise()
-
     def _finish(self, tid, u, d, e):
         # This is called from the storage interface's tpc_finish() method.
         # Its responsibilities are to finish the transaction with the
@@ -263,6 +258,9 @@
                         # changed for Zope 2.4, to make it more convenient to
                         # use.  Gotta stick with the backwards compatible
                         # version for now.
+                        #
+                        # FIXME: need to watch for two object revisions in the
+                        # same transaction and only bump the refcount once.
                         refdoids = []
                         referencesf(pickle, refdoids)
                         for roid in refdoids:
@@ -327,6 +325,9 @@
         c = None                                  # the currentVersions cursor
         self._lock_acquire()
         try:
+            # We can't abort the empty version, 'cause it ain't a version! :)
+            if not version:
+                raise POSException.VersionError
             # Let KeyErrors percolate up.  This is how we ensure that the
             # version we're aborting is not the empty string.
             vid = self._vids[version]
@@ -485,27 +486,15 @@
             # pickle pointer revid.
             rec = self._metadata[oid+revid]
             vid, nvrevid, lrevid = struct.unpack('>8s8s8s', rec[:24])
-            # If the object isn't living in a version, or if the version the
-            # object is living in is equal to the version that's being
-            # requested, then we can simply return the pickle referenced by
-            # the revid.
-            if vid == ZERO and version:
-                raise POSException.VersionError(
-                    'Object not found in version: %s' % version)
             if lrevid == DNE:
-                raise ObjectDoesNotExist('Object no longer exists', revid)
-            if vid == ZERO or self._versions[vid] == version:
+                raise KeyError, 'Object does not exist'
+            # If the object isn't living in a version, or if the version the
+            # object is living in is the one that was requested, we simply
+            # return the current revision's pickle.
+            if vid == ZERO or self._versions.get(vid) == version:
                 return self._pickles[oid+lrevid], revid
-            # Otherwise, we recognize that an object cannot be stored in more
-            # than one version at a time (although this may change if/when
-            # "Unlocked" versions are added).  So we return the non-version
-            # revision of the object.  Make sure the version is empty though.
-            if version:
-                if not self._vids.has_key(version):
-                    errmsg = 'Undefined version: %s' % version
-                else:
-                    errmsg = 'Object not found in version: %s' % version
-                raise POSException.VersionError(errmsg)
+            # The object was living in a version, but not the one requested.
+            # Semantics here are to return the non-version revision.
             lrevid = self._metadata[oid+nvrevid][16:24]
             return self._pickles[oid+lrevid], nvrevid
         finally:
@@ -613,13 +602,12 @@
             raise POSException.StorageTransactionError(self, transaction)
 
         newrevs = []
-        oids = []
         c = None
         self._lock_acquire()
         try:
             # First, make sure the transaction isn't protected by a pack.
-            status = self._txnMetadata[tid][1]
-            if status == PROTECTED_TRANSACTION:
+            status = self._txnMetadata[tid][0]
+            if status <> UNDOABLE_TRANSACTION:
                 raise POSException.UndoError, 'Transaction cannot be undone'
 
             # Calculate all the oids modified in the transaction
@@ -647,8 +635,10 @@
                     vid, nvrevid, lrevid, prevrevid = struct.unpack(
                         '>8s8s8s8s', self._metadata[oid+tid])
                     if prevrevid == ZERO:
-                        # We're undoing the object's creation
-                        newrevs.append((oid, vid+nvrevid+DNE+tid))
+                        # We're undoing the object's creation.  The only thing
+                        # to undo from there is the zombification of the
+                        # object, i.e. restore the current revision.
+                        newrevs.append((oid, vid+nvrevid+DNE+revid))
                     else:
                         newrevs.append((oid, self._metadata[oid+prevrevid]))
                 else:
@@ -656,35 +646,59 @@
                     # transaction previous to the current one, and the
                     # transaction previous to the one we want to undo.  If
                     # their lrevids are the same, it's undoable.
-                    target_prevrevid = self._metadata[oid+tid][24:]
-                    if target_prevrevid == ZERO:
-                        raise POSException.UndoError, 'Nothing to undo'
-                    target_metadata  = self._metadata[oid+target_prevrevid]
-                    target_lrevid    = target_metadata[16:24]
                     last_prevrevid = self._metadata[oid+revid][24:]
-                    last_lrevid    = self._metadata[oid+last_prevrevid][16:24]
-                    # BAW: Here's where application level conflict resolution,
-                    # or pickle equivalence testing would go.
-                    if target_lrevid <> last_lrevid:
+                    target_prevrevid = self._metadata[oid+tid][24:]
+                    if target_prevrevid == last_prevrevid == ZERO:
+                        # We're undoing the object's creation, so the only
+                        # thing to undo from there is the zombification of the
+                        # object, i.e. the last transaction for this object.
+                        vid, nvrevid = struct.unpack(
+                            '>8s8s', self._metadata[oid+tid][:16])
+                        newrevs.append((oid, vid+nvrevid+DNE+revid))
+                        continue
+                    elif target_prevrevid == ZERO or last_prevrevid == ZERO:
+                        # The object's revision is in it's initial creation
+                        # state but we're asking for an undo of something
+                        # other than the initial creation state.  No, no.
+                        raise POSException.UndoError, 'Undoing mismatch'
+                    last_lrevid     = self._metadata[oid+last_prevrevid][16:24]
+                    target_metadata = self._metadata[oid+target_prevrevid]
+                    target_lrevid   = target_metadata[16:24]
+                    # If the pickle pointers of the object's last revision
+                    # and the undo-target revision are the same, then the
+                    # transaction can be undone.  Note that we take a short
+                    # cut here, since we really want to test pickle equality,
+                    # but this is good enough for now.
+                    if target_lrevid == last_lrevid:
+                        newrevs.append((oid, target_metadata))
+                    # They weren't equal, but let's see if there are undos
+                    # waiting to be committed.
+                    elif target_lrevid == self._commitlog.get_prevrevid(oid):
+                        newrevs.append((oid, target_metadata))
+                    else:
                         raise POSException.UndoError, 'Cannot undo transaction'
-                    # So far so good
-                    newrevs.append((oid, target_metadata))
             # Okay, we've checked all the objects affected by the transaction
             # we're about to undo, and everything looks good.  So now we'll
             # write to the log the new object records we intend to commit.
+            oids = {}
             for oid, metadata in newrevs:
                 vid, nvrevid, lrevid, prevrevid = struct.unpack(
                     '>8s8s8s8s', metadata)
-                self._commitlog.write_moved_object(oid, vid, nvrevid, lrevid,
-                                                   prevrevid)
-                oids.append(oid)
-            return oids
+                self._commitlog.write_object_undo(oid, vid, nvrevid, lrevid,
+                                                  prevrevid)
+                # We're slightly inefficent here: if we make two undos to the
+                # same object during the same transaction, we'll write two
+                # records to the commit log.  This works because the last one
+                # will always overwrite previous ones, but it also means we'll
+                # see duplicate oids in this iteration.
+                oids[oid] = 1
+            return oids.keys()
         finally:
             if c:
                 c.close()
             self._lock_release()
 
-    def undoLog(self, first, last, filter=None):
+    def undoLog(self, first=0, last=-20, filter=None):
         # Get a list of transaction ids that can be undone, based on the
         # determination of the filter.  filter is a function which takes a
         # transaction description and returns true or false.
@@ -694,6 +708,11 @@
         # to implement undoInfo() because BaseStorage (which we eventually
         # inherit from) mixes in the UndoLogCompatible class which provides an
         # implementation written in terms of undoLog().
+        #
+        # Interface specifies that if last is < 0, its absolute value is the
+        # maximum number of transactions to return.
+        if last < 0:
+            last = abs(last)
         c = None                                  # tnxMetadata cursor
         txnDescriptions = []                      # the return value
         i = 0                                     # first <= i < last
@@ -704,16 +723,17 @@
             # can stop early if we find a transaction that is earlier than a
             # pack.  We still have the potential to scan through all the
             # transactions.
-            rec = c.get_last()
+            rec = c.last()
             while rec and i < last:
                 tid, data = rec
+                rec = c.prev()
                 status = data[0]
                 if status == PROTECTED_TRANSACTION:
                     break
-                userlen, desclen = struct.unpack('>II', data[1:17])
-                user = data[17:17+userlen]
-                desc = data[17+userlen:17+userlen+desclen]
-                ext = data[17+userlen+desclen:]
+                userlen, desclen = struct.unpack('>II', data[1:9])
+                user = data[9:9+userlen]
+                desc = data[9+userlen:9+userlen+desclen]
+                ext = data[9+userlen+desclen:]
                 # Create a dictionary for the TransactionDescription
                 txndesc = {'id'         : tid,
                            'time'       : TimeStamp(tid).timeTime(),
@@ -737,8 +757,6 @@
                     if i >= first:
                         txnDescriptions.append(txndesc)
                     i = i + 1
-                # And get the previous record
-                rec = c.get_prev()
             return txnDescriptions
         finally:
             if c: