Commit 89999e98 authored by Jim Fulton's avatar Jim Fulton Committed by GitHub

Merge pull request #129 from zopefoundation/storage-byte-appreciation

transaction user and description: text above, bytes below
parents 14aa616f 83992521
...@@ -2,6 +2,15 @@ ...@@ -2,6 +2,15 @@
Change History Change History
================ ================
- ZODB now translates transaction meta data, ``user`` and
``description`` from text to bytes before passing them to storages,
and converts them back to text when retrieving them from storages in
the ``history``, ``undoLog`` and ``undoInfo`` methods.
The ``IDatabase`` interface was updated to reflect that ``history``,
``undoLog`` and ``undoInfo`` are available on database objects.
(They were always available, but not documented in the interface.)
5.0.1 (2016-11-17) 5.0.1 (2016-11-17)
================== ==================
......
...@@ -139,7 +139,7 @@ statement. Transaction managers are context managers, so we can use ...@@ -139,7 +139,7 @@ statement. Transaction managers are context managers, so we can use
them with the ``with`` statement directly:: them with the ``with`` statement directly::
with my_transaction_manager as trans: with my_transaction_manager as trans:
trans.note("incrementing x") trans.note(u"incrementing x")
conn.root.x += 1 conn.root.x += 1
.. -> src .. -> src
...@@ -181,14 +181,15 @@ So, for example, if we wanted to set a transaction note:: ...@@ -181,14 +181,15 @@ So, for example, if we wanted to set a transaction note::
with db.transaction() as conn2: with db.transaction() as conn2:
conn2.transaction_manager.get().note("incrementing x again") conn2.transaction_manager.get().note(u"incrementing x again")
conn2.root.x += 1 conn2.root.x += 1
.. -> src .. -> src
>>> exec(src) >>> exec(src)
>>> str(db.history(conn.root()._p_oid)[0]['description']) >>> (db.history(conn.root()._p_oid)[0]['description'] ==
'incrementing x again' ... u'incrementing x again')
True
Here, we used the Here, we used the
:meth:`~transaction.interfaces.ITransactionManager.get` method to get :meth:`~transaction.interfaces.ITransactionManager.get` method to get
......
...@@ -141,7 +141,7 @@ setup(name="ZODB", ...@@ -141,7 +141,7 @@ setup(name="ZODB",
'persistent >= 4.2.0', 'persistent >= 4.2.0',
'BTrees >= 4.2.0', 'BTrees >= 4.2.0',
'ZConfig', 'ZConfig',
'transaction >= 1.6.1', 'transaction >= 2.0.3',
'six', 'six',
'zc.lockfile', 'zc.lockfile',
'zope.interface', 'zope.interface',
......
...@@ -191,7 +191,7 @@ class BaseStorage(UndoLogCompatible): ...@@ -191,7 +191,7 @@ class BaseStorage(UndoLogCompatible):
user = transaction.user user = transaction.user
desc = transaction.description desc = transaction.description
ext = transaction._extension ext = transaction.extension
if ext: if ext:
ext = dumps(ext, _protocol) ext = dumps(ext, _protocol)
else: else:
......
...@@ -27,6 +27,7 @@ from persistent import PickleCache ...@@ -27,6 +27,7 @@ from persistent import PickleCache
from persistent.interfaces import IPersistentDataManager from persistent.interfaces import IPersistentDataManager
from ZODB.interfaces import IConnection from ZODB.interfaces import IConnection
from ZODB.interfaces import IBlobStorage from ZODB.interfaces import IBlobStorage
from ZODB.interfaces import IStorageTransactionMetaData
from ZODB.blob import Blob, rename_or_copy_blob, remove_committed_dir from ZODB.blob import Blob, rename_or_copy_blob, remove_committed_dir
from transaction.interfaces import ISavepointDataManager from transaction.interfaces import ISavepointDataManager
from transaction.interfaces import IDataManagerSavepoint from transaction.interfaces import IDataManagerSavepoint
...@@ -51,6 +52,7 @@ import six ...@@ -51,6 +52,7 @@ import six
from .mvccadapter import HistoricalStorageAdapter from .mvccadapter import HistoricalStorageAdapter
from . import valuedoc from . import valuedoc
from . import _compat
global_reset_counter = 0 global_reset_counter = 0
...@@ -458,15 +460,22 @@ class Connection(ExportImport, object): ...@@ -458,15 +460,22 @@ class Connection(ExportImport, object):
def tpc_begin(self, transaction): def tpc_begin(self, transaction):
"""Begin commit of a transaction, starting the two-phase commit.""" """Begin commit of a transaction, starting the two-phase commit."""
self._modified = [] self._modified = []
meta_data = TransactionMetaData(
transaction.user,
transaction.description,
transaction.extension)
transaction.set_data(self, meta_data)
# _creating is a list of oids of new objects, which is used to # _creating is a list of oids of new objects, which is used to
# remove them from the cache if a transaction aborts. # remove them from the cache if a transaction aborts.
self._creating.clear() self._creating.clear()
self._normal_storage.tpc_begin(transaction) self._normal_storage.tpc_begin(meta_data)
def commit(self, transaction): def commit(self, transaction):
"""Commit changes to an object""" """Commit changes to an object"""
transaction = transaction.data(self)
if self._savepoint_storage is not None: if self._savepoint_storage is not None:
# We first checkpoint the current changes to the savepoint # We first checkpoint the current changes to the savepoint
...@@ -611,6 +620,8 @@ class Connection(ExportImport, object): ...@@ -611,6 +620,8 @@ class Connection(ExportImport, object):
obj._p_serial = s obj._p_serial = s
def tpc_abort(self, transaction): def tpc_abort(self, transaction):
transaction = transaction.data(self)
if self._import: if self._import:
self._import = None self._import = None
...@@ -667,6 +678,9 @@ class Connection(ExportImport, object): ...@@ -667,6 +678,9 @@ class Connection(ExportImport, object):
vote = self._storage.tpc_vote vote = self._storage.tpc_vote
except AttributeError: except AttributeError:
return return
transaction = transaction.data(self)
try: try:
s = vote(transaction) s = vote(transaction)
except ReadConflictError as v: except ReadConflictError as v:
...@@ -683,6 +697,8 @@ class Connection(ExportImport, object): ...@@ -683,6 +697,8 @@ class Connection(ExportImport, object):
def tpc_finish(self, transaction): def tpc_finish(self, transaction):
"""Indicate confirmation that the transaction is done. """Indicate confirmation that the transaction is done.
""" """
transaction = transaction.data(self)
serial = self._storage.tpc_finish(transaction) serial = self._storage.tpc_finish(transaction)
assert type(serial) is bytes, repr(serial) assert type(serial) is bytes, repr(serial)
for oid_iterator in self._modified, self._creating: for oid_iterator in self._modified, self._creating:
...@@ -1270,3 +1286,38 @@ large_record_size option of the ZODB.DB constructor (or the ...@@ -1270,3 +1286,38 @@ large_record_size option of the ZODB.DB constructor (or the
large-record-size option in a configuration file) to specify a larger large-record-size option in a configuration file) to specify a larger
size. size.
""" """
@implementer(IStorageTransactionMetaData)
class TransactionMetaData(object):
def __init__(self, user=u'', description=u'', extension=b''):
if not isinstance(user, bytes):
user = user.encode('utf-8')
self.user = user
if not isinstance(description, bytes):
description = description.encode('utf-8')
self.description = description
if not isinstance(extension, dict):
extension = _compat.loads(extension) if extension else {}
self.extension = extension
def note(self, text): # for tests
text = text.strip()
if not isinstance(text, bytes):
text = text.encode('utf-8')
if self.description:
self.description = self.description.strip() + b' ' + text
else:
self.description = text
@property
def _extension(self):
warnings.warn("_extension is deprecated, use extension",
DeprecationWarning)
return self.extension
@_extension.setter
def _extension(self, v):
self.extension = v
...@@ -24,7 +24,7 @@ from . import utils ...@@ -24,7 +24,7 @@ from . import utils
from ZODB.broken import find_global from ZODB.broken import find_global
from ZODB.utils import z64 from ZODB.utils import z64
from ZODB.Connection import Connection from ZODB.Connection import Connection, TransactionMetaData
from ZODB._compat import Pickler, _protocol, BytesIO from ZODB._compat import Pickler, _protocol, BytesIO
import ZODB.serialize import ZODB.serialize
...@@ -469,7 +469,7 @@ class DB(object): ...@@ -469,7 +469,7 @@ class DB(object):
self.large_record_size = large_record_size self.large_record_size = large_record_size
# Make sure we have a root: # Make sure we have a root:
with self.transaction('initial database creation') as conn: with self.transaction(u'initial database creation') as conn:
try: try:
conn.get(z64) conn.get(z64)
except KeyError: except KeyError:
...@@ -901,7 +901,7 @@ class DB(object): ...@@ -901,7 +901,7 @@ class DB(object):
See :meth:`ZODB.interfaces.IStorage.history`. See :meth:`ZODB.interfaces.IStorage.history`.
""" """
return self.storage.history(oid, size) return _text_transaction_info(self.storage.history(oid, size))
def supportsUndo(self): def supportsUndo(self):
"""Return whether the database supports undo. """Return whether the database supports undo.
...@@ -920,7 +920,7 @@ class DB(object): ...@@ -920,7 +920,7 @@ class DB(object):
if not self.supportsUndo(): if not self.supportsUndo():
return () return ()
return self.storage.undoLog(*args, **kw) return _text_transaction_info(self.storage.undoLog(*args, **kw))
def undoInfo(self, *args, **kw): def undoInfo(self, *args, **kw):
"""Return a sequence of descriptions for transactions. """Return a sequence of descriptions for transactions.
...@@ -929,7 +929,7 @@ class DB(object): ...@@ -929,7 +929,7 @@ class DB(object):
""" """
if not self.supportsUndo(): if not self.supportsUndo():
return () return ()
return self.storage.undoInfo(*args, **kw) return _text_transaction_info(self.storage.undoInfo(*args, **kw))
def undoMultiple(self, ids, txn=None): def undoMultiple(self, ids, txn=None):
"""Undo multiple transactions identified by ids. """Undo multiple transactions identified by ids.
...@@ -1037,19 +1037,28 @@ class TransactionalUndo(object): ...@@ -1037,19 +1037,28 @@ class TransactionalUndo(object):
pass pass
def tpc_begin(self, transaction): def tpc_begin(self, transaction):
self._storage.tpc_begin(transaction) tdata = TransactionMetaData(
transaction.user,
transaction.description,
transaction.extension)
transaction.set_data(self, tdata)
self._storage.tpc_begin(tdata)
def commit(self, transaction): def commit(self, transaction):
transaction = transaction.data(self)
for tid in self._tids: for tid in self._tids:
self._storage.undo(tid, transaction) self._storage.undo(tid, transaction)
def tpc_vote(self, transaction): def tpc_vote(self, transaction):
transaction = transaction.data(self)
self._storage.tpc_vote(transaction) self._storage.tpc_vote(transaction)
def tpc_finish(self, transaction): def tpc_finish(self, transaction):
transaction = transaction.data(self)
self._storage.tpc_finish(transaction) self._storage.tpc_finish(transaction)
def tpc_abort(self, transaction): def tpc_abort(self, transaction):
transaction = transaction.data(self)
self._storage.tpc_abort(transaction) self._storage.tpc_abort(transaction)
def sortKey(self): def sortKey(self):
...@@ -1064,3 +1073,12 @@ def connection(*args, **kw): ...@@ -1064,3 +1073,12 @@ def connection(*args, **kw):
managing a separate database object. managing a separate database object.
""" """
return DB(*args, **kw).open_then_close_db_when_connection_closes() return DB(*args, **kw).open_then_close_db_when_connection_closes()
_transaction_meta_data_text_variables = 'user_name', 'description'
def _text_transaction_info(info):
for d in info:
for name in _transaction_meta_data_text_variables:
if name in d:
d[name] = d[name].decode('utf-8')
return info
...@@ -20,6 +20,7 @@ import unittest ...@@ -20,6 +20,7 @@ import unittest
import ZODB.blob import ZODB.blob
import ZODB.FileStorage import ZODB.FileStorage
import ZODB.tests.util import ZODB.tests.util
from ZODB.Connection import TransactionMetaData
from zope.testing import renormalizing from zope.testing import renormalizing
checker = renormalizing.RENormalizing([ checker = renormalizing.RENormalizing([
...@@ -124,7 +125,7 @@ def pack_with_repeated_blob_records(): ...@@ -124,7 +125,7 @@ def pack_with_repeated_blob_records():
Now, create a transaction with multiple saves: Now, create a transaction with multiple saves:
>>> trans = tm.begin() >>> trans = TransactionMetaData()
>>> fs.tpc_begin(trans) >>> fs.tpc_begin(trans)
>>> with open('ablob', 'w') as file: >>> with open('ablob', 'w') as file:
... _ = file.write('some data') ... _ = file.write('some data')
...@@ -151,7 +152,7 @@ _save_index can fail for large indexes. ...@@ -151,7 +152,7 @@ _save_index can fail for large indexes.
>>> import ZODB.utils >>> import ZODB.utils
>>> fs = ZODB.FileStorage.FileStorage('data.fs') >>> fs = ZODB.FileStorage.FileStorage('data.fs')
>>> t = transaction.begin() >>> t = TransactionMetaData()
>>> fs.tpc_begin(t) >>> fs.tpc_begin(t)
>>> oid = 0 >>> oid = 0
>>> for i in range(5000): >>> for i in range(5000):
......
...@@ -340,7 +340,7 @@ class TransactionRecord: ...@@ -340,7 +340,7 @@ class TransactionRecord:
self.tid = tid self.tid = tid
self.user = transaction.user self.user = transaction.user
self.description = transaction.description self.description = transaction.description
extension = transaction._extension extension = transaction.extension
self.extension = extension self.extension = extension
self.data = data self.data = data
......
This diff is collapsed.
...@@ -19,12 +19,12 @@ http://www.zope.org/Documentation/Developer/Models/ZODB/ZODB_Architecture_Storag ...@@ -19,12 +19,12 @@ http://www.zope.org/Documentation/Developer/Models/ZODB/ZODB_Architecture_Storag
All storages should be able to pass these tests. All storages should be able to pass these tests.
""" """
from ZODB import POSException from ZODB import POSException
from ZODB.Connection import TransactionMetaData
from ZODB.tests.MinPO import MinPO from ZODB.tests.MinPO import MinPO
from ZODB.tests.StorageTestBase import zodb_unpickle, zodb_pickle from ZODB.tests.StorageTestBase import zodb_unpickle, zodb_pickle
import threading import threading
import time import time
import transaction
import zope.interface import zope.interface
import zope.interface.verify import zope.interface.verify
...@@ -36,7 +36,7 @@ class BasicStorage: ...@@ -36,7 +36,7 @@ class BasicStorage:
def checkBasics(self): def checkBasics(self):
self.assertEqual(self._storage.lastTransaction(), ZERO) self.assertEqual(self._storage.lastTransaction(), ZERO)
t = transaction.Transaction() t = TransactionMetaData()
self._storage.tpc_begin(t) self._storage.tpc_begin(t)
self.assertRaises(POSException.StorageTransactionError, self.assertRaises(POSException.StorageTransactionError,
self._storage.tpc_begin, t) self._storage.tpc_begin, t)
...@@ -48,22 +48,22 @@ class BasicStorage: ...@@ -48,22 +48,22 @@ class BasicStorage:
self.assertRaises( self.assertRaises(
POSException.StorageTransactionError, POSException.StorageTransactionError,
self._storage.store, self._storage.store,
ZERO, ZERO, b'', '', transaction.Transaction()) ZERO, ZERO, b'', '', TransactionMetaData())
self.assertRaises( self.assertRaises(
POSException.StorageTransactionError, POSException.StorageTransactionError,
self._storage.store, self._storage.store,
ZERO, 1, b'2', '', transaction.Transaction()) ZERO, 1, b'2', '', TransactionMetaData())
self.assertRaises( self.assertRaises(
POSException.StorageTransactionError, POSException.StorageTransactionError,
self._storage.tpc_vote, transaction.Transaction()) self._storage.tpc_vote, TransactionMetaData())
self._storage.tpc_abort(t) self._storage.tpc_abort(t)
def checkSerialIsNoneForInitialRevision(self): def checkSerialIsNoneForInitialRevision(self):
eq = self.assertEqual eq = self.assertEqual
oid = self._storage.new_oid() oid = self._storage.new_oid()
txn = transaction.Transaction() txn = TransactionMetaData()
self._storage.tpc_begin(txn) self._storage.tpc_begin(txn)
# Use None for serial. Don't use _dostore() here because that coerces # Use None for serial. Don't use _dostore() here because that coerces
# serial=None to serial=ZERO. # serial=None to serial=ZERO.
...@@ -106,7 +106,7 @@ class BasicStorage: ...@@ -106,7 +106,7 @@ class BasicStorage:
def checkWriteAfterAbort(self): def checkWriteAfterAbort(self):
oid = self._storage.new_oid() oid = self._storage.new_oid()
t = transaction.Transaction() t = TransactionMetaData()
self._storage.tpc_begin(t) self._storage.tpc_begin(t)
self._storage.store(oid, ZERO, zodb_pickle(MinPO(5)), '', t) self._storage.store(oid, ZERO, zodb_pickle(MinPO(5)), '', t)
# Now abort this transaction # Now abort this transaction
...@@ -119,7 +119,7 @@ class BasicStorage: ...@@ -119,7 +119,7 @@ class BasicStorage:
oid1 = self._storage.new_oid() oid1 = self._storage.new_oid()
revid1 = self._dostore(oid=oid1, data=MinPO(-2)) revid1 = self._dostore(oid=oid1, data=MinPO(-2))
oid = self._storage.new_oid() oid = self._storage.new_oid()
t = transaction.Transaction() t = TransactionMetaData()
self._storage.tpc_begin(t) self._storage.tpc_begin(t)
self._storage.store(oid, ZERO, zodb_pickle(MinPO(5)), '', t) self._storage.store(oid, ZERO, zodb_pickle(MinPO(5)), '', t)
# Now abort this transaction # Now abort this transaction
...@@ -180,9 +180,9 @@ class BasicStorage: ...@@ -180,9 +180,9 @@ class BasicStorage:
def checkNote(self): def checkNote(self):
oid = self._storage.new_oid() oid = self._storage.new_oid()
t = transaction.Transaction() t = TransactionMetaData()
self._storage.tpc_begin(t) self._storage.tpc_begin(t)
t.note('this is a test') t.note(u'this is a test')
self._storage.store(oid, ZERO, zodb_pickle(MinPO(5)), '', t) self._storage.store(oid, ZERO, zodb_pickle(MinPO(5)), '', t)
self._storage.tpc_vote(t) self._storage.tpc_vote(t)
self._storage.tpc_finish(t) self._storage.tpc_finish(t)
...@@ -194,18 +194,14 @@ class BasicStorage: ...@@ -194,18 +194,14 @@ class BasicStorage:
def checkMultipleEmptyTransactions(self): def checkMultipleEmptyTransactions(self):
# There was a bug in handling empty transactions in mapping # There was a bug in handling empty transactions in mapping
# storage that caused the commit lock not to be released. :( # storage that caused the commit lock not to be released. :(
transaction.begin() t = TransactionMetaData()
t = transaction.get()
self._storage.tpc_begin(t) self._storage.tpc_begin(t)
self._storage.tpc_vote(t) self._storage.tpc_vote(t)
self._storage.tpc_finish(t) self._storage.tpc_finish(t)
t.commit() t = TransactionMetaData()
transaction.begin()
t = transaction.get()
self._storage.tpc_begin(t) # Hung here before self._storage.tpc_begin(t) # Hung here before
self._storage.tpc_vote(t) self._storage.tpc_vote(t)
self._storage.tpc_finish(t) self._storage.tpc_finish(t)
t.commit()
def _do_store_in_separate_thread(self, oid, revid, voted): def _do_store_in_separate_thread(self, oid, revid, voted):
# We'll run the competing trans in a separate thread: # We'll run the competing trans in a separate thread:
...@@ -224,8 +220,7 @@ class BasicStorage: ...@@ -224,8 +220,7 @@ class BasicStorage:
#---------------------------------------------------------------------- #----------------------------------------------------------------------
# stale read # stale read
transaction.begin() t = TransactionMetaData()
t = transaction.get()
self._storage.tpc_begin(t) self._storage.tpc_begin(t)
try: try:
self._storage.store(b'\0\0\0\0\0\0\0\xf1', self._storage.store(b'\0\0\0\0\0\0\0\xf1',
...@@ -243,8 +238,7 @@ class BasicStorage: ...@@ -243,8 +238,7 @@ class BasicStorage:
#---------------------------------------------------------------------- #----------------------------------------------------------------------
# non-stale read, no stress. :) # non-stale read, no stress. :)
transaction.begin() t = TransactionMetaData()
t = transaction.get()
self._storage.tpc_begin(t) self._storage.tpc_begin(t)
self._storage.store(b'\0\0\0\0\0\0\0\xf2', self._storage.store(b'\0\0\0\0\0\0\0\xf2',
b'\0\0\0\0\0\0\0\0', data, '', t) b'\0\0\0\0\0\0\0\0', data, '', t)
...@@ -255,8 +249,7 @@ class BasicStorage: ...@@ -255,8 +249,7 @@ class BasicStorage:
#---------------------------------------------------------------------- #----------------------------------------------------------------------
# non-stale read, competition after vote. The competing # non-stale read, competition after vote. The competing
# transaction must produce a tid > this transaction's tid # transaction must produce a tid > this transaction's tid
transaction.begin() t = TransactionMetaData()
t = transaction.get()
self._storage.tpc_begin(t) self._storage.tpc_begin(t)
self._storage.store(b'\0\0\0\0\0\0\0\xf3', self._storage.store(b'\0\0\0\0\0\0\0\xf3',
b'\0\0\0\0\0\0\0\0', data, '', t) b'\0\0\0\0\0\0\0\0', data, '', t)
...@@ -275,8 +268,7 @@ class BasicStorage: ...@@ -275,8 +268,7 @@ class BasicStorage:
#---------------------------------------------------------------------- #----------------------------------------------------------------------
# non-stale competing trans after checkCurrentSerialInTransaction # non-stale competing trans after checkCurrentSerialInTransaction
transaction.begin() t = TransactionMetaData()
t = transaction.get()
self._storage.tpc_begin(t) self._storage.tpc_begin(t)
self._storage.store(b'\0\0\0\0\0\0\0\xf4', self._storage.store(b'\0\0\0\0\0\0\0\xf4',
b'\0\0\0\0\0\0\0\0', data, '', t) b'\0\0\0\0\0\0\0\0', data, '', t)
...@@ -312,7 +304,7 @@ class BasicStorage: ...@@ -312,7 +304,7 @@ class BasicStorage:
# verify that a storage gets it right. # verify that a storage gets it right.
# First, some initial data. # First, some initial data.
t = transaction.get() t = TransactionMetaData()
self._storage.tpc_begin(t) self._storage.tpc_begin(t)
self._storage.store(ZERO, ZERO, b'x', '', t) self._storage.store(ZERO, ZERO, b'x', '', t)
self._storage.tpc_vote(t) self._storage.tpc_vote(t)
...@@ -322,7 +314,7 @@ class BasicStorage: ...@@ -322,7 +314,7 @@ class BasicStorage:
# OK, now we'll start a new transaction, take it to finish, # OK, now we'll start a new transaction, take it to finish,
# and then block finish while we do some other operations. # and then block finish while we do some other operations.
t = transaction.get() t = TransactionMetaData()
self._storage.tpc_begin(t) self._storage.tpc_begin(t)
self._storage.store(ZERO, tids[0], b'y', '', t) self._storage.store(ZERO, tids[0], b'y', '', t)
self._storage.tpc_vote(t) self._storage.tpc_vote(t)
......
...@@ -14,9 +14,10 @@ ...@@ -14,9 +14,10 @@
"""Tests for application-level conflict resolution.""" """Tests for application-level conflict resolution."""
from ZODB import DB from ZODB import DB
from ZODB.Connection import TransactionMetaData
from ZODB.POSException import ConflictError, UndoError from ZODB.POSException import ConflictError, UndoError
from persistent import Persistent from persistent import Persistent
from transaction import Transaction, TransactionManager from transaction import TransactionManager
from ZODB.tests.StorageTestBase import zodb_unpickle, zodb_pickle from ZODB.tests.StorageTestBase import zodb_unpickle, zodb_pickle
...@@ -148,7 +149,7 @@ class ConflictResolvingTransUndoStorage: ...@@ -148,7 +149,7 @@ class ConflictResolvingTransUndoStorage:
# Start the undo # Start the undo
info = self._storage.undoInfo() info = self._storage.undoInfo()
tid = info[1]['id'] tid = info[1]['id']
t = Transaction() t = TransactionMetaData()
self._storage.tpc_begin(t) self._storage.tpc_begin(t)
self._storage.undo(tid, t) self._storage.undo(tid, t)
self._storage.tpc_vote(t) self._storage.tpc_vote(t)
...@@ -170,6 +171,6 @@ class ConflictResolvingTransUndoStorage: ...@@ -170,6 +171,6 @@ class ConflictResolvingTransUndoStorage:
# Start the undo # Start the undo
info = self._storage.undoInfo() info = self._storage.undoInfo()
tid = info[1]['id'] tid = info[1]['id']
t = Transaction() t = TransactionMetaData()
self.assertRaises(UndoError, self._begin_undos_vote, t, tid) self.assertRaises(UndoError, self._begin_undos_vote, t, tid)
self._storage.tpc_abort(t) self._storage.tpc_abort(t)
...@@ -41,7 +41,8 @@ Now we'll use the new deleteObject API to delete the objects. We can't ...@@ -41,7 +41,8 @@ Now we'll use the new deleteObject API to delete the objects. We can't
go through the database to do this, so we'll have to manage the go through the database to do this, so we'll have to manage the
transaction ourselves. transaction ourselves.
>>> txn = transaction.begin() >>> from ZODB.Connection import TransactionMetaData
>>> txn = TransactionMetaData()
>>> storage.tpc_begin(txn) >>> storage.tpc_begin(txn)
>>> storage.deleteObject(oid0, s0, txn) >>> storage.deleteObject(oid0, s0, txn)
>>> storage.deleteObject(oid1, s1, txn) >>> storage.deleteObject(oid1, s1, txn)
...@@ -116,7 +117,7 @@ isn't current: ...@@ -116,7 +117,7 @@ isn't current:
>>> conn.root()[0].x = 1 >>> conn.root()[0].x = 1
>>> transaction.commit() >>> transaction.commit()
>>> txn = transaction.begin() >>> txn = TransactionMetaData()
>>> storage.tpc_begin(txn) >>> storage.tpc_begin(txn)
>>> storage.deleteObject(oid, bad_serial, txn); storage.tpc_vote(txn) >>> storage.deleteObject(oid, bad_serial, txn); storage.tpc_vote(txn)
... # doctest: +ELLIPSIS ... # doctest: +ELLIPSIS
......
...@@ -18,12 +18,11 @@ all these tests. ...@@ -18,12 +18,11 @@ all these tests.
""" """
from ZODB.Connection import TransactionMetaData
from ZODB.tests.MinPO import MinPO from ZODB.tests.MinPO import MinPO
from ZODB.tests.StorageTestBase import zodb_pickle, zodb_unpickle from ZODB.tests.StorageTestBase import zodb_pickle, zodb_unpickle
from ZODB.utils import U64, p64, load_current from ZODB.utils import U64, p64, load_current
from transaction import Transaction
import ZODB.blob import ZODB.blob
try: try:
...@@ -67,7 +66,7 @@ class IteratorStorage(IteratorCompare): ...@@ -67,7 +66,7 @@ class IteratorStorage(IteratorCompare):
info = self._storage.undoInfo() info = self._storage.undoInfo()
tid = info[0]['id'] tid = info[0]['id']
# Undo the creation of the object, rendering it a zombie # Undo the creation of the object, rendering it a zombie
t = Transaction() t = TransactionMetaData()
self._storage.tpc_begin(t) self._storage.tpc_begin(t)
oids = self._storage.undo(tid, t) oids = self._storage.undo(tid, t)
self._storage.tpc_vote(t) self._storage.tpc_vote(t)
...@@ -105,7 +104,7 @@ class IteratorStorage(IteratorCompare): ...@@ -105,7 +104,7 @@ class IteratorStorage(IteratorCompare):
# Then the code in FileIterator.next() hasn't yet been fixed. # Then the code in FileIterator.next() hasn't yet been fixed.
# Should automate that check. # Should automate that check.
oid = self._storage.new_oid() oid = self._storage.new_oid()
t = Transaction() t = TransactionMetaData()
data = zodb_pickle(MinPO(0)) data = zodb_pickle(MinPO(0))
try: try:
self._storage.tpc_begin(t) self._storage.tpc_begin(t)
......
...@@ -8,6 +8,7 @@ import six ...@@ -8,6 +8,7 @@ import six
import transaction import transaction
import ZODB import ZODB
from ZODB.Connection import TransactionMetaData
from ZODB.tests.StorageTestBase import zodb_pickle, zodb_unpickle from ZODB.tests.StorageTestBase import zodb_pickle, zodb_unpickle
from ZODB.tests.MinPO import MinPO from ZODB.tests.MinPO import MinPO
from ZODB.POSException import ConflictError from ZODB.POSException import ConflictError
...@@ -140,7 +141,7 @@ class StorageClientThread(TestThread): ...@@ -140,7 +141,7 @@ class StorageClientThread(TestThread):
def dostore(self, i): def dostore(self, i):
data = zodb_pickle(MinPO((self.getName(), i))) data = zodb_pickle(MinPO((self.getName(), i)))
t = transaction.Transaction() t = TransactionMetaData()
oid = self.oid() oid = self.oid()
self.pause() self.pause()
......
...@@ -144,13 +144,13 @@ class PackableStorageBase: ...@@ -144,13 +144,13 @@ class PackableStorageBase:
try: try:
load_current(self._storage, ZERO) load_current(self._storage, ZERO)
except KeyError: except KeyError:
from transaction import Transaction from ZODB.Connection import TransactionMetaData
file = BytesIO() file = BytesIO()
p = Pickler(file, _protocol) p = Pickler(file, _protocol)
p.dump((PersistentMapping, None)) p.dump((PersistentMapping, None))
p.dump({'_container': {}}) p.dump({'_container': {}})
t=Transaction() t = TransactionMetaData()
t.description='initial database creation' t.description = u'initial database creation'
self._storage.tpc_begin(t) self._storage.tpc_begin(t)
self._storage.store(ZERO, None, file.getvalue(), '', t) self._storage.store(ZERO, None, file.getvalue(), '', t)
self._storage.tpc_vote(t) self._storage.tpc_vote(t)
...@@ -575,7 +575,7 @@ class PackableUndoStorage(PackableStorageBase): ...@@ -575,7 +575,7 @@ class PackableUndoStorage(PackableStorageBase):
root = conn.root() root = conn.root()
txn = transaction.get() txn = transaction.get()
txn.note('root') txn.note(u'root')
txn.commit() txn.commit()
now = packtime = time.time() now = packtime = time.time()
...@@ -587,12 +587,12 @@ class PackableUndoStorage(PackableStorageBase): ...@@ -587,12 +587,12 @@ class PackableUndoStorage(PackableStorageBase):
root['obj'] = obj root['obj'] = obj
txn = transaction.get() txn = transaction.get()
txn.note('root -> o1') txn.note(u'root -> o1')
txn.commit() txn.commit()
del root['obj'] del root['obj']
txn = transaction.get() txn = transaction.get()
txn.note('root -x-> o1') txn.note(u'root -x-> o1')
txn.commit() txn.commit()
self._storage.pack(packtime, referencesf) self._storage.pack(packtime, referencesf)
...@@ -601,7 +601,7 @@ class PackableUndoStorage(PackableStorageBase): ...@@ -601,7 +601,7 @@ class PackableUndoStorage(PackableStorageBase):
tid = log[0]['id'] tid = log[0]['id']
db.undo(tid) db.undo(tid)
txn = transaction.get() txn = transaction.get()
txn.note('undo root -x-> o1') txn.note(u'undo root -x-> o1')
txn.commit() txn.commit()
conn.sync() conn.sync()
......
...@@ -11,8 +11,8 @@ ...@@ -11,8 +11,8 @@
# FOR A PARTICULAR PURPOSE. # FOR A PARTICULAR PURPOSE.
# #
############################################################################## ##############################################################################
from ZODB.Connection import TransactionMetaData
from ZODB.POSException import ReadOnlyError, Unsupported from ZODB.POSException import ReadOnlyError, Unsupported
import transaction
from ZODB.utils import load_current from ZODB.utils import load_current
...@@ -48,7 +48,7 @@ class ReadOnlyStorage: ...@@ -48,7 +48,7 @@ class ReadOnlyStorage:
def checkWriteMethods(self): def checkWriteMethods(self):
self._make_readonly() self._make_readonly()
self.assertRaises(ReadOnlyError, self._storage.new_oid) self.assertRaises(ReadOnlyError, self._storage.new_oid)
t = transaction.Transaction() t = TransactionMetaData()
self.assertRaises(ReadOnlyError, self._storage.tpc_begin, t) self.assertRaises(ReadOnlyError, self._storage.tpc_begin, t)
self.assertRaises(ReadOnlyError, self._storage.store, self.assertRaises(ReadOnlyError, self._storage.store,
......
...@@ -14,7 +14,7 @@ ...@@ -14,7 +14,7 @@
"""More recovery and iterator tests.""" """More recovery and iterator tests."""
import transaction import transaction
from transaction import Transaction from ZODB.Connection import TransactionMetaData
from ZODB.tests.IteratorStorage import IteratorDeepCompare from ZODB.tests.IteratorStorage import IteratorDeepCompare
from ZODB.tests.StorageTestBase import MinPO, snooze from ZODB.tests.StorageTestBase import MinPO, snooze
from ZODB import DB from ZODB import DB
...@@ -73,15 +73,15 @@ class RecoveryStorage(IteratorDeepCompare): ...@@ -73,15 +73,15 @@ class RecoveryStorage(IteratorDeepCompare):
root = conn.root() root = conn.root()
root.obj = obj1 = MinPO(1) root.obj = obj1 = MinPO(1)
txn = transaction.get() txn = transaction.get()
txn.note('root -> obj') txn.note(u'root -> obj')
txn.commit() txn.commit()
root.obj.obj = obj2 = MinPO(2) root.obj.obj = obj2 = MinPO(2)
txn = transaction.get() txn = transaction.get()
txn.note('root -> obj -> obj') txn.note(u'root -> obj -> obj')
txn.commit() txn.commit()
del root.obj del root.obj
txn = transaction.get() txn = transaction.get()
txn.note('root -X->') txn.note(u'root -X->')
txn.commit() txn.commit()
# Now copy the transactions to the destination # Now copy the transactions to the destination
self._dst.copyTransactionsFrom(self._storage) self._dst.copyTransactionsFrom(self._storage)
...@@ -147,7 +147,7 @@ class RecoveryStorage(IteratorDeepCompare): ...@@ -147,7 +147,7 @@ class RecoveryStorage(IteratorDeepCompare):
# Undo the attribute creation. # Undo the attribute creation.
info = self._storage.undoInfo() info = self._storage.undoInfo()
tid = info[0]['id'] tid = info[0]['id']
t = Transaction() t = TransactionMetaData()
self._storage.tpc_begin(t) self._storage.tpc_begin(t)
oids = self._storage.undo(tid, t) oids = self._storage.undo(tid, t)
self._storage.tpc_vote(t) self._storage.tpc_vote(t)
...@@ -171,7 +171,7 @@ class RecoveryStorage(IteratorDeepCompare): ...@@ -171,7 +171,7 @@ class RecoveryStorage(IteratorDeepCompare):
# Undo the undo (restore the attributes). # Undo the undo (restore the attributes).
info = self._storage.undoInfo() info = self._storage.undoInfo()
tid = info[0]['id'] tid = info[0]['id']
t = Transaction() t = TransactionMetaData()
self._storage.tpc_begin(t) self._storage.tpc_begin(t)
oids = self._storage.undo(tid, t) oids = self._storage.undo(tid, t)
self._storage.tpc_vote(t) self._storage.tpc_vote(t)
......
...@@ -13,12 +13,11 @@ ...@@ -13,12 +13,11 @@
############################################################################## ##############################################################################
"""Check loadSerial() on storages that support historical revisions.""" """Check loadSerial() on storages that support historical revisions."""
from ZODB.Connection import TransactionMetaData
from ZODB.tests.MinPO import MinPO from ZODB.tests.MinPO import MinPO
from ZODB.tests.StorageTestBase import zodb_unpickle, zodb_pickle, snooze from ZODB.tests.StorageTestBase import zodb_unpickle, zodb_pickle, snooze
from ZODB.utils import p64, u64, load_current from ZODB.utils import p64, u64, load_current
import transaction
ZERO = '\0'*8 ZERO = '\0'*8
class RevisionStorage: class RevisionStorage:
...@@ -142,7 +141,7 @@ class RevisionStorage: ...@@ -142,7 +141,7 @@ class RevisionStorage:
oid = self._storage.new_oid() oid = self._storage.new_oid()
def helper(tid, revid, x): def helper(tid, revid, x):
data = zodb_pickle(MinPO(x)) data = zodb_pickle(MinPO(x))
t = transaction.Transaction() t = TransactionMetaData()
try: try:
self._storage.tpc_begin(t, p64(tid)) self._storage.tpc_begin(t, p64(tid))
self._storage.store(oid, revid, data, '', t) self._storage.store(oid, revid, data, '', t)
......
...@@ -21,8 +21,8 @@ single object revision. ...@@ -21,8 +21,8 @@ single object revision.
from __future__ import print_function from __future__ import print_function
import sys import sys
import time import time
import transaction
from ZODB.Connection import TransactionMetaData
from ZODB.utils import u64, z64 from ZODB.utils import u64, z64
from ZODB.tests.MinPO import MinPO from ZODB.tests.MinPO import MinPO
from ZODB._compat import PersistentPickler, Unpickler, BytesIO, _protocol from ZODB._compat import PersistentPickler, Unpickler, BytesIO, _protocol
...@@ -144,7 +144,7 @@ class StorageTestBase(ZODB.tests.util.TestCase): ...@@ -144,7 +144,7 @@ class StorageTestBase(ZODB.tests.util.TestCase):
if not already_pickled: if not already_pickled:
data = zodb_pickle(data) data = zodb_pickle(data)
# Begin the transaction # Begin the transaction
t = transaction.Transaction() t = TransactionMetaData()
if user is not None: if user is not None:
t.user = user t.user = user
if description is not None: if description is not None:
...@@ -170,8 +170,8 @@ class StorageTestBase(ZODB.tests.util.TestCase): ...@@ -170,8 +170,8 @@ class StorageTestBase(ZODB.tests.util.TestCase):
def _undo(self, tid, expected_oids=None, note=None): def _undo(self, tid, expected_oids=None, note=None):
# Undo a tid that affects a single object (oid). # Undo a tid that affects a single object (oid).
# This is very specialized. # This is very specialized.
t = transaction.Transaction() t = TransactionMetaData()
t.note(note or "undo") t.note(note or u"undo")
self._storage.tpc_begin(t) self._storage.tpc_begin(t)
undo_result = self._storage.undo(tid, t) undo_result = self._storage.undo(tid, t)
vote_result = self._storage.tpc_vote(t) vote_result = self._storage.tpc_vote(t)
......
...@@ -62,7 +62,7 @@ tested? Is it a general restriction? ...@@ -62,7 +62,7 @@ tested? Is it a general restriction?
""" """
from transaction import Transaction from ZODB.Connection import TransactionMetaData
from ZODB.POSException import StorageTransactionError from ZODB.POSException import StorageTransactionError
OID = "\000" * 8 OID = "\000" * 8
...@@ -75,43 +75,43 @@ class SynchronizedStorage: ...@@ -75,43 +75,43 @@ class SynchronizedStorage:
self.assertRaises(StorageTransactionError, callable, *args) self.assertRaises(StorageTransactionError, callable, *args)
def verifyWrongTrans(self, callable, *args): def verifyWrongTrans(self, callable, *args):
t = Transaction() t = TransactionMetaData()
self._storage.tpc_begin(t) self._storage.tpc_begin(t)
self.assertRaises(StorageTransactionError, callable, *args) self.assertRaises(StorageTransactionError, callable, *args)
self._storage.tpc_abort(t) self._storage.tpc_abort(t)
def checkStoreNotCommitting(self): def checkStoreNotCommitting(self):
self.verifyNotCommitting(self._storage.store, self.verifyNotCommitting(self._storage.store,
OID, SERIALNO, b"", "", Transaction()) OID, SERIALNO, b"", "", TransactionMetaData())
def checkStoreWrongTrans(self): def checkStoreWrongTrans(self):
self.verifyWrongTrans(self._storage.store, self.verifyWrongTrans(self._storage.store,
OID, SERIALNO, b"", "", Transaction()) OID, SERIALNO, b"", "", TransactionMetaData())
def checkAbortNotCommitting(self): def checkAbortNotCommitting(self):
self._storage.tpc_abort(Transaction()) self._storage.tpc_abort(TransactionMetaData())
def checkAbortWrongTrans(self): def checkAbortWrongTrans(self):
t = Transaction() t = TransactionMetaData()
self._storage.tpc_begin(t) self._storage.tpc_begin(t)
self._storage.tpc_abort(Transaction()) self._storage.tpc_abort(TransactionMetaData())
self._storage.tpc_abort(t) self._storage.tpc_abort(t)
def checkFinishNotCommitting(self): def checkFinishNotCommitting(self):
t = Transaction() t = TransactionMetaData()
self.assertRaises(StorageTransactionError, self.assertRaises(StorageTransactionError,
self._storage.tpc_finish, t) self._storage.tpc_finish, t)
self._storage.tpc_abort(t) self._storage.tpc_abort(t)
def checkFinishWrongTrans(self): def checkFinishWrongTrans(self):
t = Transaction() t = TransactionMetaData()
self._storage.tpc_begin(t) self._storage.tpc_begin(t)
self.assertRaises(StorageTransactionError, self.assertRaises(StorageTransactionError,
self._storage.tpc_finish, Transaction()) self._storage.tpc_finish, TransactionMetaData())
self._storage.tpc_abort(t) self._storage.tpc_abort(t)
def checkBeginCommitting(self): def checkBeginCommitting(self):
t = Transaction() t = TransactionMetaData()
self._storage.tpc_begin(t) self._storage.tpc_begin(t)
self._storage.tpc_abort(t) self._storage.tpc_abort(t)
......
...@@ -17,11 +17,14 @@ Any storage that supports undo() must pass these tests. ...@@ -17,11 +17,14 @@ Any storage that supports undo() must pass these tests.
""" """
import time import time
from six import PY3
from persistent import Persistent from persistent import Persistent
import transaction import transaction
from transaction import Transaction from transaction import Transaction
from ZODB import POSException from ZODB import POSException
from ZODB.Connection import TransactionMetaData
from ZODB.serialize import referencesf from ZODB.serialize import referencesf
from ZODB.utils import p64, load_current from ZODB.utils import p64, load_current
from ZODB import DB from ZODB import DB
...@@ -53,7 +56,7 @@ def listeq(L1, L2): ...@@ -53,7 +56,7 @@ def listeq(L1, L2):
class TransactionalUndoStorage: class TransactionalUndoStorage:
def _multi_obj_transaction(self, objs): def _multi_obj_transaction(self, objs):
t = Transaction() t = TransactionMetaData()
self._storage.tpc_begin(t) self._storage.tpc_begin(t)
for oid, rev, data in objs: for oid, rev, data in objs:
self._storage.store(oid, rev, data, '', t) self._storage.store(oid, rev, data, '', t)
...@@ -82,7 +85,7 @@ class TransactionalUndoStorage: ...@@ -82,7 +85,7 @@ class TransactionalUndoStorage:
return oids return oids
def undo(self, tid, note=None): def undo(self, tid, note=None):
t = Transaction() t = TransactionMetaData()
if note is not None: if note is not None:
t.note(note) t.note(note)
oids = self._begin_undos_vote(t, tid) oids = self._begin_undos_vote(t, tid)
...@@ -182,7 +185,7 @@ class TransactionalUndoStorage: ...@@ -182,7 +185,7 @@ class TransactionalUndoStorage:
oid2 = self._storage.new_oid() oid2 = self._storage.new_oid()
revid1 = revid2 = ZERO revid1 = revid2 = ZERO
# Store two objects in the same transaction # Store two objects in the same transaction
t = Transaction() t = TransactionMetaData()
self._storage.tpc_begin(t) self._storage.tpc_begin(t)
self._storage.store(oid1, revid1, p31, '', t) self._storage.store(oid1, revid1, p31, '', t)
self._storage.store(oid2, revid2, p51, '', t) self._storage.store(oid2, revid2, p51, '', t)
...@@ -190,7 +193,7 @@ class TransactionalUndoStorage: ...@@ -190,7 +193,7 @@ class TransactionalUndoStorage:
self._storage.tpc_vote(t) self._storage.tpc_vote(t)
tid = self._storage.tpc_finish(t) tid = self._storage.tpc_finish(t)
# Update those same two objects # Update those same two objects
t = Transaction() t = TransactionMetaData()
self._storage.tpc_begin(t) self._storage.tpc_begin(t)
self._storage.store(oid1, tid, p32, '', t) self._storage.store(oid1, tid, p32, '', t)
self._storage.store(oid2, tid, p52, '', t) self._storage.store(oid2, tid, p52, '', t)
...@@ -242,7 +245,7 @@ class TransactionalUndoStorage: ...@@ -242,7 +245,7 @@ class TransactionalUndoStorage:
info = self._storage.undoInfo() info = self._storage.undoInfo()
tid = info[0]['id'] tid = info[0]['id']
tid1 = info[1]['id'] tid1 = info[1]['id']
t = Transaction() t = TransactionMetaData()
oids = self._begin_undos_vote(t, tid, tid1) oids = self._begin_undos_vote(t, tid, tid1)
serial = self._storage.tpc_finish(t) serial = self._storage.tpc_finish(t)
# We may get the finalization stuff called an extra time, # We may get the finalization stuff called an extra time,
...@@ -275,7 +278,7 @@ class TransactionalUndoStorage: ...@@ -275,7 +278,7 @@ class TransactionalUndoStorage:
revid1 = self._dostore(oid1, data=p31, already_pickled=1) revid1 = self._dostore(oid1, data=p31, already_pickled=1)
revid2 = self._dostore(oid2, data=p51, already_pickled=1) revid2 = self._dostore(oid2, data=p51, already_pickled=1)
# Update those same two objects # Update those same two objects
t = Transaction() t = TransactionMetaData()
self._storage.tpc_begin(t) self._storage.tpc_begin(t)
self._storage.store(oid1, revid1, p32, '', t) self._storage.store(oid1, revid1, p32, '', t)
self._storage.store(oid2, revid2, p52, '', t) self._storage.store(oid2, revid2, p52, '', t)
...@@ -291,7 +294,7 @@ class TransactionalUndoStorage: ...@@ -291,7 +294,7 @@ class TransactionalUndoStorage:
eq(zodb_unpickle(data), MinPO(51)) eq(zodb_unpickle(data), MinPO(51))
# Like the above, but this time, the second transaction contains only # Like the above, but this time, the second transaction contains only
# one object. # one object.
t = Transaction() t = TransactionMetaData()
self._storage.tpc_begin(t) self._storage.tpc_begin(t)
self._storage.store(oid1, revid1, p33, '', t) self._storage.store(oid1, revid1, p33, '', t)
self._storage.store(oid2, revid2, p53, '', t) self._storage.store(oid2, revid2, p53, '', t)
...@@ -320,7 +323,7 @@ class TransactionalUndoStorage: ...@@ -320,7 +323,7 @@ class TransactionalUndoStorage:
# Start the undo # Start the undo
info = self._storage.undoInfo() info = self._storage.undoInfo()
tid = info[1]['id'] tid = info[1]['id']
t = Transaction() t = TransactionMetaData()
self.assertRaises(POSException.UndoError, self.assertRaises(POSException.UndoError,
self._begin_undos_vote, t, tid) self._begin_undos_vote, t, tid)
self._storage.tpc_abort(t) self._storage.tpc_abort(t)
...@@ -334,7 +337,7 @@ class TransactionalUndoStorage: ...@@ -334,7 +337,7 @@ class TransactionalUndoStorage:
p81, p82, p91, p92 = map(zodb_pickle, p81, p82, p91, p92 = map(zodb_pickle,
map(MinPO, (81, 82, 91, 92))) map(MinPO, (81, 82, 91, 92)))
t = Transaction() t = TransactionMetaData()
self._storage.tpc_begin(t) self._storage.tpc_begin(t)
self._storage.store(oid1, revid1, p81, '', t) self._storage.store(oid1, revid1, p81, '', t)
self._storage.store(oid2, revid2, p91, '', t) self._storage.store(oid2, revid2, p91, '', t)
...@@ -352,7 +355,7 @@ class TransactionalUndoStorage: ...@@ -352,7 +355,7 @@ class TransactionalUndoStorage:
self.assertNotEqual(tid, revid2) self.assertNotEqual(tid, revid2)
info = self._storage.undoInfo() info = self._storage.undoInfo()
tid = info[1]['id'] tid = info[1]['id']
t = Transaction() t = TransactionMetaData()
self.assertRaises(POSException.UndoError, self.assertRaises(POSException.UndoError,
self._begin_undos_vote, t, tid) self._begin_undos_vote, t, tid)
self._storage.tpc_abort(t) self._storage.tpc_abort(t)
...@@ -411,7 +414,7 @@ class TransactionalUndoStorage: ...@@ -411,7 +414,7 @@ class TransactionalUndoStorage:
root['obj'] = o1 root['obj'] = o1
o1.obj = o2 o1.obj = o2
txn = transaction.get() txn = transaction.get()
txn.note('o1 -> o2') txn.note(u'o1 -> o2')
txn.commit() txn.commit()
now = packtime = time.time() now = packtime = time.time()
while packtime <= now: while packtime <= now:
...@@ -420,12 +423,12 @@ class TransactionalUndoStorage: ...@@ -420,12 +423,12 @@ class TransactionalUndoStorage:
o3 = C() o3 = C()
o2.obj = o3 o2.obj = o3
txn = transaction.get() txn = transaction.get()
txn.note('o1 -> o2 -> o3') txn.note(u'o1 -> o2 -> o3')
txn.commit() txn.commit()
o1.obj = o3 o1.obj = o3
txn = transaction.get() txn = transaction.get()
txn.note('o1 -> o3') txn.note(u'o1 -> o3')
txn.commit() txn.commit()
log = self._storage.undoLog() log = self._storage.undoLog()
...@@ -443,7 +446,7 @@ class TransactionalUndoStorage: ...@@ -443,7 +446,7 @@ class TransactionalUndoStorage:
tid = log[0]['id'] tid = log[0]['id']
db.undo(tid) db.undo(tid)
txn = transaction.get() txn = transaction.get()
txn.note('undo') txn.note(u'undo')
txn.commit() txn.commit()
# undo does a txn-undo, but doesn't invalidate # undo does a txn-undo, but doesn't invalidate
conn.sync() conn.sync()
...@@ -470,14 +473,14 @@ class TransactionalUndoStorage: ...@@ -470,14 +473,14 @@ class TransactionalUndoStorage:
root["key1"] = MinPO(1) root["key1"] = MinPO(1)
root["key2"] = MinPO(2) root["key2"] = MinPO(2)
txn = transaction.get() txn = transaction.get()
txn.note("create 3 keys") txn.note(u"create 3 keys")
txn.commit() txn.commit()
set_pack_time() set_pack_time()
del root["key1"] del root["key1"]
txn = transaction.get() txn = transaction.get()
txn.note("delete 1 key") txn.note(u"delete 1 key")
txn.commit() txn.commit()
set_pack_time() set_pack_time()
...@@ -489,7 +492,7 @@ class TransactionalUndoStorage: ...@@ -489,7 +492,7 @@ class TransactionalUndoStorage:
L = db.undoInfo() L = db.undoInfo()
db.undo(L[0]["id"]) db.undo(L[0]["id"])
txn = transaction.get() txn = transaction.get()
txn.note("undo deletion") txn.note(u"undo deletion")
txn.commit() txn.commit()
set_pack_time() set_pack_time()
...@@ -521,7 +524,7 @@ class TransactionalUndoStorage: ...@@ -521,7 +524,7 @@ class TransactionalUndoStorage:
transaction.commit() transaction.commit()
rt["test"] = MinPO(3) rt["test"] = MinPO(3)
txn = transaction.get() txn = transaction.get()
txn.note("root of undo") txn.note(u"root of undo")
txn.commit() txn.commit()
packtimes = [] packtimes = []
...@@ -529,7 +532,7 @@ class TransactionalUndoStorage: ...@@ -529,7 +532,7 @@ class TransactionalUndoStorage:
L = db.undoInfo() L = db.undoInfo()
db.undo(L[0]["id"]) db.undo(L[0]["id"])
txn = transaction.get() txn = transaction.get()
txn.note("undo %d" % i) txn.note(u"undo %d" % i)
txn.commit() txn.commit()
rt._p_deactivate() rt._p_deactivate()
cn.sync() cn.sync()
...@@ -570,7 +573,7 @@ class TransactionalUndoStorage: ...@@ -570,7 +573,7 @@ class TransactionalUndoStorage:
orig = [] orig = []
for i in range(BATCHES): for i in range(BATCHES):
t = Transaction() t = TransactionMetaData()
tid = p64(i + 1) tid = p64(i + 1)
s.tpc_begin(t, tid) s.tpc_begin(t, tid)
for j in range(OBJECTS): for j in range(OBJECTS):
...@@ -593,7 +596,7 @@ class TransactionalUndoStorage: ...@@ -593,7 +596,7 @@ class TransactionalUndoStorage:
def undo(i): def undo(i):
info = s.undoInfo() info = s.undoInfo()
t = Transaction() t = TransactionMetaData()
s.tpc_begin(t) s.tpc_begin(t)
base = i * OBJECTS + i base = i * OBJECTS + i
for j in range(OBJECTS): for j in range(OBJECTS):
...@@ -646,9 +649,9 @@ class TransactionalUndoStorage: ...@@ -646,9 +649,9 @@ class TransactionalUndoStorage:
def checkUndoLogMetadata(self): def checkUndoLogMetadata(self):
# test that the metadata is correct in the undo log # test that the metadata is correct in the undo log
t = transaction.get() t = transaction.get()
t.note('t1') t.note(u't1')
t.setExtendedInfo('k2', 'this is transaction metadata') t.setExtendedInfo('k2', 'this is transaction metadata')
t.setUser('u3',path='p3') t.setUser(u'u3',path=u'p3')
db = DB(self._storage) db = DB(self._storage)
conn = db.open() conn = db.open()
root = conn.root() root = conn.root()
...@@ -733,7 +736,8 @@ class TransactionalUndoStorage: ...@@ -733,7 +736,8 @@ class TransactionalUndoStorage:
for i in range(4): for i in range(4):
with db.transaction() as conn: with db.transaction() as conn:
conn.transaction_manager.get().note(str(i)) conn.transaction_manager.get().note(
(str if PY3 else unicode)(i))
conn.root.x.inc() conn.root.x.inc()
ids = [l['id'] for l in db.undoLog(1, 3)] ids = [l['id'] for l in db.undoLog(1, 3)]
......
...@@ -377,7 +377,8 @@ If a transaction is aborted in the middle of 2-phase commit, any data ...@@ -377,7 +377,8 @@ If a transaction is aborted in the middle of 2-phase commit, any data
stored are discarded. stored are discarded.
>>> olddata, oldserial = blob_storage.load(blob._p_oid, '') >>> olddata, oldserial = blob_storage.load(blob._p_oid, '')
>>> t = transaction.get() >>> from ZODB.Connection import TransactionMetaData
>>> t = TransactionMetaData()
>>> blob_storage.tpc_begin(t) >>> blob_storage.tpc_begin(t)
>>> with open('blobfile', 'wb') as file: >>> with open('blobfile', 'wb') as file:
... _ = file.write(b'This data should go away') ... _ = file.write(b'This data should go away')
......
...@@ -30,19 +30,19 @@ def create_dangling_ref(db): ...@@ -30,19 +30,19 @@ def create_dangling_ref(db):
rt = db.open().root() rt = db.open().root()
rt[1] = o1 = P() rt[1] = o1 = P()
transaction.get().note("create o1") transaction.get().note(u"create o1")
transaction.commit() transaction.commit()
rt[2] = o2 = P() rt[2] = o2 = P()
transaction.get().note("create o2") transaction.get().note(u"create o2")
transaction.commit() transaction.commit()
c = o1.child = P() c = o1.child = P()
transaction.get().note("set child on o1") transaction.get().note(u"set child on o1")
transaction.commit() transaction.commit()
o1.child = P() o1.child = P()
transaction.get().note("replace child on o1") transaction.get().note(u"replace child on o1")
transaction.commit() transaction.commit()
time.sleep(2) time.sleep(2)
...@@ -53,11 +53,11 @@ def create_dangling_ref(db): ...@@ -53,11 +53,11 @@ def create_dangling_ref(db):
print(repr(c._p_oid)) print(repr(c._p_oid))
o2.child = c o2.child = c
transaction.get().note("set child on o2") transaction.get().note(u"set child on o2")
transaction.commit() transaction.commit()
def main(): def main():
fs = FileStorage("dangle.fs") fs = FileStorage(u"dangle.fs")
db = DB(fs) db = DB(fs)
create_dangling_ref(db) create_dangling_ref(db)
db.close() db.close()
......
...@@ -21,6 +21,8 @@ import sys ...@@ -21,6 +21,8 @@ import sys
import unittest import unittest
import transaction import transaction
from transaction import Transaction
import ZODB.tests.util import ZODB.tests.util
from ZODB.config import databaseFromString from ZODB.config import databaseFromString
from ZODB.utils import p64, u64, z64 from ZODB.utils import p64, u64, z64
...@@ -52,7 +54,7 @@ class ConnectionDotAdd(ZODB.tests.util.TestCase): ...@@ -52,7 +54,7 @@ class ConnectionDotAdd(ZODB.tests.util.TestCase):
self.db = StubDatabase() self.db = StubDatabase()
self.datamgr = Connection(self.db) self.datamgr = Connection(self.db)
self.datamgr.open() self.datamgr.open()
self.transaction = StubTransaction() self.transaction = Transaction()
def test_add(self): def test_add(self):
from ZODB.POSException import InvalidObjectReference from ZODB.POSException import InvalidObjectReference
...@@ -492,7 +494,7 @@ def doctest_transaction_retry_convenience(): ...@@ -492,7 +494,7 @@ def doctest_transaction_retry_convenience():
>>> import ZODB.POSException >>> import ZODB.POSException
>>> for attempt in transaction.manager.attempts(): >>> for attempt in transaction.manager.attempts():
... with attempt as t: ... with attempt as t:
... t.note('test') ... t.note(u'test')
... six.print_(dm['ntry'], ntry) ... six.print_(dm['ntry'], ntry)
... ntry += 1 ... ntry += 1
... dm['ntry'] = ntry ... dm['ntry'] = ntry
...@@ -700,7 +702,6 @@ def doctest_readCurrent(): ...@@ -700,7 +702,6 @@ def doctest_readCurrent():
>>> bad = set() >>> bad = set()
>>> def checkCurrentSerialInTransaction(oid, serial, trans): >>> def checkCurrentSerialInTransaction(oid, serial, trans):
... six.print_('checkCurrentSerialInTransaction', repr(oid)) ... six.print_('checkCurrentSerialInTransaction', repr(oid))
... if trans != transaction.get(): print('oops')
... if oid in bad: ... if oid in bad:
... raise ReadConflictError(oid=oid) ... raise ReadConflictError(oid=oid)
...@@ -1192,9 +1193,6 @@ class EstimatedSizeTests(ZODB.tests.util.TestCase): ...@@ -1192,9 +1193,6 @@ class EstimatedSizeTests(ZODB.tests.util.TestCase):
class StubObject(Persistent): class StubObject(Persistent):
pass pass
class StubTransaction:
pass
class ErrorOnGetstateException(Exception): class ErrorOnGetstateException(Exception):
pass pass
......
...@@ -11,6 +11,7 @@ ...@@ -11,6 +11,7 @@
# FOR A PARTICULAR PURPOSE. # FOR A PARTICULAR PURPOSE.
# #
############################################################################## ##############################################################################
from six import PY2
from ZODB.tests.MinPO import MinPO from ZODB.tests.MinPO import MinPO
import doctest import doctest
...@@ -75,6 +76,39 @@ class DBTests(ZODB.tests.util.TestCase): ...@@ -75,6 +76,39 @@ class DBTests(ZODB.tests.util.TestCase):
import ZODB.serialize import ZODB.serialize
self.assertTrue(self.db.references is ZODB.serialize.referencesf) self.assertTrue(self.db.references is ZODB.serialize.referencesf)
def test_history_and_undo_meta_data_text_handlinf(self):
db = self.db
conn = db.open()
for i in range(3):
with conn.transaction_manager as t:
t.note(u'work %s' % i)
t.setUser(u'user%s' % i)
conn.root()[i] = 42
conn.close()
from ZODB.utils import z64
def check(info, text):
for i, h in enumerate(reversed(info)):
for (name, expect) in (('description', 'work %s'),
('user_name', '/ user%s')):
expect = expect % i
if not text:
expect = expect.encode('ascii')
self.assertEqual(h[name], expect)
if PY2:
expect = unicode if text else str
for name in 'description', 'user_name':
self.assertTrue(isinstance(h[name], expect))
check(db.storage.history(z64, 3), False)
check(db.storage.undoLog(0, 3) , False)
check(db.storage.undoInfo(0, 3) , False)
check(db.history(z64, 3), True)
check(db.undoLog(0, 3) , True)
check(db.undoInfo(0, 3) , True)
def test_invalidateCache(): def test_invalidateCache():
"""The invalidateCache method invalidates a connection caches for all of """The invalidateCache method invalidates a connection caches for all of
......
...@@ -24,6 +24,7 @@ import ZODB.tests.testblob ...@@ -24,6 +24,7 @@ import ZODB.tests.testblob
import zope.testing.setupstack import zope.testing.setupstack
from ZODB import POSException from ZODB import POSException
from ZODB import DB from ZODB import DB
from ZODB.Connection import TransactionMetaData
from ZODB.fsIndex import fsIndex from ZODB.fsIndex import fsIndex
from ZODB.utils import U64, p64, z64, load_current from ZODB.utils import U64, p64, z64, load_current
...@@ -182,7 +183,7 @@ class FileStorageTests( ...@@ -182,7 +183,7 @@ class FileStorageTests(
# If .store() is handed an oid bigger than the storage knows # If .store() is handed an oid bigger than the storage knows
# about already, it's crucial that the storage bump its notion # about already, it's crucial that the storage bump its notion
# of the largest oid in use. # of the largest oid in use.
t = transaction.Transaction() t = TransactionMetaData()
self._storage.tpc_begin(t) self._storage.tpc_begin(t)
giant_oid = b'\xee' * 8 giant_oid = b'\xee' * 8
# Store an object. # Store an object.
...@@ -199,7 +200,7 @@ class FileStorageTests( ...@@ -199,7 +200,7 @@ class FileStorageTests(
# knows about already, it's crucial that the storage bump its notion # knows about already, it's crucial that the storage bump its notion
# of the largest oid in use. Because copyTransactionsFrom(), and # of the largest oid in use. Because copyTransactionsFrom(), and
# ZRS recovery, use the .restore() method, this is plain critical. # ZRS recovery, use the .restore() method, this is plain critical.
t = transaction.Transaction() t = TransactionMetaData()
self._storage.tpc_begin(t) self._storage.tpc_begin(t)
giant_oid = b'\xee' * 8 giant_oid = b'\xee' * 8
# Store an object. # Store an object.
...@@ -289,7 +290,7 @@ class FileStorageTests( ...@@ -289,7 +290,7 @@ class FileStorageTests(
def checkFlushAfterTruncate(self, fail=False): def checkFlushAfterTruncate(self, fail=False):
r0 = self._dostore(z64) r0 = self._dostore(z64)
storage = self._storage storage = self._storage
t = transaction.Transaction() t = TransactionMetaData()
storage.tpc_begin(t) storage.tpc_begin(t)
storage.store(z64, r0, b'foo', b'', t) storage.store(z64, r0, b'foo', b'', t)
storage.tpc_vote(t) storage.tpc_vote(t)
...@@ -421,7 +422,7 @@ class AnalyzeDotPyTest(StorageTestBase.StorageTestBase): ...@@ -421,7 +422,7 @@ class AnalyzeDotPyTest(StorageTestBase.StorageTestBase):
self._storage.store(oid, revid, data, "", t) self._storage.store(oid, revid, data, "", t)
for i in range(2): for i in range(2):
t = transaction.Transaction() t = TransactionMetaData()
self._storage.tpc_begin(t) self._storage.tpc_begin(t)
# sometimes data is in this format # sometimes data is in this format
......
...@@ -16,6 +16,7 @@ import unittest ...@@ -16,6 +16,7 @@ import unittest
from persistent.mapping import PersistentMapping from persistent.mapping import PersistentMapping
import transaction import transaction
from ZODB.Connection import TransactionMetaData
from ZODB.DB import DB from ZODB.DB import DB
from ZODB.tests.MVCCMappingStorage import MVCCMappingStorage from ZODB.tests.MVCCMappingStorage import MVCCMappingStorage
import ZODB.blob import ZODB.blob
...@@ -83,11 +84,11 @@ class MVCCTests: ...@@ -83,11 +84,11 @@ class MVCCTests:
storage = c1._storage storage = c1._storage
t = transaction.Transaction() t = transaction.Transaction()
t.description = 'isolation test 1' t.description = u'isolation test 1'
storage.tpc_begin(t) c1.tpc_begin(t)
c1.commit(t) c1.commit(t)
storage.tpc_vote(t) storage.tpc_vote(t.data(c1))
storage.tpc_finish(t) storage.tpc_finish(t.data(c1))
# The second connection will now load root['alpha'], but due to # The second connection will now load root['alpha'], but due to
# MVCC, it should continue to see the old state. # MVCC, it should continue to see the old state.
...@@ -109,11 +110,11 @@ class MVCCTests: ...@@ -109,11 +110,11 @@ class MVCCTests:
storage = c1._storage storage = c1._storage
t = transaction.Transaction() t = transaction.Transaction()
t.description = 'isolation test 2' t.description = u'isolation test 2'
storage.tpc_begin(t) c1.tpc_begin(t)
c1.commit(t) c1.commit(t)
storage.tpc_vote(t) storage.tpc_vote(t.data(c1))
storage.tpc_finish(t) storage.tpc_finish(t.data(c1))
# The second connection will now load root[3], but due to MVCC, # The second connection will now load root[3], but due to MVCC,
# it should continue to see the old state. # it should continue to see the old state.
...@@ -161,7 +162,7 @@ class MVCCMappingStorageTests( ...@@ -161,7 +162,7 @@ class MVCCMappingStorageTests(
import time import time
from ZODB.utils import newTid from ZODB.utils import newTid
from ZODB.TimeStamp import TimeStamp from ZODB.TimeStamp import TimeStamp
t = transaction.Transaction() t = TransactionMetaData()
self._storage.tpc_begin(t) self._storage.tpc_begin(t)
self._storage.tpc_vote(t) self._storage.tpc_vote(t)
self._storage.tpc_finish(t) self._storage.tpc_finish(t)
...@@ -173,7 +174,7 @@ class MVCCMappingStorageTests( ...@@ -173,7 +174,7 @@ class MVCCMappingStorageTests(
transactions[fake_timestamp] = transactions.values()[0] transactions[fake_timestamp] = transactions.values()[0]
# Verify the next transaction comes after the fake transaction # Verify the next transaction comes after the fake transaction
t = transaction.Transaction() t = TransactionMetaData()
self._storage.tpc_begin(t) self._storage.tpc_begin(t)
self.assertEqual(self._storage._tid, b'zzzzzzzz') self.assertEqual(self._storage._tid, b'zzzzzzzz')
......
...@@ -23,9 +23,8 @@ old code, developers will have a hard time testing the new code. ...@@ -23,9 +23,8 @@ old code, developers will have a hard time testing the new code.
import unittest import unittest
import sys import sys
from transaction import Transaction
import ZODB import ZODB
from ZODB.Connection import TransactionMetaData
from ZODB.MappingStorage import MappingStorage from ZODB.MappingStorage import MappingStorage
from six import PY2 from six import PY2
...@@ -47,7 +46,7 @@ class PMTests(unittest.TestCase): ...@@ -47,7 +46,7 @@ class PMTests(unittest.TestCase):
return return
# insert the pickle in place of the root # insert the pickle in place of the root
s = MappingStorage() s = MappingStorage()
t = Transaction() t = TransactionMetaData()
s.tpc_begin(t) s.tpc_begin(t)
s.store('\000' * 8, None, pickle, '', t) s.store('\000' * 8, None, pickle, '', t)
s.tpc_vote(t) s.tpc_vote(t)
......
...@@ -48,7 +48,7 @@ class ZODBTests(ZODB.tests.util.TestCase): ...@@ -48,7 +48,7 @@ class ZODBTests(ZODB.tests.util.TestCase):
root['test'] = pm = PersistentMapping() root['test'] = pm = PersistentMapping()
for n in range(100): for n in range(100):
pm[n] = PersistentMapping({0: 100 - n}) pm[n] = PersistentMapping({0: 100 - n})
transaction.get().note('created test data') transaction.get().note(u'created test data')
transaction.commit() transaction.commit()
conn.close() conn.close()
...@@ -67,7 +67,7 @@ class ZODBTests(ZODB.tests.util.TestCase): ...@@ -67,7 +67,7 @@ class ZODBTests(ZODB.tests.util.TestCase):
def duplicate(self, conn, abort_it): def duplicate(self, conn, abort_it):
transaction.begin() transaction.begin()
transaction.get().note('duplication') transaction.get().note(u'duplication')
root = conn.root() root = conn.root()
ob = root['test'] ob = root['test']
assert len(ob) > 10, 'Insufficient test data' assert len(ob) > 10, 'Insufficient test data'
...@@ -424,7 +424,7 @@ class ZODBTests(ZODB.tests.util.TestCase): ...@@ -424,7 +424,7 @@ class ZODBTests(ZODB.tests.util.TestCase):
for state_num in range(6): for state_num in range(6):
transaction.begin() transaction.begin()
root['state'] = state_num root['state'] = state_num
transaction.get().note('root["state"] = %d' % state_num) transaction.get().note(u'root["state"] = %d' % state_num)
transaction.commit() transaction.commit()
# Undo all but the first. Note that no work is actually # Undo all but the first. Note that no work is actually
...@@ -433,7 +433,7 @@ class ZODBTests(ZODB.tests.util.TestCase): ...@@ -433,7 +433,7 @@ class ZODBTests(ZODB.tests.util.TestCase):
log = self._db.undoLog() log = self._db.undoLog()
self._db.undoMultiple([log[i]['id'] for i in range(5)]) self._db.undoMultiple([log[i]['id'] for i in range(5)])
transaction.get().note('undo states 1 through 5') transaction.get().note(u'undo states 1 through 5')
# Now attempt all those undo operations. # Now attempt all those undo operations.
transaction.commit() transaction.commit()
......
...@@ -49,7 +49,7 @@ Let's add a BTree: ...@@ -49,7 +49,7 @@ Let's add a BTree:
>>> root = db.open().root() >>> root = db.open().root()
>>> root['tree'] = OOBTree() >>> root['tree'] = OOBTree()
>>> txn.get().note('added an OOBTree') >>> txn.get().note(u'added an OOBTree')
>>> txn.get().commit() >>> txn.get().commit()
>>> fsdump(path) #doctest: +ELLIPSIS >>> fsdump(path) #doctest: +ELLIPSIS
Trans #00000 tid=... time=... offset=<OFFSET> Trans #00000 tid=... time=... offset=<OFFSET>
......
...@@ -76,7 +76,7 @@ Let's add a BTree and try again: ...@@ -76,7 +76,7 @@ Let's add a BTree and try again:
>>> root = db.open().root() >>> root = db.open().root()
>>> root['tree'] = OOBTree() >>> root['tree'] = OOBTree()
>>> txn.get().note('added an OOBTree') >>> txn.get().note(u'added an OOBTree')
>>> txn.get().commit() >>> txn.get().commit()
>>> t = Tracer(path) >>> t = Tracer(path)
>>> t.register_oids(0, 1) >>> t.register_oids(0, 1)
...@@ -104,7 +104,7 @@ One more, storing a reference in the BTree back to the root object: ...@@ -104,7 +104,7 @@ One more, storing a reference in the BTree back to the root object:
>>> tree = root['tree'] >>> tree = root['tree']
>>> tree['root'] = root >>> tree['root'] = root
>>> txn.get().note('circling back to the root') >>> txn.get().note(u'circling back to the root')
>>> txn.get().commit() >>> txn.get().commit()
>>> t = Tracer(path) >>> t = Tracer(path)
>>> t.register_oids(0, 1, 2) >>> t.register_oids(0, 1, 2)
......
...@@ -25,6 +25,7 @@ import transaction ...@@ -25,6 +25,7 @@ import transaction
import unittest import unittest
import warnings import warnings
import ZODB.utils import ZODB.utils
from ZODB.Connection import TransactionMetaData
import zope.testing.setupstack import zope.testing.setupstack
from zope.testing import renormalizing from zope.testing import renormalizing
...@@ -161,7 +162,7 @@ def store(storage, oid, value='x', serial=ZODB.utils.z64): ...@@ -161,7 +162,7 @@ def store(storage, oid, value='x', serial=ZODB.utils.z64):
oid = ZODB.utils.p64(oid) oid = ZODB.utils.p64(oid)
if not isinstance(serial, bytes): if not isinstance(serial, bytes):
serial = ZODB.utils.p64(serial) serial = ZODB.utils.p64(serial)
t = transaction.get() t = TransactionMetaData()
storage.tpc_begin(t) storage.tpc_begin(t)
storage.store(oid, serial, value, '', t) storage.store(oid, serial, value, '', t)
storage.tpc_vote(t) storage.tpc_vote(t)
......
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