Commit 824123b8 authored by Jim Fulton's avatar Jim Fulton

cross-database wekrefs weren't handled correctly.

https://bugs.launchpad.net/zodb/+bug/435547
parent 8fd960bc
...@@ -22,6 +22,10 @@ Bugs Fixed ...@@ -22,6 +22,10 @@ Bugs Fixed
https://bugs.launchpad.net/zodb/+bug/428039 https://bugs.launchpad.net/zodb/+bug/428039
- cross-database wekrefs weren't handled correctly.
https://bugs.launchpad.net/zodb/+bug/435547
- The mkzeoinst script was fixed to tell people to - The mkzeoinst script was fixed to tell people to
install and use the mkzeoinstance script. :) install and use the mkzeoinstance script. :)
......
...@@ -116,13 +116,17 @@ class PersistentReference(object): ...@@ -116,13 +116,17 @@ class PersistentReference(object):
# 'm' = multi_persistent: (database_name, oid, klass) # 'm' = multi_persistent: (database_name, oid, klass)
# 'n' = multi_oid: (database_name, oid) # 'n' = multi_oid: (database_name, oid)
# 'w' = persistent weakref: (oid) # 'w' = persistent weakref: (oid)
# or persistent weakref: (oid, database_name)
# else it is a weakref: reference_type # else it is a weakref: reference_type
if reference_type == 'm': if reference_type == 'm':
self.database_name, self.oid, self.klass = data[1] self.database_name, self.oid, self.klass = data[1]
elif reference_type == 'n': elif reference_type == 'n':
self.database_name, self.oid = data[1] self.database_name, self.oid = data[1]
elif reference_type == 'w': elif reference_type == 'w':
try:
self.oid, = data[1] self.oid, = data[1]
except ValueError:
self.oid, self.database_name = data[1]
self.weak = True self.weak = True
else: else:
assert len(data) == 1, 'unknown reference format' assert len(data) == 1, 'unknown reference format'
......
...@@ -474,6 +474,16 @@ integrity issues. ...@@ -474,6 +474,16 @@ integrity issues.
>>> ref3.weak >>> ref3.weak
True True
>>> ref3a = PersistentReference(['w', ('my_oid', 'other_db')])
>>> ref3a.oid
'my_oid'
>>> print ref3a.klass
None
>>> ref3a.database_name
'other_db'
>>> ref3a.weak
True
>>> ref4 = PersistentReference(['m', ('other_db', 'my_oid', 'my_class')]) >>> ref4 = PersistentReference(['m', ('other_db', 'my_oid', 'my_class')])
>>> ref4.oid >>> ref4.oid
'my_oid' 'my_oid'
...@@ -508,7 +518,7 @@ integrity issues. ...@@ -508,7 +518,7 @@ integrity issues.
>>> ref1 == ref1 and ref2 == ref2 and ref4 == ref4 and ref5 == ref5 >>> ref1 == ref1 and ref2 == ref2 and ref4 == ref4 and ref5 == ref5
True True
>>> ref3 == ref3 and ref6 == ref6 # weak references >>> ref3 == ref3 and ref3a == ref3a and ref6 == ref6 # weak references
True True
Non-weak references with the same oid and database_name are equal. Non-weak references with the same oid and database_name are equal.
......
...@@ -100,7 +100,8 @@ oid ...@@ -100,7 +100,8 @@ oid
The following reference types are defined: The following reference types are defined:
'w' 'w'
Persistent weak reference. The arguments consist of an oid. Persistent weak reference. The arguments consist of an oid
and optionally a database name.
The following are planned for the future: The following are planned for the future:
...@@ -298,8 +299,8 @@ class ObjectWriter: ...@@ -298,8 +299,8 @@ class ObjectWriter:
oid = obj.oid oid = obj.oid
if oid is None: if oid is None:
obj = obj() # get the referenced object target = obj() # get the referenced object
oid = obj._p_oid oid = target._p_oid
if oid is None: if oid is None:
# Here we are causing the object to be saved in # Here we are causing the object to be saved in
# the database. One could argue that we shouldn't # the database. One could argue that we shouldn't
...@@ -308,10 +309,16 @@ class ObjectWriter: ...@@ -308,10 +309,16 @@ class ObjectWriter:
# assume that the object will be added eventually. # assume that the object will be added eventually.
oid = self._jar.new_oid() oid = self._jar.new_oid()
obj._p_jar = self._jar target._p_jar = self._jar
obj._p_oid = oid target._p_oid = oid
self._stack.append(obj) self._stack.append(target)
obj.oid = oid
obj.dm = target._p_jar
obj.database_name = obj.dm.db().database_name
if obj.dm is self._jar:
return ['w', (oid, )] return ['w', (oid, )]
else:
return ['w', (oid, obj.database_name)]
# Since we have an oid, we have either a persistent instance # Since we have an oid, we have either a persistent instance
...@@ -530,10 +537,20 @@ class ObjectReader: ...@@ -530,10 +537,20 @@ class ObjectReader:
loaders['m'] = load_multi_persistent loaders['m'] = load_multi_persistent
def load_persistent_weakref(self, oid): def load_persistent_weakref(self, oid, database_name=None):
obj = WeakRef.__new__(WeakRef) obj = WeakRef.__new__(WeakRef)
obj.oid = oid obj.oid = oid
if database_name is None:
obj.dm = self._conn obj.dm = self._conn
else:
obj.database_name = database_name
try:
obj.dm = self._conn.get_connection(database_name)
except KeyError:
# XXX Not sure what to do here. It seems wrong to
# fail since this is a weak reference. For now we'll
# just pretend that the target object has gone.
pass
return obj return obj
loaders['w'] = load_persistent_weakref loaders['w'] = load_persistent_weakref
...@@ -649,7 +666,7 @@ def referencesf(p, oids=None): ...@@ -649,7 +666,7 @@ def referencesf(p, oids=None):
return oids return oids
oid_klass_loaders = { oid_klass_loaders = {
'w': lambda oid: None, 'w': lambda oid, database_name=None: None,
} }
def get_refs(a_pickle): def get_refs(a_pickle):
......
...@@ -99,6 +99,61 @@ class WeakRef(object): ...@@ -99,6 +99,61 @@ class WeakRef(object):
Always explicitly close databases: :) Always explicitly close databases: :)
>>> db.close() >>> db.close()
>>> del ob, ref, db, conn1, conn2, conn3
When multiple databases are in use, a weakref in one database may
point to an object in a different database. Let's create two new
databases to demonstrate this.
>>> dbA = ZODB.tests.util.DB(
... database_name = 'dbA',
... )
>>> dbB = ZODB.tests.util.DB(
... database_name = 'dbB',
... databases = dbA.databases,
... )
>>> connA1 = dbA.open()
>>> connB1 = connA1.get_connection('dbB')
Now create and add a new object and a weak reference, and add them
to different databases.
>>> ob = ZODB.tests.MinPO.MinPO()
>>> ref = WeakRef(ob)
>>> connA1.root()['ob'] = ob
>>> connA1.add(ob)
>>> connB1.root()['ref'] = ref
>>> transaction.commit()
After a succesful commit, the reference should know the oid,
database name and connection of the object.
>>> ref.oid == ob._p_oid
True
>>> ref.database_name == 'dbA'
True
>>> ref.dm is ob._p_jar is connA1
True
If we open new connections, we should be able to use the reference.
>>> connA2 = dbA.open()
>>> connB2 = connA2.get_connection('dbB')
>>> ref2 = connB2.root()['ref']
>>> ob2 = connA2.root()['ob']
>>> ref2() is ob2
True
>>> ref2.oid == ob2._p_oid
True
>>> ref2.database_name == 'dbA'
True
>>> ref2.dm is ob2._p_jar is connA2
True
Always explicitly close databases: :)
>>> dbA.close()
>>> dbB.close()
""" """
...@@ -110,6 +165,8 @@ class WeakRef(object): ...@@ -110,6 +165,8 @@ class WeakRef(object):
self._v_ob = ob self._v_ob = ob
self.oid = ob._p_oid self.oid = ob._p_oid
self.dm = ob._p_jar self.dm = ob._p_jar
if self.dm is not None:
self.database_name = self.dm.db().database_name
def __call__(self): def __call__(self):
try: try:
...@@ -117,7 +174,7 @@ class WeakRef(object): ...@@ -117,7 +174,7 @@ class WeakRef(object):
except AttributeError: except AttributeError:
try: try:
self._v_ob = self.dm[self.oid] self._v_ob = self.dm[self.oid]
except KeyError: except (KeyError, AttributeError):
return None return None
return self._v_ob return self._v_ob
......
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