Commit 173b1df9 authored by Jim Fulton's avatar Jim Fulton

We check for implicitly adding objects by looking for "new" objects

reachable from multiple connections.  Previously, we thought that we
could limit the time that an object was new to a single savepoint, but
that didn't work because savepoints of different connections are too
independent.   Now an object is considered new for the full extent of
the transaction in which it was created.

Made it possible to use connection add methods to explicitly control
the database an object is added too.
parent bbe3a92b
......@@ -311,6 +311,22 @@ class Connection(ExportImport, object):
connection = new_con
return connection
def _implicitlyAdding(self, oid):
"""Are we implicitly adding an object within the current transaction
This is used in a check to avoid implicitly adding an object
to a database in a multi-database situation.
See serialize.ObjectWriter.persistent_id.
"""
return (self._creating.get(oid, 0)
or
((self._savepoint_storage is not None)
and
self._savepoint_storage.creating.get(oid, 0)
)
)
def sync(self):
"""Manually update the view on the database."""
self.transaction_manager.abort()
......@@ -520,10 +536,15 @@ class Connection(ExportImport, object):
if serial == z64:
# obj is a new object
self._creating[oid] = 1
# Because obj was added, it is now in _creating, so it can
# be removed from _added.
self._added.pop(oid, None)
# Because obj was added, it is now in _creating, so it
# can be removed from _added. If oid wasn't in
# adding, then we are adding it implicitly.
implicitly_adding = self._added.pop(oid, None) is None
self._creating[oid] = implicitly_adding
else:
if (oid in self._invalidated
and not hasattr(obj, '_p_resolveConflict')):
......
......@@ -91,18 +91,47 @@ reachable from multiple databases:
>>> tm.abort()
To resolve this ambiguity, we need to commit, or create a savepoint
before an object becomes reachable from multiple databases. Here
we'll use a savepoint to make sure that p4 lands in database 1:
To resolve this ambiguity, we can commit before an object becomes
reachable from multiple databases.
>>> p4 = MyClass()
>>> p1.p4 = p4
>>> s = tm.savepoint()
>>> tm.commit()
>>> p2.p4 = p4
>>> tm.commit()
>>> p4._p_jar.db().database_name
'1'
This doesn't work with a savepoint:
>>> p5 = MyClass()
>>> p1.p5 = p5
>>> s = tm.savepoint()
>>> p2.p5 = p5
>>> tm.commit() # doctest: +NORMALIZE_WHITESPACE
Traceback (most recent call last):
...
InvalidObjectReference: A new object is reachable from multiple
databases. Won't try to guess which one was correct!
>>> tm.abort()
(Maybe it should.)
We can disambiguate this situation by using the connection add method
to explicitly say waht database an object belongs to:
>>> p5 = MyClass()
>>> p1.p5 = p5
>>> p2.p5 = p5
>>> conn1.add(p5)
>>> tm.commit()
>>> p5._p_jar.db().database_name
'1'
The advantage of using a savepoint is that we aren't making a
commitment. Changes made in the savepoint will be rolled back if the
transaction is aborted.
This the most explicit and thus the best way, when practical, to avoid
the ambiguity.
NOTE
----
......
......@@ -347,8 +347,7 @@ class ObjectWriter:
# OK, we have an object from another database.
# Lets make sure the object ws not *just* loaded.
# TODO: shouldn't depend on underware (_creating)
if oid in obj._p_jar._creating:
if obj._p_jar._implicitlyAdding(oid):
raise InvalidObjectReference(
"A new object is reachable from multiple databases. "
"Won't try to guess which one was correct!"
......
......@@ -121,6 +121,30 @@ if we get the same objects:
>>> db2.close()
"""
def test_explicit_adding_with_savepoint():
"""
>>> import ZODB.tests.util, transaction, persistent
>>> databases = {}
>>> db1 = ZODB.tests.util.DB(databases=databases, database_name='1')
>>> db2 = ZODB.tests.util.DB(databases=databases, database_name='2')
>>> tm = transaction.TransactionManager()
>>> conn1 = db1.open(transaction_manager=tm)
>>> conn2 = conn1.get_connection('2')
>>> z = MyClass()
>>> conn1.root()['z'] = z
>>> conn1.add(z)
>>> s = tm.savepoint()
>>> conn2.root()['z'] = z
>>> tm.commit()
>>> z._p_jar.db().database_name
'1'
>>> db1.close()
>>> db2.close()
"""
def tearDownDbs(test):
test.globs['db1'].close()
......
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