Commit 316ea575 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 527e2a38
...@@ -311,6 +311,22 @@ class Connection(ExportImport, object): ...@@ -311,6 +311,22 @@ class Connection(ExportImport, object):
connection = new_con connection = new_con
return connection 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): def sync(self):
"""Manually update the view on the database.""" """Manually update the view on the database."""
self.transaction_manager.abort() self.transaction_manager.abort()
...@@ -520,10 +536,15 @@ class Connection(ExportImport, object): ...@@ -520,10 +536,15 @@ class Connection(ExportImport, object):
if serial == z64: if serial == z64:
# obj is a new object # obj is a new object
self._creating[oid] = 1
# Because obj was added, it is now in _creating, so it can # Because obj was added, it is now in _creating, so it
# be removed from _added. # can be removed from _added. If oid wasn't in
self._added.pop(oid, None) # adding, then we are adding it implicitly.
implicitly_adding = self._added.pop(oid, None) is None
self._creating[oid] = implicitly_adding
else: else:
if (oid in self._invalidated if (oid in self._invalidated
and not hasattr(obj, '_p_resolveConflict')): and not hasattr(obj, '_p_resolveConflict')):
......
...@@ -91,18 +91,47 @@ reachable from multiple databases: ...@@ -91,18 +91,47 @@ reachable from multiple databases:
>>> tm.abort() >>> tm.abort()
To resolve this ambiguity, we need to commit, or create a savepoint To resolve this ambiguity, we can commit before an object becomes
before an object becomes reachable from multiple databases. Here reachable from multiple databases.
we'll use a savepoint to make sure that p4 lands in database 1:
>>> p4 = MyClass() >>> p4 = MyClass()
>>> p1.p4 = p4 >>> p1.p4 = p4
>>> s = tm.savepoint() >>> tm.commit()
>>> p2.p4 = p4 >>> 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 This the most explicit and thus the best way, when practical, to avoid
commitment. Changes made in the savepoint will be rolled back if the the ambiguity.
transaction is aborted.
NOTE NOTE
---- ----
......
...@@ -347,8 +347,7 @@ class ObjectWriter: ...@@ -347,8 +347,7 @@ class ObjectWriter:
# OK, we have an object from another database. # OK, we have an object from another database.
# Lets make sure the object ws not *just* loaded. # Lets make sure the object ws not *just* loaded.
# TODO: shouldn't depend on underware (_creating) if obj._p_jar._implicitlyAdding(oid):
if oid in obj._p_jar._creating:
raise InvalidObjectReference( raise InvalidObjectReference(
"A new object is reachable from multiple databases. " "A new object is reachable from multiple databases. "
"Won't try to guess which one was correct!" "Won't try to guess which one was correct!"
......
...@@ -121,6 +121,30 @@ if we get the same objects: ...@@ -121,6 +121,30 @@ if we get the same objects:
>>> db2.close() >>> 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): def tearDownDbs(test):
test.globs['db1'].close() 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