[Zodb-checkins] SVN: ZODB/branches/shane-cross-database-seatbelt/src/ZODB/ The proposed cross-database reference seat belt mechanism, with tests.
Shane Hathaway
shane at hathawaymix.org
Tue Apr 28 03:37:42 EDT 2009
Log message for revision 99551:
The proposed cross-database reference seat belt mechanism, with tests.
Changed:
U ZODB/branches/shane-cross-database-seatbelt/src/ZODB/DB.py
U ZODB/branches/shane-cross-database-seatbelt/src/ZODB/cross-database-references.txt
U ZODB/branches/shane-cross-database-seatbelt/src/ZODB/serialize.py
-=-
Modified: ZODB/branches/shane-cross-database-seatbelt/src/ZODB/DB.py
===================================================================
--- ZODB/branches/shane-cross-database-seatbelt/src/ZODB/DB.py 2009-04-28 07:31:40 UTC (rev 99550)
+++ ZODB/branches/shane-cross-database-seatbelt/src/ZODB/DB.py 2009-04-28 07:37:42 UTC (rev 99551)
@@ -399,6 +399,7 @@
historical_cache_size=1000,
historical_cache_size_bytes=0,
historical_timeout=300,
+ check_xrefs=False,
database_name='unnamed',
databases=None,
):
@@ -419,6 +420,10 @@
the historical connection.
- `historical_timeout`: minimum number of seconds that
an unused historical connection will be kept, or None.
+ - `check_xrefs`: if true, cross-database references will only
+ be allowed from objects whose _p_check_xref method allows
+ them. If false (the default), cross-database references
+ will be allowed implicitly.
"""
if isinstance(storage, basestring):
from ZODB import FileStorage
@@ -437,6 +442,7 @@
self._cache_size_bytes = cache_size_bytes
self._historical_cache_size = historical_cache_size
self._historical_cache_size_bytes = historical_cache_size_bytes
+ self.check_xrefs = check_xrefs
# Setup storage
self.storage = storage
Modified: ZODB/branches/shane-cross-database-seatbelt/src/ZODB/cross-database-references.txt
===================================================================
--- ZODB/branches/shane-cross-database-seatbelt/src/ZODB/cross-database-references.txt 2009-04-28 07:31:40 UTC (rev 99550)
+++ ZODB/branches/shane-cross-database-seatbelt/src/ZODB/cross-database-references.txt 2009-04-28 07:37:42 UTC (rev 99551)
@@ -133,6 +133,86 @@
This the most explicit and thus the best way, when practical, to avoid
the ambiguity.
+Cross-database reference seat belt
+----------------------------------
+
+Some applications create unintentional cross-database references when
+they move objects between containers stored in ZODB. For example, an
+object might be created in a session container located in a volatile
+database, then later moved (rather than copied) to the main database.
+In that case, the reference will break when the session expires or the
+server restarts.
+
+ZODB provides an optional seat belt that prevents unintentional
+cross-database references. The seat belt is disabled by default, but
+can be enabled by passing "check_xrefs=True" to the DB constructor.
+
+When the seat belt is enabled, every cross-database reference is
+checked by the _p_check_xref method of the persistent object holding
+the reference; objects that do not have a _p_check_xref method are
+not allowed to hold any cross-database references.
+
+Lets set up a multi-database with 2 databases, this time checking
+cross-database references from `main_db` but not from `session_db`:
+
+ >>> import ZODB.tests.util, transaction, persistent
+ >>> databases = {}
+ >>> main_db = ZODB.tests.util.DB(databases=databases, database_name='main',
+ ... check_xrefs=True)
+ >>> session_db = ZODB.tests.util.DB(databases=databases,
+ ... database_name='sessions')
+
+Create a persistent object in both databases:
+
+ >>> tm = transaction.TransactionManager()
+ >>> main_conn = main_db.open(transaction_manager=tm)
+ >>> main_obj = MyClass()
+ >>> main_conn.root()['p'] = main_obj
+ >>> session_conn = main_conn.get_connection('sessions')
+ >>> session_obj = MyClass()
+ >>> session_conn.root()['p'] = session_obj
+ >>> tm.commit()
+
+Try to create a reference from `main_obj` to `session_obj`, which is not
+allowed because the seat belt is enabled in `main_db` and `main_obj` has
+no _p_check_xref method:
+
+ >>> main_obj.someattr = session_obj
+ >>> tm.commit() # doctest: +NORMALIZE_WHITESPACE
+ Traceback (most recent call last):
+ ...
+ InvalidObjectReference: Attempt to store a cross database reference from an object that does not have a _p_check_xref method
+ >>> tm.abort()
+ >>> hasattr(main_obj, 'someattr')
+ False
+
+A reference from `session_obj` to `main_obj` is allowed, however.
+
+ >>> session_obj.someattr = main_obj
+ >>> tm.commit()
+ >>> hasattr(session_obj, 'someattr')
+ True
+
+The reference from `main_obj` can be allowed by its _p_check_xref method:
+
+ >>> def _p_check_xref(obj):
+ ... return obj is session_obj
+ >>> main_obj._p_check_xref = _p_check_xref
+ >>> main_obj.someattr = session_obj
+ >>> tm.commit()
+
+The _p_check_xref method can also disallow a reference:
+
+ >>> def _p_check_xref(obj):
+ ... return False
+ >>> main_obj._p_check_xref = _p_check_xref
+ >>> main_obj.someattr = session_obj
+ >>> tm.commit() # doctest: +NORMALIZE_WHITESPACE
+ Traceback (most recent call last):
+ ...
+ InvalidObjectReference: A cross database reference was disallowed by a _p_check_xref method
+ >>> tm.abort()
+
NOTE
----
Modified: ZODB/branches/shane-cross-database-seatbelt/src/ZODB/serialize.py
===================================================================
--- ZODB/branches/shane-cross-database-seatbelt/src/ZODB/serialize.py 2009-04-28 07:31:40 UTC (rev 99550)
+++ ZODB/branches/shane-cross-database-seatbelt/src/ZODB/serialize.py 2009-04-28 07:37:42 UTC (rev 99551)
@@ -175,6 +175,7 @@
self._p = cPickle.Pickler(self._file, 1)
self._p.inst_persistent_id = self.persistent_id
self._stack = []
+ self._obj = obj
if obj is not None:
self._stack.append(obj)
jar = obj._p_jar
@@ -352,8 +353,19 @@
"A new object is reachable from multiple databases. "
"Won't try to guess which one was correct!"
)
-
+ if self._jar.db().check_xrefs:
+ method = getattr(self._obj, '_p_check_xref', None)
+ if method is None:
+ raise InvalidObjectReference(
+ "Attempt to store a cross database reference "
+ "from an object that does not have a _p_check_xref "
+ "method")
+ elif not method(obj):
+ raise InvalidObjectReference(
+ "A cross database reference was disallowed "
+ "by a _p_check_xref method")
+
klass = type(obj)
if hasattr(klass, '__getnewargs__'):
# We don't want to save newargs in object refs.
More information about the Zodb-checkins
mailing list