Commit 25a17098 authored by Barry Warsaw's avatar Barry Warsaw

transactionalUndo(): A new form of undo that is non-destructive,

i.e. it undoes by writing new records.  This means undone transactions
can be redone.  It also has lots of other benefits.
parent 53d7f498
...@@ -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.10 $ # $Revision: 1.11 $
__version__ = '0.1' __version__ = '0.1'
import struct import struct
...@@ -607,8 +607,8 @@ class Full(BerkeleyBase): ...@@ -607,8 +607,8 @@ class Full(BerkeleyBase):
return self._serial return self._serial
def transactionalUndo(self, tid, transaction): def transactionalUndo(self, tid, transaction):
# FIXME: what if we undo an abortVersion or commitVersion, don't we global zero
# need to re-populate the currentVersions table?
if transaction is not self._transaction: if transaction is not self._transaction:
raise POSException.StorageTransactionError(self, transaction) raise POSException.StorageTransactionError(self, transaction)
...@@ -616,7 +616,7 @@ class Full(BerkeleyBase): ...@@ -616,7 +616,7 @@ class Full(BerkeleyBase):
c = None c = None
self._lock_acquire() self._lock_acquire()
try: try:
# First, make sure the transaction isn't protected by a pack # First, make sure the transaction isn't protected by a pack.
status = self._txnMetadata[tid][1] status = self._txnMetadata[tid][1]
if status == PROTECTED_TRANSACTION: if status == PROTECTED_TRANSACTION:
raise POSException.UndoError, 'Transaction cannot be undone' raise POSException.UndoError, 'Transaction cannot be undone'
...@@ -625,42 +625,53 @@ class Full(BerkeleyBase): ...@@ -625,42 +625,53 @@ class Full(BerkeleyBase):
c = self._txnoids.cursor() c = self._txnoids.cursor()
rec = c.set(tid) rec = c.set(tid)
while rec: while rec:
oid = rec[1] oid = rec[1] # ignore the key
rec = c.next_dup()
# In order to be able to undo this transaction, we must be # In order to be able to undo this transaction, we must be
# undoing either the current revision of the object, or we # undoing either the current revision of the object, or we
# must be restoring the exact same pickle (identity compared) # must be restoring the exact same pickle (identity compared)
# that would be restored if we were undoing the current # that would be restored if we were undoing the current
# revision. # revision.
#
# Note that we could do pickle equivalence comparisions
# instead. That would be "temporaly clean" in that we'd still
# be restoring the same state. We decided not to do this for
# now. Eventually, when we have application level conflict
# resolution, we can ask the object if it can resolve the
# state change, and then we'd reject the undo only if any of
# the state changes couldn't be resolved.
revid = self._serials[oid] revid = self._serials[oid]
if revid == tid: if revid == tid:
# We can always undo the last transaction
prevrevid = self._metadata[oid+tid][24:] prevrevid = self._metadata[oid+tid][24:]
if prevrevid == zero:
raise POSException.UndoError, 'Nothing to undo'
newrevs.append((oid, self._metadata[oid+prevrevid])) newrevs.append((oid, self._metadata[oid+prevrevid]))
else: else:
# Compare the lrevid (pickle pointers) for the current # We need to compare the lrevid (pickle pointers) of the
# revision of the object and the revision previous to the # transaction previous to the current one, and the
# one we're undoing. # transaction previous to the one we want to undo. If
lrevid = self._metadata[oid+revid][16:24] # their lrevids are the same, it's undoable.
# When we undo this transaction, the previous record will target_prevrevid = self._metadata[oid+tid][24:]
# become the current record. if target_prevrevid == zero:
prevrevid = self._metadata[oid+tid][24:] raise POSException.UndoError, 'Nothing to undo'
# And here's the pickle pointer for that potentially target_metadata = self._metadata[oid+target_prevrevid]
# soon-to-be current record target_lrevid = target_metadata[16:24]
prevrec = self._metadata[oid+prevrevid] last_prevrevid = self._metadata[oid+revid][24:]
if lrevid <> prevrec[16:24]: last_lrevid = self._metadata[oid+last_prevrevid][16:24]
# They aren't the same, so we cannot undo this txn # BAW: Here's where application level conflict resolution,
# or pickle equivalence testing would go.
if target_lrevid <> last_lrevid:
raise POSException.UndoError, 'Cannot undo transaction' raise POSException.UndoError, 'Cannot undo transaction'
newrevs.append((oid, prevrec)) # So far so good
# Check the next txnoid record newrevs.append((oid, target_metadata))
rec = c.next() # Okay, we've checked all the objects affected by the transaction
# Okay, we've checked all the oids affected by the transaction
# we're about to undo, and everything looks good. So now we'll # 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. # write to the log the new object records we intend to commit.
c.close()
c = None
oids = [] oids = []
for oid, rec in newrevs: for oid, metadata in newrevs:
vid, nvrevid, lrevid, prevrevid = struct.unpack( vid, nvrevid, lrevid, prevrevid = struct.unpack(
'>8s8s8s8s', rec) '>8s8s8s8s', metadata)
self._commitlog.write_moved_object(oid, vid, nvrevid, lrevid, self._commitlog.write_moved_object(oid, vid, nvrevid, lrevid,
prevrevid) prevrevid)
oids.append(oid) oids.append(oid)
......
...@@ -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.10 $ # $Revision: 1.11 $
__version__ = '0.1' __version__ = '0.1'
import struct import struct
...@@ -607,8 +607,8 @@ class Full(BerkeleyBase): ...@@ -607,8 +607,8 @@ class Full(BerkeleyBase):
return self._serial return self._serial
def transactionalUndo(self, tid, transaction): def transactionalUndo(self, tid, transaction):
# FIXME: what if we undo an abortVersion or commitVersion, don't we global zero
# need to re-populate the currentVersions table?
if transaction is not self._transaction: if transaction is not self._transaction:
raise POSException.StorageTransactionError(self, transaction) raise POSException.StorageTransactionError(self, transaction)
...@@ -616,7 +616,7 @@ class Full(BerkeleyBase): ...@@ -616,7 +616,7 @@ class Full(BerkeleyBase):
c = None c = None
self._lock_acquire() self._lock_acquire()
try: try:
# First, make sure the transaction isn't protected by a pack # First, make sure the transaction isn't protected by a pack.
status = self._txnMetadata[tid][1] status = self._txnMetadata[tid][1]
if status == PROTECTED_TRANSACTION: if status == PROTECTED_TRANSACTION:
raise POSException.UndoError, 'Transaction cannot be undone' raise POSException.UndoError, 'Transaction cannot be undone'
...@@ -625,42 +625,53 @@ class Full(BerkeleyBase): ...@@ -625,42 +625,53 @@ class Full(BerkeleyBase):
c = self._txnoids.cursor() c = self._txnoids.cursor()
rec = c.set(tid) rec = c.set(tid)
while rec: while rec:
oid = rec[1] oid = rec[1] # ignore the key
rec = c.next_dup()
# In order to be able to undo this transaction, we must be # In order to be able to undo this transaction, we must be
# undoing either the current revision of the object, or we # undoing either the current revision of the object, or we
# must be restoring the exact same pickle (identity compared) # must be restoring the exact same pickle (identity compared)
# that would be restored if we were undoing the current # that would be restored if we were undoing the current
# revision. # revision.
#
# Note that we could do pickle equivalence comparisions
# instead. That would be "temporaly clean" in that we'd still
# be restoring the same state. We decided not to do this for
# now. Eventually, when we have application level conflict
# resolution, we can ask the object if it can resolve the
# state change, and then we'd reject the undo only if any of
# the state changes couldn't be resolved.
revid = self._serials[oid] revid = self._serials[oid]
if revid == tid: if revid == tid:
# We can always undo the last transaction
prevrevid = self._metadata[oid+tid][24:] prevrevid = self._metadata[oid+tid][24:]
if prevrevid == zero:
raise POSException.UndoError, 'Nothing to undo'
newrevs.append((oid, self._metadata[oid+prevrevid])) newrevs.append((oid, self._metadata[oid+prevrevid]))
else: else:
# Compare the lrevid (pickle pointers) for the current # We need to compare the lrevid (pickle pointers) of the
# revision of the object and the revision previous to the # transaction previous to the current one, and the
# one we're undoing. # transaction previous to the one we want to undo. If
lrevid = self._metadata[oid+revid][16:24] # their lrevids are the same, it's undoable.
# When we undo this transaction, the previous record will target_prevrevid = self._metadata[oid+tid][24:]
# become the current record. if target_prevrevid == zero:
prevrevid = self._metadata[oid+tid][24:] raise POSException.UndoError, 'Nothing to undo'
# And here's the pickle pointer for that potentially target_metadata = self._metadata[oid+target_prevrevid]
# soon-to-be current record target_lrevid = target_metadata[16:24]
prevrec = self._metadata[oid+prevrevid] last_prevrevid = self._metadata[oid+revid][24:]
if lrevid <> prevrec[16:24]: last_lrevid = self._metadata[oid+last_prevrevid][16:24]
# They aren't the same, so we cannot undo this txn # BAW: Here's where application level conflict resolution,
# or pickle equivalence testing would go.
if target_lrevid <> last_lrevid:
raise POSException.UndoError, 'Cannot undo transaction' raise POSException.UndoError, 'Cannot undo transaction'
newrevs.append((oid, prevrec)) # So far so good
# Check the next txnoid record newrevs.append((oid, target_metadata))
rec = c.next() # Okay, we've checked all the objects affected by the transaction
# Okay, we've checked all the oids affected by the transaction
# we're about to undo, and everything looks good. So now we'll # 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. # write to the log the new object records we intend to commit.
c.close()
c = None
oids = [] oids = []
for oid, rec in newrevs: for oid, metadata in newrevs:
vid, nvrevid, lrevid, prevrevid = struct.unpack( vid, nvrevid, lrevid, prevrevid = struct.unpack(
'>8s8s8s8s', rec) '>8s8s8s8s', metadata)
self._commitlog.write_moved_object(oid, vid, nvrevid, lrevid, self._commitlog.write_moved_object(oid, vid, nvrevid, lrevid,
prevrevid) prevrevid)
oids.append(oid) oids.append(oid)
......
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