Commit 1a888ac3 authored by Jim Fulton's avatar Jim Fulton

Added an option to disallow cross-database references.

Include extra data in InvalidObjectReference exceptions to make
debugging easier.
parent a8346702
......@@ -125,7 +125,6 @@
size. This option is ignored if shared_blob_dir is true.
</description>
</key>
<key name="storage" default="1">
<description>
The name of the storage that the client wants to use. If the
......@@ -272,12 +271,13 @@
</description>
</key>
<key name="historical-timeout" datatype="time-interval"
default="5m"/>
default="5m">
<description>
The minimum interval that an unused historical connection should be
kept.
</description>
<key name="database-name" default="unnamed"/>
</key>
<key name="database-name">
<description>
When multidatabases are in use, this is the name given to this
database in the collection. The name must be unique across all
......@@ -288,6 +288,14 @@
their own config files, using the "databases" parameter of a DB
constructor.
</description>
</key>
<key name="allow-implicit-cross-references" datatype="boolean">
<description>
If set to false, implicit cross references (the only kind
currently possible) are disallowed.
</description>
</key>
</sectiontype>
<sectiontype name="blobstorage" datatype=".BlobStorage"
......@@ -302,6 +310,4 @@
</component>
......@@ -58,11 +58,13 @@ It isn't valid to create references outside a multi database:
>>> tm.commit()
>>> p2.p3 = p3
>>> tm.commit() # doctest: +NORMALIZE_WHITESPACE
>>> tm.commit() # doctest: +NORMALIZE_WHITESPACE +ELLIPSIS
Traceback (most recent call last):
...
InvalidObjectReference:
Attempt to store an object from a foreign database connection
('Attempt to store an object from a foreign database connection',
<Connection at ...>,
<ZODB.tests.testcrossdatabasereferences.MyClass...>)
>>> tm.abort()
......@@ -84,11 +86,14 @@ in? This sort of ambiguity could lead to subtle bugs. For that reason,
an error is generated if we commit changes when new objects are
reachable from multiple databases:
>>> tm.commit() # doctest: +NORMALIZE_WHITESPACE
>>> tm.commit() # doctest: +NORMALIZE_WHITESPACE +ELLIPSIS
Traceback (most recent call last):
...
InvalidObjectReference: A new object is reachable from multiple
databases. Won't try to guess which one was correct!
InvalidObjectReference:
("A new object is reachable from multiple databases. Won't try to
guess which one was correct!",
<Connection at ...>,
<ZODB.tests.testcrossdatabasereferences.MyClass...>)
>>> tm.abort()
......@@ -109,11 +114,14 @@ This doesn't work with a savepoint:
>>> p1.p5 = p5
>>> s = tm.savepoint()
>>> p2.p5 = p5
>>> tm.commit() # doctest: +NORMALIZE_WHITESPACE
>>> tm.commit() # doctest: +NORMALIZE_WHITESPACE +ELLIPSIS
Traceback (most recent call last):
...
InvalidObjectReference: A new object is reachable from multiple
databases. Won't try to guess which one was correct!
InvalidObjectReference:
("A new object is reachable from multiple databases. Won't try to guess
which one was correct!",
<Connection at ...>,
<ZODB.tests.testcrossdatabasereferences.MyClass...>)
>>> tm.abort()
......@@ -133,6 +141,37 @@ to explicitly say what database an object belongs to:
This the most explicit and thus the best way, when practical, to avoid
the ambiguity.
Dissallowing implicit cross-database references
-----------------------------------------------
The database contructor accepts a xrefs keyword argument that defaults
to True. If False is passed, the implicit cross database references
are disallowed. (Note that currently, implicit cross references are
the only kind of cross references allowed.)
>>> databases = {}
>>> db1 = ZODB.tests.util.DB(databases=databases, database_name='1')
>>> db2 = ZODB.tests.util.DB(databases=databases, database_name='2',
... xrefs=False)
In this example, we allow cross-references from db1 to db2, but not
the other way around.
>>> c1 = db1.open()
>>> c2 = c1.get_connection('2')
>>> c1.root.x = c2.root()
>>> transaction.commit()
>>> c2.root.x = c1.root()
>>> transaction.commit() # doctest: +NORMALIZE_WHITESPACE +ELLIPSIS
Traceback (most recent call last):
...
InvalidObjectReference:
("Database '2' doesn't allow implicit cross-database references",
<Connection at ...>,
{'x': {}})
>>> transaction.abort()
NOTE
----
......
......@@ -186,6 +186,7 @@ class ObjectWriter:
>>> from ZODB.tests.util import P
>>> class DummyJar:
... xrefs = True
... def new_oid(self):
... return 42
... def db(self):
......@@ -229,11 +230,13 @@ class ObjectWriter:
If the jar doesn't match that of the writer, an error is raised:
>>> bob._p_jar = DummyJar()
>>> writer.persistent_id(bob) # doctest: +NORMALIZE_WHITESPACE
>>> writer.persistent_id(bob)
... # doctest: +NORMALIZE_WHITESPACE +ELLIPSIS
Traceback (most recent call last):
...
InvalidObjectReference: Attempt to store an object from a
foreign database connection
InvalidObjectReference:
('Attempt to store an object from a foreign database connection',
<ZODB.serialize.DummyJar instance at ...>, P(bob))
Constructor arguments used by __new__(), as returned by
__getnewargs__(), can affect memory allocation, but may also
......@@ -324,6 +327,11 @@ class ObjectWriter:
self._stack.append(obj)
elif obj._p_jar is not self._jar:
if not self._jar.db().xrefs:
raise InvalidObjectReference(
"Database %r doesn't allow implicit cross-database "
"references" % self._jar.db().database_name,
self._jar, obj)
try:
otherdb = obj._p_jar.db()
......@@ -334,14 +342,14 @@ class ObjectWriter:
if self._jar.db().databases.get(database_name) is not otherdb:
raise InvalidObjectReference(
"Attempt to store an object from a foreign "
"database connection"
"database connection", self._jar, obj,
)
if self._jar.get_connection(database_name) is not obj._p_jar:
raise InvalidObjectReference(
"Attempt to store a reference to an object from "
"a separate connection to the same database or "
"multidatabase"
"multidatabase", self._jar, obj,
)
# OK, we have an object from another database.
......@@ -350,10 +358,10 @@ class ObjectWriter:
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!"
"Won't try to guess which one was correct!",
self._jar, obj,
)
klass = type(obj)
if hasattr(klass, '__getnewargs__'):
# We don't want to save newargs in object refs.
......
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