Commit 30fd2029 authored by Jim Fulton's avatar Jim Fulton

Renamed IStorageDB to IStorageWrapper and expanded it to provide

methods for transforming and untransforming data records. This was
needed to make storage wrappers that transform data work with
conflict resolution.
parent 6222e53a
......@@ -32,6 +32,11 @@ New Features
- You can now pass None (rather than a storage or file name) to get
a database with a mapping storage.
- Renamed IStorageDB to IStorageWrapper and expanded it to provide
methods for transforming and untransforming data records. This was
needed to make storage wrappers that transform data work with
conflict resolution.
Bugs Fixed
----------
......
......@@ -778,6 +778,7 @@ class StorageServerDB:
def invalidateCache(self):
self.server._invalidateCache(self.storage_id)
transform_record_data = untransform_record_data = lambda self, data: data
class StorageServer:
......
......@@ -53,6 +53,7 @@ def find_global(*args):
def state(self, oid, serial, prfactory, p=''):
p = p or self.loadSerial(oid, serial)
p = self._crs_wrapper.untransform_record_data(p)
file = StringIO(p)
unpickler = Unpickler(file)
unpickler.find_global = find_global
......@@ -80,13 +81,13 @@ class IPersistentReference(zope.interface.Interface):
def __cmp__(other):
'''if other is equivalent reference, return 0; else raise ValueError.
Equivalent in this case means that oid and database_name are the same.
If either is a weak reference, we only support `is` equivalence, and
otherwise raise a ValueError even if the datbase_names and oids are
the same, rather than guess at the correct semantics.
It is impossible to sort reliably, since the actual persistent
class may have its own comparison, and we have no idea what it is.
We assert that it is reasonably safe to assume that an object is
......@@ -135,7 +136,7 @@ class PersistentReference(object):
def __cmp__(self, other):
if self is other or (
isinstance(other, PersistentReference) and
isinstance(other, PersistentReference) and
self.oid == other.oid and
self.database_name == other.database_name and
not self.weak and
......@@ -179,6 +180,7 @@ def tryToResolveConflict(self, oid, committedSerial, oldSerial, newpickle,
# class_tuple, old, committed, newstate = ('',''), 0, 0, 0
try:
prfactory = PersistentReferenceFactory()
newpickle = self._crs_wrapper.untransform_record_data(newpickle)
file = StringIO(newpickle)
unpickler = Unpickler(file)
unpickler.find_global = find_global
......@@ -215,7 +217,7 @@ def tryToResolveConflict(self, oid, committedSerial, oldSerial, newpickle,
pickler.inst_persistent_id = persistent_id
pickler.dump(meta)
pickler.dump(resolved)
return file.getvalue(1)
return self._crs_wrapper.transform_record_data(file.getvalue(1))
except (ConflictError, BadClassName):
return None
except:
......@@ -227,7 +229,11 @@ def tryToResolveConflict(self, oid, committedSerial, oldSerial, newpickle,
logger.error("Unexpected error", exc_info=True)
return None
class ConflictResolvingStorage:
class ConflictResolvingStorage(object):
"Mix-in class that provides conflict resolution handling for storages"
tryToResolveConflict = tryToResolveConflict
def registerDB(self, wrapper):
self._crs_wrapper = wrapper
super(ConflictResolvingStorage, self).registerDB(wrapper)
......@@ -705,6 +705,8 @@ class DB(object):
"""
self._connectionMap(lambda c: c.invalidateCache())
transform_record_data = untransform_record_data = lambda self, data: data
def objectCount(self):
return len(self.storage)
......
......@@ -287,23 +287,32 @@ class IConnection(Interface):
"""
class IStorageDB(Interface):
"""Database interface exposed to storages
class IStorageWrapper(Interface):
"""Storage wrapper interface
This interface provides 2 facilities:
This interface provides 3 facilities:
- Out-of-band invalidation support
A storage can notify it's database of object invalidations that
A storage can notify it's wrapper of object invalidations that
don't occur due to direct operations on the storage. Currently
this is only used by ZEO client storages to pass invalidation
messages sent from a server.
- Record-reference extraction.
- Record-reference extraction
The references method can be used to extract referenced object
IDs from a database record. This can be used by storages to
provide more advanced garbage collection.
provide more advanced garbage collection. A wrapper storage
that transforms data will provide a references method that
untransforms data passed to it and then pass the data to the
layer above it.
- Record transformation
A storage wrapper may transform data, for example for
compression or encryption. Methods are provided to transform or
untransform data.
This interface may be implemented by storage adapters or other
intermediaries. For example, a storage adapter that provides
......@@ -337,6 +346,16 @@ class IStorageDB(Interface):
be created and returned.
"""
def transform_record_data(data):
"""Return transformed data
"""
def untransform_record_data(data):
"""Return untransformed data
"""
IStorageDB = IStorageWrapper # for backward compatibility
class IDatabase(IStorageDB):
"""ZODB DB.
......@@ -595,12 +614,18 @@ class IStorage(Interface):
revisions.
"""
def registerDB(db):
"""Register an IStorageDB.
def registerDB(wrapper):
"""Register a storage wrapper IStorageWrapper.
The passed object is a wrapper object that provides an upcall
interface to support composition.
Note that, for historical reasons, an implementation may
require a second argument, however, if required, the None will
be passed as the second argument.
Also, for historical reasons, this is called registerDB rather
than register_wrapper.
"""
def sortKey():
......
......@@ -53,9 +53,15 @@ class PCounter4(PCounter):
def _p_resolveConflict(self, oldState, savedState):
raise RuntimeError("Can't get here; not enough args")
class StorageWrapper:
transform_record_data = untransform_record_data = lambda self, data: data
class ConflictResolvingStorage:
def checkResolve(self):
self._storage.registerDB(StorageWrapper())
obj = PCounter()
obj.inc()
......@@ -76,6 +82,8 @@ class ConflictResolvingStorage:
self.assertEqual(inst._value, 5)
def checkUnresolvable(self):
self._storage.registerDB(StorageWrapper())
obj = PCounter2()
obj.inc()
......@@ -97,11 +105,15 @@ class ConflictResolvingStorage:
self.fail("Expected ConflictError")
def checkZClassesArentResolved(self):
self._storage.registerDB(StorageWrapper())
from ZODB.ConflictResolution import find_global, BadClassName
dummy_class_tuple = ('*foobar', ())
self.assertRaises(BadClassName, find_global, '*foobar', ())
def checkBuggyResolve1(self):
self._storage.registerDB(StorageWrapper())
obj = PCounter3()
obj.inc()
......@@ -120,6 +132,8 @@ class ConflictResolvingStorage:
oid, revid=revid1, data=zodb_pickle(obj))
def checkBuggyResolve2(self):
self._storage.registerDB(StorageWrapper())
obj = PCounter4()
obj.inc()
......@@ -144,6 +158,8 @@ class ConflictResolvingTransUndoStorage:
# TransactionalUndoStorage test suite. Except here, conflict
# resolution should allow us to undo the transaction anyway.
self._storage.registerDB(StorageWrapper())
obj = PCounter()
obj.inc()
oid = self._storage.new_oid()
......@@ -166,6 +182,8 @@ class ConflictResolvingTransUndoStorage:
# TransactionalUndoStorage test suite. Except here, conflict
# resolution should allow us to undo the transaction anyway.
self._storage.registerDB(StorageWrapper())
obj = PCounter2()
obj.inc()
oid = self._storage.new_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