Commit 6a3a3773 authored by Christian Theune's avatar Christian Theune

- added testing and fixes for optimistic savepoint support

parent 02955468
...@@ -21,6 +21,8 @@ We need a database with a blob supporting storage: ...@@ -21,6 +21,8 @@ We need a database with a blob supporting storage:
>>> from ZODB.Blobs.BlobStorage import BlobStorage >>> from ZODB.Blobs.BlobStorage import BlobStorage
>>> from ZODB.DB import DB >>> from ZODB.DB import DB
>>> import transaction >>> import transaction
>>> import tempfile
>>> tempfile.tempdir = "/home/ctheune/blobtemp"
>>> from tempfile import mkdtemp >>> from tempfile import mkdtemp
>>> base_storage = MappingStorage("test") >>> base_storage = MappingStorage("test")
>>> blob_dir = mkdtemp() >>> blob_dir = mkdtemp()
...@@ -177,6 +179,14 @@ connections should result in a write conflict error. ...@@ -177,6 +179,14 @@ connections should result in a write conflict error.
... ...
ConflictError: database conflict error (oid 0x01, class ZODB.Blobs.Blob.Blob) ConflictError: database conflict error (oid 0x01, class ZODB.Blobs.Blob.Blob)
After the conflict, the winning transaction's result is visible on both
connections:
>>> root3['blob1'].open('r').read()
'this is blob 1woot!this is from connection 3'
>>> root2['blob1'].open('r').read()
'this is blob 1woot!this is from connection 3'
BlobStorages implementation of getSize() includes the blob data and adds it to BlobStorages implementation of getSize() includes the blob data and adds it to
the underlying storages result of getSize(): the underlying storages result of getSize():
...@@ -185,6 +195,48 @@ the underlying storages result of getSize(): ...@@ -185,6 +195,48 @@ the underlying storages result of getSize():
>>> blob_size - underlying_size >>> blob_size - underlying_size
91L 91L
Savepoints and Blobs
--------------------
We do support optimistic savepoints :
>>> connection5 = database.open()
>>> root5 = connection5.root()
>>> blob = Blob()
>>> blob_fh = blob.open("wb")
>>> blob_fh.write("I'm a happy blob.")
>>> blob_fh.close()
>>> root5['blob'] = blob
>>> transaction.commit()
>>> root5['blob'].open("rb").read()
"I'm a happy blob."
>>> blob_fh = root5['blob'].open("a")
>>> blob_fh.write(" And I'm singing.")
>>> blob_fh.close()
>>> root5['blob'].open("rb").read()
"I'm a happy blob. And I'm singing."
>>> savepoint = transaction.savepoint(optimistic=True)
>>> root5['blob'].open("rb").read()
"I'm a happy blob. And I'm singing."
>>> transaction.get().commit()
We do not support non-optimistic savepoints:
>>> blob_fh = root5['blob'].open("a")
>>> blob_fh.write(" And the weather is beautiful.")
>>> blob_fh.close()
>>> root5['blob'].open("rb").read()
"I'm a happy blob. And I'm singing. And the weather is beautiful."
>>> savepoint = transaction.savepoint() # doctest: +ELLIPSIS
Traceback (most recent call last):
...
TypeError: ('Savepoints unsupported', <MultiObjectResourceAdapter for <ZODB.Blobs.Blob.BlobDataManager instance at ...> at ...>)
Teardown
--------
We don't need the storage directory and databases anymore: We don't need the storage directory and databases anymore:
>>> import shutil >>> import shutil
...@@ -192,4 +244,3 @@ We don't need the storage directory and databases anymore: ...@@ -192,4 +244,3 @@ We don't need the storage directory and databases anymore:
>>> tm1.get().abort() >>> tm1.get().abort()
>>> tm2.get().abort() >>> tm2.get().abort()
>>> database.close() >>> database.close()
...@@ -21,6 +21,7 @@ import tempfile ...@@ -21,6 +21,7 @@ import tempfile
import threading import threading
import warnings import warnings
import os import os
import shutil
from time import time from time import time
from persistent import PickleCache from persistent import PickleCache
...@@ -1090,6 +1091,11 @@ class Connection(ExportImport, object): ...@@ -1090,6 +1091,11 @@ class Connection(ExportImport, object):
else: else:
s = self._storage.storeBlob(oid, serial, data, blobfilename, s = self._storage.storeBlob(oid, serial, data, blobfilename,
self._version, transaction) self._version, transaction)
# we invalidate the object here in order to ensure
# that that the next attribute access of its name
# unghostify it, which will cause its blob data
# to be reattached "cleanly"
self.invalidate(s, {oid:True})
self._handle_serial(s, oid, change=False) self._handle_serial(s, oid, change=False)
src.close() src.close()
...@@ -1139,6 +1145,8 @@ BLOB_DIRTY = "store" ...@@ -1139,6 +1145,8 @@ BLOB_DIRTY = "store"
class TmpStore: class TmpStore:
"""A storage-like thing to support savepoints.""" """A storage-like thing to support savepoints."""
implements(IBlobStorage)
def __init__(self, base_version, storage): def __init__(self, base_version, storage):
self._storage = storage self._storage = storage
for method in ( for method in (
...@@ -1149,7 +1157,8 @@ class TmpStore: ...@@ -1149,7 +1157,8 @@ class TmpStore:
self._base_version = base_version self._base_version = base_version
self._file = tempfile.TemporaryFile() self._file = tempfile.TemporaryFile()
self._blobdir = tempfile.mkdtemp() self._blobdir = tempfile.mkdtemp() # XXX this needs to go to the
# storage dependent blob area
# position: current file position # position: current file position
# _tpos: file position at last commit point # _tpos: file position at last commit point
self.position = 0L self.position = 0L
...@@ -1162,6 +1171,7 @@ class TmpStore: ...@@ -1162,6 +1171,7 @@ class TmpStore:
def close(self): def close(self):
self._file.close() self._file.close()
shutil.rmtree(self._blobdir)
def load(self, oid, version): def load(self, oid, version):
pos = self.index.get(oid) pos = self.index.get(oid)
...@@ -1195,7 +1205,6 @@ class TmpStore: ...@@ -1195,7 +1205,6 @@ class TmpStore:
def storeBlob(self, oid, serial, data, blobfilename, version, def storeBlob(self, oid, serial, data, blobfilename, version,
transaction): transaction):
# XXX we need to clean up after ourselves!
serial = self.store(oid, serial, data, version, transaction) serial = self.store(oid, serial, data, version, transaction)
assert isinstance(serial, str) # XXX in theory serials could be assert isinstance(serial, str) # XXX in theory serials could be
# something else # something else
......
...@@ -193,3 +193,4 @@ However, using a savepoint invalidates any savepoints that come after it: ...@@ -193,3 +193,4 @@ However, using a savepoint invalidates any savepoints that come after it:
InvalidSavepointRollbackError InvalidSavepointRollbackError
>>> transaction.abort() >>> transaction.abort()
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